diff --git a/lib/Alchemy/Phrasea/TaskManager/Editor/FtpPullEditor.php b/lib/Alchemy/Phrasea/TaskManager/Editor/FtpPullEditor.php
new file mode 100644
index 0000000000..723836695f
--- /dev/null
+++ b/lib/Alchemy/Phrasea/TaskManager/Editor/FtpPullEditor.php
@@ -0,0 +1,74 @@
+
+
+
+
+ 0
+ 0
+
+
+
+
+ 21
+
+
+EOF;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getFormProperties()
+ {
+ return array(
+ 'proxy' => static::FORM_TYPE_STRING,
+ 'proxyport' => static::FORM_TYPE_STRING,
+ 'passive' => static::FORM_TYPE_BOOLEAN,
+ 'ssl' => static::FORM_TYPE_BOOLEAN,
+ 'password' => static::FORM_TYPE_STRING,
+ 'user' => static::FORM_TYPE_STRING,
+ 'ftppath' => static::FORM_TYPE_STRING,
+ 'localpath' => static::FORM_TYPE_STRING,
+ 'port' => static::FORM_TYPE_INTEGER,
+ 'host' => static::FORM_TYPE_STRING,
+ );
+ }
+}
diff --git a/lib/Alchemy/Phrasea/TaskManager/Job/FtpPullJob.php b/lib/Alchemy/Phrasea/TaskManager/Job/FtpPullJob.php
new file mode 100644
index 0000000000..7af2ddbe01
--- /dev/null
+++ b/lib/Alchemy/Phrasea/TaskManager/Job/FtpPullJob.php
@@ -0,0 +1,135 @@
+getApplication();
+ $settings = simplexml_load_string($data->getTask()->getSettings());
+
+ $proxy = (string) $settings->proxy;
+ $proxyport = (string) $settings->proxyport;
+ $localPath = (string) $settings->localpath;
+ $ftpPath = (string) $settings->ftppath;
+ $host = (string) $settings->host;
+ $port = (string) $settings->port;
+ $user = (string) $settings->user;
+ $password = (string) $settings->password;
+ $ssl = (Boolean) (string) $settings->ssl;
+ $passive = (Boolean) (string) $settings->passive;
+
+ foreach (array(
+ 'localpath' => $localPath,
+ 'host' => $host,
+ 'port' => $host,
+ 'user' => $user,
+ 'password' => $password,
+ 'ftppath' => $ftpPath,
+ ) as $name => $value) {
+ if (trim($value) === '') {
+ // maybe throw an exception to consider the job as failing ?
+ $this->log('error', sprintf('setting `%s` must be set', $name));
+ throw new RuntimeException(sprintf('`%s` setting is empty', $name));
+ }
+ }
+
+ $app['filesystem']->mkdir($localPath, 0750);
+
+ if (!is_dir($localPath)) {
+ $this->log('error', sprintf('`%s` does not exists', $localPath));
+ throw new RuntimeException(sprintf('`%s` does not exists', $localPath));
+ }
+ if (!is_writeable($localPath)) {
+ $this->log('error', sprintf('`%s` is not writeable', $localPath));
+ throw new RuntimeException(sprintf('`%s` is not writeable', $localPath));
+ }
+
+ $ftp = $app['phraseanet.ftp.client']($host, $port, 90, $ssl, $proxy, $proxyport);
+ $ftp->passive($passive);
+ $ftp->login($user, $password);
+ $ftp->chdir($ftpPath);
+ $list_1 = $ftp->list_directory(true);
+
+ $done = 0;
+ $this->log('debug', "attente de 25sec pour avoir les fichiers froids...");
+ $this->pause(25);
+ if (!$this->isStarted()) {
+ $ftp->close();
+ $this->log('debug', "Stopping");
+ return;
+ }
+
+ $list_2 = $ftp->list_directory(true);
+
+ foreach ($list_1 as $filepath => $timestamp) {
+ $done++;
+ if (!isset($list_2[$filepath])) {
+ $this->log('debug', "le fichier $filepath a disparu...\n");
+ continue;
+ }
+ if ($list_2[$filepath] !== $timestamp) {
+ $this->log('debug', "le fichier $filepath a ete modifie depuis le dernier passage...");
+ continue;
+ }
+
+ $finalpath = \p4string::addEndSlash($localPath) . ($filepath[0] == '/' ? mb_substr($filepath, 1) : $filepath);
+ $this->log('debug', "Rappatriement de $filepath vers $finalpath\n");
+
+ if (file_exists($finalpath)) {
+ $this->log('debug', "Un fichier du meme nom ($finalpath) existe deja, skipping");
+ continue;
+ }
+
+ $this->log('debug', "Create ".dirname($finalpath)."");
+ $app['filesystem']->mkdir(dirname($finalpath), 0750);
+
+ $this->log('debug', "Get $filepath to $finalpath");
+ $ftp->get($finalpath, $filepath);
+ $this->log('debug', "Remove $filepath");
+ $ftp->delete($filepath);
+ }
+
+ $ftp->close();
+ }
+}
diff --git a/templates/web/admin/task-manager/task-editor/ftp-pull.html.twig b/templates/web/admin/task-manager/task-editor/ftp-pull.html.twig
new file mode 100644
index 0000000000..706e243884
--- /dev/null
+++ b/templates/web/admin/task-manager/task-editor/ftp-pull.html.twig
@@ -0,0 +1,91 @@
+{% extends 'admin/task-manager/task-editor/task.html.twig' %}
+
+{% block form %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+{% endblock %}
+
+
+{% block javascript %}
+ function taskFillGraphic(xml)
+ {
+ if (xml) {
+ xml = $.parseXML(xml);
+ xml = $(xml);
+
+ with(document.forms['graphicForm'])
+ {
+ proxy.value = xml.find("proxy").text();
+ proxyport.value = xml.find("proxyport").text();
+ localpath.value = xml.find("localpath").text();
+ ftppath.value = xml.find("ftppath").text();
+ host.value = xml.find("host").text();
+ port.value = xml.find("port").text();
+ user.value = xml.find("user").text();
+ password.value = xml.find("password").text();
+ ssl.checked = Number(xml.find("ssl").text()) > 0;
+ passive.checked = Number(xml.find("passive").text()) > 0;
+ }
+ }
+ }
+{% endblock %}
diff --git a/tests/Alchemy/Tests/Phrasea/TaskManager/Editor/FtpPullEditorTest.php b/tests/Alchemy/Tests/Phrasea/TaskManager/Editor/FtpPullEditorTest.php
new file mode 100644
index 0000000000..3726c1d3e7
--- /dev/null
+++ b/tests/Alchemy/Tests/Phrasea/TaskManager/Editor/FtpPullEditorTest.php
@@ -0,0 +1,62 @@
+
+
+00
+', '
+
+', array()
+ ),
+ array('
+
+ 1234
+ 5678
+ 1
+ 1
+ a password
+ username
+ nice path
+ path to the future
+ 22
+ example.com
+
+', '
+1280021
+', array('proxy' => 1234, 'proxyport' => 5678, 'passive' => 1, 'ssl' => 1, 'password' => 'a password', 'user' => 'username', 'ftppath' => 'nice path', 'localpath' => 'path to the future', 'port' => 22, 'host' => 'example.com')
+ ),
+ array('
+
+ value
+
+
+ 0
+ 0
+
+
+
+
+
+
+
+', '
+
+ value
+', array()
+ ),
+ );
+ }
+
+ protected function getEditor()
+ {
+ return new FtpPullEditor();
+ }
+}
diff --git a/tests/Alchemy/Tests/Phrasea/TaskManager/Job/FtpPullJobTest.php b/tests/Alchemy/Tests/Phrasea/TaskManager/Job/FtpPullJobTest.php
new file mode 100644
index 0000000000..8eff75af1e
--- /dev/null
+++ b/tests/Alchemy/Tests/Phrasea/TaskManager/Job/FtpPullJobTest.php
@@ -0,0 +1,13 @@
+