diff --git a/VERSIONS_WIP/63069 b/VERSIONS_WIP/63069
new file mode 100644
index 0000000000000000000000000000000000000000..046a87024a55ed8a091bf568f98229de0e91bca9
--- /dev/null
+++ b/VERSIONS_WIP/63069
@@ -0,0 +1 @@
+ - ticket #63069 : Ajout d'un batch permettant moissonner un calendrier externe automatiquement
\ No newline at end of file
diff --git a/application/modules/admin/controllers/ExternalAgendasController.php b/application/modules/admin/controllers/ExternalAgendasController.php
index 2398e0110a99fc46a8957a2e43809b1ffb4a21f0..0c78b90ddca93cff18bed8e96406b38e20e30bb8 100644
--- a/application/modules/admin/controllers/ExternalAgendasController.php
+++ b/application/modules/admin/controllers/ExternalAgendasController.php
@@ -23,7 +23,7 @@
 class Admin_ExternalAgendasController extends ZendAfi_Controller_Action {
   public function getPlugins() {
     return ['ZendAfi_Controller_Plugin_ResourceDefinition_ExternalAgenda',
-            'ZendAfi_Controller_Plugin_Manager_Manager'];
+            'ZendAfi_Controller_Plugin_Manager_ExternalAgenda'];
   }
 
 
@@ -32,17 +32,9 @@ class Admin_ExternalAgendasController extends ZendAfi_Controller_Action {
       return $this->_redirectToIndex();
 
     $this->view->titre = $this->_('Moissonnage des évènements de l\'agenda "%s"', $agenda->getLibelle());
-    $agenda->import($this);
-  }
-
-
-  public function visitNewEvents($events) {
-    $this->view->new_events = $events;
-  }
-
-
-  public function visitUpdatedEvents($events) {
-    $this->view->updated_events = $events;
+    $results = $agenda->import();
+    $this->view->new_events = $results['new'];
+    $this->view->updated_events = $results['update'];
   }
 
 
diff --git a/application/modules/admin/views/scripts/external-agendas/index.phtml b/application/modules/admin/views/scripts/external-agendas/index.phtml
index e79537bceb9b075979f778070a7cccca1b942f59..be3fca0d36c35381ce3ac65ffd9a4351a5fecd9c 100644
--- a/application/modules/admin/views/scripts/external-agendas/index.phtml
+++ b/application/modules/admin/views/scripts/external-agendas/index.phtml
@@ -15,9 +15,15 @@ $number_of_events = function($model, $attrib)
 
 $ical_url = function($model, $attrib)
 {
+  $part = $model->getUrl();
+  if (strlen($model->getUrl()) >40) {
+    $parts = explode('/',$part);
+    $part=reset($parts).'//.../'.end($parts);
+  }
+
   return $this->tagAnchor($model->getUrl(),
-                          $model->getUrl(),
-                          ['target' => '_blank']);
+                          $part,
+                          ['title' => $model->getUrl(),'target' => '_blank']);
 };
 
 $category = function($model, $attribs) {
@@ -28,27 +34,15 @@ $category = function($model, $attribs) {
                           Class_ArticleCategorie::find($cat_id)->getLibelle());
 };
 
