diff --git a/VERSIONS_HOTLINE/117324 b/VERSIONS_HOTLINE/117324
new file mode 100644
index 0000000000000000000000000000000000000000..439fc1bb06317ece2434359b1080afc91706729d
--- /dev/null
+++ b/VERSIONS_HOTLINE/117324
@@ -0,0 +1 @@
+ - ticket #117324 : Administration : Ajout de la possibilité de personnaliser la configuration de l'éditeur HTML CKEditor
\ No newline at end of file
diff --git a/VERSIONS_HOTLINE/136049 b/VERSIONS_HOTLINE/136049
new file mode 100644
index 0000000000000000000000000000000000000000..b31d9193cec5b9ac49f32c1a17aa7a2a2bc1eb7a
--- /dev/null
+++ b/VERSIONS_HOTLINE/136049
@@ -0,0 +1 @@
+ - ticket #136049 : Vue notice : Ajout des urls indexpresse à la liste des urls reconnues pour le paramétrage des zones de vignette
\ No newline at end of file
diff --git a/VERSIONS_HOTLINE/136610 b/VERSIONS_HOTLINE/136610
new file mode 100644
index 0000000000000000000000000000000000000000..5df728610dba074a102b40eda2ddb049a592590d
--- /dev/null
+++ b/VERSIONS_HOTLINE/136610
@@ -0,0 +1 @@
+ - ticket #136610 : Activités : Les redacteurs portail avec le droit de diriger une activité doivent avoir la possibilité d'editer un article lié à une activitée
\ No newline at end of file
diff --git a/VERSIONS_HOTLINE/136863 b/VERSIONS_HOTLINE/136863
new file mode 100644
index 0000000000000000000000000000000000000000..3eba8a44ab4881fe20f704897546aabfcb404f2d
--- /dev/null
+++ b/VERSIONS_HOTLINE/136863
@@ -0,0 +1 @@
+ - ticket #136863 : Zotero : Ajout du support de la durée pour les vidéos
\ No newline at end of file
diff --git a/VERSIONS_HOTLINE/137233 b/VERSIONS_HOTLINE/137233
new file mode 100644
index 0000000000000000000000000000000000000000..a7b62ecc314afc3534b055396a88bec2c035c1f5
--- /dev/null
+++ b/VERSIONS_HOTLINE/137233
@@ -0,0 +1 @@
+ - ticket #137233 : Espace mon compte : amélioration des performances
\ No newline at end of file
diff --git a/application/modules/admin/controllers/IndexController.php b/application/modules/admin/controllers/IndexController.php
index 55211656d8462d2bfe57b55db1d2fd4fe7c1a45d..b1718abf6bc8148e0d7ca73b7d0e8ae4a47c9f44 100644
--- a/application/modules/admin/controllers/IndexController.php
+++ b/application/modules/admin/controllers/IndexController.php
@@ -142,8 +142,12 @@ class Admin_IndexController extends ZendAfi_Controller_Action {
     $reader = new Class_Profil_SkinUpdateReader();
     $this->view->reader = $reader;
 
-    if('pull' === $this->_getParam('git'))
+    $git = $this->_getParam('git');
+    if ($git === 'pull')
       $this->_askGitPull($reader);
+
+    if ($git === 'clone')
+      $this->_askGitClone($reader, $this->_getParam('url'));
   }
 
 
@@ -155,4 +159,14 @@ class Admin_IndexController extends ZendAfi_Controller_Action {
     $this->_helper->notify($message);
     $this->_redirect('admin/index/update-skin');
   }
+
+
+  protected function _askGitClone($reader,$skin_name) {
+    $message = $reader->askGitClone($skin_name)
+      ? $this->_('La demande d\'installation a été envoyée au serveur')
+      : $this->_('Erreur : La demande d\'installation n\'a pas pu être envoyée au serveur');
+
+    $this->_helper->notify($message);
+    $this->_redirect('admin/index/update-skin');
+  }
 }
\ No newline at end of file
diff --git a/library/Class/AdminVar.php b/library/Class/AdminVar.php
index 8319f0abc8ef4ba0a41402b4229e9a3555101ff3..6e177da829dcd63e1d523dfff282f0a7b4ccd3ad 100644
--- a/library/Class/AdminVar.php
+++ b/library/Class/AdminVar.php
@@ -437,6 +437,7 @@ Pour vous désabonner de la lettre d\'information, merci de cliquer sur le lien
             'IMPORT_AVIS_OPAC2' => Class_AdminVar_Meta::newOnOff($this->_('Activation de l\'import des avis de l\'opac2')),
             'AGENDA_KEEP_LOCAL_CONTENT' => Class_AdminVar_Meta::newOnOff($this->_('Agenda externe : conserver les évenements modifiés localement')),
             'ENABLED_SEARCH_USER_AGE' => Class_AdminVar_Meta::newOnOff($this->_('Recherche utilisateur : ajouter le filtre d\'age')),
+            'CKEDITOR_CONFIG' => (new Class_AdminVar_CkEditorConfig)->meta(),
 ];
   }
 