-echo $this->tagModelTable($this->agendas,
-
-                          [$this->_('Libellé'),
-                           $this->_('Nombre d\'événements'),
-                           $this->_('URL'),
-                           $this->_('Categorie')],
-
-                          ['label',
-                           'number-of-events',
-                           'url',
-                           'category'],
-
-                          [ ['action' => 'import', 'content' => $this->boutonIco('type=test',
-                                                                                 'bulle='. $this->_('Moissonner'))],
-                            ['action' => 'edit', 'content' => $this->boutonIco('type=edit')],
-                            ['action' => 'delete', 'content' => $this->boutonIco('type=del')],
-                            ],
-
-                          'agendas',
-                          null,
-
-                          ['number-of-events' => $number_of_events,
-                           'url' => $ical_url,
-                           'category' => $category]);
+$description = (new Class_TableDescription('agendas'))
+  ->addColumn($this->_('Libellé'), 'label')
+  ->addColumn($this->_('Nombre d\'événements'), $number_of_events)
+  ->addColumn($this->_('URL'), $ical_url)
+  ->addColumn($this->_('Categorie'), $category)
+  ->addColumn($this->_('MAJ auto.'), function($model)
+              {
+                return $model->autoharvest ? $this->_('Oui') : $this->_('Non');
+              })
+  ->addRowAction(function($model) { return $this->renderPluginsActions($model); });
+
+echo $this->renderTable($description, $this->agendas, ['sorter' => true]);
diff --git a/cosmogramme/sql/patch/patch_334.php b/cosmogramme/sql/patch/patch_334.php
new file mode 100644
index 0000000000000000000000000000000000000000..8bd70b3464b6f10eb20e53ede87e273dd7626526
--- /dev/null
+++ b/cosmogramme/sql/patch/patch_334.php
@@ -0,0 +1,8 @@
+<?php
+try{
+  Zend_Db_Table_Abstract::getDefaultAdapter()
+    ->query('alter table external_agenda add column autoharvest tinyint not null default 0');
+  Zend_Db_Table_Abstract::getDefaultAdapter()
+    ->query('alter table external_agenda add key autoharvest(autoharvest)');
+} catch(Exception $e) {}
+?>
diff --git a/library/Class/Batch.php b/library/Class/Batch.php
index c8469a79541add4601be117ac6e713acb645594c..e5d7f861b7328ca79f2e68f364e1a5a15ae7ce64 100644
--- a/library/Class/Batch.php
+++ b/library/Class/Batch.php
@@ -41,7 +41,8 @@ class Class_BatchLoader extends Storm_Model_Loader {
                         Class_Batch_AutocompleteRecordAuthor::TYPE => new Class_Batch_AutocompleteRecordAuthor(),
                         Class_Batch_BuildSiteMap::TYPE => new Class_Batch_BuildSiteMap(),
                         Class_Batch_PremierChapitre::TYPE => new Class_Batch_PremierChapitre(),
-                        Class_Batch_NoveltyFacet::TYPE => new Class_Batch_NoveltyFacet()]);
+                        Class_Batch_NoveltyFacet::TYPE => new Class_Batch_NoveltyFacet(),
+                        Class_Batch_ExternalAgenda::TYPE => new Class_Batch_ExternalAgenda]);
   }
 
 
diff --git a/library/Class/Batch/ExternalAgenda.php b/library/Class/Batch/ExternalAgenda.php
new file mode 100644
index 0000000000000000000000000000000000000000..f69be445238fefb3c91fce7d23ad003fe11e7cae
--- /dev/null
+++ b/library/Class/Batch/ExternalAgenda.php
@@ -0,0 +1,34 @@
+<?php
+/**
+ * Copyright (c) 2012-2014, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class Class_Batch_ExternalAgenda extends Class_Batch_Abstract {
+  const TYPE = 'EXTERNAL_AGENDA';
+
+  public function getLabel() {
+    return $this->_('Moissonner les agendas externes');
+  }
+
+  public function run() {
+    Class_ExternalAgenda::harvest($this->getLogger());
+  }
+}
+?>
\ No newline at end of file
diff --git a/library/Class/ExternalAgenda.php b/library/Class/ExternalAgenda.php
index 9816b744e7e5507fd3651945c6d44327f55b8533..ada348c13beea5ca555dee3b77937e297cc2cefb 100644
--- a/library/Class/ExternalAgenda.php
+++ b/library/Class/ExternalAgenda.php
@@ -20,6 +20,19 @@
  */
 
 