diff --git a/library/Class/AdminVar/CkEditorConfig.php b/library/Class/AdminVar/CkEditorConfig.php
new file mode 100644
index 0000000000000000000000000000000000000000..a1637b93461b859c7c70738c915eb4a7cc352da7
--- /dev/null
+++ b/library/Class/AdminVar/CkEditorConfig.php
@@ -0,0 +1,51 @@
+<?php
+/**
+ * Copyright (c) 2012-2021, 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_AdminVar_CkEditorConfig {
+  use Trait_Translator;
+
+  const
+    NAME_KEY = 'name',
+    VALUE_KEY = 'value';
+
+  public function meta() {
+    $fields = [['name' => static::NAME_KEY, 'label' => $this->_('Nom')],
+               ['name' => static::VALUE_KEY, 'label' => $this->_('Valeur')]];
+
+    return Class_AdminVar_Meta::newMultiInput($this->_('Configurations supplémentaires de CKEditor<br>Toute configuration spécifiée dans cette variable remplacera celle éventuellement définie par Bokeh'),
+                                              ['options' => ['fields' => $fields]]);
+  }
+
+
+  public function injectInto($result) {
+    if (!$conf = json_decode(Class_AdminVar::getValueOrDefault('CKEDITOR_CONFIG'), true))
+      return $result;
+
+    if (!isset($conf[static::NAME_KEY]) || !isset($conf[static::VALUE_KEY]))
+      return $result;
+
+    if (!$map = array_combine($conf[static::NAME_KEY], $conf[static::VALUE_KEY]))
+      return $result;
+
+    return array_merge($result, $map);
+  }
+}
diff --git a/library/Class/Catalogue.php b/library/Class/Catalogue.php
index c36a40d719a2ebecc641a97f325c11333fce9e9f..24db05655f4377960dd1afbfa5aef043f61ff05d 100644
--- a/library/Class/Catalogue.php
+++ b/library/Class/Catalogue.php
@@ -25,6 +25,8 @@ class CatalogueLoader extends Storm_Model_Loader {
 
   const DEFAULT_ITEMS_BY_PAGE = 100;
 
+  protected $_indexable_cache;
+
   public function loadNoticesFor($catalogue, $itemsByPage = self::DEFAULT_ITEMS_BY_PAGE, $page = 1, $find_all_params = null) {
     if (null == $catalogue)
       return [];
@@ -45,8 +47,7 @@ class CatalogueLoader extends Storm_Model_Loader {
 
 
   public function findAllCataloguesAIndexer() {
-    return array_filter(Class_Catalogue::findAllBy(['indexer' => true,
-                                                    'order' => 'libelle']),
+    return array_filter(Class_Catalogue::findAllIndexable(),
                         function ($catalogue)
                         {
                           return '' != Class_Catalogue::clausesFor($catalogue);
@@ -54,6 +55,23 @@ class CatalogueLoader extends Storm_Model_Loader {
   }
 
 
+  public function findAllIndexableNotEmpty() {
+    return array_filter(Class_Catalogue::findAllIndexable(),
+                        function($catalog)
+                        {
+                          return !$catalog->isEmpty();
+                        });
+  }
+
+
+  public function findAllIndexable() {
+    return $this->_indexable_cache
+      ? $this->_indexable_cache
+      : $this->_indexable_cache = Class_Catalogue::findAllBy(['indexer' => true,
+                                                              'order' => 'libelle']);
+  }
+
+
   public function getDomainsForBreadcrumb($breadcrumb) {
     $domains_ids = array_filter(explode(';', $breadcrumb));
     $domains = [];
@@ -602,7 +620,7 @@ class CatalogueLoader extends Storm_Model_Loader {
 
 
   public function hasViewableDomain() {
-    return 0 < count(Class_Catalogue::findAllCataloguesAIndexer());
+    return 0 < count(Class_Catalogue::findAllIndexableNotEmpty());
   }
 
 
diff --git a/library/Class/Notice/MetaData.php b/library/Class/Notice/MetaData.php
index b57fa641b4076f1d6b6e58bd2d21e3d317ef5fec..370319b3ee64ad9c7c491db6eb116a3c2f9233e0 100644
--- a/library/Class/Notice/MetaData.php
+++ b/library/Class/Notice/MetaData.php
@@ -198,6 +198,12 @@ class Class_Notice_MetaData_VideoMovie extends Class_Notice_MetaData {
     $script_loader->addMeta('dc.identifier',  $this->_record->getEan());
     return $this;
   }
+
+
+  protected function _addPages($script_loader) {
+    $script_loader->addMeta('Z.runningTime', $this->_record->getCollation());
+    return $this;
+  }
 }
 
 
diff --git a/library/Class/Profil/SkinUpdateReader.php b/library/Class/Profil/SkinUpdateReader.php
index 6ff69a570ab05c52190ddb010c4beaf37049795c..7a5a32479a6094e4c22a859d291c342040581614 100644
--- a/library/Class/Profil/SkinUpdateReader.php
+++ b/library/Class/Profil/SkinUpdateReader.php
@@ -27,7 +27,8 @@ class Class_Profil_SkinUpdateReader {
     Trait_StaticCommand;
 
   public static $skins_update_log = 'skins_update_log.json',
-    $command = 'git pull --rebase';
+    $command_pull = 'git reset --hard HEAD && git pull --rebase',
+    $command_clone = 'git clone';
 
 
   public static function getLogPath() {
@@ -43,25 +44,60 @@ class Class_Profil_SkinUpdateReader {
   public function askGitPull() {
     $data = [];
     foreach($this->getUpdatableSkins() as $skin)
-      $data[$skin->getLabel()] = ['Status' => $this->_('En attente depuis le %s', static::getCurrentDateTime())];
+      $data[$skin->getLabel()] = GIT_REALTIME
+      ? $this->_runGitPullIn($skin->getLabel())
+      : ['Status' => $this->_('En attente depuis le %s', static::getCurrentDateTime())];
 
     return $this->_writeInLog($data, true);
   }
 
 
+  public function askGitClone($skin) {
+    if (GIT_REALTIME)
+      return $this->_runGitClone($skin);
+
+    $data[$skin] = ['Status' => $this->_('En attente depuis le %s', static::getCurrentDateTime()),
+                     'url' => GIT_SKINS . '/' . $skin];
+    return $this->_writeInLog($data, true);
+  }
+
+
   public function runGitPull() {
     if(!$this->_shouldRun())
-      return $this->_('Aucune demande d\'exécution de la commande "%s" trouvée dans le fichier "%s"', static::$command, static::$skins_update_log);
+      return $this->_('Aucune demande d\'exécution de la commande "%s" trouvée dans le fichier "%s"', static::$command_pull, static::$skins_update_log);
 
     $data = [];
     foreach($this->getUpdatableSkins() as $skin) {
       $data[$skin->getLabel()] = ['Status' => $this->_runGitPullIn($skin->getLabel())];
     }
 
+    $data = ($clone_data = $this->runGitClone())
+      ? array_merge( $data , $clone_data)
+      : array_merge( $data , []);
+
     return $this->_writeInLog($data, false);
   }
 
 
+  public function runGitClone() {
+    $data = [];
+    foreach ($this->getJson() as $skin) {
+      if (!is_array($skin))
+        continue;
+
+      if (!in_array('url',array_keys($skin)))
+        continue;
+
+      $label = end(explode('/', $skin['url']));
+      $data[$label] = ['Status' => $this->_runGitClone($skin['url'])];
+    }
+
+    return empty($data)
+      ? false
+      : $this->_writeInLog($data, false);
+  }
+
+
   public function getJson() {
     return json_decode($this->getFileWriter()->getContents(static::getLogPath()), true);
   }
@@ -87,11 +123,7 @@ class Class_Profil_SkinUpdateReader {
   }
 
 
-  protected function _runGitPullIn($folder) {
-    $commands = ['cd ./skins/' . $folder . ' 2>&1',
-                 static::$command . ' 2>&1',
-                 'cd ../../ 2>&1'];
-
+  protected function _runAndLogCommands($commands) {
     $command = implode(' && ', $commands);
     $runner = $this->getCommand();
     $runner->exec($command);
@@ -105,6 +137,23 @@ class Class_Profil_SkinUpdateReader {
   }
 
 
+  protected function _runGitPullIn($folder) {
+    $commands = ['cd ./' . SKINS . '/' . $folder . ' 2>&1',
+                 static::$command_pull . ' 2>&1',
+                 'cd ../../ 2>&1'];
+
+    return $this->_runAndLogCommands($commands);
+  }
+
+
+  protected function _runGitClone($url) {
+    $commands = ['cd ' . SKINS . ' 2>&1',
+                 static::$command_clone . ' ' . $url . ' 2>&1',
+                 'cd ' . ROOT_PATH . ' 2>&1'];
+    return $this->_runAndLogCommands($commands);
+  }
+
+
   protected function _writeInLog($data, $should_run) {
     $data['should_run'] = $should_run;
     return static::getFileWriter()->putContents(static::getLogPath(), json_encode($data));
diff --git a/library/Class/SessionActivity.php b/library/Class/SessionActivity.php
index ce75f2a5c232b7a6aacc85aae3479de5e639c4ed..da6b24742487414fe81c7fee6c0a087a2e195340 100644
--- a/library/Class/SessionActivity.php
+++ b/library/Class/SessionActivity.php
@@ -90,6 +90,11 @@ class SessionActivityLoader extends Storm_Model_Loader {
   }
 
 
+  public function countByArticle($article) {
+    return Class_SessionActivity::countBy(['article_id' => $article->getId()]);
+  }
+
+
   public function findAllNotifiable() {
     if (!$delay = (int)Class_AdminVar::get('ACTIVITY_NOTIFICATION_DELAY'))
       return [];
diff --git a/library/Class/UserGroup.php b/library/Class/UserGroup.php
index 4a64bb4bcde1152062e1b38178fbd29065342479..a7566a5f289cbdcb9a225491c5ef3f4a3e8d926b 100644
--- a/library/Class/UserGroup.php
+++ b/library/Class/UserGroup.php
@@ -623,6 +623,9 @@ class Class_UserGroup extends Storm_Model_Abstract {
 
 
   public function hasParentPermissionOn($permission, $model) {
+    if (!$model)
+      return false;
+
     if ($parent = $model->getPermissionsParent())
       return $this->hasPermissionOn($permission, $parent);
 
@@ -722,4 +725,4 @@ class Class_UserGroup extends Storm_Model_Abstract {
       return $this->_criteria_cache;
     return $this->_criteria_cache = (new Class_UserGroup_Filter($this->getFiltersAsArray()));
   }
-}
\ No newline at end of file
+}
diff --git a/library/ZendAfi/Controller/Plugin/Manager/Article.php b/library/ZendAfi/Controller/Plugin/Manager/Article.php
index 97475c779ebb76cc3918a2188b9a014fd4111cd8..f4459b7f25aae63e82c3ed34b795286798dad233 100644
--- a/library/ZendAfi/Controller/Plugin/Manager/Article.php
+++ b/library/ZendAfi/Controller/Plugin/Manager/Article.php
@@ -206,8 +206,13 @@ class ZendAfi_Controller_Plugin_Manager_Article extends ZendAfi_Controller_Plugi
 
 
   protected function _canEdit($model) {
-    $this->_setParam('id_cat',null);
-    return $this->_canModify($model->getCategorie());
+    $this->_setParam('id_cat', null);
+
+    if ($this->_canModify($model->getCategorie()))
+      return true;
+
+    return Class_SessionActivity::countByArticle($model) > 0
+      && Class_Users::getIdentity()->hasRightDirigerActivity();
   }
 
 
@@ -431,4 +436,4 @@ class ZendAfi_Controller_Plugin_Manager_Article extends ZendAfi_Controller_Plugi
              }]
     ];
   }
-}
\ No newline at end of file
+}
diff --git a/library/ZendAfi/Form/User/Settings.php b/library/ZendAfi/Form/User/Settings.php
index d3ba16f7c29891f70d50eadbd4b511262fc49429..c6007618022f111a18a86bc880698637189abb80 100644
--- a/library/ZendAfi/Form/User/Settings.php
+++ b/library/ZendAfi/Form/User/Settings.php
@@ -54,18 +54,8 @@ class ZendAfi_Form_User_Settings extends ZendAfi_Form {
                    'library_ids',
                    ['label' => $this->_('Mes bibliothèques préférées'),
                     'name' => 'library_ids',
-                    'rubrique' => function()
-                    {
-                      $libraries = Class_Profil::getCurrentProfil()->getLibraries();
-                      return (new Class_Entity())
-                      ->whenCalledDo('getList', function() use ($libraries)
-                    {
-                      $datas = [];
-                      foreach($libraries as $library)
-                        $datas[$library->getId()] = $library->getLabel();
-
-                      return $datas;
-                    });
+                    'rubrique' => function() {
+                       return new ZendAfi_Form_User_Settings_LibrariesSource;
                     },
                     'selected_all_means_nothing' => false])
 
@@ -82,18 +72,8 @@ class ZendAfi_Form_User_Settings extends ZendAfi_Form {
                    'domain_ids',
                    ['label' => $this->_('Mes thèmes préférés'),
                     'name' => 'domain_ids',
-                    'rubrique' => function()
-                    {
-                      $domains = Class_Catalogue::findAllCataloguesAIndexer();
-                      return (new Class_Entity())
-                      ->whenCalledDo('getList',
-                                     function() use ($domains)
-                    {
-                      $datas = [];
-                      foreach($domains as $domain)
-                        $datas[$domain->getId()] = $domain->getLibelle();
-                      return $datas;
-                    });
+                    'rubrique' => function() {
+                       return new ZendAfi_Form_User_Settings_DomainsSource;
                     },
                     'selected_all_means_nothing' => false])
 
@@ -111,4 +91,28 @@ class ZendAfi_Form_User_Settings extends ZendAfi_Form {
   public function setBackUrl($url) {
     $this->setAttrib('data-backurl', $url);
   }
+}
+
+
+
+
+class ZendAfi_Form_User_Settings_LibrariesSource {
+  public function getList() {
+    $datas = [];
+    foreach(Class_Profil::getCurrentProfil()->getLibraries() as $library)
+      $datas[$library->getId()] = $library->getLabel();
+    return $datas;
+  }
+}
+
+
+
+
+class ZendAfi_Form_User_Settings_DomainsSource {
+  public function getList() {
+    $datas = [];
+    foreach(Class_Catalogue::findAllIndexableNotEmpty() as $domain)
+      $datas[$domain->getId()] = $domain->getLibelle();
+    return $datas;
+  }
 }
\ No newline at end of file
diff --git a/library/ZendAfi/Validate/VignetteUrl.php b/library/ZendAfi/Validate/VignetteUrl.php
index 97bf110a54f8d63b3d3c52e3fd314aed783ed9f4..23bc81607dbbc6d07e2f7e5122b312b8a8d51cbd 100644
--- a/library/ZendAfi/Validate/VignetteUrl.php
+++ b/library/ZendAfi/Validate/VignetteUrl.php
@@ -23,13 +23,14 @@ class ZendAfi_Validate_VignetteUrl extends Zend_Validate_Abstract {
   const INVALID_URL_FORMAT = 'invalidURLFormat';
 
   protected
-    $_messageTemplates = [self::INVALID_URL_FORMAT   => "'%value%' n'est pas une URL de vignette correspondant aux formats acceptés."],
+    $_messageTemplates = [self::INVALID_URL_FORMAT => "'%value%' n'est pas une URL de vignette correspondant aux formats acceptés."],
     $_valid_url_patterns = ['.+\.(png|jpg|jpeg|gif)$',
                             '\/cgi-bin\/koha\/opac-image\.pl\?thumbnail=',
                             'www\.adav-assoc\.com\/.*\/GetImage\/',
                             '\/dam_picture.php\?id=',
                             '\/getimage.php\?url_image=',
                             'assets\.edenlivres\.fr',
+                            'vignette\.indexpresse\.fr',
     ];
 
 
diff --git a/library/ZendAfi/View/Helper/Admin/UpdateSkins.php b/library/ZendAfi/View/Helper/Admin/UpdateSkins.php
index 74f60e610d5dc3cc329cadff9c3b93cb1c4e4d92..f530f47175408aca7d5ea3caaae95e8b5813d63a 100644
--- a/library/ZendAfi/View/Helper/Admin/UpdateSkins.php
+++ b/library/ZendAfi/View/Helper/Admin/UpdateSkins.php
@@ -24,17 +24,24 @@ class ZendAfi_View_Helper_Admin_UpdateSkins extends ZendAfi_View_Helper_BaseHelp
 
   public function Admin_UpdateSkins($reader) {
     $skins = $reader->getUpdatableSkins();
-    if(empty($skins)) {
-      return $this->_('Vous n\'avez pas de charte graphique d\'installée dans Bokeh');
-    }
-
-    $html =
-      $this->_tag('p', $this->_('Cette fonctionnalité nécessite la mise en place d\'une tâche plannifiée sur le serveur d\'hébergement pour fonctionner.'))
-      . $this->view->button((new Class_Entity())
-                            ->setText($this->_('Demander la mise à jour'))
-                            ->setUrl($this->view->url(['git' => 'pull']))
-                            ->setImage($this->view->tagImg(Class_Admin_Skin::current()
-                                                           ->getIconUrl('buttons',
+
+    $html = $this->_tag('p', $this->_('Cette fonctionnalité nécessite la mise en place d\'une tâche planifiée sur le serveur d\'hébergement pour fonctionner.'));
+
+    if (Class_Users::isCurrentUserSuperAdmin())
+      $html .= $this->view->renderForm((new ZendAfi_Form)
+                                       ->addElement('text',
+                                                    'url',
+                                                    ['label' => $this->_('Nom du thème à installer'),
+                                                     'size' => 50,
+                                                     'required' => true,
+                                                     'allowEmpty' => false])
+                                       ->setAction('/admin/index/update-skin/git/clone'));
+
+    $html .= $this->view->button((new Class_Entity())
+                                 ->setText($this->_('Demander la mise à jour'))
+                                 ->setUrl($this->view->url(['git' => 'pull']))
+                                 ->setImage($this->view->tagImg(Class_Admin_Skin::current()
+                                                                ->getIconUrl('buttons',
                                                                              'generate'))));
 
     $lis = '';
diff --git a/library/ZendAfi/View/Helper/CkEditor.php b/library/ZendAfi/View/Helper/CkEditor.php
index efcf099b2c39a2d805ad07f851a5a264a68f407d..89aaf68ccef7a06f2db980af59fce631107a1268 100644
--- a/library/ZendAfi/View/Helper/CkEditor.php
+++ b/library/ZendAfi/View/Helper/CkEditor.php
@@ -82,13 +82,14 @@ class ZendAfi_View_Helper_CkEditor extends ZendAfi_View_Helper_BaseHelper {
         'styles' => '*'
       ]
     ];
-    if (($user = Class_Users::getIdentity()) ? $user->isBibliothecaire():false) {
-      $config['allowedContent']=true;
+
+    if (($user = Class_Users::getIdentity()) && $user->isBibliothecaire()) {
+      $config['allowedContent'] = true;
       $config['autoParagraph'] = false;
     }
 
     if (Class_AdminVar::isCmsFormulairesEnabled()) {
-      $config['toolbar'][]=['Form', 'Checkbox', 'Radio', 'TextField', 'Textarea', 'Select', 'Button', 'ImageButton', 'HiddenField'];
+      $config['toolbar'][] = ['Form', 'Checkbox', 'Radio', 'TextField', 'Textarea', 'Select', 'Button', 'ImageButton', 'HiddenField'];
     }
 
     $config['toolbar']=array_merge($config['toolbar'],
@@ -98,6 +99,7 @@ class ZendAfi_View_Helper_CkEditor extends ZendAfi_View_Helper_BaseHelper {
                                     ['NumberedList','BulletedList','-','Outdent','Indent'],
                                     ['JustifyLeft','JustifyCenter','JustifyRight','JustifyBlock']
                                    ]);
+
     $config['kiosk_widget_url'] = Class_Url::assemble(['module' => 'admin',
                                                        'controller' => 'ckeditor',
                                                        'action' => 'get-form',
@@ -105,6 +107,7 @@ class ZendAfi_View_Helper_CkEditor extends ZendAfi_View_Helper_BaseHelper {
                                                        'render' => 'popup']);
     $config['extraPlugins'] = 'colordialog,bokeh_kiosk';
 
+    $config = (new Class_AdminVar_CkEditorConfig)->injectInto($config);
 
     $oCKeditor = new CKeditor(CKBASEURL);
     $oCKeditor->returnOutput = true;
diff --git a/library/startup.php b/library/startup.php
index 192d0490f1b604cfc3c52ef470d3360759e94d95..f58691f3e24e3d5ef19fae1bcf3b7886b5daf354 100644
--- a/library/startup.php
+++ b/library/startup.php
@@ -100,6 +100,8 @@ class Bokeh_Engine {
     defineConstant('USERFILESURL', BASE_URL . '/' . USERFILES . '/');
 
     defineConstant('SKINS', 'skins');
+    defineConstant('GIT_REALTIME', false);
+    defineConstant('GIT_SKINS', 'git://git.afi-sa.net/opac-skins');
 
     defineConstant('PATH_TEMP',  ROOT_PATH . 'temp/');
 
@@ -144,6 +146,7 @@ class Bokeh_Engine {
     defineConstant('THUMBNAIL_FIT_WIDTH_HEIGHT', '500');
 
     defineConstant('PATCH_PATH', ROOT_PATH . '/cosmogramme/sql/patch/');
+    defineConstant('GIT_REALTIME',false);
     return $this;
   }
 
diff --git a/tests/application/modules/admin/controllers/IndexControllerUpdateSkinTest.php b/tests/application/modules/admin/controllers/IndexControllerUpdateSkinTest.php
index 91c117561956ac0adab3c734f46856e11a7d95a8..990e4b025788b0f8f235746201aeb8a91600ae30 100644
--- a/tests/application/modules/admin/controllers/IndexControllerUpdateSkinTest.php
+++ b/tests/application/modules/admin/controllers/IndexControllerUpdateSkinTest.php
@@ -21,6 +21,8 @@
 
 
 abstract class IndexControllerUpdateSkinTestCase extends Admin_AbstractControllerTestCase {
+  protected $_file_writer;
+
   public function setUp() {
     parent::setUp();
 
@@ -32,10 +34,10 @@ abstract class IndexControllerUpdateSkinTestCase extends Admin_AbstractControlle
     $time_source = new TimeSourceForTest('2016-05-02 12:30:00');
     Class_Profil_SkinUpdateReader::setTimeSource($time_source);
 
-    $file_writer = $this->mock();
-    Class_Profil_SkinUpdateReader::setFileWriter($file_writer);
+    $this->_file_writer = $this->mock();
+    Class_Profil_SkinUpdateReader::setFileWriter($this->_file_writer);
 
-    $file_writer
+    $this->_file_writer
       ->whenCalled('getContents')
       ->with(Class_Profil_SkinUpdateReader::getLogPath())
       ->answers(json_encode(['Valence' => ['Status' => '25/04/2016 15:01:37']]))
@@ -79,12 +81,22 @@ class IndexControllerUpdateSkinDispatchTest extends IndexControllerUpdateSkinTes
 
   /** @test */
   public function buttonUpdateSkinShouldBePresent() {
-    $this->assertContains('admin/index/update-skin/git/pull', $this->_response->getBody());
+    $this->assertXPathContentContains('//button[@data-url="/admin/index/update-skin/git/pull"]',
+                                      'Demander la mise à jour');
+  }
+
+
+  /** @test */
+  public function formInstallSkinShouldBePresent() {
+    $this->assertXPathContentContains('//form[@action="/admin/index/update-skin/git/clone"]',
+                                      'Nom du thème à installer');
   }
 }
 
 
 
+
+
 class IndexControllerUpdateSkinGitPullTest extends IndexControllerUpdateSkinTestCase {
   public function setUp() {
     parent::setUp();
@@ -96,4 +108,27 @@ class IndexControllerUpdateSkinGitPullTest extends IndexControllerUpdateSkinTest
   public function askSendShouldBeDisplay() {
     $this->assertFlashMessengerContentContains('La demande de mise à jour a été envoyée au serveur');
   }
+}
+
+
+
+
+class IndexControllerInstallSkinGitCloneTest extends IndexControllerUpdateSkinTestCase {
+  public function setUp() {
+    parent::setUp();
+    $this->_file_writer
+      ->whenCalled('putContents')
+      ->with(Class_Profil_SkinUpdateReader::getLogPath(),
+             '{"Valence":{"Status":"En attente depuis le 2016-05-02 12:30:00","url":"git:\/\/git.afi-sa.net\/opac-skins\/Valence"},"should_run":true}')
+      ->answers(true)
+      ->beStrict();
+
+    $this->dispatch('admin/index/update-skin/git/clone/url/Valence');
+  }
+
+
+  /** @test */
+  public function askInstallShouldBeDisplay() {
+    $this->assertFlashMessengerContentContains('La demande d\'installation a été envoyée au serveur');
+  }
 }
\ No newline at end of file
diff --git a/tests/library/Class/Profil/SkinUpdateReaderTest.php b/tests/library/Class/Profil/SkinUpdateReaderTest.php
index 2d9cd1fa7bc8937ecf0e77fbc5de42a77824d79b..dfd0520c43e71bda543254eee84f8956189cc14c 100644
--- a/tests/library/Class/Profil/SkinUpdateReaderTest.php
+++ b/tests/library/Class/Profil/SkinUpdateReaderTest.php
@@ -42,7 +42,7 @@ class Class_SkinUpdateReaderTest extends ModelTestCase {
 
     $command
       ->whenCalled('exec')
-      ->with('cd ./skins/Valence 2>&1 && git pull --rebase 2>&1 && cd ../../ 2>&1')
+      ->with('cd ./skins/Valence 2>&1 && git reset --hard HEAD && git pull --rebase 2>&1 && cd ../../ 2>&1')
       ->answers('')
 
       ->whenCalled('getOutput')
diff --git a/tests/library/ZendAfi/View/Helper/CkEditorTest.php b/tests/library/ZendAfi/View/Helper/CkEditorTest.php
index 0ec93f33bb446684fb3c4aef8834599a49297db9..364150e8123b2d2c2420da925bf24c411b76b0e3 100644
--- a/tests/library/ZendAfi/View/Helper/CkEditorTest.php
+++ b/tests/library/ZendAfi/View/Helper/CkEditorTest.php
@@ -18,8 +18,6 @@
  * along with BOKEH; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
-require_once 'ViewHelperTestCase.php';
-require_once 'ZendAfi/View/Helper/CkEditor.php';
 
 
 abstract class CkEditorTestCase extends ViewHelperTestCase {
@@ -37,22 +35,18 @@ abstract class CkEditorTestCase extends ViewHelperTestCase {
 
 
 
-class CkEditorWithFormulaireEnabledTest extends CkEditorTestCase {
+class CkEditorWithFormulaireTest extends CkEditorTestCase {
   /** @test **/
-  public function pageShouldContainsFormConfiguration() {
-    Class_AdminVar::newInstanceWithId('CMS_FORMULAIRES')->setValeur(1);
+  public function whenEnabledPageShouldContainsFormConfiguration() {
+    Class_AdminVar::set('CMS_FORMULAIRES', 1);
     $this->_html = $this->_helper->ckeditor('', '', false);
     $this->assertContains('HiddenField', $this->_html);
   }
-}
-
-
 
 
-class CkEditorWithFormulaireDisabledTest extends CkEditorTestCase {
-  /** @test **/
-  public function pageShouldNotContainsFormCongiguration() {
-    Class_AdminVar::newInstanceWithId('CMS_FORMULAIRES')->setValeur(0);
+  /** @test */
+  public function whenDisabledPageShouldNotContainsFormCongiguration() {
+    Class_AdminVar::set('CMS_FORMULAIRES', 0);
     $this->_html = $this->_helper->ckeditor('', '', false);
     $this->assertNotContains('HiddenField', $this->_html);
   }
@@ -61,7 +55,7 @@ class CkEditorWithFormulaireDisabledTest extends CkEditorTestCase {
 
 
 
-class CkEditorViewHelperTest extends CkEditorTestCase {
+class CkEditorViewHelperNotLoggedTest extends CkEditorTestCase {
   public function setUp() {
     parent::setUp();
     $this->_html = $this->_helper->ckeditor('', '', false);
@@ -75,21 +69,21 @@ class CkEditorViewHelperTest extends CkEditorTestCase {
 
 
   /** @test */
-  public function editorShouldNotAllowContent() {
-    $this->assertNotContains('"allowedContent":true', $this->_html);
+  public function editorShouldNotContainsAllowContent() {
+    $this->assertNotContains('"allowedContent"', $this->_html);
   }
 
 
   /** @test */
-  public function editorShouldAddParagraphTags() {
-    $this->assertNotContains('"autoParagraph":false', $this->_html);
+  public function editorShouldNotContainsAutoParagraph() {
+    $this->assertNotContains('"autoParagraph"', $this->_html);
   }
 }
 
 
 
 
-abstract class CkEditorAdminTestCase extends CkEditorTestCase {
+abstract class CkEditorLoggedTestCase extends CkEditorTestCase {
   public function setUp() {
     parent::setUp();
     $annecy = $this->fixture('Class_Bib', ['id' => 1,
@@ -115,7 +109,7 @@ abstract class CkEditorAdminTestCase extends CkEditorTestCase {
 
 
 
-class CkEditorAdminWithoutFileBrowserTest extends CkEditorAdminTestCase {
+class CkEditorAdminWithoutFileBrowserTest extends CkEditorLoggedTestCase {
   public function setUp() {
     parent::setUp();
     $this->_html = $this->_helper->ckeditor('', '', false);
@@ -123,21 +117,33 @@ class CkEditorAdminWithoutFileBrowserTest extends CkEditorAdminTestCase {
 
 
   /** @test */
-  public function shouldAllowContent() {
+  public function editorConfigShouldNotContainsSelectableExtensions() {
+    $this->assertNotContains('/file-manager', $this->_html);
+  }
+
+
+  /** @test */
+  public function editorShouldContainsAllowedContentTrue() {
     $this->assertContains('"allowedContent":true', $this->_html);
   }
 
 
   /** @test */
-  public function editorShouldNotAddParagraphTags() {
+  public function editorShouldContainsAutoParagraphFalse() {
     $this->assertContains('"autoParagraph":false', $this->_html);
   }
+
+
+  /** @test */
+  public function shouldNotContainsFlashButton() {
+    $this->assertNotContains('"Flash",', $this->_html);
+  }
 }
 
 
 
 
-class CkEditorAdminWithFileBrowserTest extends CkEditorAdminTestCase {
+class CkEditorAdminWithFileBrowserTest extends CkEditorLoggedTestCase {
   public function setUp() {
     parent::setUp();
     $this->_html = $this->_helper->ckeditor('', '');
@@ -172,7 +178,7 @@ class CkEditorAdminWithFileBrowserTest extends CkEditorAdminTestCase {
 
 
 
-class CkEditorAdminWithTemplateHistoricTest extends CkEditorAdminTestCase {
+class CkEditorAdminWithTemplateHistoricTest extends CkEditorLoggedTestCase {
   public function setUp() {
     parent::setUp();
     Class_Profil::getCurrentProfil()->setTemplate('');
@@ -205,7 +211,7 @@ class CkEditorAdminWithTemplateHistoricTest extends CkEditorAdminTestCase {
 
 
 
-class CkEditorAdminWithTemplateIntonationTest extends CkEditorAdminTestCase {
+class CkEditorAdminWithTemplateIntonationTest extends CkEditorLoggedTestCase {
   public function setUp() {
     parent::setUp();
 
@@ -258,8 +264,7 @@ class CkEditorAdminWithTemplateIntonationTest extends CkEditorAdminTestCase {
 
 
 
-class CkEditorAdminWithTemplateFromIntonationFamilyTest
-  extends CkEditorAdminTestCase {
+class CkEditorAdminWithTemplateFromIntonationFamilyTest extends CkEditorLoggedTestCase {
 
   protected function _runFor($template_name) {
     Class_Profil::getCurrentProfil()
@@ -293,3 +298,36 @@ class CkEditorAdminWithTemplateFromIntonationFamilyTest
     $this->assertContains('Intonation\/Assets\/css\/intonation.css', $this->_html);
   }
 }
+
+
+
+
+class CkEditorLoggedCustomConfigTest extends CkEditorLoggedTestCase {
+  /** @test */
+  public function withoutCustomConfigEditorShouldNotContainsRemoveFormatTagsFontAndSpan() {
+    Class_AdminVar::set('CKEDITOR_CONFIG', '');
+    $this->_html = $this->_helper->ckeditor('', '');
+    $this->assertNotContains('"removeFormatTags"', $this->_html);
+  }
+
+
+  /** @test */
+  public function withCustomConfigEditorShouldContainsRemoveFormatTagsFontAndSpan() {
+    Class_AdminVar::set('CKEDITOR_CONFIG', '{"name":["removeFormatTags"],"value":["font,span"]}');
+    $this->_html = $this->_helper->ckeditor('', '');
+    $this->assertContains('"removeFormatTags":"font,span"', $this->_html);
+  }
+}
+
+
+
+
+class CkEditorAdminVarTest extends Admin_AbstractControllerTestCase {
+  protected $_storm_default_to_volatile = true;
+
+  /** @test */
+  public function variablesListShouldContainsCkeditorConfig() {
+    $this->dispatch('/admin/index/adminvar');
+    $this->assertXPathContentContains('//td', 'CKEDITOR CONFIG');
+  }
+}
\ No newline at end of file
diff --git a/tests/library/ZendAfi/View/Helper/Notice/VignetteTest.php b/tests/library/ZendAfi/View/Helper/Notice/VignetteTest.php
index c64b8fd1fceefc4c2cb883c16597e06d56687b5f..53102952966e737a3c6219861f56a7d574ddf222 100644
--- a/tests/library/ZendAfi/View/Helper/Notice/VignetteTest.php
+++ b/tests/library/ZendAfi/View/Helper/Notice/VignetteTest.php
@@ -134,6 +134,7 @@ class ZendAfi_View_Helper_Notice_VignetteNoThumbnailTest
             ['http://www.adAv-Assoc.com/4DACTION/GetImage/207155'],
             ['https://pmb.somewhere.net/pmb/getimage.php?url_image=http%3A%2F%2Fimages.server.com%2Fimages%2FP%2F!!isbn!!.08.MZZZZZZZ.jpg&noticecode=9782204134156&vigurl='],
             ['https://assets.edenlivres.fr/medias/5a/7d085242b36baea096417183b7c42d792b065b.jpg?h=-&amp;w=65'],
+            ['http://vignette.indexpresse.fr/vignetteB.asp?not=5124973'],
     ];
   }
 
diff --git a/tests/scenarios/Activities/ActivityArticleTest.php b/tests/scenarios/Activities/ActivityArticleTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..14967f21fa81b1b1d50d56d119770b9a19b70e36
--- /dev/null
+++ b/tests/scenarios/Activities/ActivityArticleTest.php
@@ -0,0 +1,173 @@
+<?php
+/**
+ * Copyright (c) 2021, 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
+ */
+
+
+/* hotline: #136610 */
+abstract class ActivityArticleWithRightTestCase
+  extends Admin_AbstractControllerTestCase {
+
+  protected
+    $_storm_default_to_volatile = true,
+    $_linked_article;
+
+  public function setUp() {
+    parent::setUp();
+    Class_AdminVar::set('ACTIVITY', '1');
+
+    $activity = $this->fixture(Class_Activity::class,
+                               ['id' => 333,
+                                'libelle' => 'Festival annimation']);
+
+    $session_activity = $this->fixture(Class_SessionActivity::class,
+                                       ['id' => 444,
+                                        'activity' => $activity,
+                                        'date_debut' => '2012-03-27',
+                                        'date_fin' => '2012-03-29',
+                                        'duree'=> 8,
+                                        'horaires' => '9h - 12h, 13h - 18h',
+                                        'intervenants' => [],
+                                       ]);
+    $activity
+      ->setSessions([$session_activity])
+      ->assertSave();
+
+    $this->_linked_article = $session_activity->getArticle();
+    $this->_doBeforeDispatch();
+    $this->dispatch('/admin/cms/edit/id/' . $this->_linked_article->getId());
+  }
+
+
+  protected function _doBeforeDispatch() {
+  }
+}
+
+
+
+
+class ActivityArticleRightModoPortailAndDirigerActivityTest
+  extends ActivityArticleWithRightTestCase {
+
+  protected function _doBeforeDispatch() {
+    $logged_user = $this->fixture(Class_Users::class,
+                                  ['id' => 6,
+                                   'login' => 'totoro',
+                                   'password' => '123456'])
+                        ->beModoPortail()
+                        ->setUserGroups([$this->fixture(Class_UserGroup::class,
+                                                        ['id' => 20,
+                                                         'libelle' => 'Redacteur portail'])
+                                         ->addRightDirigerActivity()
+                                         ]);
+    ZendAfi_Auth::getInstance()->logUser($logged_user);
+  }
+
+
+  /** @test */
+  public function modoPortailShouldNotBeRedirect() {
+    $this->assertNotRedirect();
+  }
+
+
+  /** @test */
+  public function modoPortailShouldHaveRightToEditAcitvityArticle() {
+    $this->assertXPath('//input[@id="titre"][@value="Festival annimation"]');
+  }
+}
+
+
+
+
+class ActivityArticleRightModoPortailAndNotDirigerActivityTest
+  extends ActivityArticleWithRightTestCase {
+
+  protected function _doBeforeDispatch() {
+    $logged_user = $this->fixture(Class_Users::class,
+                                  ['id' => 6,
+                                   'login' => 'totoro',
+                                   'password' => '123456'])
+                        ->beModoPortail()
+                        ->setUserGroups([$this->fixture(Class_UserGroup::class,
+                                                        ['id' => 20,
+                                                         'libelle' => 'Redacteur portail'])
+                                         ]);
+    ZendAfi_Auth::getInstance()->logUser($logged_user);
+  }
+
+
+  /** @test */
+  public function modoPortailShouldBeRedirect() {
+    $this->assertRedirect();
+  }
+
+
+  /** @test */
+  public function flashMessengerShouldBeVousNavezPasLaPermission() {
+    $this->assertFlashMessengerContentContains('Vous n\'avez pas la permission');
+  }
+}
+
+
+
+
+class ActivityArticleRightModoPortailAndPermissionCreateArticleTest
+  extends ActivityArticleWithRightTestCase {
+
+  protected function _doBeforeDispatch() {
+    $category = $this->fixture(Class_ArticleCategorie::class,
+                               ['id' => 555,
+                                'libelle' => 'Categorie']);
+
+    $this->fixture(Class_Permission::class,
+                   ['id' => 333,
+                    'module' => 'ARTICLE',
+                    'code' => 'ARTICLE']);
+
+    $this->_linked_article
+      ->setCategorie($category)
+      ->assertSave();
+
+    $user_group = $this->fixture(Class_UserGroup::class,
+                                 ['id' => 20,
+                                  'libelle' => 'Redacteur portail']);
+
+    $logged_user = $this->fixture(Class_Users::class,
+                                  ['id' => 6,
+                                   'login' => 'totoro',
+                                   'password' => '123456'])
+                        ->beModoPortail()
+                        ->setUserGroups([$user_group]);
+
+    Class_Permission::createArticle()->permitTo($user_group, $category);
+    ZendAfi_Auth::getInstance()->logUser($logged_user);
+  }
+
+
+  /** @test */
+  public function modoPortailShouldNotBeRedirect() {
+    $this->assertNotRedirect();
+  }
+
+
+  /** @test */
+  public function modoPortailShouldHaveRightToEditActivityArticle() {
+    $this->assertXPath('//input[@id="titre"][@value="Festival annimation"]');
+  }
+}
diff --git a/tests/scenarios/Zotero/ZoteroTest.php b/tests/scenarios/Zotero/ZoteroTest.php
index b5ab428a51962a7cdaaf30fe4c09766a5c828204..b7338815a9eca75b7b05e4ee43b7d25da0e88f1d 100644
--- a/tests/scenarios/Zotero/ZoteroTest.php
+++ b/tests/scenarios/Zotero/ZoteroTest.php
@@ -222,6 +222,7 @@ class ZoteroVideoTest extends ZoteroTestCase {
             ['dc.creator', 'Cleese, John'],
             ['dc.creator', 'Coltrane, Robbie'],
             ['dc.creator', 'Davis, Warwick'],
+            ['Z.runningTime', '2 DVD vidéo monoface zone 2 (2 h 27 min) ; coul.'],
             ['Z.volume', 1]
     ];
   }