+class Class_ExternalAgendaLoader extends Storm_Model_Loader {
+
+  public function harvest($logger) {
+    foreach (Class_ExternalAgenda::findAllBy(['autoharvest' => 1]) as $agenda) {
+      $results = $agenda->import();
+      $logger->log($agenda->getLabel().":\n");
+      $logger->log($agenda->_("Nombre d\'événements créés : %s\n",count($results['new'])));
+      $logger->log($agenda->_("Nombre d\'événements mis à jour : %s\n",count($results['update'])));
+    }
+  }
+}
+
+
 class Class_ExternalAgenda extends Storm_Model_Abstract {
   use Trait_Translator;
 
@@ -30,6 +43,7 @@ class Class_ExternalAgenda extends Storm_Model_Abstract {
                             'location' => ['model' => 'Class_Lieu',
                                            'referenced_in' => 'id_lieu']];
 
+  protected $_loader_class = 'Class_ExternalAgendaLoader';
 
   public function getLibelle() {
     return $this->getLabel();
@@ -43,16 +57,13 @@ class Class_ExternalAgenda extends Storm_Model_Abstract {
   }
 
 
-  public function import($logger) {
+  public function import() {
     $service = new Class_WebService_ICalendar();
     $events = $service->import($this);
-
-    $logger->visitNewEvents($events->select('isNew'));
-    $logger->visitUpdatedEvents($events->reject('isNew'));
-
+    $results['new'] = $events->select('isNew');
+    $results['update'] = $events->reject('isNew');
     $events->eachDo('save');
-
-    return $events;
+    return $results;
   }
 
 
diff --git a/library/ZendAfi/Controller/Plugin/Manager/ExternalAgenda.php b/library/ZendAfi/Controller/Plugin/Manager/ExternalAgenda.php
new file mode 100644
index 0000000000000000000000000000000000000000..3a92d345d0fa282a0c7c9529b7b546b149396525
--- /dev/null
+++ b/library/ZendAfi/Controller/Plugin/Manager/ExternalAgenda.php
@@ -0,0 +1,40 @@
+<?php
+/**
+ * Copyright (c) 2012-2017, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class ZendAfi_Controller_Plugin_Manager_ExternalAgenda extends ZendAfi_Controller_Plugin_Manager_Manager {
+  public function getActions($model) {
+    return [
+            ['url' => '/admin/external-agendas/import/id/%s',
+             'icon' => 'test',
+             'label' => $this->_('Moissonner')
+            ],
+            ['url' => '/admin/external-agendas/edit/id/%s',
+             'icon' => 'edit',
+             'label' => $this->_('Modifier'),
+             'anchorOptions' => ['data-popup' => 'true']],
+            ['url' => '/admin/external-agendas/delete/id/%s',
+             'label' => $this->_('Supprimer'),
+             'icon' => 'delete',
+             'anchorOptions' => ['data-popup' => 'true']]
+            ];
+  }
+}
\ No newline at end of file
diff --git a/library/ZendAfi/Form/Admin/ExternalAgenda.php b/library/ZendAfi/Form/Admin/ExternalAgenda.php
index bef1b8f0ce6ba0f0ae432b0d0409b96e5f0251c3..295cf12deba7661f7a46cc610fd7cb4be7fba2a8 100644
--- a/library/ZendAfi/Form/Admin/ExternalAgenda.php
+++ b/library/ZendAfi/Form/Admin/ExternalAgenda.php
@@ -38,6 +38,10 @@ class ZendAfi_Form_Admin_ExternalAgenda extends ZendAfi_Form {
                     'required' => true,
                     'allowEmpty' => false,
                     'title' => $this->_('Le flux doit être au format iCalendar')])
+      ->addElement('checkbox',
+                   'autoharvest',
+                   ['label' => $this->_('Moissonnage automatique'),
+                    ])
 
       ->addElement('comboCategories',
                    'cat_id',
@@ -52,7 +56,7 @@ class ZendAfi_Form_Admin_ExternalAgenda extends ZendAfi_Form {
                    ['label' => $this->_('Lieu'),
                     'multiOptions' => ['0' => $this->_('Aucun')] + Class_Lieu::getAllLibelles()]);
 
-    $elements = ['label', 'url', 'cat_id', 'id_lieu'];
+    $elements = ['label', 'url','autoharvest', 'cat_id', 'id_lieu'];
 
     if (Class_AdminVar::isWorkFlowEnabled()) {
       $this->addElement('radio', 'status',
diff --git a/tests/application/modules/admin/controllers/BatchControllerTest.php b/tests/application/modules/admin/controllers/BatchControllerTest.php
index bfd78004098037b925b467c6140081fa181fd835..bb673bdc9c607a5ed720b9af91ea7038989daf12 100644
--- a/tests/application/modules/admin/controllers/BatchControllerTest.php
+++ b/tests/application/modules/admin/controllers/BatchControllerTest.php
@@ -115,7 +115,8 @@ class BatchControllerAddTest extends BatchControllerTestCase {
             ['AUTOCOMPLETE_RECORD_TITLE', 'Indexer les titres de notice pour l\'autocompletion', true],
             ['AUTOCOMPLETE_RECORD_AUTHOR', 'Indexer les auteurs de notice pour l\'autocompletion', true],
             ['BUILD_SITE_MAP', 'Régénère le sitemap XML', true],
-            ['NOVELTY_FACET', 'Supprimer les facettes de nouveauté périmées', true]];
+            ['NOVELTY_FACET', 'Supprimer les facettes de nouveauté périmées', true],
+            ['EXTERNAL_AGENDA', 'Moissonner les agendas externes',true]];
   }
 
 
diff --git a/tests/db/UpgradeDBTest.php b/tests/db/UpgradeDBTest.php
index 501402b0624aa23fd30a723b1ec5a595c4fa1f26..7a1303bbd2183a2782a3f2993414663f099deff8 100644
--- a/tests/db/UpgradeDBTest.php
+++ b/tests/db/UpgradeDBTest.php
@@ -1658,4 +1658,23 @@ class UpgradeDB_333_Test extends UpgradeDBTestCase {
     $album = $this->query('select url_origine from album where id=' . static::$album_id)->fetch();
     $this->assertEquals(['url_origine' => 'https://export.1dtouch.com/oai'], $album);
   }
+}
+
+
+
+class UpgradeDB_334_Test extends UpgradeDBTestCase {
+  public function prepare() {
+    try {
+      $this->query('ALTER TABLE external_agenda DROP column autoharvest');
+    }
+    catch (Exception $e) {
+    }
+  }
+
+
+
+  /** @test */
+  public function columnAutoharvestShouldBePresent() {
+    $this->assertColumn('external_agenda','autoharvest');
+  }
 }
\ No newline at end of file
diff --git a/tests/scenarios/ExternalAgendas/ExternalAgendasBatchTest.php b/tests/scenarios/ExternalAgendas/ExternalAgendasBatchTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..0bf5fd171e1a76e42500c3a3ddfb6470ce40f450
--- /dev/null
+++ b/tests/scenarios/ExternalAgendas/ExternalAgendasBatchTest.php
@@ -0,0 +1,104 @@
+<?php
+/**
+ * Copyright (c) 2012-2014, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+abstract class Class_Batch_ExternalAgendasBatchTestCase extends ModelTestCase {
+  protected
+    $_storm_default_to_volatile = true,
+    $_log = '',
+    $_debug_log = '';
+
+  public function setUp() {
+    parent::setUp();
+  }
+
+  public function getLogger() {
+    return $this->logger = $this->mock()
+                                ->whenCalled('log')
+                                ->willDo(
+                                         function($message, $target = '') {
+                                           if ($target == 'debug') {
+                                             $this->_debug_log .= $message . "\n";
+                                             return;
+                                           }
+                                           $this->_log .= $message;
+                                         });
+  }
+}
+
+
+
+class Class_Batch_ExternalAgendasBatchSimpleTest extends Class_Batch_ExternalAgendasBatchTestCase {
+  protected $_batch;
+
+  public function setUp() {
+    parent::setUp();
+
+
+    $this->valleiry = $this->fixture('Class_Bib',
+                                     ['id' => 1,
+                                      'libelle' => 'Valleiry']);
+
+    $events_category = $this->fixture('Class_ArticleCategorie',
+                                            ['id' => 1,
+                                             'libelle' => 'Events',
+                                             'bib' => $this->valleiry]);
+
+    $this->fixture('Class_ExternalAgenda',
+                   ['id' => 1,
+                    'label' => 'Personal Agenda',
+                    'url' => 'http://my.server.com/calendar.ics',
+                    'autoharvest' => 0,
+                    'status' => Class_Article::STATUS_VALIDATED,
+                    'category' => $events_category]);
+
+    $this->fixture('Class_ExternalAgenda',
+                   ['id' => 34,
+                    'label' => 'Extra Agenda',
+                    'url' => 'http://external.agenda.com/export?type=ics',
+                    'category' => $events_category,
+                    'id_lieu' => 0,
+                    'autoharvest' => 1,
+                    'status' => Class_Article::STATUS_VALIDATED]);
+
+    Class_WebService_ICalendar::setDefaultHttpClient($this->mock()
+                                                     ->whenCalled('open_url')
+                                                     ->with('http://external.agenda.com/export?type=ics')
+                                                     ->answers(file_get_contents(__DIR__.'/ical-event-second.ics')));
+
+    $this->_batch = new Class_Batch_ExternalAgenda();
+    $this->_batch->setLogger($this->getLogger());
+    $this->_batch->run();
+  }
+
+
+  /** @test */
+  public function labelShouldBeHarvestExternalAgenda() {
+    $this->assertEquals('Moissonner les agendas externes', $this->_batch->getLabel());
+  }
+
+
+  /** @test */
+  public function logShouldDisplayNumberOfCreatedEvents() {
+    $this->assertEquals("Extra Agenda:\nNombre d\'événements créés : 4\nNombre d\'événements mis à jour : 0\n",$this->_log);
+
+  }
+}
diff --git a/tests/scenarios/ExternalAgendas/ExternalAgendasTest.php b/tests/scenarios/ExternalAgendas/ExternalAgendasTest.php
index 6d7f510e48b7e4c454f7db1f6d2810f1eb8b3f44..3d45ce45151c0d5be99a0ee9b567a5bd2c4dc373 100644
--- a/tests/scenarios/ExternalAgendas/ExternalAgendasTest.php
+++ b/tests/scenarios/ExternalAgendas/ExternalAgendasTest.php
@@ -338,6 +338,13 @@ class ExternalAgendasAdminEditWithWorkflowTest extends ExternalAgendasAdminTestC
   }
 
 
+  /** @test */
+  public function checkboxAutomaticHarvestShouldBePresent() {
+    $this->assertXPathContentContains('//form//label[@for="autoharvest"]','Moissonnage automatique');
+    $this->assertXPath('//form//input[@type="checkbox"][@name="autoharvest"]');
+  }
+
+
   /** @test */
   public function titleShouldBeAjouterUnNouvelAgenda() {
     $this->assertXPathContentContains('//h1', 'Modifier un agenda');