diff --git a/FEATURES/135407 b/FEATURES/135407
new file mode 100644
index 0000000000000000000000000000000000000000..c3a775e3f0d44234f493d011a5a61080f4e7d98d
--- /dev/null
+++ b/FEATURES/135407
@@ -0,0 +1,10 @@
+        '135407' =>
+            ['Label' => $this->_('Sélection multiple des boites'),
+             'Desc' => $this->_('Administration : Il est maintenant possible de faire de la sélection multiple de boites dans les profils qui utilisent un thème du magasin'),
+             'Image' => '',
+             'Video' => 'https://youtu.be/YyVDgqUNCV0',
+             'Category' => $this->_('Administration'),
+             'Right' => function($feature_description, $user) {return true;},
+             'Wiki' => 'https://wiki.bokeh-library-portal.org/index.php?title=Boites_-_Traitement_par_lot',
+             'Test' => '',
+             'Date' => '2021-12-01'],
\ No newline at end of file
diff --git a/VERSIONS_HOTLINE/115719 b/VERSIONS_HOTLINE/115719
new file mode 100644
index 0000000000000000000000000000000000000000..83ddab7eb5718e4a987d83b97334da1855d74ae7
--- /dev/null
+++ b/VERSIONS_HOTLINE/115719
@@ -0,0 +1 @@
+ - ticket #115719 : Facettes dynamiques : corrections de la génération de facettes qui dans certains cas ne pouvait aller au delà de 503 entrées
\ No newline at end of file
diff --git a/VERSIONS_HOTLINE/128200 b/VERSIONS_HOTLINE/128200
new file mode 100644
index 0000000000000000000000000000000000000000..ce51880ff4465579696ebe07328fcbcd6546a866
--- /dev/null
+++ b/VERSIONS_HOTLINE/128200
@@ -0,0 +1 @@
+ - ticket #128200 : Gestion des favoris en résultat de recherche : correction de la fermeture / réouverture de la popup selon le résultat de la validation du formulaire
\ No newline at end of file
diff --git a/VERSIONS_HOTLINE/137145 b/VERSIONS_HOTLINE/137145
new file mode 100644
index 0000000000000000000000000000000000000000..91da8f51e96417d2ec8fb98012f13477ab73d67a
--- /dev/null
+++ b/VERSIONS_HOTLINE/137145
@@ -0,0 +1 @@
+ - ticket #137145 : Avis : ne plus afficher d'avis en doublons
\ No newline at end of file
diff --git a/VERSIONS_HOTLINE/138089 b/VERSIONS_HOTLINE/138089
new file mode 100644
index 0000000000000000000000000000000000000000..5e640ccbd9363c612c7e799ba122152bcd459c26
--- /dev/null
+++ b/VERSIONS_HOTLINE/138089
@@ -0,0 +1 @@
+ - ticket #138089 : les articles peuvent prendre ds ids de notices déjà existantes.
\ No newline at end of file
diff --git a/VERSIONS_HOTLINE/143141 b/VERSIONS_HOTLINE/143141
new file mode 100644
index 0000000000000000000000000000000000000000..f0623663b7b77cff3c15a4ea63e138d459eea11c
--- /dev/null
+++ b/VERSIONS_HOTLINE/143141
@@ -0,0 +1 @@
+ - ticket #143141 : Administration : Album nettoyer les documents PNB pour supprimer les facettes associées à des codifications supprimées
\ No newline at end of file
diff --git a/VERSIONS_HOTLINE/143652 b/VERSIONS_HOTLINE/143652
new file mode 100644
index 0000000000000000000000000000000000000000..8ada5da373617a2905e5024db2a9562985b3c22e
--- /dev/null
+++ b/VERSIONS_HOTLINE/143652
@@ -0,0 +1,2 @@
+ - ticket #143652 : Magasin de thèmes : Affichage d'informations complémentaires sur les sélections lorsqu'Inspecteur Gadget est activé.
+   	  	    Correction du fonctionnement avec le rendu de liste à interactions.
\ No newline at end of file
diff --git a/VERSIONS_HOTLINE/143811 b/VERSIONS_HOTLINE/143811
new file mode 100644
index 0000000000000000000000000000000000000000..955e0de49fe3e4cdbaa3e937d8a88711bb739d23
--- /dev/null
+++ b/VERSIONS_HOTLINE/143811
@@ -0,0 +1 @@
+ - ticket #143811 : Magasin de thèmes : Correction du tri aléatoire dans les carousels de notices.
\ No newline at end of file
diff --git a/VERSIONS_HOTLINE/145083 b/VERSIONS_HOTLINE/145083
new file mode 100644
index 0000000000000000000000000000000000000000..b17fdb923845d31dcf3c60d0a83ddceb8e3ee3a5
--- /dev/null
+++ b/VERSIONS_HOTLINE/145083
@@ -0,0 +1 @@
+ - ticket #145083 : Magasin de Thèmes : Les articles affichés dans une boite libre respectent maintenant le statut de workflow
\ No newline at end of file
diff --git a/VERSIONS_HOTLINE/145531 b/VERSIONS_HOTLINE/145531
new file mode 100644
index 0000000000000000000000000000000000000000..f29743a3f098538a36c43f27344701d82a29fd76
--- /dev/null
+++ b/VERSIONS_HOTLINE/145531
@@ -0,0 +1 @@
+ - ticket #145531 : Barre de recherche : problème affichage des bibliothèques
\ No newline at end of file
diff --git a/VERSIONS_WIP/135407 b/VERSIONS_WIP/135407
new file mode 100644
index 0000000000000000000000000000000000000000..7daf7080b576b1a9f43140cc7cabd9ca457ee420
--- /dev/null
+++ b/VERSIONS_WIP/135407
@@ -0,0 +1 @@
+ - ticket #135407 : Administration : Il est maintenant possible de faire de la sélection multiple de boites dans les profils qui utilisent un thème du magasin.
\ No newline at end of file
diff --git a/application/modules/admin/controllers/ProfilController.php b/application/modules/admin/controllers/ProfilController.php
index 53f764def7a540bc211e7ee39fd561cb2e774bb5..145ceaff2ba8c6f56bdc045537df78d4f354b5ef 100644
--- a/application/modules/admin/controllers/ProfilController.php
+++ b/application/modules/admin/controllers/ProfilController.php
@@ -28,7 +28,11 @@ class Admin_ProfilController extends ZendAfi_Controller_Action {
 
 
   public function getPlugins() {
-    return ['ZendAfi_Controller_Plugin_Versionning_Profile'];
+    $plugins = [ZendAfi_Controller_Plugin_Versionning_Profile::class];
+    if ( ! Class_Template::current()->isLegacy())
+      $plugins [] = ZendAfi_Controller_Plugin_MultiSelection_Widget::class;
+
+    return $plugins;
   }
 
 
diff --git a/application/modules/admin/controllers/UploadController.php b/application/modules/admin/controllers/UploadController.php
index 8aaf562b3e0e140f077fa34d3dd010acaeb7f06a..11d369491ae45a1b8071ff4bac47b01f623dde48 100644
--- a/application/modules/admin/controllers/UploadController.php
+++ b/application/modules/admin/controllers/UploadController.php
@@ -71,6 +71,8 @@ $(document).ready(function () {
 
 
       $result = $model->addFile($this->_request);
+      if ($modelClass == 'Class_Album')
+        $model->updateDateMaj();
       $model->save();
       $model->index();
       $this->_helper->json($result);
diff --git a/application/modules/opac/controllers/AbonneController.php b/application/modules/opac/controllers/AbonneController.php
index 28eefc4e706ddb52acdbfdf261babb8c7da41faa..e1dead96884d90c6b0927ef6fe6540c3e443e542 100644
--- a/application/modules/opac/controllers/AbonneController.php
+++ b/application/modules/opac/controllers/AbonneController.php
@@ -1161,24 +1161,27 @@ class AbonneController extends ZendAfi_Controller_Action {
 
 
   public function manageSettingsAction() {
-    if ($this->_request->isPost() && !$this->_getParam('library_ids')
-        && Class_AdminVar::isModuleEnabled('ENABLE_BOOKMARKABLE_LIBRARIES')) {
-      $this->_helper->notify($this->_('Au moins une bibliothèque favorite doit être sélectionnée!'));
-      return $this->_redirectToReferer();
-    }
+    $this->view->titre = $this->view->_('Gérer mes favoris');
+    $this->view->form = $form = ZendAfi_Form_User_Settings::forUser($this->_user);
 
-    if ($this->_request->isPost())
-      return $this->_saveUserPreferencesWithPost();
+    if (!$this->_request->isPost())
+      return;
 
-    $this->view->form = $form = ZendAfi_Form_User_Settings::forUser($this->_user);
-    $this->view->titre = $this->view->_('Gérer mes favoris');
+    if (Class_AdminVar::isModuleEnabled('ENABLE_BOOKMARKABLE_LIBRARIES')
+        &&
+        !$this->_getParam('library_ids'))
+      return $form
+        ->getElement('library_ids')
+        ->addError($this->_('Au moins une bibliothèque favorite doit être sélectionnée!'));
+
+    return $this->_saveUserPreferencesWithPost();
   }
 
 
   public function managePreferencesAction() {
     return $this->_request->isPost()
       ? $this->_saveUserPreferencesWithPost()
-      : $this->_redirectToReferer();
+      : $this->_redirectClose($this->_getReferer());
   }
 
 
@@ -1191,8 +1194,7 @@ class AbonneController extends ZendAfi_Controller_Action {
 
     Class_User_Settings::clearCache();
     $this->_helper->notify($this->_('Mes paramètres ont bien été sauvegardé'));
-
-    return $this->_redirectToReferer();
+    return $this->_redirectClose($this->_getReferer());
   }
 
 
diff --git a/library/Class/Album.php b/library/Class/Album.php
index dcf0a0e5563fe832c69dc127c2267923d1a7dcbb..a80d18ae9e853febe3be9ec82eeb5377595699b9 100644
--- a/library/Class/Album.php
+++ b/library/Class/Album.php
@@ -180,7 +180,6 @@ class Class_Album extends Storm_Model_Abstract {
   protected
     $_marc,
     $path_flash,
-    $_date_maj_enabled = true,
     $_authors;
 
 
@@ -992,7 +991,6 @@ class Class_Album extends Storm_Model_Abstract {
   public function beforeSave() {
     $this
       ->updateThumbnailFromMedia()
-      ->updateDateMaj()
       ->collectAuthors()
       ->collectEditors()
       ->collectCollections()
@@ -1000,13 +998,6 @@ class Class_Album extends Storm_Model_Abstract {
   }
 
 
-  public function saveWithoutDateMaj() {
-    $this->_date_maj_enabled = false;
-    $this->save();
-    $this->_date_maj_enabled = true;
-  }
-
-
   public function collectAuthors() {
     $authors = $this->_collect('authors', self::AUTHOR_FIELD);
     $this->_authors = null;
@@ -1051,9 +1042,6 @@ class Class_Album extends Storm_Model_Abstract {
 
 
   public function updateDateMaj() {
-    if (!$this->_date_maj_enabled)
-      return $this;
-
     return $this->setDateMaj(date('Y-m-d H:i:s', self::getTimeSource()->time()));
   }
 
diff --git a/library/Class/Article/Loader.php b/library/Class/Article/Loader.php
index 42ab4c257f46181d76779fac01295cccc67f8973..d7e28b34147c8329e35d027b75740282c49d63e4 100644
--- a/library/Class/Article/Loader.php
+++ b/library/Class/Article/Loader.php
@@ -27,6 +27,7 @@ class Class_Article_Loader extends Storm_Model_Loader {
     ORDER_COMMENTS_ASC = 'CommentCountAsc',
     ORDER_COMMENTS = 'CommentCount';
 
+  protected static $_select_tool;
 
   protected $_all_articles,
     $_filter_by_local_callback;
@@ -94,12 +95,15 @@ class Class_Article_Loader extends Storm_Model_Loader {
    * @return array
    */
   public function getArticlesByPreferences($preferences) {
-    $preferences = array_merge($this->getArticlesByPreferencesDefaults(),
-                               $preferences);
+    $preferences =
+      array_intersect_key($preferences,
+                          $this->getArticlesByPreferencesDefaults());
 
-    $select = Class_AdminVar::isModuleEnabled('ENABLE_ARTICLES_TIMINGS')
-      ? new Class_Article_SelectWithTimings($this)
-      : new Class_Article_Select($this);
+    $preferences =
+      array_merge($this->getArticlesByPreferencesDefaults(),
+                  $preferences);
+
+    $select = $this->_getSelectTool();
 
     $articles = $select->findAll($preferences);
     $articles = $this->_filterByCustomFields($articles,
@@ -268,4 +272,24 @@ class Class_Article_Loader extends Storm_Model_Loader {
   public function setFilterByLocalCallback($callback) {
     $this->_filter_by_local_callback = $callback;
   }
+
+
+  protected function _getSelectTool() {
+    if ( static::$_select_tool)
+      return static::$_select_tool;
+
+    return Class_AdminVar::isModuleEnabled('ENABLE_ARTICLES_TIMINGS')
+      ? new Class_Article_SelectWithTimings($this)
+      : new Class_Article_Select($this);
+  }
+
+
+  public static function setSelectTool($select_tool) {
+    static::$_select_tool = $select_tool;
+  }
+
+
+  public static function reset() {
+    static::$_select_tool = null;
+  }
 }
diff --git a/library/Class/Article/Select.php b/library/Class/Article/Select.php
index 191a7dab3649de8ea9f0315659b60f5bfe1a1a1a..991bbee957e389fe5b6c1dddedfd5bd25e481303 100644
--- a/library/Class/Article/Select.php
+++ b/library/Class/Article/Select.php
@@ -50,8 +50,9 @@ class Class_Article_Select {
     if ($this->_sort_order == Class_Article_Loader::ORDER_SELECTION && !$this->_has_selection)
       return [];
 
-    $select = $this->_buildSelect();
-    return Class_Article::findAll($select);
+    return ($select = $this->_buildSelect())
+      ? Class_Article::findAll($select)
+      : [];
   }
 
 
diff --git a/library/Class/Bib.php b/library/Class/Bib.php
index c170d7e7de1212199ca839ed7fa626b485348725..ebba4601314ded48d88ddd977f61152001fe6b9f 100644
--- a/library/Class/Bib.php
+++ b/library/Class/Bib.php
@@ -208,6 +208,8 @@ class BibLoader extends Storm_Model_Loader {
     foreach(Class_Bib::findAllWithData() as $library)
       $facets [Class_Bib::CODE_FACETTE . $library->getId()] = $library->getLibelle();
 
+    asort($facets, SORT_NATURAL | SORT_FLAG_CASE);
+    array_unshift($facets, $this->_('toutes'));
     return $facets;
   }
 
diff --git a/library/Class/CodifAnnexe.php b/library/Class/CodifAnnexe.php
index 69b8c74c589d721cc6353ff2386aa8a397770b27..dddd7c36dbad46d5ca64d5eb3dea6a3cf67e5865 100644
--- a/library/Class/CodifAnnexe.php
+++ b/library/Class/CodifAnnexe.php
@@ -63,7 +63,7 @@ class CodifAnnexeLoader extends Storm_Model_Loader {
     foreach ($annexes as $annexe)
       $multi_options_facets[$annexe->getFacetCode()] = $annexe->getLabel();
 
-    asort($multi_options_facets);
+    asort($multi_options_facets, SORT_NATURAL | SORT_FLAG_CASE);
     array_unshift($multi_options_facets, $this->_('tous'));
     return $multi_options_facets;
   }
diff --git a/library/Class/CodifThesaurus.php b/library/Class/CodifThesaurus.php
index 839b7d3d7ee840f492cfa7da1848f0bd7e3acee7..62f47cbe199208800c8087f0a8e27e1f3db8f8dd 100644
--- a/library/Class/CodifThesaurus.php
+++ b/library/Class/CodifThesaurus.php
@@ -815,7 +815,7 @@ class Class_CodifThesaurus extends Storm_Model_Abstract {
     $empty_key = str_repeat('0', self::ID_KEY_LENGTH);
 
     $this->checkAttribute('id_thesaurus',
-                          $my_key != $empty_key,
+                          $my_key !== $empty_key,
                           $this->_('Nombre maximum d\'élément à ce niveau déjà atteint (%s)',
                                    str_repeat('Z', self::ID_KEY_LENGTH)));
   }
diff --git a/library/Class/Cosmogramme/Integration/PhasePseudoRecord.php b/library/Class/Cosmogramme/Integration/PhasePseudoRecord.php
index e5040754c2fc215c8b0e6d0aaba2c597ded71469..0eccce87b05fd6760475817cbc1b1294be77d2e5 100644
--- a/library/Class/Cosmogramme/Integration/PhasePseudoRecord.php
+++ b/library/Class/Cosmogramme/Integration/PhasePseudoRecord.php
@@ -108,6 +108,11 @@ abstract class Class_Cosmogramme_Integration_PhasePseudoRecord
       return;
     }
 
+    if ($model->getTypeDocId() != $old_record->getTypeDoc()) {
+      $this->_incrementCount(Class_Cosmogramme_Integration_Phase::RECORD_INSERT);
+      return;
+    }
+
     $this->_incrementCount(Class_Cosmogramme_Integration_Phase::RECORD_FULLUPDATE);
   }
 
diff --git a/library/Class/Indexation/PseudoNotice.php b/library/Class/Indexation/PseudoNotice.php
index fa21644fba8b640397820153371c84df770373e3..5156769caa5244238ad11a7f7c0af6dbc66338f1 100644
--- a/library/Class/Indexation/PseudoNotice.php
+++ b/library/Class/Indexation/PseudoNotice.php
@@ -28,7 +28,6 @@ class Class_Indexation_PseudoNotice {
     $_table,
     $_id,
     $_label,
-    $_id_notice = 'ID_NOTICE',
     $_notice,
     $_exemplaire,
     $_type_doc;
@@ -88,9 +87,30 @@ class Class_Indexation_PseudoNotice {
 
 
   protected function _updateLinkToRecord() {
-    $this->_model
-      ->updateAttributes([$this->_id_notice => $this->_notice->getId()])
-      ->save();
+    if ($this->getModelRecordId() == $this->_notice->getId())
+      return true;
+
+    $this->setModelRecordId($this->_notice->getId())
+         ->setModelRecordAssocation($this->_notice);
+
+    return $this->_model->save();
+  }
+
+
+  public function getModelRecordId(){
+    return $this->_model->getIdNotice();
+  }
+
+
+  public function setModelRecordId($id){
+    $this->_model->setIdNotice($id);
+    return $this;
+  }
+
+
+  public function setModelRecordAssocation($record){
+    $this->_model->setNotice($record);
+    return $this;
   }
 
 
@@ -159,14 +179,33 @@ class Class_Indexation_PseudoNotice {
 
 
   protected function _ensureRecord() {
-    if ($this->_notice = $this->_model->getNotice())
-      return $this->_notice->setTypeDoc($this->_type_doc)->save();
+    if ( ! $this->_notice = $this->_model->getNotice()) {
+      $this->_notice = Class_Notice::newInstance(['type_doc' => $this->_type_doc]);
+      return $this->_notice->save();
+    }
+
+    if ((string) $this->_type_doc == (string) $this->_notice->getTypeDoc())
+      return true;
+
+    if ($this->_couldUpdateRecord()) {
+      $this->_notice->setTypeDoc($this->_type_doc);
+      return $this->_notice->save();
+    }
+
+    if ( ! $this->_notice->getFirstExemplaire())
+      $this->_notice->delete();
 
     $this->_notice = Class_Notice::newInstance(['type_doc' => $this->_type_doc]);
+
     return $this->_notice->save();
   }
 
 
+  protected function _couldUpdateRecord(){
+    return ! $this->_notice->isSigb();
+  }
+
+
   public function getAuthorsNames() {
     $names = [];
     foreach($authors = $this->extractAuthors() as $author)
@@ -400,8 +439,24 @@ class Class_Indexation_PseudoNotice_Album extends Class_Indexation_PseudoNotice{
   protected
     $_model_name = Class_Album::class,
     $_id = 'id',
-    $_label = 'm4',
-    $_id_notice = 'notice_id';
+    $_label = 'm4';
+
+
+  public function getModelRecordId(){
+    return $this->_model->getNoticeId();
+  }
+
+
+  public function setModelRecordId($id){
+    $this->_model->setNoticeId($id);
+    return $this;
+  }
+
+
+  public function setModelRecordAssocation($record){
+    $this->_model->setNotice($record);
+    return $this;
+  }
 
 
   public function __construct($model) {
@@ -430,13 +485,6 @@ class Class_Indexation_PseudoNotice_Album extends Class_Indexation_PseudoNotice{
   }
 
 
-  protected function _updateLinkToRecord() {
-    $this->_model
-      ->setNoticeId($this->_notice->getId())
-      ->saveWithoutDateMaj();
-  }
-
-
   protected function _modelIdAcceptVisitor($visitor) {}
 
 
@@ -603,6 +651,23 @@ class Class_Indexation_PseudoNotice_Cms extends Class_Indexation_PseudoNotice{
   }
 
 
+  public function getModelRecordId(){
+    return $this->_model->getIdNotice();
+  }
+
+
+  public function setModelRecordId($id){
+    $this->_model->setIdNotice($id);
+    return $this;
+  }
+
+
+  public function setModelRecordAssocation($record){
+    $this->_model->setNotice($record);
+    return $this;
+  }
+
+
   public function delete() {
     (new Class_Indexation_Model_WithManyDomains($this->_model))->unindex();
     parent::delete();
diff --git a/library/Class/Migration/AlbumChangeCodif.php b/library/Class/Migration/AlbumChangeCodif.php
new file mode 100644
index 0000000000000000000000000000000000000000..260c3ce9136bad9296b445be30138bd4b72e987b
--- /dev/null
+++ b/library/Class/Migration/AlbumChangeCodif.php
@@ -0,0 +1,100 @@
+<?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_Migration_AlbumChangeCodif {
+
+  protected $_cat_id,
+    $_codif_name,
+    $_id_origin,
+    $_id_replace,
+    $_map_attrib_class = [
+                          'genre' => Class_CodifGenre::class,
+                          'sections' => Class_CodifSection::class,
+                          'annexes' => Class_Bib::class,
+                          'matiere' => Class_CodifMatiere::class
+    ];
+
+  public function __construct($cat_id, $codif_name, $id_origin, $id_replace) {
+    $this->_cat_id = $cat_id;
+    $this->_codif_name = $codif_name;
+    $this->_id_origin = $id_origin;
+    $this->_id_replace = $id_replace;
+  }
+
+
+  public function run() : string {
+    try {
+      if (!$this->_cat_id
+          || !$category = Class_AlbumCategorie::find($this->_cat_id))
+        return "Category can't be found with id: " . $this->_cat_id;
+
+      if (!$this->_codif_name
+          || !isset($this->_map_attrib_class[$this->_codif_name]))
+        return sprintf("Codif %s doesn't exist", $this->_codif_name);
+
+      $codif = $this->_map_attrib_class[$this->_codif_name];
+      if (!$this->_id_origin
+          || $codif::find($this->_id_origin))
+        return sprintf("%s with id: %s should not exists", $codif, $this->_id_origin);
+
+      if (!$this->_id_replace
+          || !$codif::find($this->_id_replace))
+        return sprintf("%s can't be found with id: %s", $codif, $this->_id_replace);
+
+      $albums = Class_Album::findAllBy(['cat_id' => $category
+                                        ->getSousCategoriesIds()]);
+
+      $count = 0;
+      foreach ($albums as $album)
+        $count += $this->_withAlbumDo($album);
+
+      return sprintf('Number of album updated: %s / %s, for category id: %s',
+                     $count, count($albums), $this->_cat_id);
+    } catch (Exception $e) {
+      return 'An exception has occured with stack trace: ' . $e;
+    }
+  }
+
+
+  protected function _withAlbumDo(Class_Album $album) : int {
+    $list_codif =
+      array_map(
+                function ($codif_id) {
+                  return $this->_id_origin == $codif_id
+                    ? $this->_id_replace
+                    : $codif_id;
+                },
+                explode(';',
+                        $album->callGetterByAttributeName($this->_codif_name)));
+
+    $album->callSetterByAttributeName($this->_codif_name,
+                                      implode(';', $list_codif));
+
+    if ($album->hasChangedAttribute($this->_codif_name)) {
+      $album->save();
+      $album->index();
+      return 1;
+    }
+
+    return 0;
+  }
+}
diff --git a/library/Class/Migration/AlbumClearCodif.php b/library/Class/Migration/AlbumClearCodif.php
index fc39244914f8db2599b8fca6c6c0f7fb0e075141..8d320e83e14e35afd929bafbb412b35549a892e5 100644
--- a/library/Class/Migration/AlbumClearCodif.php
+++ b/library/Class/Migration/AlbumClearCodif.php
@@ -24,7 +24,13 @@ class Class_Migration_AlbumClearCodif {
 
   protected
     $_cat_id,
-    $_list_codif_status;
+    $_list_codif_status,
+    $_map_attrib_class = [
+                          'genre' => Class_CodifGenre::class,
+                          'sections' => Class_CodifSection::class,
+                          'annexes' => Class_Bib::class,
+                          'matiere' => Class_CodifMatiere::class
+    ];
 
   public function __construct($cat_id) {
     $this->_cat_id = $cat_id;
@@ -32,146 +38,108 @@ class Class_Migration_AlbumClearCodif {
   }
 
 
-  public function run() {
+  public function run() : string {
     try {
       if (!$this->_cat_id
           || !$category = Class_AlbumCategorie::find($this->_cat_id))
-        return;
+        return "Category can't be found with id: " . $this->_cat_id;
 
-      $albums = Class_Album::findAllBy(['cat_id' => $category->getSousCategoriesIds()]);
-      array_map(function ($album) {
-        $this->_withAlbumDo($album);
-      }, $albums);
+      $albums = Class_Album::findAllBy(['cat_id' => $category
+                                        ->getSousCategoriesIds()]);
 
-      (new Storm_Cache())->clean();
+      $count = 0;
+      foreach ($albums as $album)
+        $count += $this->_withAlbumDo($album);
+
+      return sprintf('Number of album updated: %s / %s, for category id: %s',
+                     $count, count($albums), $this->_cat_id);
     } catch (Exception $e) {
-      return;
+      return 'An exception has occured with stack trace: ' . $e;
     }
   }
 
 
-  protected function _withAlbumDo($album) {
-    $must_save = $this->_checkGenre($album);
-    $must_save |= $this->_checkSection($album);
-    $must_save |= $this->_checkAnnexe($album);
-    $must_save |= $this->_checkMatiere($album);
+  protected function _withAlbumDo(Class_Album $album) : int {
+    $has_changed = false;
+    foreach ($this->_map_attrib_class as $attrib => $class_name) {
+      $this->_checkCodif($album, $attrib, $class_name);
+      $has_changed |= $album->hasChangedAttribute($attrib);
+    }
 
-    if ($must_save) {
+    if ($has_changed) {
       $album->save();
       $album->index();
     }
-  }
-
-
-  protected function _checkGenre($album) {
-    return $this->_checkCodif($album, 'genre', Class_CodifGenre::class);
-  }
-
-
-  protected function _checkSection($album) {
-    return $this->_checkCodif($album, 'sections', Class_CodifSection::class);
-  }
-
-
-  protected function _checkAnnexe($album) {
-    return $this->_checkCodif($album, 'annexes', Class_CodifAnnexe::class);
-  }
 
-
-  protected function _checkMatiere($album) {
-    return $this->_checkCodif($album, 'matiere', Class_CodifMatiere::class);
+    return $has_changed ? 1 : 0;
   }
 
 
-  protected function _checkCodif($album, $attrib, $class_name) {
-    $is_updated = false;
-    $list_codif = [];
-    foreach (explode(';', $album->$attrib) as $codif_id)
-      $is_updated |= $this->_isUpdatedListCodif($class_name, $codif_id, $list_codif);
+  protected function _checkCodif(Class_Album $album,
+                                 string $attrib,
+                                 string $class_name) {
+    $list_codif = array_filter(explode(';', $album->$attrib),
+                               function ($codif_id) use ($class_name) {
+                                 return $this->_existForClassName($class_name,
+                                                                  $codif_id);
+                               });
 
-    if ($is_updated)
-      $album->$attrib = implode(';', $list_codif);
+    $album->$attrib = implode(';', $list_codif);
 
-    return $is_updated;
+    return $this;
   }
 
 
-  protected function _isUpdatedListCodif($class_name, $codif_id, &$list_codif) {
-    $valid = $this->_existForClassName($class_name, $codif_id);
-    if ($valid)
-      $list_codif [] = $codif_id;
-
-    return !$valid;
-  }
+  protected function _existForClassName(string $class_name,
+                                        string $codif_id) : bool {
+    if (!$codif_id)
+      return false;
 
-
-  protected function _existForClassName($class_name, $codif_id) {
     $codif_for_type = $this->_list_codif_status
       ->detect(function ($codif_status) use ($class_name, $codif_id) {
         return $class_name == $codif_status->getClassName()
           && $codif_id == $codif_status->getCodifId();
       });
 
-    if (!$codif_for_type)
-      $codif_for_type = $this->_addNewCodif($class_name, $codif_id);
+    if (!$codif_for_type) {
+      $codif_for_type = (new AlbumClearCodif_CodifStatus($class_name, $codif_id));
+      $this->_list_codif_status->add($codif_for_type);
+    }
 
     return $codif_for_type->isExist();
   }
-
-
-  protected function _addNewCodif($type, $codif_id) {
-    $new_codif = (new CodifStatus($type, $codif_id))->init();
-    if ($new_codif->isValid())
-      $this->_list_codif_status->add($new_codif);
-
-    return $new_codif;
-  }
 }
 
 
 
 
-class CodifStatus {
+class AlbumClearCodif_CodifStatus {
 
   protected
     $_exist = false,
     $_class_name,
     $_codif_id;
 
-  public function __construct($class_name, $codif_id) {
+  public function __construct(string $class_name, string $codif_id) {
     $this->_class_name = $class_name;
     $this->_codif_id = $codif_id;
-  }
-
-
-  public function isValid() {
-    return $this->_class_name && $this->_codif_id;
-  }
-
-
-  public function init() {
-    if (!$this->isValid())
-      return $this;
-
     $codif = $this->_class_name::find($this->_codif_id);
     if ($codif)
       $this->_exist = true;
-
-    return $this;
   }
 
 
-  public function getClassName() {
+  public function getClassName() : string {
     return $this->_class_name;
   }
 
 
-  public function getCodifId() {
+  public function getCodifId() : string {
     return $this->_codif_id;
   }
 
 
-  public function isExist() {
+  public function isExist() : bool {
     return $this->_exist;
   }
 }
diff --git a/library/Class/MultiSelection/Abstract.php b/library/Class/MultiSelection/Abstract.php
index 120705ace2b72809ec63409afbce94c437f67fdb..4eae6938efe7bfe8c6efdd8c5a86ee19603eaa32 100644
--- a/library/Class/MultiSelection/Abstract.php
+++ b/library/Class/MultiSelection/Abstract.php
@@ -24,6 +24,9 @@ abstract class Class_MultiSelection_Abstract {
   use Trait_Translator;
 
 
+  protected $_ajax = false;
+
+
   public static function isSelected($id) {
     $instance = new static();
     return $instance->_getStorage()->contains($id);
@@ -85,6 +88,17 @@ abstract class Class_MultiSelection_Abstract {
   }
 
 
+  public function beAjax() {
+    $this->_ajax = true;
+    return $this;
+  }
+
+
+  public function isAjax() {
+    return $this->_ajax;
+  }
+
+
   public function acceptWidgetVisitor($visitor) {
     return $this;
   }
diff --git a/library/Class/MultiSelection/Widget.php b/library/Class/MultiSelection/Widget.php
new file mode 100644
index 0000000000000000000000000000000000000000..5a5db5415d76bf7a9d47837b3a9ac31ce435ef81
--- /dev/null
+++ b/library/Class/MultiSelection/Widget.php
@@ -0,0 +1,130 @@
+<?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_MultiSelection_Widget extends Class_MultiSelection_Abstract {
+
+  public function getModelIdsFromCategory($id) {
+    return [];
+  }
+
+
+  public function getModels() {
+    return
+      Class_Systeme_Widget_Widget::withMixedIdsDo($this->getValues(),
+                                                  function($collection, $widget)
+                                                  {
+                                                    $collection->add($widget);
+                                                  });
+  }
+
+
+  /* ZendAfi_View_Helper_Plugin_MultiSelection_Widget $visitor */
+  public function acceptWidgetVisitor($visitor) {
+    $models = $this->getModels();
+    $count = $models->count();
+
+    $visitor
+      ->visitTitle($this->_('Sélection multiple de boites'))
+
+      ->visitCount($this->_plural($count,
+                                  '',
+                                  'Vous avez sélectionné 1 boite',
+                                  'Vous avez sélectionné %s boites',
+                                  $count))
+
+      ->visitEditSelection($this->_plural($count,
+                                          '',
+                                          'Modifier la boite sélectionnée',
+                                          'Modifier les %s boites sélectionnées',
+                                          $count))
+
+      ->visitShowSelection($this->_plural($count,
+                                          '',
+                                          'Voir la boite sélectionnée',
+                                          'Voir la liste des %s boites sélectionnées',
+                                          $count))
+
+      ->visitClearSelection($this->_plural($count,
+                                           '',
+                                           'Déselectionner la boite sélectionnée',
+                                           'Déselectionner les %s boites sélectionnées',
+                                           $count))
+
+      ->visitDeleteSelection($this->_plural($count,
+                                            '',
+                                            'Supprimer la boite sélectionnée',
+                                            'Supprimer les %s boites sélectionnées',
+                                            $count))
+
+      ->visitSelectedItems($models->getArrayCopy())
+
+      ->visitTitleKey('titre')
+      ->visitStrategy('widget')
+      ->visitCategoryLabel(function($model)
+                           {
+                             return $this->_('%s de la %s du profil %s',
+                                             $model->getTypeLabel(),
+                                             strtolower($model->getDivisionLabel()),
+                                             $model->getProfileOrParentProfileId());
+                           });
+    return $this;
+  }
+
+
+  public function acceptActionsVisitor($visitor) {
+    $visitor
+      ->visitControllerName('profil')
+
+      ->visitLeafId(function($model)
+                    {
+                      return $model->getIdWithProfile();
+                    })
+
+      ->visitLeafCondition(function($model)
+                           {
+                             return $this->isSelected($model->getIdWithProfile());
+                           })
+
+      ->visitNodeCondition(function($model)
+                           {
+                             return $this->isCategorySelected($model->getId());
+                           })
+
+      ->visitAddLeaf(function($model)
+                     {
+                       return $this->_('Ajouter %s à la sélection de boites',
+                                       $model->getTitre());
+                     })
+
+      ->visitRemoveLeaf(function($model)
+                        {
+                          return $this->_('Supprimer %s de la sélection de boites',
+                                          $model->getTitre());
+                        });
+    return $this;
+  }
+
+
+  protected function _getStorage() {
+    return new Class_MultiSelection_SessionStorage('widget');
+  }
+}
\ No newline at end of file
diff --git a/library/Class/Systeme/ModulesAccueil.php b/library/Class/Systeme/ModulesAccueil.php
index e579ab575afc20d91db5da167703ccfc3b83dc73..c2a0fc5d37a03e6de99d00e85bfca80ff3e3c614 100644
--- a/library/Class/Systeme/ModulesAccueil.php
+++ b/library/Class/Systeme/ModulesAccueil.php
@@ -30,6 +30,7 @@ class Class_Systeme_ModulesAccueil extends Class_Systeme_ModulesAbstract {
 
   protected static
     $_modules,
+    $_divisions,
     $_instance;
 
 
@@ -106,6 +107,21 @@ class Class_Systeme_ModulesAccueil extends Class_Systeme_ModulesAbstract {
   }
 
 
+  public function getDivisions() {
+    if (null !== static::$_divisions)
+      return static::$_divisions;
+
+    return static::$_divisions =
+      [Class_Profil::DIV_FIRST_SIDE => $this->_('Division gauche'),
+       Class_Profil::DIV_MAIN => $this->_('Division principale'),
+       Class_Profil::DIV_SECOND_SIDE => $this->_('Division droite'),
+       Class_Profil::DIV_BANNIERE => $this->_('Division bannière'),
+       Class_Profil::DIV_FLOTANTTE => $this->_('Division flottante'),
+       Class_Profil::DIV_FOOTER => $this->_('Division pied de page')
+      ];
+  }
+
+
   /** @return array */
   public static function getModules() {
     if (null !== static::$_modules)
@@ -173,7 +189,23 @@ class Class_Systeme_ModulesAccueil extends Class_Systeme_ModulesAbstract {
   }
 
 
+  public function getLabel($type) {
+    $modules = static::getModules();
+    return isset($modules[$type])
+      ? $modules[$type]->getLibelle()
+      : '';
+  }
+
+
   public function getViewHelper($type) {
     return $this->getModuleByCode($type)->getViewHelper();
   }
+
+
+  public function getDivisionLabel($id) {
+    $divisions = $this->getDivisions();
+    return isset($divisions[$id])
+      ? $divisions[$id]
+      : '';
+  }
 }
diff --git a/library/Class/Systeme/Widget/Abstract.php b/library/Class/Systeme/Widget/Abstract.php
index 59eba39c4c06c222f9e9b902907397568447cd5b..dd9c7199e569574a8a51f7b31cadc0e77f7128c2 100644
--- a/library/Class/Systeme/Widget/Abstract.php
+++ b/library/Class/Systeme/Widget/Abstract.php
@@ -183,7 +183,9 @@ abstract class Class_Systeme_Widget_Abstract extends Class_Entity {
 
 
   public function getResourcesDefinition() {
-    return $this->get('ResourcesDefinition');
+    return ($ressources_definition = $this->get('ResourcesDefinition'))
+      ? $ressources_definition
+      : (new Class_Systeme_ModulesAccueil_Null);
   }
 
 
diff --git a/library/Class/Systeme/Widget/Widget.php b/library/Class/Systeme/Widget/Widget.php
index 8663f1537a328fcbe1b1662bf008fa77df49d49a..a980b6a16be53d579927f5412da843f162d01e36 100644
--- a/library/Class/Systeme/Widget/Widget.php
+++ b/library/Class/Systeme/Widget/Widget.php
@@ -111,4 +111,127 @@ class Class_Systeme_Widget_Widget extends Class_Systeme_Widget_Abstract {
       ->setNewDatas(['division' => $section])
       ->_update();
   }
+
+
+  public function getIdWithProfile() {
+    return sprintf('%s-%s',
+                   $this->getProfileOrParentProfileId(),
+                   $this->getId());
+  }
+
+
+  public function getProfileOrParentProfileId() {
+    if ( !$division = $this->getdivision())
+      return $this->getProfileId();
+
+    return in_array((int) $division, [Class_Profil::DIV_BANNIERE,
+                                      Class_Profil::DIV_FOOTER])
+      ? $this->_getParentProfileId()
+      : $this->getProfileId();
+  }
+
+
+  protected function _getParentProfileId() {
+    return $this->getProfile()->hasParentProfil()
+      ? $this->getProfile()->getParentId()
+      : $this->getProfileId();
+  }
+
+
+  public function getdivision() {
+    return $this->get('division');
+  }
+
+
+  public function getDivisionLabel() {
+    return (new Class_Systeme_ModulesAccueil)
+      ->getDivisionLabel($this->getdivision());
+  }
+
+
+  public function getTypeLabel() {
+    return (new Class_Systeme_ModulesAccueil)
+      ->getLabel($this->getModuleType());
+  }
+
+
+  public static function deleteBy($ids) {
+    isset($ids[ZendAfi_Controller_Plugin_MultiSelection_Widget::MODEL_ID_KEY])
+      ? static::withMixedIdsDo($ids[ZendAfi_Controller_Plugin_MultiSelection_Widget::MODEL_ID_KEY],
+                               function($collection, $widget)
+                               {
+                                 $widget->delete();
+                               })
+      : null;
+  }
+
+
+  public static function withMixedIdsDo($ids, $callback) {
+    $collection = new Storm_Collection;
+
+    if ( ! $ids )
+      return $collection;
+
+    if ( ! $callback )
+      return $collection;
+
+    foreach($ids as $mixed_id)
+      static::withMixedIdDo($mixed_id, $callback, $collection);
+
+    return $collection;
+  }
+
+
+  public static function withMixedIdDo($mixed_id, $callback, $collection) {
+    $ids_as_array = explode('-', $mixed_id);
+
+    if ( ! $profile_id = array_shift($ids_as_array) )
+      return;
+
+    if ( ! $widget_id = array_shift($ids_as_array) )
+      return;
+
+    if ( ! $widget = (new Class_Systeme_Widget_Widget)
+        ->setId($widget_id)
+        ->setProfileId($profile_id)
+        ->load())
+      return;
+
+    $callback($collection, $widget);
+  }
+
+
+  public function delete() {
+    return $this
+      ->getProfile()
+      ->removeBoiteFromDiv($this->getId(),
+                           $this->getdivision())->save();
+  }
+
+
+  public function isValid() {
+    return true;
+  }
+
+
+  public function validate() {
+    return true;
+  }
+
+
+  public function save() {
+    return $this->_update();
+  }
+
+
+  public function updateAttributes($data) {
+    $this
+      ->setNewDatas($data);
+    return parent::updateAttributes($data);
+  }
+
+
+  public function isCarousel() {
+    return $this->getResourcesDefinition() instanceOf Intonation_Library_Widget_Carousel_Definition;
+  }
 }
diff --git a/library/Class/TypeDoc.php b/library/Class/TypeDoc.php
index d181336e6ec93e4ec96b9d7c378aa29b05326e38..586ff9fc86e825986cf066c2d4fc8dc5d0f5c217 100644
--- a/library/Class/TypeDoc.php
+++ b/library/Class/TypeDoc.php
@@ -50,8 +50,15 @@ class TypeDocLoader extends Class_CosmoVar_ModelLoader  {
   }
 
 
+  /** For Tests */
+  public function setUsedIdsCache($idscache){
+    return $this->_used_ids_cache = $idscache;
+  }
+
+
   public function reset() {
     $this->_all_instances = null;
+    $this->_used_ids_cache = null;
     return $this;
   }
 
@@ -146,6 +153,7 @@ class TypeDocLoader extends Class_CosmoVar_ModelLoader  {
   }
 
 
+
   public function getLabelFor($type_doc_id) {
     return ($type_doc = Class_TypeDoc::find($type_doc_id))
       ? $type_doc->getLabel()
@@ -195,12 +203,19 @@ class TypeDocLoader extends Class_CosmoVar_ModelLoader  {
 
   public function getMultiOptionsFacets() {
     $multi_options = $this->getMultiOptions();
-    $multi_options_facets = ['' => $this->_('tous')];
+
+    $multi_options_facets=[];
+
     foreach ($multi_options as $id => $label) {
       if (!$doctype = Class_TypeDoc::find($id))
         continue;
       $multi_options_facets[$doctype->getFacetCode()] = $doctype->getLabel();
     }
+    if (!$multi_options_facets)
+      return;
+
+    asort($multi_options_facets, SORT_NATURAL | SORT_FLAG_CASE);
+    array_unshift($multi_options_facets, $this->_('tous'));
 
     return $multi_options_facets;
   }
diff --git a/library/ZendAfi/Controller/Plugin/Manager/Album.php b/library/ZendAfi/Controller/Plugin/Manager/Album.php
index fba9ace95424eabf2f664bc350c32a6fa52ef692..8fb2be3d8a6b0b50529ec853dcc44b3dfcdd6f10 100644
--- a/library/ZendAfi/Controller/Plugin/Manager/Album.php
+++ b/library/ZendAfi/Controller/Plugin/Manager/Album.php
@@ -245,6 +245,12 @@ class ZendAfi_Controller_Plugin_Manager_Album extends ZendAfi_Controller_Plugin_
   }
 
 
+  protected function _doBeforeSave($album) {
+    $album->updateDateMaj();
+    return parent::_doBeforeSave($album);
+  }
+
+
   public function sortressourcesAction() {
     $album = Class_Album::find((int)$this->_getParam('id'));
     $album->sortRessourceByFileName()->save();
diff --git a/library/ZendAfi/Controller/Plugin/MultiSelection/Abstract.php b/library/ZendAfi/Controller/Plugin/MultiSelection/Abstract.php
index 9a57a23ad1e639b85f367a67064b095bd7e96f5e..a1e3f3935551155d43c59328d53d5955ff2e9425 100644
--- a/library/ZendAfi/Controller/Plugin/MultiSelection/Abstract.php
+++ b/library/ZendAfi/Controller/Plugin/MultiSelection/Abstract.php
@@ -44,20 +44,25 @@ abstract class ZendAfi_Controller_Plugin_MultiSelection_Abstract extends ZendAfi
     $values = $this->_getValuesFromParams();
     $limit = (int) Class_AdminVar::getValueOrDefault('LIMIT_MULTIPLE_SELECTION');
 
-    if($this->_getMultiSelection()->isFullWith($values, $limit)) {
+    $multi_selection = $this->_getMultiSelection()->beAjax();
+
+    if($multi_selection->isFullWith($values, $limit)) {
       return $this->_helper->json(['selection' => $this->renderWidget(),
                                    'response' => $this->_('Il n\'est pas possible de sélectionner plus de %d éléments',
                                                           $limit)]);
     }
 
-    $this->_multi_selection = $this->_getMultiSelection()->addValues($values);
+    $this->_multi_selection = $multi_selection->addValues($values);
     return $this->_helper->json(['selection' => $this->renderWidget(),
                                  'response' => 'ok']);
   }
 
 
   public function removeModelFromSelectionAjaxAction() {
-    $this->_getMultiSelection()->removeValues($this->_getValuesFromParams());
+    $this
+      ->_getMultiSelection()
+      ->beAjax()
+      ->removeValues($this->_getValuesFromParams());
 
     return $this->_helper->json(['selection' => $this->renderWidget(),
                                  'response' => 'ok']);
@@ -87,7 +92,7 @@ abstract class ZendAfi_Controller_Plugin_MultiSelection_Abstract extends ZendAfi
                                    $this->_pluralizeModelName());
 
     if ($this->_setupFormAndUpdateModels($models->getArrayCopy())) {
-      $this->_helper->notify($this->_('Les %d %s sélectionnés ont bien été sauvegardés',
+      $this->_helper->notify($this->_('Les %d %s sélectionné(e)s ont bien été sauvegardé(e)s',
                                       $count,
                                       $this->_pluralizeModelName()));
       $this->_redirectToReferer();
@@ -168,7 +173,9 @@ abstract class ZendAfi_Controller_Plugin_MultiSelection_Abstract extends ZendAfi
     $custom_form = $this->_getCustomFieldForm($model_values);
     $custom_form->populate($this->getCustomValues());
     $custom_form->updateModelValues();
-    $model_values->save();
+
+    if ($model_values)
+      $model_values->save();
 
     $this->_doAfterSave($model);
     return true;
diff --git a/library/ZendAfi/Controller/Plugin/MultiSelection/AbstractActions.php b/library/ZendAfi/Controller/Plugin/MultiSelection/AbstractActions.php
index b1eeb78d1067814dfd2e44f6b9756d1f144e719a..b113dd66d251efc2a9265f60a453e87b0953ed40 100644
--- a/library/ZendAfi/Controller/Plugin/MultiSelection/AbstractActions.php
+++ b/library/ZendAfi/Controller/Plugin/MultiSelection/AbstractActions.php
@@ -21,11 +21,28 @@
 
 
 abstract class ZendAfi_Controller_Plugin_MultiSelection_AbstractActions {
+  protected
+    $_leaf_id,
+    $_add_leaf,
+    $_remove_leaf,
+    $_add_node,
+    $_remove_node,
+    $_leaf_condition,
+    $_node_condition,
+    $_controller_name;
+
+
   public function __construct($multi_selection) {
     $multi_selection->acceptActionsVisitor($this);
   }
 
 
+  public function visitLeafId($callback) {
+    $this->_leaf_id = $callback;
+    return $this;
+  }
+
+
   public function visitAddLeaf($callback) {
     $this->_add_leaf = $callback;
     return $this;
@@ -69,5 +86,4 @@ abstract class ZendAfi_Controller_Plugin_MultiSelection_AbstractActions {
 
 
   abstract public function getActions();
-}
-?>
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/library/ZendAfi/Controller/Plugin/MultiSelection/LeafActions.php b/library/ZendAfi/Controller/Plugin/MultiSelection/LeafActions.php
index f39a5f6c77855a4553ab00a93d9be2170660f8ef..0ce3e3a26fbf955db9e313f03c53c2b56012da6b 100644
--- a/library/ZendAfi/Controller/Plugin/MultiSelection/LeafActions.php
+++ b/library/ZendAfi/Controller/Plugin/MultiSelection/LeafActions.php
@@ -22,29 +22,47 @@
 
 class ZendAfi_Controller_Plugin_MultiSelection_LeafActions extends ZendAfi_Controller_Plugin_MultiSelection_AbstractActions{
   public function getActions() {
-    return [
-            ['url' => ['controller' => $this->_controller_name,
-                       'action' => 'remove-model-from-selection',
-                       'select_id' => '%s'],
-             'anchorOptions' => ['onclick' => 'addMultiSelection(\'selected-%s\');return false;',
-                                 'class' => 'selected-%s'],
-             'icon'  => 'cancel',
-             'label' => $this->_remove_leaf,
-             'condition' => $this->_leaf_condition],
-
-            ['url' => ['controller' => $this->_controller_name,
-                       'action' => 'add-model-to-selection',
-                       'select_id' => '%s'],
-             'icon'  => 'basket',
-             'label' => $this->_add_leaf,
-
-             'anchorOptions' => ['onclick' => 'addMultiSelection(\'selected-%s\');return false;',
-                                 'class' => 'selected-%s'],
-
-             'condition' => function($model)
-              {
-                return !call_user_func($this->_leaf_condition, $model);
-              }]];
-  }
+    $remove =
+      [ZendAfi_View_Helper_RenderModelAction::MODEL_ID => $this->_leaf_id,
+
+       ZendAfi_View_Helper_RenderModelAction::URL =>
+       ['controller' => $this->_controller_name,
+        'action' => 'remove-model-from-selection',
+        'select_id' => '%s'],
+
+       ZendAfi_View_Helper_RenderModelAction::ANCHOR_OPTIONS =>
+       ['onclick' => 'addMultiSelection(\'selected-%s\');return false;',
+        'class' => 'selected-%s'],
+
+       ZendAfi_View_Helper_RenderModelAction::ICON  => 'cancel',
+
+       ZendAfi_View_Helper_RenderModelAction::LABEL => $this->_remove_leaf,
+
+       ZendAfi_View_Helper_RenderModelAction::CONDITION => $this->_leaf_condition
+      ];
+
+    $add =
+      [ZendAfi_View_Helper_RenderModelAction::MODEL_ID => $this->_leaf_id,
+
+       ZendAfi_View_Helper_RenderModelAction::URL =>
+       ['controller' => $this->_controller_name,
+        'action' => 'add-model-to-selection',
+        'select_id' => '%s'],
+
+       ZendAfi_View_Helper_RenderModelAction::ANCHOR_OPTIONS =>
+       ['onclick' => 'addMultiSelection(\'selected-%s\');return false;',
+        'class' => 'selected-%s'],
+
+       ZendAfi_View_Helper_RenderModelAction::ICON  => 'basket',
+
+       ZendAfi_View_Helper_RenderModelAction::LABEL => $this->_add_leaf,
+
+       ZendAfi_View_Helper_RenderModelAction::CONDITION => function($model)
+        {
+          return !call_user_func($this->_leaf_condition, $model);
+        }
+      ];
+
+  return [$remove, $add];
 }
-?>
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/library/ZendAfi/Controller/Plugin/MultiSelection/Widget.php b/library/ZendAfi/Controller/Plugin/MultiSelection/Widget.php
new file mode 100644
index 0000000000000000000000000000000000000000..c396ae9a66363081225f16a181b3196dcfc38b18
--- /dev/null
+++ b/library/ZendAfi/Controller/Plugin/MultiSelection/Widget.php
@@ -0,0 +1,128 @@
+<?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 ZendAfi_Controller_Plugin_MultiSelection_Widget extends ZendAfi_Controller_Plugin_MultiSelection_Abstract {
+
+  const MODEL_ID_KEY = 'profile_id_minus_widget_id';
+
+
+  public function __construct($controller) {
+    parent::__construct($controller);
+    $this->_multi_selection = new Class_MultiSelection_Widget;
+  }
+
+
+  public function getActions($model) {
+    if (!$model)
+      return [];
+
+    if ( $model instanceOf Class_Profil)
+      return (new ZendAfi_Controller_Plugin_MultiSelection_NodeActions($this->_multi_selection))
+        ->getActions();
+
+    return (new ZendAfi_Controller_Plugin_MultiSelection_LeafActions($this->_multi_selection))
+        ->getActions();
+  }
+
+
+  public function render() {
+    return (('accueil' == $this->_request->getActionName())
+        || ('edit-multiple' == $this->_request->getActionName()))
+      ? $this->renderWidget()
+      : '';
+  }
+
+
+  protected function _getModelLoader() {
+    return Class_Systeme_Widget_Widget::class;
+  }
+
+
+  protected function _getModelId() {
+    return static::MODEL_ID_KEY;
+  }
+
+
+  protected function _pluralizeModelName() {
+    return $this->_('boites');
+  }
+
+
+  protected function _getDefaultModel($models) {
+    return (new Class_Systeme_Widget_Widget)
+      ->setForm($this->_getFormClass($models));
+  }
+
+
+  protected function _getFormClass($models) {
+    $defaults_widget = (new Storm_Collection($models))
+      ->reject(function($widget)
+               {
+                 return $widget->isCarousel();
+               });
+
+    return $defaults_widget->isEmpty()
+      ? Intonation_Library_Widget_Carousel_Form::class
+      : ZendAfi_Form_Configuration_Widget_Base::class;
+  }
+
+
+  protected function _getForm($model) {
+    $form = $model->getFormInstance();
+    $form->removeElement('order');
+    $form->removeElement('data_sources');
+    $form->removeDisplayGroup('selection_group');
+    return $form;
+  }
+
+
+  protected function processMulticheckboxFromPost($form, $post, $clean = false) {
+    return $clean
+      ? $form->deleteUnchanged($post)
+      : $post;
+  }
+
+
+  protected function _doBeforeSave($widget) {
+    return;
+  }
+
+
+  protected function _doAfterSave($widget) {
+    return;
+  }
+
+
+  protected function _getCustomFieldModelValues($model) {
+    return;
+  }
+
+
+  protected function _getCustomFieldForm($model_values) {
+    return new ZendAfi_Form_Admin_CustomFields_ModelValues(['model_values' => $model_values]);
+  }
+
+
+  protected function getCustomValues() {
+    return [];
+  }
+}
\ No newline at end of file
diff --git a/library/ZendAfi/Form/Admin/CustomFields/ModelValues.php b/library/ZendAfi/Form/Admin/CustomFields/ModelValues.php
index 3d324c009c62df66cd3fddf2f786031f6d58d213..0f80c4d91257c66907759095ec9e72a276af06aa 100644
--- a/library/ZendAfi/Form/Admin/CustomFields/ModelValues.php
+++ b/library/ZendAfi/Form/Admin/CustomFields/ModelValues.php
@@ -27,7 +27,9 @@ class ZendAfi_Form_Admin_CustomFields_ModelValues extends ZendAfi_Form {
     $_model_values;
 
   public function setModel_Values($model_values) {
-    $this->_model_values = $model_values;
+    if ( ! $this->_model_values = $model_values)
+      return;
+
     $this->setAttrib('data-backurl',
                      Class_Url::assemble($this->_model_values->getEditURL()));
   }
@@ -36,6 +38,9 @@ class ZendAfi_Form_Admin_CustomFields_ModelValues extends ZendAfi_Form {
   public function init() {
     parent::init();
 
+    if ( ! $this->_model_values)
+      return;
+
     if (!$values = $this->_model_values->getFieldValues())
       return;
 
diff --git a/library/ZendAfi/Form/Element/ProfileComposition.php b/library/ZendAfi/Form/Element/ProfileComposition.php
index 85626e9abfe2c6774f74e25ddb8b9242d3244d18..478a48ab0aeb7b20cc85b7ab69f2d20aab9fbffb 100644
--- a/library/ZendAfi/Form/Element/ProfileComposition.php
+++ b/library/ZendAfi/Form/Element/ProfileComposition.php
@@ -31,5 +31,4 @@ class ZendAfi_Form_Element_ProfileComposition extends Zend_Form_Element {
       $this->_decorators[$name] = $value;
     $this->removeDecorator('ViewHelper');
   }
-}
-?>
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/library/ZendAfi/View/Helper/FonctionsAdmin/Boite.php b/library/ZendAfi/View/Helper/FonctionsAdmin/Boite.php
index 698b9ac66298dbfe87a93a0e32c7fd6c641557d2..c4245f4c3aea476bb57f57ebfeef556aeec826c1 100644
--- a/library/ZendAfi/View/Helper/FonctionsAdmin/Boite.php
+++ b/library/ZendAfi/View/Helper/FonctionsAdmin/Boite.php
@@ -22,7 +22,9 @@
 class ZendAfi_View_Helper_FonctionsAdmin_Boite extends ZendAfi_View_Helper_FonctionsAdmin {
   public function getConfig($id_module = 0, $type_module = false, $division = null, $extended_actions = []) {
     parent::fonctionsAdmin($id_module, $type_module, $division, $extended_actions);
-    return $this->_config();
+    $html = $this->_config();
+
+    return $html . $this->view->renderPluginsActions($this->_widget);
   }
 
 
diff --git a/library/ZendAfi/View/Helper/Plugin/MultiSelection/Widget.php b/library/ZendAfi/View/Helper/Plugin/MultiSelection/Widget.php
index 78db27f53d698252edeac70d1232d70e728a092a..a5ffad543d89a0fa27ba9ec62494a7fb79d50ac0 100644
--- a/library/ZendAfi/View/Helper/Plugin/MultiSelection/Widget.php
+++ b/library/ZendAfi/View/Helper/Plugin/MultiSelection/Widget.php
@@ -32,9 +32,11 @@ class ZendAfi_View_Helper_Plugin_MultiSelection_Widget extends ZendAfi_View_Help
     $_count,
     $_title_key,
     $_strategy,
-    $_category_label;
+    $_category_label,
+    $_multi_selection;
 
   public function Plugin_MultiSelection_Widget($multi_selection) {
+    $this->_multi_selection = $multi_selection;
     $this->_current_skin = Class_Admin_Skin::current();
 
     $icon_add = $this->_current_skin->renderActionIconOn('basket',
@@ -75,9 +77,15 @@ class ZendAfi_View_Helper_Plugin_MultiSelection_Widget extends ZendAfi_View_Help
              $this->_getActions(),
              $this->_getSelectedItems()];
 
-    return $this->_tag('div',
-                       $this->_tag('div', implode($html)),
-                       ['class' => 'selected_items_widget show']);
+    $content = $this->_tag('div',
+                           implode($html),
+                           ['class' => 'selected_items_widget_content']);
+
+    return $this->_multi_selection->isAjax()
+      ? $content
+      :  $this->_tag('div',
+                     $content,
+                     ['class' => 'selected_items_widget show']);
   }
 
 
@@ -109,11 +117,11 @@ class ZendAfi_View_Helper_Plugin_MultiSelection_Widget extends ZendAfi_View_Help
                                                'title' => $this->_delete_selection]);
     }
 
-    $action_list[] = $this->view->tagAnchor('#',
-                                            $this->_('Afficher'),
-                                            ['class' => 'multiple_widget_action',
-                                             'onclick' => "\$('.selected_items_widget').toggleClass('list');",
-                                             'title' => $this->_show_selection]);
+    $action_list[] = $this->view->tag('a',
+                                      $this->_('Afficher'),
+                                      ['class' => 'multiple_widget_action',
+                                       'onclick' => "\$('.selected_items_widget').toggleClass('list');",
+                                       'title' => $this->_show_selection]);
 
     $actions= implode($action_list);
 
diff --git a/library/ZendAfi/View/Helper/ProfileComposition.php b/library/ZendAfi/View/Helper/ProfileComposition.php
index da92bbbaa00821d954de6e73ef4254979ef766f4..76c09b1f625c117e9b31363eba9dd473bbc483b1 100644
--- a/library/ZendAfi/View/Helper/ProfileComposition.php
+++ b/library/ZendAfi/View/Helper/ProfileComposition.php
@@ -149,17 +149,18 @@ class ZendAfi_View_Helper_ProfileComposition extends ZendAfi_View_Helper_BaseHel
 
   protected function _getHTMLForDivision($division) {
     $profil = Class_Profil::getCurrentProfil();
-    $html = '';
     $modules = $profil->getBoitesDivision($division);
-
     $helper = $this->view->getHelper('FonctionsAdmin_Boite');
+
+    $html = [];
     foreach($modules as $id_module => $module)
-      $html .= $this->_getItemModule($module['type_module'],
-                                     Class_Systeme_ModulesAccueil::moduleByCode($module['type_module'])->getLibelle(),
-                                     $id_module,
-                                     $this->_getWidgetActions($helper, $id_module, $module['type_module'], $division));
+      $html [] =
+        $this->_getItemModule($module['type_module'],
+                              $this->_getLabel($module),
+                              $id_module,
+                              $this->_getWidgetActions($helper, $id_module, $module['type_module'], $division));
 
-    return $html;
+    return implode($html);
   }
 
 
@@ -168,4 +169,18 @@ class ZendAfi_View_Helper_ProfileComposition extends ZendAfi_View_Helper_BaseHel
                               $type_module,
                               $division);
   }
+
+
+  protected function _getLabel($module) {
+    $module_label = Class_Systeme_ModulesAccueil::moduleByCode($module['type_module'])->getLibelle();
+
+    $title = (isset($module['preferences']) && isset($module['preferences']['titre']))
+      ? $module['preferences']['titre']
+      : '';
+
+    return $module_label
+      . ($title
+         ? ' : ' . $title
+         : '');
+  }
 }
\ No newline at end of file
diff --git a/library/templates/Intonation/Library/View/Wrapper/Abstract.php b/library/templates/Intonation/Library/View/Wrapper/Abstract.php
index 751235c53ad7225bef956f243a051716d259476f..8d9a811091eb3dc74916dcd5e86c41cebd7dfe94 100644
--- a/library/templates/Intonation/Library/View/Wrapper/Abstract.php
+++ b/library/templates/Intonation/Library/View/Wrapper/Abstract.php
@@ -173,6 +173,11 @@ abstract class Intonation_Library_View_Wrapper_Abstract {
   }
 
 
+  public function updateWithModel($model) {
+    return $this;
+  }
+
+
   protected function _truncate($text) {
     $number_of_word = $this->_widget_context
       ? $this->_widget_context->getDescriptionLength()
diff --git a/library/templates/Intonation/Library/View/Wrapper/ReviewsByRecord.php b/library/templates/Intonation/Library/View/Wrapper/ReviewsByRecord.php
index 7f31e47b68caae5bea3f15fae1da7eb2872b67cd..4822a26f08796872ea9765c2acd1c6ecf3a6a524 100644
--- a/library/templates/Intonation/Library/View/Wrapper/ReviewsByRecord.php
+++ b/library/templates/Intonation/Library/View/Wrapper/ReviewsByRecord.php
@@ -27,19 +27,26 @@ class Intonation_Library_View_Wrapper_ReviewsByRecord extends Intonation_Library
 
 
   public function setModel($model) {
-    if ( ! $model)
-      return parent::setModel($model);
+    $this->updateWithModel($model);
+    return parent::setModel($model);;
+  }
+
+
+  public function updateWithModel($model) {
+    if (!$model)
+      return $this;
 
     $this->_record = (new Intonation_Library_View_Wrapper_Record)
       ->setView($this->_view)
       ->setModel($model->getRecord());
 
+    $this->_reviews = [];
     foreach ($model->getReviews() as $review)
       $this->_reviews [] = (new Intonation_Library_View_Wrapper_ReviewInRecord)
-      ->setView($this->_view)
-      ->setModel($review);
+        ->setView($this->_view)
+        ->setModel($review);
 
-    return parent::setModel($model);
+    return $this;
   }
 
 
@@ -185,4 +192,4 @@ class Intonation_Library_View_Wrapper_ReviewsByRecord extends Intonation_Library
       ->setRecord($record)
       ->setReviews($reviews);
   }
-}
\ No newline at end of file
+}
diff --git a/library/templates/Intonation/Library/Widget/Carousel/Form.php b/library/templates/Intonation/Library/Widget/Carousel/Form.php
index 6b4c366c3f40d50148e8e704e6f88a8620cb3b28..15578db45ea0c636be4970bdc2c7c4315cdb375e 100644
--- a/library/templates/Intonation/Library/Widget/Carousel/Form.php
+++ b/library/templates/Intonation/Library/Widget/Carousel/Form.php
@@ -20,7 +20,7 @@
  */
 
 
-abstract class Intonation_Library_Widget_Carousel_Form extends ZendAfi_Form_Configuration_Widget_Base {
+class Intonation_Library_Widget_Carousel_Form extends ZendAfi_Form_Configuration_Widget_Base {
 
   public function init() {
     parent::init();
@@ -176,5 +176,7 @@ abstract class Intonation_Library_Widget_Carousel_Form extends ZendAfi_Form_Conf
   }
 
 
-  abstract public function getOrders();
+  public function getOrders() {
+    return [];
+  }
 }
\ No newline at end of file
diff --git a/library/templates/Intonation/Library/Widget/Carousel/Record/View.php b/library/templates/Intonation/Library/Widget/Carousel/Record/View.php
index bd5327031c8cbd77efb948786a25c231cf2228cf..5aa59476b6d2a209142d616a1f18bc5a8213ccb1 100644
--- a/library/templates/Intonation/Library/Widget/Carousel/Record/View.php
+++ b/library/templates/Intonation/Library/Widget/Carousel/Record/View.php
@@ -22,6 +22,7 @@
 
 class Intonation_Library_Widget_Carousel_Record_View extends Intonation_Library_Widget_Carousel_View {
 
+  protected static $_storm_limit = 100;
 
   protected function _findElements() {
     $order = $this->_settings->getOrder();
@@ -36,18 +37,27 @@ class Intonation_Library_Widget_Carousel_Record_View extends Intonation_Library_
                            'url_image=""',
                            $order]);
 
-    $params = ['limit' => $this->_getSize(),
+    $params = ['limit' => $this->_getLimit(),
                'order' => new Class_MoteurRecherche_OrderCriteria(implode(', ', $order))];
 
     $records = $this->_findRecords($params);
 
-    if ($this->_settings->isDisplayRandom())
+    if ($this->_settings->isDisplayRandom()) {
       shuffle($records);
+      array_slice($records, 0, $this->_getSize());
+    }
 
     return $records;
   }
 
 
+  protected function _getLimit() {
+    return $this->_settings->isDisplayRandom()
+      ? static::$_storm_limit
+      : $this->_getSize();
+  }
+
+
   protected function _findRecords($params) {
     $selection_id = $this->_settings->getIdPanier();
     $order = $this->_settings->getOrder();
@@ -89,7 +99,7 @@ class Intonation_Library_Widget_Carousel_Record_View extends Intonation_Library_
       return [];
 
     return Class_Notice::findAllByRequeteRecherche($request_ids,
-                                                   $this->_getSize(), 1);
+                                                   $this->_getLimit(), 1);
   }
 
 
diff --git a/library/templates/Intonation/Library/Widget/Free/View.php b/library/templates/Intonation/Library/Widget/Free/View.php
index 2112f4ebdfb0ae1267dffbf35a71af833ac2b0c0..27af3ee0e3a25f6db641ea7886cd0ca20629e984 100644
--- a/library/templates/Intonation/Library/Widget/Free/View.php
+++ b/library/templates/Intonation/Library/Widget/Free/View.php
@@ -47,12 +47,13 @@ class Intonation_Library_Widget_Free_View extends Zendafi_View_Helper_Accueil_Ba
 
 
   protected function _fetchArticle() {
-    if (empty($this->preferences['id_items']))
+    if (! (isset($this->preferences['id_items']) && $this->preferences['id_items']))
       return null;
 
-    if (!$articles = Class_Article::getArticlesByPreferences($this->preferences))
-      return null;
+    $this->preferences['filter_by_workflow'] = true;
+    $this->preferences['filter_by_local'] = true;
 
+    $articles = Class_Article::getArticlesByPreferences($this->preferences);
     return array_shift($articles);
   }
 
diff --git a/library/templates/Intonation/Library/Widget/Search/View.php b/library/templates/Intonation/Library/Widget/Search/View.php
index 3b2ef7e2ce54bd32ffac0ae32f44e87975413193..35ee54b91825d5fb06f51ff3834160a4f0c0b502 100644
--- a/library/templates/Intonation/Library/Widget/Search/View.php
+++ b/library/templates/Intonation/Library/Widget/Search/View.php
@@ -267,8 +267,8 @@ abstract class IntonationSearchRenderAbstract {
     $form = new ZendAfi_Form;
     $elements = [];
 
-    if ($this->_settings->getSelectDoc()) {
-      $options = Class_TypeDoc::getMultiOptionsFacets();
+    if ($this->_settings->getSelectDoc()
+        && $options = Class_TypeDoc::getMultiOptionsFacets()) {
       $label = $this->_settings->getDocTypeSelectionLabel();
       $elements [] =
         $form->createElement('select',
@@ -280,11 +280,9 @@ abstract class IntonationSearchRenderAbstract {
                               'multiOptions' => $options]);
     }
 
-    if ($this->_settings->getSelectAnnexe()) {
-      $options = Class_CodifAnnexe::getMultiOptionsFacets();
-      asort($options, SORT_NATURAL | SORT_FLAG_CASE);
+    if ($this->_settings->getSelectAnnexe()
+        && $options = Class_CodifAnnexe::getMultiOptionsFacets()) {
       $label = $this->_settings->getAnnexeSelectionLabel();
-
       $elements [] =
         $form->createElement('select',
                              'custom_multifacets_annexe',
@@ -295,10 +293,10 @@ abstract class IntonationSearchRenderAbstract {
                               'multiOptions' => $options]);
     }
 
-    if ($this->_settings->getSelectBib()) {
-      $options = Class_Bib::getMultiOptionsFacets();
-      asort($options, SORT_NATURAL | SORT_FLAG_CASE);
+    if ($this->_settings->getSelectBib()
+        && $options = Class_Bib::getMultiOptionsFacets()) {
       $label = $this->_settings->getLibrarySelectionLabel();
+
       $elements [] =
         $form->createElement('select',
                              'custom_multifacets_bib',
@@ -451,13 +449,12 @@ class IntonationSearchRenderInline extends IntonationSearchRenderAbstract {
   public function render() {
     $form = $this->_getMainForm();
 
-    $optional_form_elements = [];
-
     foreach($this->_getOptionalElements() as $element) {
       $label = $element->getLabel();
       $element->setLabel('');
       $multi_options = $element->getAttrib('options');
       unset($multi_options['']);
+      unset($multi_options[0]);
       array_unshift($multi_options, $label);
       $element->setAttrib('options', $multi_options);
       $form->addElement($element);
diff --git a/library/templates/Intonation/View/RenderAjaxPaginatedList.php b/library/templates/Intonation/View/RenderAjaxPaginatedList.php
index d7be82038ddbe2221be225fd4b951e8162667d73..feb7dff707abf17244bd8c40278739c9f86b678d 100644
--- a/library/templates/Intonation/View/RenderAjaxPaginatedList.php
+++ b/library/templates/Intonation/View/RenderAjaxPaginatedList.php
@@ -36,10 +36,10 @@ class Intonation_View_RenderAjaxPaginatedList extends ZendAfi_View_Helper_BaseHe
     $callback = $callback
       ? $callback
       : function($element) use ($helper) {
-        $element->setView($this->view);
-        $element->setModel($element->getModel());
-        return call_user_func_array([$this->view, $helper->getRendering()], [$element]);
-      };
+      $element->setView($this->view);
+      $element->updateWithModel($element->getModel());
+      return call_user_func_array([$this->view, $helper->getRendering()], [$element]);
+    };
 
     $html_collection =
       $this->view->div(['class' => 'list-group bg-transparent no_border',
@@ -95,12 +95,12 @@ class Intonation_View_RenderAjaxPaginatedList extends ZendAfi_View_Helper_BaseHe
 
     $previous_page = $current_page -1;
 
-    $anchor_previous_url = $this->view->url(['controller' => 'index',
-                                             'action' => 'ajax-paginated-list',
-                                             'id' => $this->_id,
-                                             'size' => $page_size,
-                                             'page' => $current_page -1,
-                                             'search' => $search], null, true);
+    $anchor_previous_url = Class_Url::assemble(['controller' => 'index',
+                                                'action' => 'ajax-paginated-list',
+                                                'id' => $this->_id,
+                                                'size' => $page_size,
+                                                'page' => $previous_page,
+                                                'search' => $search]);
 
     $anchor_previous = $this->view->tagAnchor($anchor_previous_url,
                                               $this->_tag('i','',['class' => 'fas fa-chevron-left m-0']),
@@ -112,15 +112,13 @@ class Intonation_View_RenderAjaxPaginatedList extends ZendAfi_View_Helper_BaseHe
 
     $next_page = $current_page +1;
 
-    $anchor_next_url = $this->view->url(['controller' => 'index',
-                                         'action' => 'ajax-paginated-list',
-                                         'id_profil' => Class_Profil::getCurrentProfil()->getId(),
-                                         'id' => $this->_id,
-                                         'size' => $page_size,
-                                         'search' => $search,
-                                         'page' => $current_page +1],
-                                        null,
-                                        true);
+    $anchor_next_url = Class_Url::assemble(['controller' => 'index',
+                                            'action' => 'ajax-paginated-list',
+                                            'id_profil' => Class_Profil::getCurrentProfil()->getId(),
+                                            'id' => $this->_id,
+                                            'size' => $page_size,
+                                            'search' => $search,
+                                            'page' => $next_page]);
 
     $anchor_next = $this->view->tagAnchor($anchor_next_url,
                                           $this->_tag('i','',['class' => 'fas fa-chevron-right m-0']),
@@ -145,13 +143,13 @@ class Intonation_View_RenderAjaxPaginatedList extends ZendAfi_View_Helper_BaseHe
     $form = new ZendAfi_Form;
 
     $form
-      ->setAction($this->view->url(array_filter(['controller' => 'index',
-                                                 'action' => 'ajax-paginated-list',
-                                                 'id_profil' => Class_Profil::getCurrentProfil()->getId(),
-                                                 'id' => $this->_id,
-                                                 'size' => $helper->getPageSize(),
-                                                 'order' => $helper->getNewOrder(),
-                                                 'page' => 1]), null, true))
+      ->setAction(Class_Url::assemble(array_filter(['controller' => 'index',
+                                                    'action' => 'ajax-paginated-list',
+                                                    'id_profil' => Class_Profil::getCurrentProfil()->getId(),
+                                                    'id' => $this->_id,
+                                                    'size' => $helper->getPageSize(),
+                                                    'order' => $helper->getNewOrder(),
+                                                    'page' => 1])))
 
       ->addElement('select',
                    'select_' . $this->_id,
diff --git a/public/admin/css/config_accueil.css b/public/admin/css/config_accueil.css
index 9332dd3a7258fcb113ef74904c1db41af296bd3f..ca678d87ecf114a6eae4dbbc12ad0f7862034db6 100644
--- a/public/admin/css/config_accueil.css
+++ b/public/admin/css/config_accueil.css
@@ -165,3 +165,7 @@ ul.source_list {
 .profile_composition .container_list.deleted_widget {
     border:1px solid var(--error-background);
 }
+
+.profile_composition .container_list li:hover {
+    font-weight: bold;
+}
diff --git a/public/admin/skins/bokeh74/global.css b/public/admin/skins/bokeh74/global.css
index d20f73445f2c6fdd32b0993370ff1d037408fa67..b0d8c5fab35341b8b41e30263c91d4de26708818 100755
--- a/public/admin/skins/bokeh74/global.css
+++ b/public/admin/skins/bokeh74/global.css
@@ -1140,3 +1140,21 @@ table#album_item th, table#album_usage_report th {
 #journal_details pre.code-added {
     color: #859900;
 }
+
+
+.profil_accueil .selected_items_widget {
+    position: fixed;
+    top: 125px;
+    bottom: unset;
+    right: 5%;
+    left: unset;
+    z-index: 101;
+}
+
+.profil_accueil .selected_items_widget > div {
+    background: var(--widget-background);
+}
+
+.profil_accueil .selected_items_widget.show ~ .profils.form {
+    margin-top: 80px;
+}
diff --git a/scripts/album_change_codif.php b/scripts/album_change_codif.php
new file mode 100644
index 0000000000000000000000000000000000000000..b9ea32f628710cdef7842d938895ef974c3d62fd
--- /dev/null
+++ b/scripts/album_change_codif.php
@@ -0,0 +1,11 @@
+<?php
+require __DIR__ . '/../console.php';
+
+if (5 !== $argc) {
+  echo 'This script must have 4 arguments' . "\n\n";
+  return;
+}
+
+echo (new Class_Migration_AlbumChangeCodif($argv[1], $argv[2], $argv[3], $argv[4]))
+  ->run();
+echo "\n\n";
diff --git a/scripts/album_clear_codif.php b/scripts/album_clear_codif.php
index ba1d9f1afefbb0605c8e7ab699d8f0c42225ad41..152e45466417d54932b4b98f5f524f223a72972e 100644
--- a/scripts/album_clear_codif.php
+++ b/scripts/album_clear_codif.php
@@ -1,4 +1,10 @@
 <?php
 require __DIR__ . '/../console.php';
 
-(new Class_Migration_AlbumClearCodif($argv[1]))->run();
+if (2 !== $argc) {
+  echo 'This script should have one argument, the category id' . "\n\n";
+  return;
+}
+
+echo (new Class_Migration_AlbumClearCodif($argv[1]))->run();
+echo "\n\n";
diff --git a/tests/TearDown.php b/tests/TearDown.php
index 19532ce8e10cc964df0ae24c58638ac109e2511b..325b4bb4d582a1217ccee149e994737e12479321 100644
--- a/tests/TearDown.php
+++ b/tests/TearDown.php
@@ -44,6 +44,7 @@ class TearDown {
     Bokeh_Engine::reset();
 
     Class_Album::setFileSystem(null);
+    Class_Article_Loader::reset();
 
     Class_Codification::reset();
     Class_Codification::resetInstance();
diff --git a/tests/application/modules/admin/controllers/AlbumControllerTest.php b/tests/application/modules/admin/controllers/AlbumControllerTest.php
index b30bea0cec87f25a93d01535bd15743f4eb32866..27aa586998937fd44bf4e349fc281d7165823257 100644
--- a/tests/application/modules/admin/controllers/AlbumControllerTest.php
+++ b/tests/application/modules/admin/controllers/AlbumControllerTest.php
@@ -1231,6 +1231,7 @@ abstract class Admin_AlbumControllerEditAlbumMesBDTestCase extends Admin_AlbumCo
       ->setDuration('00:05:00')
       ->addAuthor('Pba')
       ->addEditor('Glo')
+      ->updateDateMaj()
       ->addCollection('Ratm')
       ->setDistributor('Geffen Records')
       ->setDroits(Class_Album::getPublicDomain())
@@ -1401,7 +1402,7 @@ class Admin_AlbumControllerEditAlbumMesBDTest extends Admin_AlbumControllerEditA
 
   /** @test */
   public function divShouldDisplayMajDate() {
-    $this->assertXPathContentContains("//div", '14 décembre 2013 09:00:00',$this->_response->getBody());
+    $this->assertXPathContentContains("//div", '14 décembre 2013 09:00:00');
   }
 
 
@@ -1753,6 +1754,7 @@ class Admin_AlbumControllerPostEditAlbumMesBDTest extends Admin_AlbumControllerT
                          $this->fixture('Class_Notice',
                                         ['id' => 72,
                                          'titre_principal' => 'Mes Romans',
+                                         'type_doc' => 201,
                                          'exemplaires' => [
                                                            $this->fixture('Class_Exemplaire',
                                                                           ['id' => 2])]
diff --git a/tests/application/modules/admin/controllers/SystemeControllerWebServicesTest.php b/tests/application/modules/admin/controllers/SystemeControllerWebServicesTest.php
index 5c564c8b301c9954034a51135b1183ab3f539523..d2a76b184a2762760b96bba1c20bc5cb23844ad3 100644
--- a/tests/application/modules/admin/controllers/SystemeControllerWebServicesTest.php
+++ b/tests/application/modules/admin/controllers/SystemeControllerWebServicesTest.php
@@ -67,6 +67,6 @@ class SystemeControllerWebServicesActionTest extends Admin_AbstractControllerTes
     $this->dispatch('/admin/systeme/webservices/id_service/Premiere/id_fonction/1', true);
 
     $this->assertXPathContentContains('//pre[@class="resultat"]',
-                                      'Jake Sully est un ancien marine');
+                                      'Jake Sully est un ancien marine', $this->_response->getBody());
   }
 }
\ No newline at end of file
diff --git a/tests/application/modules/opac/controllers/AbonneControllerSettingsTest.php b/tests/application/modules/opac/controllers/AbonneControllerSettingsTest.php
index 007149db1d0618f1177b451aeb1e8464f641c6f9..1cc9a995dc0c97ebc381cd37f4abf5b5c22e1725 100644
--- a/tests/application/modules/opac/controllers/AbonneControllerSettingsTest.php
+++ b/tests/application/modules/opac/controllers/AbonneControllerSettingsTest.php
@@ -213,12 +213,12 @@ class AbonneControllerSettingsFormTest extends AbonneControllerSettingsTestCase
 
 
 
-class AbonneControllerSettingsFormPostTest extends AbonneControllerSettingsTestCase {
 
+class AbonneControllerSettingsFormPostTest extends AbonneControllerSettingsTestCase {
   public function setUp() {
     parent::setUp();
 
-    $this->fixture('Class_Bib',
+    $this->fixture(Class_Bib::class,
                    ['id' => 59,
                     'libelle' => 'ANNECY']);
 
@@ -242,16 +242,16 @@ class AbonneControllerSettingsFormPostTest extends AbonneControllerSettingsTestC
 
 
 
-class AbonneControllerPreferencesFormPostTest extends AbonneControllerSettingsTestCase {
 
+class AbonneControllerPreferencesFormPostInPopupTest extends AbonneControllerSettingsTestCase {
   public function setUp() {
     parent::setUp();
 
-    $this->fixture('Class_Bib',
+    $this->fixture(Class_Bib::class,
                    ['id' => 59,
                     'libelle' => 'ANNECY']);
 
-    $this->postDispatch('/abonne/manage-preferences',
+    $this->postDispatch('/abonne/manage-preferences/render/popup',
                         ['library_ids' => '56',
                          'domain_ids' => '1-2']);
   }
@@ -267,17 +267,30 @@ class AbonneControllerPreferencesFormPostTest extends AbonneControllerSettingsTe
   public function userSettingsShouldHaveBeenUpdatedWithLibs() {
     $this->assertEquals('MEL', $this->_user->getBookmarkedLibraries()[0]->getLibelle());
   }
+
+
+  /** @test */
+  public function flashMessengerShouldNotContainsPopup() {
+    $this->assertNotFlashMessengerPopup();
+  }
+
+
+  /** @test */
+  public function responseShouldNotRedirect() {
+    $this->assertNotRedirect();
+  }
 }
 
 
 
 
 class AbonneControllerSettingsBookmarkedLibDisabledTest extends AbonneControllerSettingsTestCase {
-
   public function setUp() {
     parent::setUp();
 
-    $this->fixture('Class_Bib',
+    $_SERVER['HTTP_REFERER'] = '/recherche/simple';
+
+    $this->fixture(Class_Bib::class,
                    ['id' => 59,
                     'libelle' => 'ANNECY']);
 
@@ -288,9 +301,16 @@ class AbonneControllerSettingsBookmarkedLibDisabledTest extends AbonneController
   }
 
 
+  public function tearDown() {
+    unset($_SERVER['HTTP_REFERER']);
+    parent::tearDown();
+  }
+
+
   /** @test */
   public function userSettingsShouldHaveBeenUpdated() {
-    $this->assertEquals([$this->_music, $this->_sport], $this->_user->getBookmarkedDomains());
+    $this->assertEquals([$this->_music, $this->_sport],
+                        $this->_user->getBookmarkedDomains());
   }
 
 
@@ -298,9 +318,14 @@ class AbonneControllerSettingsBookmarkedLibDisabledTest extends AbonneController
   public function userSettingsShouldHaveBeenUpdatedWithNoLibs() {
     $this->assertEquals(0, count($this->_user->getBookmarkedLibraries()));
   }
-}
 
 
+  /** @test */
+  public function responseShouldRedirectToReferer() {
+    $this->assertRedirectTo('/recherche/simple');
+  }
+}
+
 
 
 
@@ -309,7 +334,7 @@ class AbonneControllerSettingsFormChangeTest extends AbonneControllerSettingsTes
   public function setUp() {
     parent::setUp();
 
-    $this->fixture('Class_Bib',
+    $this->fixture(Class_Bib::class,
                    ['id' => 59,
                     'libelle' => 'ANNECY']);
 
@@ -319,7 +344,6 @@ class AbonneControllerSettingsFormChangeTest extends AbonneControllerSettingsTes
       ->setBookmarkedLibraries([56])
       ->save();
 
-
     $post2 = ['library_ids' => ''];
     $this->postDispatch('/abonne/manage-settings', $post2);
   }
@@ -338,12 +362,21 @@ class AbonneControllerSettingsFormChangeTest extends AbonneControllerSettingsTes
 
 
   /** @test */
-  public function notificationShouldContainsAuMoinsUneBibliotheque() {
-    $this->assertFlashMessengerContentContains("Au moins une bibliothèque");
+  public function pageShouldDisplayErrorAuMoinsUneBibliotheque() {
+    $this->assertXPathContentContains('//ul[@class="errors"]/li',
+                                      'Au moins une bibliothèque');
+  }
+
+
+  /** @test */
+  public function responseShouldNotRedirect() {
+    $this->assertNotRedirect();
   }
 }
 
 
+
+
 class AbonneControllerSettingsViewableDomainTest extends AbonneControllerSettingsTestCase {
   public function setUp() {
     parent::setUp();
diff --git a/tests/application/modules/opac/controllers/BibNumeriqueControllerTest.php b/tests/application/modules/opac/controllers/BibNumeriqueControllerTest.php
index 6f832fe9c3c411d633e140c92b2a3f915a014696..10f41d60861b64a144d5948e8e91804d4593951f 100644
--- a/tests/application/modules/opac/controllers/BibNumeriqueControllerTest.php
+++ b/tests/application/modules/opac/controllers/BibNumeriqueControllerTest.php
@@ -572,6 +572,7 @@ abstract class BibNumeriqueControllerAlbumMultiMediasTestCase extends AbstractCo
       ->setDescription('<p>pour passer la soirée</p>')
 
       ->addEditor('Gallimard')
+      ->updateDateMaj()
       ->setIdLang('lat')
       ->setDroits('CC-BY-SA')
 
diff --git a/tests/application/modules/opac/controllers/RechercheControllerPrintActionTest.php b/tests/application/modules/opac/controllers/RechercheControllerPrintActionTest.php
index bb0a914c9bb71e3f618126119b534b38b9dd4eff..e4d5d4e989366e09c4b9da5cc8efbdcb1f6df61d 100644
--- a/tests/application/modules/opac/controllers/RechercheControllerPrintActionTest.php
+++ b/tests/application/modules/opac/controllers/RechercheControllerPrintActionTest.php
@@ -156,11 +156,14 @@ class RechercheControllerPrintActionViewNoticeWithRecordsTest extends AbstractCo
                                           'type' => Class_ModeleFusion::RECORD_TEMPLATE]);
 
 
-    Class_Indexation_PseudoNotice::index( $this->fixture('Class_Article' , ['id' => 12,
-                                                                            'titre' => 'transmetropolitan',
-                                                                            'contenu' => '<p>bd</p>',
-                                                                            'notice' => new Class_Notice(),
-                                                                            'type_doc_id' => Class_TypeDoc::ARTICLE]));
+    Class_Indexation_PseudoNotice::index( $this->fixture('Class_Article' ,
+                                                         ['id' => 12,
+                                                          'titre' => 'transmetropolitan',
+                                                          'contenu' => '<p>bd</p>',
+                                                          'notice' => ($this->fixture(Class_Notice::class,
+                                                                                      ['id' => 1,
+                                                                                       'type_doc' => Class_TypeDoc::ARTICLE])),
+                                                          'type_doc_id' => Class_TypeDoc::ARTICLE]));
     $avis = [
              $this->fixture('Class_AvisNotice', [
                                                  'id' => 1,
@@ -202,6 +205,7 @@ class RechercheControllerPrintActionViewNoticeWithRecordsTest extends AbstractCo
     $this->assertXPathContentContains("//div//h1[1]", "transmetropolitan", $this->_response->getBody());
   }
 
+
   /** @test */
   public function avisShouldBeDisplayed() {
     $this->assertXPathContentContains('//div//p', "Lies are news and truth is obsolete.",$this->_response->getBody());
diff --git a/tests/library/Class/Article/SelectForTest.php b/tests/library/Class/Article/SelectForTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..3dc5f2ecd5fa7d867379dfe9b4bf85bf49c872a6
--- /dev/null
+++ b/tests/library/Class/Article/SelectForTest.php
@@ -0,0 +1,35 @@
+<?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_Article_SelectForTest {
+  public function findAll() {
+    return Class_Article::findAll();
+  }
+
+  public function getSortOrder() {
+    return '';
+  }
+
+  public function getNumberOfArticles() {
+    return Class_Article::count();
+  }
+}
diff --git a/tests/library/Class/Article/TableSelectForTest.php b/tests/library/Class/Article/TableSelectForTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..ef0c5e636511f284d60d72a4570eb195fb90e56d
--- /dev/null
+++ b/tests/library/Class/Article/TableSelectForTest.php
@@ -0,0 +1,29 @@
+<?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_Article_TableSelectForTest extends Class_TableSelectForTest {
+  public function __construct() {
+    $this->_model_class = Class_Article::class;
+    $this->_model_table = 'cms_article';
+    parent::__construct();
+  }
+}
\ No newline at end of file
diff --git a/tests/library/Class/ArticleLoaderTest.php b/tests/library/Class/ArticleLoaderTest.php
index 4d9486df50acdd7b20a75d4ce7155f208c04d28e..b70463bfd35cc44960c7546a35389ea2abc19df2 100644
--- a/tests/library/Class/ArticleLoaderTest.php
+++ b/tests/library/Class/ArticleLoaderTest.php
@@ -24,23 +24,14 @@ abstract class ArticleLoaderGetArticlesByPreferencesTestCase extends ModelTestCa
   const WHERE_VISIBLE_CLAUSE =
     '((DEBUT IS NULL) OR (DEBUT <= CURDATE())) AND ((FIN IS NULL) OR (FIN >= CURDATE()))';
 
-  public function setUp() {
-    Class_AdminVar::set('ENABLE_ARTICLES_TIMINGS', 0);
+  protected $select;
 
-    $this->select = new Zend_Db_Table_Select(
-                      new Storm_Model_Table(['name' => 'cms_article']));
 
-    $this->tbl_articles = $this->_buildTableMock(Class_Article::class,
-                                                 ['select', 'fetchAll']);
-    $this->tbl_articles
-      ->expects($this->any())
-      ->method('select')
-      ->will($this->returnValue($this->select));
+  public function setUp() {
+    Class_AdminVar::set('ENABLE_ARTICLES_TIMINGS', 0);
 
-    $this->tbl_articles
-      ->expects($this->any())
-      ->method('fetchAll')
-      ->will($this->returnValue($this->_buildRowset($this->_articlesFixtures())));
+    $this->select = (new Class_Article_TableSelectForTest)
+      ->setFetchAllResult($this->_articlesFixtures());
 
     Class_ArticleCategorie::getLoader()
       ->newInstanceWithId(2)
diff --git a/tests/library/Class/AvisNoticeTest.php b/tests/library/Class/AvisNoticeTest.php
index 38ebf73c13f7f14270bf5c20398bbea1df35933a..643ca34612e520bf88c770f31ca8b56f63c5a4f3 100644
--- a/tests/library/Class/AvisNoticeTest.php
+++ b/tests/library/Class/AvisNoticeTest.php
@@ -21,43 +21,6 @@
 require_once 'Class/AvisNotice.php';
 require_once 'ModelTestCase.php';
 
-class AvisNoticeFixtures extends TestFixtures {
-  protected $_fixtures = array(
-                               'marcus_on_millenium' => array('ID' => 23,
-                                                              'ID_USER' => 2,
-                                                              'ENTETE' => 'excellent',
-                                                              'AVIS' => 'on en redemande',
-                                                              'CLEF_OEUVRE' => 'MILLENIUM--LARSSON',
-                                                              'DATE_AVIS' => '2010-05-21',
-                                                              'NOTE' => '4'),
-                               'marcus_on_potter' => array('ID' => 25,
-                                                           'ID_USER' => 2,
-                                                           'ENTETE' => 'pour les enfants',
-                                                           'AVIS' => 'ils aiment bien',
-                                                           'CLEF_OEUVRE' => 'POTTER',
-                                                           'DATE_AVIS' => '2010-05-20',
-                                                           'NOTE' => 3),
-                               'steve_on_millenium' => array('ID' => 12,
-                                                             'ID_USER' => 5,
-                                                             'ENTETE' => 'ça fait peur',
-                                                             'AVIS' => 'très glauque',
-                                                             'CLEF_OEUVRE' => 'MILLENIUM--LARSSON',
-                                                             'DATE_AVIS' => '2009-05-17',
-                                                             'NOTE' => null),
-                               'steve_on_potter' => array('ID' => 48,
-                                                          'ID_USER' => 5,
-                                                          'ENTETE' => 'Vive potter',
-                                                          'AVIS' => "c'est génial",
-                                                          'CLEF_OEUVRE' => 'POTTER',
-                                                          'DATE_AVIS' => '2011-05-17',
-                                                          'NOTE' => 3)
-  );
-
-  public static function instance() {
-    return new self();
-  }
-}
-
 
 class AvisTestBibAbonne extends ModelTestCase {
   public function setUp() {
@@ -114,9 +77,55 @@ class AvisTestBibAbonne extends ModelTestCase {
 
 
 
-class AvisTestSortByDateAvisDesc extends ModelTestCase {
+abstract class AvisNoticeWithFixturesTestCase extends ModelTestCase {
+  protected $_storm_default_to_volatile = true;
+
+
   public function setUp() {
-    $this->_setFindAllExpectation('Class_AvisNotice', 'AvisNoticeFixtures');
+    parent::setUp();
+    $this->fixture(Class_AvisNotice::class,
+                   ['id' => 23,
+                    'id_user' => 2,
+                    'entete' => 'excellent',
+                    'avis' => 'on en redemande',
+                    'clef_oeuvre' => 'millenium--larsson',
+                    'date_avis' => '2010-05-21',
+                    'note' => '4']);
+
+    $this->fixture(Class_AvisNotice::class,
+                   ['id' => 25,
+                    'id_user' => 2,
+                    'entete' => 'pour les enfants',
+                    'avis' => 'ils aiment bien',
+                    'clef_oeuvre' => 'potter',
+                    'date_avis' => '2010-05-20',
+                    'note' => 3]);
+
+    $this->fixture(Class_AvisNotice::class,
+                   ['id' => 12,
+                    'id_user' => 5,
+                    'entete' => 'ça fait peur',
+                    'avis' => 'très glauque',
+                    'clef_oeuvre' => 'millenium--larsson',
+                    'date_avis' => '2009-05-17',
+                    'note' => null]);
+
+    $this->fixture(Class_AvisNotice::class,
+                   ['id' => 48,
+                    'id_user' => 5,
+                    'entete' => 'Vive potter',
+                    'avis' => "c'est génial",
+                    'clef_oeuvre' => 'potter',
+                    'date_avis' => '2011-05-17',
+                    'note' => 3]);
+  }
+}
+
+
+
+class AvisTestSortByDateAvisDesc extends AvisNoticeWithFixturesTestCase {
+  public function setUp() {
+    parent::setUp();
     $this->avis = Class_AvisNotice::getLoader()->findAll();
     $this->avis_sorted = Class_AvisNotice::sortByDateAvisDesc($this->avis);
   }
@@ -150,9 +159,12 @@ class AvisNoticeTestLoader extends ModelTestCase {
   }
 }
 
-class AvisNoticeTestFindAll extends ModelTestCase {
+
+
+
+class AvisNoticeTestFindAll extends AvisNoticeWithFixturesTestCase {
   public function setUp() {
-    $this->_setFindAllExpectation('Class_AvisNotice', 'AvisNoticeFixtures');
+    parent::setUp();
     $this->avis = Class_AvisNotice::getLoader()->findAll();
   }
 
@@ -248,109 +260,6 @@ class AvisNoticeTestBelongsToUserRelation extends ModelTestCase {
 
 
 
-abstract class AvisTestFindAllTestCase extends ModelTestCase {
-  public function setUp() {
-    $this->select = new Zend_Db_Table_Select(new Storm_Model_Table(array('name' => 'notices_avis')));
-    $rs_avis = $this->_buildRowset(array(
-                                         array('ID' => 25,
-                                               'ENTETE' => 'pour les enfants',
-                                               'ID_USER' => 34,
-                                               'CLEF_OEUVRE' => 'POTTER'),
-                                         array('ID' => 48,
-                                               'ENTETE' => 'Vive potter',
-                                               'ID_USER' => 34,
-                                               'CLEF_OEUVRE' => 'POTTER')));
-
-    $tbl_avis = $this->_buildTableMock('Class_AvisNotice',
-                                       array('fetchAll', 'select'));
-
-    $tbl_avis
-      ->expects($this->once())
-      ->method('select')
-      ->will($this->returnValue($this->select));
-
-    $tbl_avis
-      ->expects($this->once())
-      ->method('fetchAll')
-      ->will($this->returnValue($rs_avis));
-  }
-}
-
-
-class AvisTestFindAllByUserTestCase extends AvisTestFindAllTestCase {
-  public function setUp() {
-    parent::setUp();
-
-    $this->steve = new Class_Users();
-    $this->steve
-      ->setPrenom('Steve')
-      ->setId(34);
-
-    $this->result = Class_AvisNotice::getLoader()->findAllBy(array('role' => 'user',
-                                                                   'model' => $this->steve));
-  }
-
-  public function testExpectedSQLQuery() {
-    $this->assertEquals("SELECT `notices_avis`.* FROM `notices_avis` WHERE (id_user=34)",
-                        $this->select->assemble());
-  }
-}
-
-
-
-class AvisTestFindAllByDateAvisWithLimitAndOrderTestCase extends AvisTestFindAllTestCase {
-  public function setUp() {
-    parent::setUp();
-    $this->result = Class_AvisNotice::getLoader()->findAllBy(array('order' => 'date_avis desc',
-                                                                   'limit' => 5));
-  }
-
-  public function testExpectedSQLQuery() {
-    $this->assertEquals("SELECT `notices_avis`.* FROM `notices_avis` ORDER BY `date_avis` desc LIMIT 5",
-                        $this->select->assemble());
-  }
-}
-
-
-class AvisTestFindAllByUsersIdTest extends AvisTestFindAllTestCase {
-  public function testExpectedSQLQuery() {
-    $this->result = Class_AvisNotice::getLoader()->findAllBy(array('id_user' => array(23, 34, 4)));
-    $this->assertEquals("SELECT `notices_avis`.* FROM `notices_avis` WHERE (id_user in (23, 34, 4))",
-                        $this->select->assemble());
-  }
-}
-
-
-class AvisTestFindAllByUserAndClefOeuvreTestCase extends AvisTestFindAllTestCase {
-  public function setUp() {
-    parent::setUp();
-    $this->result = Class_AvisNotice::getLoader()->findAllBy(array('clef_oeuvre' => 'POTTER',
-                                                                   'id_user' => 34));
-  }
-
-  public function testResultCountIsTwo() {
-    $this->assertEquals(2, count($this->result));
-  }
-
-  public function testExpectedSQLQuery() {
-    $this->assertEquals("SELECT `notices_avis`.* FROM `notices_avis` WHERE (clef_oeuvre='POTTER') AND (id_user=34)",
-                        $this->select->assemble());
-  }
-
-  public function testFirstAvis() {
-    $first = $this->result[0];
-    $this->assertEquals('pour les enfants', $first->getEntete());
-    $this->assertEquals(34, $first->getIdUser());
-  }
-
-  public function testSecondAvis() {
-    $second = $this->result[1];
-    $this->assertEquals('Vive potter', $second->getEntete());
-  }
-}
-
-
-
 
 class NoticeTestHasManyAvisTest extends ModelTestCase {
   public function setUp() {
@@ -547,127 +456,6 @@ class AvisSetAbonOuBibTest extends ModelTestCase {
 
 
 
-class AvisLoaderGetAvisFromPreferencesTest extends AvisTestFindAllTestCase {
-  public function setUp() {
-    parent::setUp();
-
-    $this->modo_avis = new Class_AdminVar();
-    $this->modo_avis
-      ->setId('MODO_AVIS')
-      ->setValeur(0);
-
-    $this->modo_avis_biblio = new Class_AdminVar();
-    $this->modo_avis_biblio
-      ->setId('MODO_AVIS_BIBLIO')
-      ->setValeur(0);
-
-    Class_AdminVar::getLoader()
-      ->cacheInstance($this->modo_avis)
-      ->cacheInstance($this->modo_avis_biblio);
-
-    $this->preferences = array( 'id_panier' => null,
-                               'id_catalogue' => null,
-                               'abon_ou_bib' => '',
-                               'only_img' => 0);
-  }
-
-  protected function assertQueryIs($expected) {
-    Class_AvisNotice::getLoader()->getAvisFromPreferences($this->preferences);
-    $this->assertEquals(trim($expected),
-                        trim(str_replace("\n", "", $this->select->assemble())));
-  }
-
-
-  public function testDefaultSQLQuery() {
-    $this->assertQueryIs("SELECT `notices_avis`.* ".
-                         "FROM `notices_avis` ".
-                         "WHERE (flags=0) ORDER BY `DATE_AVIS` DESC LIMIT 30");
-  }
-
-  public function testWithAllAndModoAPosteriori() {
-    $this->preferences['abon_ou_bib'] = 'all';
-    $this->assertQueryIs("SELECT `notices_avis`.* ".
-                         "FROM `notices_avis` ".
-                         "WHERE (flags=0) ORDER BY `DATE_AVIS` DESC LIMIT 30");
-  }
-
-
-  public function testWithAllAndModoAPrioriForReaders() {
-    $this->modo_avis->setValeur(1);
-    $this->preferences['abon_ou_bib'] = 'all';
-    $this->assertQueryIs("SELECT `notices_avis`.* ".
-                         "FROM `notices_avis` ".
-                         "WHERE (flags=0) AND ((STATUT=1 OR ABON_OU_BIB=1)) ".
-                         "ORDER BY `DATE_AVIS` DESC LIMIT 30");
-  }
-
-
-  public function testWithAllAndModoAPrioriForBiblio() {
-    $this->modo_avis_biblio->setValeur(1);
-    $this->preferences['abon_ou_bib'] = 'all';
-    $this->assertQueryIs("SELECT `notices_avis`.* ".
-                         "FROM `notices_avis` ".
-                         "WHERE (flags=0) AND ((STATUT=1 OR ABON_OU_BIB=0)) ".
-                         "ORDER BY `DATE_AVIS` DESC LIMIT 30");
-  }
-
-
-  public function testWithAllAndModoAPrioriForBiblioAndReaders() {
-    $this->modo_avis->setValeur(1);
-    $this->modo_avis_biblio->setValeur(1);
-
-    $this->preferences['abon_ou_bib'] = 'all';
-
-    $this->assertQueryIs("SELECT `notices_avis`.* ".
-                         "FROM `notices_avis` ".
-                         "WHERE (flags=0) AND ((STATUT=1 OR ABON_OU_BIB=1) ".
-                         "AND (STATUT=1 OR ABON_OU_BIB=0)) ".
-                         "ORDER BY `DATE_AVIS` DESC LIMIT 30");
-  }
-
-
-  public function testWithReadersAndModoAPosteriori() {
-    $this->preferences['abon_ou_bib'] = '0';
-    $this->assertQueryIs("SELECT `notices_avis`.* ".
-                         "FROM `notices_avis` ".
-                         "WHERE (flags=0) AND (ABON_OU_BIB='0') ".
-                         "ORDER BY `DATE_AVIS` DESC LIMIT 30");
-  }
-
-
-  public function testWithReadersAndModoAPrioriForReaders() {
-    $this->modo_avis->setValeur(1);
-
-    $this->preferences['abon_ou_bib'] = '0';
-    $this->assertQueryIs("SELECT `notices_avis`.* ".
-                         "FROM `notices_avis` ".
-                         "WHERE (flags=0) AND (ABON_OU_BIB='0') ".
-                         "AND ((STATUT=1 OR ABON_OU_BIB=1)) ".
-                         "ORDER BY `DATE_AVIS` DESC LIMIT 30");
-  }
-
-
-  public function testWithBiblioAndModoAPosteriori() {
-    $this->preferences['abon_ou_bib'] = '1';
-    $this->assertQueryIs("SELECT `notices_avis`.* ".
-                         "FROM `notices_avis` ".
-                         "WHERE (flags=0) AND (ABON_OU_BIB='1') ".
-                         "ORDER BY `DATE_AVIS` DESC LIMIT 30");
-  }
-
-  public function testWithBiblioAndModoAPriori() {
-    $this->modo_avis_biblio->setValeur(1);
-
-    $this->preferences['abon_ou_bib'] = '1';
-    $this->assertQueryIs("SELECT `notices_avis`.* ".
-                         "FROM `notices_avis` ".
-                         "WHERE (flags=0) AND (ABON_OU_BIB='1') ".
-                         "AND ((STATUT=1 OR ABON_OU_BIB=0)) ".
-                         "ORDER BY `DATE_AVIS` DESC LIMIT 30");
-  }
-}
-
-
 
 class AvisVisibilityTest extends ModelTestCase {
   public function setUp() {
diff --git a/tests/library/Class/CatalogueTest.php b/tests/library/Class/CatalogueTest.php
index f690c80f843a71f490a524e269b2419c037bb00e..164a1344a5ec21a18efb84a0caf8135aa8d1348a 100644
--- a/tests/library/Class/CatalogueTest.php
+++ b/tests/library/Class/CatalogueTest.php
@@ -18,26 +18,26 @@
  * along with BOKEH; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
-require_once 'ModelTestCase.php';
 
 class CatalogueGetRequetesPanierWithIdUserAndIdPanierTest extends ModelTestCase {
 
-  public function setUp() {
-    $panier_row = $this->_buildRowset([['ID' => 3, 'ID_USER' => 3,
-                                        'ID_PANIER' => 2, 'NOTICES' => ';STARWARS;JAMESBOND;']]);
+  protected $requetes,
+    $_requests_with_basket_order;
 
-    $this->select_paniers = new Zend_Db_Table_Select(new Storm_Model_Table(['name' => 'notices_paniers']));
 
-    $tbl_paniers = $this->_buildTableMock('Class_PanierNotice', array('fetchAll', 'select'));
-    $tbl_paniers
-      ->expects($this->exactly(2))
-      ->method('select')
-      ->will($this->returnValue($this->select_paniers));
+  public function setUp() {
+    parent::setUp();
 
-    $tbl_paniers
-      ->expects($this->exactly(2))
-      ->method('fetchAll')
-      ->will($this->returnValue($panier_row));
+    $this->fixture(Class_PanierNotice::class,
+                   ['id' => 3,
+                    'id_user' => 3,
+                    'id_panier' => 2,
+                    'notices' => ';STARWARS;JAMESBOND;']);
+
+    $this->fixture(Class_PanierNotice::class,
+                   ['id' => 2,
+                    'id_panier' => 2,
+                    'notices' => ';STARWARS;JAMESBOND;']);
 
     $search_params = ['id_panier' => 2,
                       'id_user' => 3,
diff --git a/tests/library/Class/CodifThesaurusTest.php b/tests/library/Class/CodifThesaurusTest.php
index 88166946bbbac6de5a56eb8d15fc1a41ca5bc7db..41bb52071ffb6c30cba64a3681e4fb1005820831 100644
--- a/tests/library/Class/CodifThesaurusTest.php
+++ b/tests/library/Class/CodifThesaurusTest.php
@@ -36,6 +36,16 @@ class CodifThesaurusCommonTest extends ModelTestCase {
   }
 
 
+  /** @test */
+  public function codifThesaurusWithId00E0InterpretedNumericShouldBeValid() {
+    $this->assertTrue($this->fixture(Class_CodifThesaurus::class,
+                                     ['id' => 1,
+                                      'libelle' => 'People',
+                                      'id_thesaurus' => 'PERS00E0',
+                                      'code' => 'PERS'])->isValid());
+  }
+
+
   /** @test */
   public function findNextThesaurusChildIdShouldReturnCCCC00010001() {
     $this->assertEquals('CCCC00010023',
diff --git a/tests/library/Class/Indexation/PseudoNoticeTest.php b/tests/library/Class/Indexation/PseudoNoticeTest.php
index bc111901733acafcc20de4ccd69617afb3a6492d..830a9c0cf1650b180ebd729cf8963df15873352f 100644
--- a/tests/library/Class/Indexation/PseudoNoticeTest.php
+++ b/tests/library/Class/Indexation/PseudoNoticeTest.php
@@ -94,7 +94,7 @@ class Class_Indexation_PseudoNoticeAlbumWorkKeyCompositionTest extends ModelTest
     Class_CosmoVar::setValueOf('unimarc_zone_titre','200$a;200$e');
     Class_CosmoVar::setValueOf('other_index_fields','701$a');
     Class_CosmoVar::setValueOf('unimarc_zone_matiere','600$a');
-    Class_CosmoVar::set('work_key_composition', 'TITLE;AUTHOR');
+    Class_CosmoVar::setValueOf('work_key_composition', 'TITLE;AUTHOR');
 
     $this->fixture(Class_Album::class,
                    ['id' => 896,
@@ -490,6 +490,8 @@ class Class_Indexation_PseudoNoticeArticleTest
 }
 
 
+
+
 /**
  * @see http://forge.afi-sa.fr/issues/98102
  */
@@ -612,9 +614,14 @@ class Class_Indexation_PseudoNoticeArticleUpdateTest
 
     $record = $this->fixture(Class_Notice::class,
                              ['id' => 15,
+                              'type_doc' => Class_TypeDoc::LIVRE,
                               'titres' => 'Should not be deleted']);
 
-    $article = $this->fixture(Class_Article::class,
+    $item = $this->fixture(Class_Exemplaire::class,
+                           ['id' => 231,
+                            'id_notice' => 15]);
+
+    $article = $this->fixture('Class_Article',
                               ['id' => 1,
                                'titre' => 'Should update record with id 15',
                                'contenu' => 'Index me should update my record',
@@ -632,22 +639,29 @@ class Class_Indexation_PseudoNoticeArticleUpdateTest
 
 
   /** @test */
-  public function onlyOneRecordShouldBePresent() {
-    $this->assertCount(1, Class_Notice::findAll());
+  public function twoRecordsShouldBePresent() {
+    $this->assertCount(2, Class_Notice::findAll());
   }
 
 
   /** @test */
   public function recordShouldHaveBeenUpdated() {
     $this->assertEquals('SHOULD CHOUL UPDATE UPDAT RECORD REKOR WITH OUI 15',
-                        Class_Notice::find(15)->getTitres());
+                        Class_Notice::find(16)->getTitres());
   }
 
 
   /** @test */
   public function deleteArticleShouldDeleteRecord() {
     Class_Article::find(1)->delete();
-    $this->assertNull(Class_Notice::find(15));
+    $this->assertNull(Class_Notice::find(16));
+  }
+
+
+  /** @test */
+  public function recordBookShouldBePresent() {
+    Class_Article::find(1)->delete();
+    $this->assertNotNull(Class_Notice::find(15));
   }
 }
 
@@ -693,6 +707,7 @@ class Class_Indexation_PseudoNoticeRssUpdateTest extends ModelTestCase {
     parent::setUp();
     $record = $this->fixture(Class_Notice::class,
                              ['id' => 15,
+                              'type_doc' => Class_TypeDoc::RSS,
                               'titres' => 'Should not be deleted']);
 
     $rss = $this->fixture(Class_Rss::class,
@@ -1000,3 +1015,264 @@ class Class_Indexation_PseudoNoticeSacramentariumTest extends ModelTestCase {
     $this->assertEquals(7, $item->getGenre());
   }
 }
+
+
+
+
+
+/**
+ *
+ * @see #138089
+ *
+ **/
+
+class Class_Indexation_PseudoNoticeArticleCmsTest
+  extends AbstractControllerTestCase {
+
+  protected $_storm_default_to_volatile = true;
+
+  public function setUp() {
+    parent::setUp();
+
+    $this->fixture(Class_Notice::class,
+                   ['id' => 5,
+                    'type_doc' => Class_TypeDoc::LIVRE,
+                   ]);
+
+    $author = $this->fixture(Class_Users::class,
+                             ['id' => 3,
+                              'login' => 'gm',
+                              'nom' => 'Musso',
+                              'prenom' => 'Guillaume',
+                              'password' => 'secret']);
+
+    $this->fixture(Class_Notice::class,
+                   ['id' => 1,
+                    'titre' => 'L\'Apothéose des Héros',
+                    'type_doc' => Class_TypeDoc::LIVRE,
+                    'auteur' => 'AUBRY LECOMTE HYACINTE',
+                    'unimarc' => '01795nkm0 2200373   450 00100070000000500170000703500170002403600140004109000110005509100160006609900350008210000410011710100080015810600060016611600230017213500060019520000770020121000390027821000330031721500550035023000830040530400390048830700330052733600790056033700590063937102170069845502600091561000200117566900150119570000340121080100620124485601070130697000080141328755220210306131624.0  aALEPH_156617  a000156617  a287552  a2b20201213  tNUMx0c2020-11-13d2020-11-18  a--------h----1821km-y0FREy5003----ba0 afre  as  ahi|b||||||bi||||||  ac1 aL\' Apothéose des héros français  (- Estampe)fHyacinthe Aubry-Lecomte  e[S.l]glith. de G. Engelmannh1821  eWasselonnegFlash Copyh2017  cSuite de 16 lithographies d\'après Girodet-Trioson  aDonnées iconographiques (14 fichiers JPEG : 216 Mo; 1 fichier PDF : 191 Mo )   aTitre provenant de l\'écran-titre   aManquent les planches 2 et 7  a Type de ressource électronique : données textuelles et iconographiques   aUn logiciel capable de lire un fichier au format JPEG   a Pour toute utilisation du document numérisé, indiquer la provenance comme suit : « Collection Société industrielle de Mulhouse, en dépôt à la Bibliothèque municipale de Mulhouse © Ville de Mulhouse »   943350bEstampec[S.l.] d1821fHyacinthe Aubry-Lecomtenlith. de G. EngelmannpSuite de 16 lithographies d\'après Girodet-TriosontL\' Apothéose des héros françaisuhttp://pro.bibliotheques.mulhouse.fr/cgi-bin/koha/catalogue/detail.pl?biblionumber=433500 aGirodet-Trioson  bPatrimoine 1aAubry-LecomtebHyacinthe4040  bBibliothèque municipale de MulhouseaFRcYYYYMMDDgAFNOR  abibliotheques.mulhouse.fruhttps://bibliotheques.mulhouse.fr/bib-numerique/download_album/id/12388.pdf  dArt',
+                    'clef_alpha' => "LAPOTHEOSEDESHEROSFRANCAISESTAMPE--AUBRYLECOMTEH----23"
+                   ]);
+
+    $this->fixture(Class_Exemplaire::class,
+                   ['id' => 123,
+                    'id_notice' => 1,
+                   ]);
+
+    $this->fixture(Class_Article::class,
+                   ['id' => 1,
+                    'id_notice' => 1,
+                    'titre' => 'My Article',
+                    'contenu' => 'article is about...',
+                    'description' => 'C&#39;est un m&eacute;gatest.',
+                    'date_creation' => '2018-02-17 10:22:34',
+                    'auteur' => $author]);
+
+    Class_Article::indexAll();
+  }
+
+
+  /** @test */
+  public function firstRecordTitleShouldBeLApotheoseDesHeros() {
+    $this->assertEquals('L\' Apothéose des héros français (- Estampe)',
+                        Class_Notice::find(1)->getTitrePrincipal());
+  }
+
+
+  /** @test */
+  public function articleRecordMainTitleShouldBeMyArticle() {
+    $this->assertEquals('My Article',
+                        Class_Article::find(1)->getNotice()->getTitrePrincipal());
+  }
+
+
+  /** @test */
+  public function articleRecordIdShouldBeSix() {
+    $this->assertEquals('6',
+                        Class_Article::find(1)->getIdNotice());
+  }
+}
+
+
+
+/**
+ *
+ * @see #138089
+ *
+ **/
+
+class Class_Indexation_IndexAlbumWithNewDocTypeTest
+  extends ModelTestCase {
+
+  protected $_storm_default_to_volatile = true;
+
+
+  public function setUp() {
+    parent::setUp();
+
+    $this->fixture(Class_Notice::class,
+                   ['id' => 1,
+                    'type_doc' => Class_TypeDoc::DIAPORAMA,
+                   ]);
+
+    $this->fixture(Class_Album::class,
+                   ['id' => 1,
+                    'notice_id' => 1,
+                    'visible' => true,
+                    'status' => Class_Article::STATUS_VALIDATED,
+                    'titre' => 'Tintin'
+                   ])
+         ->index();
+  }
+
+
+  /** @test */
+  public function recordOneShouldNotBeRemoved() {
+    $this->assertNotNull(Class_Notice::find(1));
+  }
+
+
+  /** @test */
+  public function newRecordTitleShouldBeTintin() {
+    $album = Class_Album::find(1)
+      ->setTypeDocId(Class_TypeDoc::EPUB);
+    $album->save();
+    $album->index();
+
+    $this->assertEquals('Tintin',
+                        Class_Album::find(1)
+                        ->getNotice()->getTitrePrincipal());
+  }
+
+
+  /** @test */
+  public function albumRecordIdShouldBeOne() {
+    $album = Class_Album::find(1)
+      ->setTypeDocId(Class_TypeDoc::EPUB);
+    $album->save();
+    $album->index();
+
+    $this->assertEquals('1',
+                        Class_Album::find(1)->getNoticeId());
+  }
+}
+
+
+
+
+/**
+ *
+ * @see #138089
+ *
+ **/
+
+class Class_Indexation_PseudoNoticeWithAlbumLinkedToWrongRecordTest
+  extends ModelTestCase {
+
+  protected $_storm_default_to_volatile = true;
+
+
+  public function setUp() {
+    parent::setUp();
+
+    $this->fixture(Class_Notice::class,
+                   ['id' => 1,
+                    'type_doc' => Class_TypeDoc::LIVRE,
+                   ]);
+
+    $this->fixture(Class_Notice::class,
+                   ['id' => 5,
+                    'type_doc' => Class_TypeDoc::LIVRE,
+                   ]);
+
+    $this->fixture(Class_Album::class,
+                   ['id' => 1,
+                    'notice_id' => 1,
+                    'visible' => true,
+                    'type_doc_id' => Class_TypeDoc::DIAPORAMA,
+                    'status' => Class_Album::STATUS_VALIDATED,
+                    'titre' => 'Tintin'
+                   ]);
+  }
+
+
+  /** @test */
+  public function recordOneShouldHaveBeenRemoved() {
+    Class_Album::find(1)->index();
+    $this->assertNull(Class_Notice::find(1));
+  }
+
+
+  /** @test */
+  public function recordOneLinkedInItemShouldNotBeenRemoved() {
+    $this->fixture(Class_Exemplaire::class,
+                   ['id' => 1,
+                    'id_notice' => 1
+                   ]);
+    Class_Album::find(1)->index();
+    $this->assertNotNull(Class_Notice::find(1));
+  }
+}
+
+
+
+
+/**
+ *
+ * @see #138089
+ *
+ **/
+
+class Class_Indexation_PseudoNoticeWithAlbumNotLinkedTest
+  extends ModelTestCase {
+
+  protected $_storm_default_to_volatile = true;
+
+
+  public function setUp() {
+    parent::setUp();
+
+    $this->fixture(Class_Notice::class,
+                   ['id' => 5,
+                    'titres' => 'manuel du guerrier de la lumière',
+                    'type_doc' => Class_TypeDoc::LIVRE,
+                   ]);
+
+    $this->fixture(Class_Article::class,
+                   ['id' => 1,
+                    'visible' => true,
+                    'contenu' => 'rétrospective de tintin',
+                    'status' => Class_Article::STATUS_VALIDATED,
+                    'titre' => 'Tintin'
+                   ]);
+    Class_Article::find(1)->index();
+  }
+
+
+  /** @test */
+  public function recordsCountShouldBeTwo() {
+    $this->assertEquals(2, Class_Notice::count());
+  }
+
+
+  /** @test */
+  public function recordTitleLinkedInArticleShouldBeTintin() {
+    $this->assertEquals('Tintin',
+                        Class_Article::find(1)->getNotice()->getTitrePrincipal());
+  }
+
+
+  /** @test */
+  public function recordIdLinkedInArticleShouldBeSix() {
+    $this->assertEquals(6,
+                        Class_Article::find(1)->getNotice()->getId());
+  }
+
+
+  /** @test */
+  public function recordTypeDocLinkedInAlbumShouldBeArticle() {
+    $this->assertEquals(Class_TypeDoc::ARTICLE,
+                        Class_Article::find(1)->getNotice()->getTypeDoc());
+  }
+}
diff --git a/tests/library/Class/Migration/AlbumChangeCodifTest.php b/tests/library/Class/Migration/AlbumChangeCodifTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..55966c38264ab32327f4364fa1b0743c2b1ab288
--- /dev/null
+++ b/tests/library/Class/Migration/AlbumChangeCodifTest.php
@@ -0,0 +1,282 @@
+<?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
+ */
+
+
+/* hotline: #143141 */
+abstract class AlbumChangeCodifTestCase extends ModelTestCase {
+
+  protected
+    $_storm_default_to_volatile = true,
+    $_message;
+
+  public function setUp() {
+    parent::setUp();
+
+    $this->fixture(Class_CodifGenre::class,
+                   ['id' => 101,
+                    'libelle' => 'Adulte'
+                   ]);
+    $this->fixture(Class_CodifGenre::class,
+                   ['id' => 110,
+                    'libelle' => 'Ado'
+                   ]);
+
+    $this->fixture(Class_CodifSection::class,
+                   ['id' => 111,
+                    'libelle' => 'Roman'
+                   ]);
+
+    $this->fixture(Class_Bib::class,
+                   ['id' => 112,
+                    'libelle' => 'Meythet'
+                   ]);
+
+    $this->fixture(Class_CodifMatiere::class,
+                   ['id' => 113,
+                    'libelle' => 'Documentaire Guides'
+                   ]);
+
+    $this->fixture(Class_AlbumCategorie::class,
+                   ['id' => 1,
+                    'parent_id' => 0
+                   ]);
+    $this->fixture(Class_AlbumCategorie::class,
+                   ['id' => 2,
+                    'parent_id' => 0
+                   ]);
+
+    $this->fixture(Class_Album::class,
+                   ['id' => 10,
+                    'cat_id' => 1,
+                    'genre' => '100;;101',
+                    'sections' => '102;103;104',
+                    'annexes' => '105',
+                    'matiere' => '107;108;109',
+                    'titre' => 'Amuse-bouche'
+                   ]);
+    $this->fixture(Class_Album::class,
+                   ['id' => 11,
+                    'cat_id' => 1,
+                    'genre' => '102;100',
+                    'sections' => '103',
+                    'annexes' => '106;105',
+                    'matiere' => '107;109',
+                    'titre' => 'Animal'
+                   ]);
+    $this->fixture(Class_Album::class,
+                   ['id' => 12,
+                    'cat_id' => 2,
+                    'genre' => '100;;101',
+                    'sections' => '102;103;104',
+                    'annexes' => '105',
+                    'matiere' => '107;108;109',
+                    'titre' => 'Documentaire'
+                   ]);
+  }
+
+
+  /** @test */
+  public function album12ShouldNotHaveLostGenreIds() {
+    $this->assertEquals('100;;101', Class_Album::find(12)->getGenre());
+  }
+
+
+  /** @test */
+  public function album12ShouldNotHaveLostSectionIds() {
+    $this->assertEquals('102;103;104', Class_Album::find(12)->getSections());
+  }
+
+
+  /** @test */
+  public function album12ShouldNotHaveLostAnnexeIds() {
+    $this->assertEquals('105', Class_Album::find(12)->getAnnexes());
+  }
+
+
+  /** @test */
+  public function album12ShouldNotHaveLostMatiereIds() {
+    $this->assertEquals('107;108;109', Class_Album::find(12)->getMatiere());
+  }
+}
+
+
+
+
+class AlbumChangeCodifForGenreTest extends AlbumChangeCodifTestCase {
+
+  public function setUp() {
+    parent::setUp();
+
+    $this->_message = (new Class_Migration_AlbumChangeCodif(1, 'genre', 100, 110))
+      ->run();
+  }
+
+
+  /** @test */
+  public function album10ShouldHaveReplaceIdGenre100By110() {
+    $this->assertEquals('110;;101', Class_Album::find(10)->getGenre());
+  }
+
+
+  /** @test */
+  public function album11ShouldHaveReplaceIdGenre100By110() {
+    $this->assertEquals('102;110', Class_Album::find(11)->getGenre());
+  }
+}
+
+
+
+
+class AlbumChangeCodifForSectionsTest extends AlbumChangeCodifTestCase {
+
+  public function setUp() {
+    parent::setUp();
+
+    $this->_message = (new Class_Migration_AlbumChangeCodif(1, 'sections', 104, 111))
+      ->run();
+  }
+
+
+  /** @test */
+  public function album10ShouldHaveReplaceIdSection104By111() {
+    $this->assertEquals('102;103;111', Class_Album::find(10)->getSections());
+  }
+
+
+  /** @test */
+  public function album11ShouldNotHaveBeenUpdated() {
+    $this->assertEquals('103', Class_Album::find(11)->getSections());
+  }
+}
+
+
+
+
+class AlbumChangeCodifForAnnexesTest extends AlbumChangeCodifTestCase {
+
+  public function setUp() {
+    parent::setUp();
+
+    $this->_message = (new Class_Migration_AlbumChangeCodif(1, 'annexes', 105, 112))
+      ->run();
+  }
+
+
+  /** @test */
+  public function album10ShouldHaveReplaceIdAnnexe105By112() {
+    $this->assertEquals('112', Class_Album::find(10)->getAnnexes());
+  }
+
+
+  /** @test */
+  public function album11ShouldHaveReplaceIdAnnexe105By112() {
+    $this->assertEquals('106;112', Class_Album::find(11)->getAnnexes());
+  }
+}
+
+
+
+
+class AlbumChangeCodifForMatiereTest extends AlbumChangeCodifTestCase {
+
+  public function setUp() {
+    parent::setUp();
+
+    $this->_message = (new Class_Migration_AlbumChangeCodif(1, 'matiere', 108, 113))
+      ->run();
+  }
+
+
+  /** @test */
+  public function album10ShouldHaveReplaceIdMatiere109By113() {
+    $this->assertEquals('107;113;109', Class_Album::find(10)->getMatiere());
+  }
+
+
+  /** @test */
+  public function album11ShouldNotHaveBeenUpdated() {
+    $this->assertEquals('107;109', Class_Album::find(11)->getMatiere());
+  }
+}
+
+
+
+
+class AlbumChangeCodifWithErrorTest extends ModelTestCase {
+
+  protected
+    $_storm_default_to_volatile = true;
+
+  /** @test */
+  public function withCatId1ShouldResponseMessageCategoryCantBeFound() {
+    $message = (new Class_Migration_AlbumChangeCodif(1, 'genre', 2, 3))
+      ->run();
+
+    $this->assertEquals("Category can't be found with id: 1", $message);
+  }
+
+
+  /** @test */
+  public function withCodifNawakShouldResponseMessageCodifNawakDoesntExist() {
+    $this->fixture(Class_AlbumCategorie::class,
+                   ['id' => 1,
+                    'parent_id' => 0
+                   ]);
+
+    $message = (new Class_Migration_AlbumChangeCodif(1, 'nawak', 2, 3))
+      ->run();
+
+    $this->assertEquals("Codif nawak doesn't exist", $message);
+  }
+
+
+  /** @test */
+  public function withCodifGenreId2ShouldResponseMessageCodifGenreCantBeFound() {
+    $this->fixture(Class_AlbumCategorie::class,
+                   ['id' => 1,
+                    'parent_id' => 0
+                   ]);
+
+    $this->fixture(Class_CodifGenre::class,
+                   ['id' => 2,
+                    'libelle' => 'Adulte'
+                   ]);
+
+    $message = (new Class_Migration_AlbumChangeCodif(1, 'genre', 2, 3))
+      ->run();
+
+    $this->assertEquals("Class_CodifGenre with id: 2 should not exists", $message);
+  }
+
+
+  /** @test */
+  public function withCodifGenreId3ShouldResponseMessageCodifGenreCantBeFound() {
+    $this->fixture(Class_AlbumCategorie::class,
+                   ['id' => 1,
+                    'parent_id' => 0
+                   ]);
+
+    $message = (new Class_Migration_AlbumChangeCodif(1, 'genre', 2, 3))
+      ->run();
+
+    $this->assertEquals("Class_CodifGenre can't be found with id: 3", $message);
+  }
+}
diff --git a/tests/library/Class/Migration/AlbumClearCodifTest.php b/tests/library/Class/Migration/AlbumClearCodifTest.php
index 96abc2590685e2ab1ff307ae4d91973f45681b7f..097d7f3335258ad7677644dfd540b65d1c7917be 100644
--- a/tests/library/Class/Migration/AlbumClearCodifTest.php
+++ b/tests/library/Class/Migration/AlbumClearCodifTest.php
@@ -23,7 +23,9 @@
 /* hotline: #143141 */
 class AlbumClearCodifTest extends ModelTestCase {
 
-  protected $_storm_default_to_volatile = true;
+  protected
+    $_storm_default_to_volatile = true,
+    $_message;
 
   public function setUp() {
     parent::setUp();
@@ -32,11 +34,11 @@ class AlbumClearCodifTest extends ModelTestCase {
                    ['id' => 1,
                     'libelle' => 'Adulte'
                    ]);
-    $this->fixture(Class_CodifAnnexe::class,
+    $this->fixture(Class_Bib::class,
                    ['id' => 4,
                     'libelle' => 'Biviers'
                    ]);
-    $this->fixture(Class_CodifAnnexe::class,
+    $this->fixture(Class_Bib::class,
                    ['id' => 7,
                     'libelle' => 'Froges'
                    ]);
@@ -68,7 +70,14 @@ class AlbumClearCodifTest extends ModelTestCase {
                     'genre' => '1;5',
                     'sections' => '2',
                     'annexes' => '4;6;7',
-                    'matiere' => '8;9;10;11;12',
+                    'matiere' => '8;9;;10;11;12',
+                    'titre' => 'Amuse-bouche'
+                   ]);
+
+    $this->fixture(Class_Album::class,
+                   ['id' => 202,
+                    'cat_id' => 100,
+                    'annexes' => '4;;7',
                     'titre' => 'Amuse-bouche'
                    ]);
     $this->fixture(Class_Album::class,
@@ -77,11 +86,18 @@ class AlbumClearCodifTest extends ModelTestCase {
                     'genre' => '1;5',
                     'sections' => '2',
                     'annexes' => '4;6;7',
-                    'matiere' => '8;9;10;11;12',
+                    'matiere' => '8;9;;10;11;12',
                     'titre' => 'Animal'
                    ]);
 
-    (new Class_Migration_AlbumClearCodif(100))->run();
+    $this->_message = (new Class_Migration_AlbumClearCodif(100))->run();
+  }
+
+
+  /** @test */
+  public function withCatId100ShouldResponseMessageNumberOfAlbumUpdated2() {
+    $this->assertEquals('Number of album updated: 2 / 2, for category id: 100',
+                        $this->_message);
   }
 
 
@@ -121,6 +137,12 @@ class AlbumClearCodifTest extends ModelTestCase {
   }
 
 
+  /** @test */
+  public function album202ShouldHaveLostAnnexeEmptyId() {
+    $this->assertEquals('4;7', Class_Album::find(202)->getAnnexes());
+  }
+
+
   /** @test */
   public function album200ShouldHaveLostMatiereId10And11() {
     $this->assertEquals('8;9;12', Class_Album::find(200)->getMatiere());
@@ -129,7 +151,28 @@ class AlbumClearCodifTest extends ModelTestCase {
 
   /** @test */
   public function album201ShouldNotHaveLostMatiereIds() {
-    $this->assertEquals('8;9;10;11;12', Class_Album::find(201)->getMatiere());
+    $this->assertEquals('8;9;;10;11;12', Class_Album::find(201)->getMatiere());
   }
+}
 
+
+
+
+class AlbumClearCodifWithErrorsTest extends ModelTestCase {
+
+  protected
+    $_storm_default_to_volatile = true,
+    $_message;
+
+  public function setUp() {
+    parent::setUp();
+
+    $this->_message = (new Class_Migration_AlbumClearCodif(100))->run();
+  }
+
+
+  /** @test */
+  public function withCatId100ShouldResponseMessageCategoryCantBeFound() {
+    $this->assertEquals("Category can't be found with id: 100", $this->_message);
+  }
 }
diff --git a/tests/library/Class/ModelTestCase.php b/tests/library/Class/ModelTestCase.php
index 02422d8d2b37b90196b5a64fab4bed0c9a66d697..de75526704a5cb4fd04a95a1225afa4502883415 100644
--- a/tests/library/Class/ModelTestCase.php
+++ b/tests/library/Class/ModelTestCase.php
@@ -44,19 +44,6 @@ abstract class ModelTestCase extends Storm_Test_ModelTestCase {
     $_registry_sql;
 
 
-  protected function _buildTableMock($model, $methods) {
-    $table = $this->createMock('Storm_Model_Table'.$model,$methods);
-    $loader = call_user_func([$model, 'getLoader']);
-    $loader->setTable($table);
-    return $table;
-  }
-
-
-  protected function _buildRowset($data) {
-    return new Zend_Db_Table_Rowset(['data' => $data]);
-  }
-
-
   protected function setUp() {
     Storm_Model_Abstract::unsetLoaders();
     if($this->_storm_default_to_volatile) {
@@ -102,29 +89,4 @@ abstract class ModelTestCase extends Storm_Test_ModelTestCase {
                  $this->_registry_sql);
     return parent::tearDown();
   }
-
-
-  protected function _setFindAllExpectation($model, $fixtures) {
-    if (!is_array($fixtures)) {
-      $finst = new $fixtures();
-      $fixtures = $finst->all();
-    }
-
-    $mock_results = $this->_buildRowset($fixtures);
-    $tbl_newsletters = $this->_buildTableMock($model, ['fetchAll']);
-
-    $tbl_newsletters
-      ->expects($this->once())
-      ->method('fetchAll')
-      ->will($this->returnValue($mock_results));
-
-    return $tbl_newsletters;
-  }
-
-
-  protected function _generateLoaderFor($model, $methods) {
-    $loader = $this->createMock('Mock'.$model, $methods);
-    Storm_Model_Abstract::setLoaderFor($model, $loader);
-    return $loader;
-  }
 }
\ No newline at end of file
diff --git a/tests/library/Class/PanierNotice/TableSelectForTest.php b/tests/library/Class/PanierNotice/TableSelectForTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..f5a1916e3980a6749b3d1b101c09de8c8399c227
--- /dev/null
+++ b/tests/library/Class/PanierNotice/TableSelectForTest.php
@@ -0,0 +1,29 @@
+<?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_PanierNotice_TableSelectForTest extends Class_TableSelectForTest {
+  public function __construct() {
+    $this->_model_class = Class_PanierNotice::class;
+    $this->_model_table = 'notices_paniers';
+    parent::__construct();
+  }
+}
diff --git a/tests/library/Class/PanierNoticeTest.php b/tests/library/Class/PanierNoticeTest.php
index 9704432ced987bc8ae773e6c04229f7b1898423e..b532bdb89d77bda2b45d5a54f602a5c19663c135 100644
--- a/tests/library/Class/PanierNoticeTest.php
+++ b/tests/library/Class/PanierNoticeTest.php
@@ -26,26 +26,8 @@ class PanierNoticeLoaderTestFindAllBelongsToAdmin extends ModelTestCase {
   public function setUp() {
     parent::setUp();
 
-    $paniers_rowset = $this->_buildRowset(array(array('ID' => 3,
-                                                      'LIBELLE' => 'fictions'),
-                                                array('ID' => 10,
-                                                      'LIBELLE' => 'musique')));
-
-    $this->select_paniers = new Zend_Db_Table_Select(new Storm_Model_Table(array('name' => 'notices_paniers')));
-
-    $tbl_paniers = $this->_buildTableMock('Class_PanierNotice', array('fetchAll', 'select'));
-    $tbl_paniers
-      ->expects($this->once())
-      ->method('select')
-      ->will($this->returnValue($this->select_paniers));
-
-    $tbl_paniers
-      ->expects($this->once())
-      ->method('fetchAll')
-      ->will($this->returnValue($paniers_rowset));
-
-
-    $this->paniers = Class_PanierNotice::getLoader()->findAllBelongsToAdmin();
+    $this->select_paniers = new Class_PanierNotice_TableSelectForTest;
+    Class_PanierNotice::findAllBelongsToAdmin();
   }
 
 
@@ -54,17 +36,6 @@ class PanierNoticeLoaderTestFindAllBelongsToAdmin extends ModelTestCase {
                         "INNER JOIN `bib_admin_users` ON notices_paniers.id_user = bib_admin_users.id_user WHERE (bib_admin_users.ROLE_LEVEL >= 3) ORDER BY `notices_paniers`.`libelle` ASC",
                         $this->select_paniers->assemble());
   }
-
-
-  public function testFirstPanier() {
-    $first = $this->paniers[0];
-    $this->assertEquals(3, $first->getId());
-  }
-
-  public function testSecondPanier() {
-    $second = $this->paniers[1];
-    $this->assertEquals(10, $second->getId());
-  }
 }
 
 
diff --git a/tests/library/Class/TableSelectForTest.php b/tests/library/Class/TableSelectForTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..4cac03f5f42866b5449daaee97678faf032b2e83
--- /dev/null
+++ b/tests/library/Class/TableSelectForTest.php
@@ -0,0 +1,80 @@
+<?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_TableSelectForTest {
+
+  protected $_select,
+    $_table,
+    $_model_table,
+    $_model_class,
+    $_mock;
+
+  public function __construct() {
+    $this->_mock = new Class_TableSelectForTestMock;
+    $this->_select =
+      new Zend_Db_Table_Select(new Storm_Model_Table(['name' => $this->_model_table]));
+
+    $this->_table = $this->_buildTableMock($this->_model_class,
+                                           ['select', 'fetchAll']);
+    $this->_table
+      ->expects($this->_mock->any())
+      ->method('select')
+      ->will($this->_mock->returnValue($this->_select));
+  }
+
+
+  protected function _buildRowset($data) {
+    return new Zend_Db_Table_Rowset(['data' => $data]);
+  }
+
+
+  protected function _buildTableMock($model, $methods) {
+    $table = $this->_mock->_createMock('Storm_Model_Table' . $model, $methods);
+    $loader = call_user_func([$model, 'getLoader']);
+    $loader->setTable($table);
+    return $table;
+  }
+
+
+  public function setFetchAllResult($fixtures_as_array) {
+    $this->_table
+      ->expects($this->_mock->any())
+      ->method('fetchAll')
+      ->will($this->_mock->returnValue($this->_buildRowset($fixtures_as_array)));
+
+    return $this;
+  }
+
+
+  public function assemble(){
+    return $this->_select->assemble();
+  }
+}
+
+
+
+
+class Class_TableSelectForTestMock extends ModelTestCase {
+  public function _createMock($model, $methods) {
+    return $this->createMock($model, $methods);
+  }
+}
diff --git a/tests/scenarios/MultiSelection/MultiSelectionTest.php b/tests/scenarios/MultiSelection/MultiSelectionTest.php
index 453c6630a703eb2912a104bcaacd2155cbc64931..410dba2bbe2dfd2b7abde7dfd1ad36ceacd1798a 100644
--- a/tests/scenarios/MultiSelection/MultiSelectionTest.php
+++ b/tests/scenarios/MultiSelection/MultiSelectionTest.php
@@ -499,7 +499,7 @@ class MultiSelectionArticlesPostDatasTest extends MultiSelectionArticlesPostTest
 
   /** @test */
   public function shouldNotifySucces() {
-    $this->assertFlashMessengerEquals([ ['notification' => ['message' => 'Les 2 articles sélectionnés ont bien été sauvegardés']]]);
+    $this->assertFlashMessengerEquals([ ['notification' => ['message' => 'Les 2 articles sélectionné(e)s ont bien été sauvegardé(e)s']]]);
   }
 
 
@@ -982,6 +982,6 @@ class MultiSelectionAlbumPostDatasTest extends MultiSelectionAlbumTestCase {
 
   /** @test */
   public function shouldNotifySuccess() {
-    $this->assertFlashMessengerEquals([ ['notification' => ['message' => 'Les 2 albums sélectionnés ont bien été sauvegardés']]]);
+    $this->assertFlashMessengerEquals([ ['notification' => ['message' => 'Les 2 albums sélectionné(e)s ont bien été sauvegardé(e)s']]]);
   }
 }
\ No newline at end of file
diff --git a/tests/scenarios/Numel/NumelMapTest.php b/tests/scenarios/Numel/NumelMapTest.php
index df5f3d78ee44953290ed6f5eafc47ac08899daf6..93571c9f4e874a4f9a79206ce07d46586ea7fd26 100644
--- a/tests/scenarios/Numel/NumelMapTest.php
+++ b/tests/scenarios/Numel/NumelMapTest.php
@@ -176,14 +176,13 @@ class NumelKioskMapLimit1000Test extends AbstractControllerTestCase {
                                    ->render()]);
 
     $notices_ids = [];
-    for ($pos = 0; $pos < 1000; $pos++)
+    for ($pos = 1; $pos <= 100; $pos++)
       $notices_ids [] = $pos;
 
-    $notices_100_ids = array_slice($notices_ids, 0, 100);
 
     $this->_mock_sql = $this->mock()
                             ->whenCalled('fetchAllByColumn')
-                            ->with('select notices.id_notice from notices Where (MATCH(facettes) AGAINST(\'Q444\' IN BOOLEAN MODE)) and type=1 order by url_image="no", url_image=""  LIMIT 0,1000')
+                            ->with('select notices.id_notice from notices Where (MATCH(facettes) AGAINST(\'Q444\' IN BOOLEAN MODE)) and type=1 order by url_image="no", url_image=""  LIMIT 0,100')
                             ->answers($notices_ids);
 
     Zend_Registry::set('sql', $this->_mock_sql);
@@ -191,8 +190,8 @@ class NumelKioskMapLimit1000Test extends AbstractControllerTestCase {
     Storm_Test_ObjectWrapper::onLoaderOfModel(Class_Notice::class)
 
       ->whenCalled('findAllBy')
-      ->with(['id_notice' => $notices_100_ids,
-              'order' => 'FIELD(id_notice, ' . implode(',', $notices_100_ids) . ')'])
+      ->with(['id_notice' => $notices_ids,
+              'order' => 'FIELD(id_notice, ' . implode(',', $notices_ids) . ')'])
       ->answers([$notice_fier])
 
       ->whenCalled('findAllBy')
@@ -211,7 +210,7 @@ class NumelKioskMapLimit1000Test extends AbstractControllerTestCase {
 
     $preferences = ['layout' => Intonation_Library_Widget_Carousel_Definition::MAP,
                     'rendering' => 'card',
-                    'size' => 1000,
+                    'size' => 50,
                     'id_catalogue' => 444,
                     'titre' => 'Les cartes postales sur la carte',
                     'order' => 'RAND()'
@@ -229,8 +228,8 @@ class NumelKioskMapLimit1000Test extends AbstractControllerTestCase {
 
 
   /** @test */
-  public function fetchAllByColumnWillBeCalledWithLimit1000() {
-    $this->assertEquals('select notices.id_notice from notices Where (MATCH(facettes) AGAINST(\'Q444\' IN BOOLEAN MODE)) and type=1 order by url_image="no", url_image=""  LIMIT 0,1000', $this->_mock_sql->getFirstAttributeForLastCallOn('fetchAllByColumn'));
+  public function fetchAllByColumnWillBeCalledWithLimit100() {
+    $this->assertEquals('select notices.id_notice from notices Where (MATCH(facettes) AGAINST(\'Q444\' IN BOOLEAN MODE)) and type=1 order by url_image="no", url_image=""  LIMIT 0,100', $this->_mock_sql->getFirstAttributeForLastCallOn('fetchAllByColumn'));
   }
 
 
diff --git a/tests/scenarios/PnbDilicom/PnbDilicomDisplayTest.php b/tests/scenarios/PnbDilicom/PnbDilicomDisplayTest.php
index dc6139f9f15fe62d64d696674e94b79332551f1c..3cb95196cb0ffb15c667c1b2f9c805dc70a28933 100644
--- a/tests/scenarios/PnbDilicom/PnbDilicomDisplayTest.php
+++ b/tests/scenarios/PnbDilicom/PnbDilicomDisplayTest.php
@@ -197,7 +197,9 @@ abstract class PnbDilicomDisplayBibNumeriqueControllerTestCase extends AbstractC
                                   'external_uri' => 'http://www.edenlivres.fr/p/23416',
                                   'type_doc_id' => Class_TypeDoc::DILICOM,
                                   'isbn' => '435465',
-                                  'notice' => $this->fixture('Class_Notice', ['id' => 38]),
+                                  'notice' => $this->fixture('Class_Notice',
+                                                             ['id' => 38,
+                                                              'type_doc' => Class_TypeDoc::DILICOM]),
                                   'items' => [$this->fixture('Class_Album_Item',
                                                              ['id' => 1,
                                                               'album_id' => 3,
diff --git a/tests/scenarios/Templates/ChiliAccordionTest.php b/tests/scenarios/Templates/ChiliAccordionTest.php
index c5fc459463b295e3cb8040c945de2641816e2674..7e793c1342ec562a8d336e85a85de0c5153b78a3 100644
--- a/tests/scenarios/Templates/ChiliAccordionTest.php
+++ b/tests/scenarios/Templates/ChiliAccordionTest.php
@@ -43,13 +43,13 @@ class ChiliAccordionWidgetTest extends AbstractControllerTestCase {
                    'size' => 5,
                    'description_length' => 4]);
 
-    $this->fixture('Class_Article',
+    $this->fixture(Class_Article::class,
                    ['id' => 56,
                     'titre' => 'Le systeme Solaire',
                     'contenu' => '9 planètes'
                    ]);
 
-    $this->fixture('Class_Article',
+    $this->fixture(Class_Article::class,
                    ['id' => 57,
                     'titre' => 'La galaxie',
                     'contenu' => 'Milkyway'
@@ -60,6 +60,7 @@ class ChiliAccordionWidgetTest extends AbstractControllerTestCase {
                     'titre' => 'Bibliographies',
                     'contenu' => '<iframe allowfullscreen="" allowtransparency="" frameborder="0" height="300" scrolling="no" src="//v.calameo.com/?bkcode=00261361896ea4b0d3648&amp;mode=mini&amp;authid=b9EvnNDuyW4J" style="margin:0 auto;" width="500"></iframe>']);
 
+    Class_Article_Loader::setSelectTool(new Class_Article_SelectForTest);
 
     $this->dispatch('/opac/index/index');
   }
diff --git a/tests/scenarios/Templates/ChiliLoginTest.php b/tests/scenarios/Templates/ChiliLoginTest.php
index fce308f9703cb9898431c2f0b055a49f4e8833c1..09543a17ed00ebee282e59a1bcfa0e7af545c6c2 100644
--- a/tests/scenarios/Templates/ChiliLoginTest.php
+++ b/tests/scenarios/Templates/ChiliLoginTest.php
@@ -263,6 +263,8 @@ class ChiliLoginDispatchMenuArticlesTest extends ChiliLoginWidgetTestCase {
                     'titre' => 'New article',
                     'contenu' => '<i>A new article is readeable.</i>']);
 
+    Class_Article_Loader::setSelectTool(new Class_Article_SelectForTest);
+
     $this->dispatch('/widget/render-menu-entry/parent/0/menu_profil/4/menu/6');
   }
 
diff --git a/tests/scenarios/Templates/ChiliTest.php b/tests/scenarios/Templates/ChiliTest.php
index ea4fc3c7a9117354884d47cf509b08c8df4e15ad..0f5e652bf88a117a30f4cc35fd62500f84aa9785 100644
--- a/tests/scenarios/Templates/ChiliTest.php
+++ b/tests/scenarios/Templates/ChiliTest.php
@@ -46,6 +46,7 @@ abstract class ChiliTemplateTestCase extends Admin_AbstractControllerTestCase {
 
     Class_Profil::setCurrentProfil(Class_Profil::find(1));
     Class_Profil::find(1)->setHeaderCss('live_editor.css')->save();
+    Class_Article_Loader::setSelectTool(new Class_Article_SelectForTest);
   }
 
 
@@ -91,6 +92,7 @@ class ChiliTemplateProfilePatcherTest extends ChiliTemplateTestCase {
                     'id_cat' => 34
                    ]);
 
+
     $this->dispatch('/opac/index/index');
   }
 
@@ -139,7 +141,8 @@ class ChiliTemplateProfilePatcherTest extends ChiliTemplateTestCase {
 
   /** @test */
   public function topHighlightCarouselShouldBePresent() {
-    $this->assertXPath('//div[contains(@class, "top_highlight_carousel")]');
+    $this->assertXPath('//div[contains(@class, "top_highlight_carousel")]',
+                       $this->_response->getBody());
   }
 }
 
@@ -1217,7 +1220,6 @@ class ChiliSearchResultPage1Test extends ChiliSearchResultPageTestCase {
     $this->dispatch('/recherche/pomme/page/1');
   }
 
-
   /** @test */
   public function spanResultPagerShouldContainsPage1On3() {
     $this->assertXPathContentContains('//li[contains(@class, "result_pager")]//span', 'Page 1 / 3');
diff --git a/tests/scenarios/Templates/MuscleTemplateTest.php b/tests/scenarios/Templates/MuscleTemplateTest.php
index b3e3dbb8afe5ddc7b56c1e2dd2c4b0fae1125220..29042a72780676be9132417d59de73fe0a709572 100644
--- a/tests/scenarios/Templates/MuscleTemplateTest.php
+++ b/tests/scenarios/Templates/MuscleTemplateTest.php
@@ -165,6 +165,8 @@ class MuscleTemplateProfilePatcherTest extends MuscleTemplateTestCase {
                     'id_cat' => 34
                    ]);
 
+    Class_Article_Loader::setSelectTool(new Class_Article_SelectForTest);
+
     $this->dispatch('/opac/index/index');
   }
 
diff --git a/tests/scenarios/Templates/TemplatesArticlesTest.php b/tests/scenarios/Templates/TemplatesArticlesTest.php
index ce4b7a53bfec90eb72c6a8725d93b1c3c959f8f6..43fad3a68bca1dfe2b7f6e9e6992d9062a32a0ce 100644
--- a/tests/scenarios/Templates/TemplatesArticlesTest.php
+++ b/tests/scenarios/Templates/TemplatesArticlesTest.php
@@ -81,6 +81,8 @@ abstract class TemplatesArticlesWidgetTestCase extends Admin_AbstractControllerT
                     'events_debut' => '2020-05-13 00:00:00',
                     'events_fin' => '2020-07-31 00:00:00',
                     'all_day' => 1]);
+
+    Class_Article_Loader::setSelectTool(new Class_Article_SelectForTest);
   }
 }
 
@@ -518,6 +520,8 @@ class TemplatesArticlesWithInvalidConfigurationTest extends TemplatesArticlesWid
   public function setUp() {
     parent::setup();
 
+    Class_Article_Loader::setSelectTool(null);
+
     Class_Profil::find(1)
       ->setModuleAccueilPreferences(1,
                                     Intonation_Library_Widget_Carousel_Article_Definition::CODE,
@@ -573,22 +577,19 @@ class TemplatesArticlesRenderWithDescriptionLengthTest extends AbstractControlle
 
   public function setUp() {
     parent::setup();
+    $profile =
+      $this->_buildTemplateProfil(['id' => 1,
+                                   'template' => 'MUSCLE']);
+
+    $this->_profile_patcher = (new Class_Template_ProfilePatcher(null))
+      ->setProfile($profile);
+
     $this->fixture('Class_Article',
                    ['id' => 9,
                     'titre' => 'Savez-vous compter ?',
                     'contenu' => implode(' ' , range(1, 100))]);
-    Class_AdminVar::set('TEMPLATING', 1);
-
-    $profile = $this->fixture('Class_Profil',
-                              ['id' => 1,
-                               'template' => 'MUSCLE'
-                              ]);
-
-    $profile->beCurrentProfil();
-
-    $this->_profile_patcher = (new Class_Template_ProfilePatcher(null))
-      ->setProfile($profile);
 
+    Class_Article_Loader::setSelectTool(new Class_Article_SelectForTest);
   }
 
 
@@ -678,6 +679,7 @@ class TemplatesArticlesWidgetWithDescriptionLengthTest extends AbstractControlle
                                  'titre' => 'Parlez-vous français ?',
                                  'description' => '<canvas></canvas>La description s\'arrête ici et pas plus loin.',
                                  'contenu' => '<p>Une b...</p>'])]);
+    Class_Article_Loader::setSelectTool(new Class_Article_SelectForTest);
 
     $profile_patcher
       ->addWidget(Intonation_Library_Widget_Carousel_Article_Definition::CODE,
@@ -768,6 +770,8 @@ abstract class TemplatesArticlesWidgetAjaxRenderingTestCase extends AbstractCont
                       'titre' => 'Encore un ' . $id,
                       'contenu' => 'Toujours le même … ' . $id,
                       'id_cat' => 1]);
+
+    Class_Article_Loader::setSelectTool(new Class_Article_SelectForTest);
   }
 
 
@@ -807,6 +811,7 @@ class TemplatesArticlesWidgetAjaxRenderingTest extends TemplatesArticlesWidgetAj
 
   public function setUp() {
     parent::setUp();
+    Class_Article_Loader::setSelectTool(new Class_Article_SelectForTest);
     $this->dispatch('/widget/render/widget_id/1/profile_id/34');
   }
 
@@ -987,6 +992,46 @@ class TemplatesArticlesWithWidgetRenderWallLoggedAsAdminTest
 
 
 
+
+class TemplatesArticlesSqlRequestTest
+  extends AbstractControllerTestCase {
+
+  protected
+    $_storm_default_to_volatile = true,
+    $_select;
+
+
+  public function setUp() {
+    parent::setUp();
+    ZendAfi_Auth::getInstance()->clearIdentity();
+
+    Class_AdminVar::set("WORKFLOW", 1);
+
+    $this->_select = new Class_Article_TableSelectForTest;
+
+    $profil = $this->_buildTemplateProfil(['id' => 12]);
+    $profile_patcher = (new Class_Template_ProfilePatcher(null))
+      ->setProfile($profil)
+      ->addWidget(Intonation_Library_Widget_Carousel_Article_Definition::CODE,
+                  Class_Profil::DIV_MAIN,
+                  ['rendering' => 'card-description',
+                   'layout' => 'list',
+                   'size' => 10,
+                   'order' => 'EventDebut',
+                   'description_html' => 1]);
+
+    $this->dispatch('/opac/index');
+  }
+
+
+  /** @test */
+  public function sqlRequestForArticleShouldContainsFilterStatus() {
+    $this->assertEquals('SELECT `cms_article`.* FROM `cms_article` WHERE ((DEBUT IS NULL) OR (DEBUT <= CURDATE())) AND ((FIN IS NULL) OR (FIN >= CURDATE())) AND (PARENT_ID=0) AND (STATUS in (3)) ORDER BY `DATE_CREATION` DESC LIMIT 1000', $this->_select->assemble());
+  }
+}
+
+
+
 class TemplatesArticlesWithLibraryUrlRewriteTest extends AbstractControllerTestCase {
   protected $_storm_default_to_volatile = true;
 
diff --git a/tests/scenarios/Templates/TemplatesFreeTest.php b/tests/scenarios/Templates/TemplatesFreeTest.php
index 8a5c944ee8e90592676e10445aee24a0914b703a..9b1d607aa57aaed3f8eb3260ec95c38dd03b1f45 100644
--- a/tests/scenarios/Templates/TemplatesFreeTest.php
+++ b/tests/scenarios/Templates/TemplatesFreeTest.php
@@ -20,43 +20,43 @@
  */
 
 
-require_once('TemplatesTest.php');
-
-
 abstract class TemplatesFreeWidgetTestCase extends Admin_AbstractControllerTestCase {
+
   protected $_storm_default_to_volatile = true;
 
 
   public function setUp() {
     parent::setUp();
-
-
-    $this->fixture(Class_Article::class,
-                   ['id' => 5,
-                    'titre' => 'I have a dream',
-                    'contenu' => 'all Bokeh should be free (of bugs ...)<iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="" frameborder="0" height="315" src="https://www.youtube.com/embed/4Op2W-V9BrQ" title="YouTube video player" width="560"></iframe>',
-                    'categorie' => $this->fixture(Class_ArticleCategorie::class,
-                                                  ['id' => 3,
-                                                   'libelle' => 'Sofware'])]);
-
-    $this->_buildTemplateProfil(['id' => 1,
-                                 'libelle' => 'Free my site'])
-         ->setBoiteOfTypeInDivision(2,
-                                    Intonation_Library_Widget_Free_Definition::CODE,
-                                    [
-                                     'id_items' => '5'
-                                    ])
-         ->setBoiteOfTypeInDivision(4,
-                                    Intonation_Library_Widget_Search_Definition::CODE,
-                                    [])
-         ->setBoiteOfTypeInDivision(3,
-                                    Intonation_Library_Widget_Free_Definition::CODE,
-                                    [
-                                     'id_items' => '',
-                                     'titre' => 'Boite seule'
-                                    ])
-         ->assertSave();
-
+    $i_have_a_dream =
+      $this->fixture(Class_Article::class,
+                     ['id' => 5,
+                      'titre' => 'I have a dream',
+                      'contenu' => 'all Bokeh should be free (of bugs ...)<iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="" frameborder="0" height="315" src="https://www.youtube.com/embed/4Op2W-V9BrQ" title="YouTube video player" width="560"></iframe>',
+                      'categorie' => $this->fixture(Class_ArticleCategorie::class,
+                                                    ['id' => 3,
+                                                     'libelle' => 'Sofware'])]);
+
+    Class_Article_Loader::setSelectTool(new Class_Article_SelectForTest);
+
+    $free_my_site = $this
+      ->_buildTemplateProfil(['id' => 123,
+                              'libelle' => 'Free my site']);
+
+    $profile_patcher = (new Class_Template_ProfilePatcher(null))
+      ->setProfile($free_my_site);
+
+    $profile_patcher
+      ->addWidget(Intonation_Library_Widget_Free_Definition::CODE,
+                  Class_Profil::DIV_MAIN,
+                  ['id_items' => '5'])
+
+      ->addWidget(Intonation_Library_Widget_Search_Definition::CODE,
+                  Class_Profil::DIV_BANNIERE)
+
+      ->addWidget(Intonation_Library_Widget_Free_Definition::CODE,
+                  Class_Profil::DIV_SECOND_SIDE,
+                  ['id_items' => '',
+                   'titre' => 'Boite seule']);
   }
 }
 
@@ -66,25 +66,26 @@ abstract class TemplatesFreeWidgetTestCase extends Admin_AbstractControllerTestC
 class TemplatesFreeWidgetIndexWithCookiesTest extends TemplatesFreeWidgetTestCase {
   public function setUp() {
     parent::setUp();
+
     Class_Adminvar_Cookies::setManager(new Class_Cookies_TarteAuCitron());
-    $this->dispatch('/opac/index/index/id_profil/1');
+    $this->dispatch('/opac/index/index');
   }
 
 
   /** @test */
-  public function widgetOneTitleShouldBeBoiteLibre() {
+  public function widgetOneShouldContainsHeaderDefaultTitleBoiteLibre() {
     $this->assertXPathContentContains('//div[@id="boite_1"]//div[contains(@class, "card-header")]',
-                                      'Boite libre');
+                                      'Boite libre',
+                                      $this->_response->getBody());
   }
 
 
   /** @test */
-  public function widgetOneShouldContainsIHaveADream() {
-    $this->assertXPathContentContains('//div[@id="boite_1"]', 'I have a dream');
+  public function widgetOneShouldContainsEditLinkIHaveADream() {
+    $this->assertXPath('//div[@id="boite_1"]//a[@title="Modifier l\'article I have a dream"]');
   }
 
 
-
   /** @test */
   public function widgetOneShouldHaveLinkToAddNewArticle() {
     $this->assertXPathContentContains('//a[contains(@href, "admin/cms/add")]',
@@ -98,7 +99,6 @@ class TemplatesFreeWidgetIndexWithCookiesTest extends TemplatesFreeWidgetTestCas
   }
 
 
-
   /** @test */
   public function widgetThreeTitleShouldBeBoiteSeule() {
     $this->assertXPathContentContains('//div[@id="boite_3"]//div[contains(@class, "card-header")]',
@@ -115,11 +115,10 @@ class TemplatesFreeWidgetIndexWithCookiesTest extends TemplatesFreeWidgetTestCas
 
 
 
-
 class TemplatesDispatchEditFreeWidgetTest extends TemplatesFreeWidgetTestCase {
   public function setUp() {
     parent::setUp();
-    $this->dispatch('/admin/widget/edit-widget/id/1/id_profil/1', true);
+    $this->dispatch('/admin/widget/edit-widget/id/1/id_profil/123');
   }
 
 
@@ -140,3 +139,35 @@ class TemplatesDispatchEditFreeWidgetTest extends TemplatesFreeWidgetTestCase {
     $this->assertXpathContentContains('//select[contains(@name, "VisibleWhenHidden")]/optgroup[contains(@label, "Division bann")]/option[@value="2"]', 'Boite recherche');
   }
 }
+
+
+
+
+class TemplatesFreeWidgetInIndexWithNotLoggedUserTest extends TemplatesFreeWidgetTestCase {
+  protected $_select;
+
+
+  public function setUp() {
+    parent::setUp();
+    Class_Article_Loader::setSelectTool(null);
+    $this->_select = (new Class_Article_TableSelectForTest);
+    ZendAfi_Auth::getInstance()->clearIdentity();
+  }
+
+
+  /** @test */
+  public function freeWidgetRequestShouldFilterArticlesByWorkflow() {
+    Class_AdminVar::set('WORKFLOW', '[{"id":10, "label":"Validé 1"}, {"id":11, "label":"Validé 2u"}, {"id":12, "label":"Pré-publié"}]');
+    $this->dispatch('/opac/index/index');
+
+    $this->assertEquals('SELECT `cms_article`.* FROM `cms_article` WHERE ((DEBUT IS NULL) OR (DEBUT <= CURDATE())) AND ((FIN IS NULL) OR (FIN >= CURDATE())) AND (id_article in (5)) AND (STATUS in (3)) ORDER BY FIELD(ID_ARTICLE, 5) ASC', $this->_select->assemble());
+  }
+
+
+  /** @test */
+  public function freeWidgetRequestShouldNotFilterArticlesByWorkflow() {
+    $this->dispatch('/opac/index/index');
+
+    $this->assertEquals('SELECT `cms_article`.* FROM `cms_article` WHERE ((DEBUT IS NULL) OR (DEBUT <= CURDATE())) AND ((FIN IS NULL) OR (FIN >= CURDATE())) AND (id_article in (5)) ORDER BY FIELD(ID_ARTICLE, 5) ASC', $this->_select->assemble());
+  }
+}
\ No newline at end of file
diff --git a/tests/scenarios/Templates/TemplatesItemsPaginatedTest.php b/tests/scenarios/Templates/TemplatesItemsPaginatedTest.php
index 688b161f951e50bd8ec7cc114781f8ab2197a567..0f0afc62ec857421a14316457dd7ef011ed30c0e 100644
--- a/tests/scenarios/Templates/TemplatesItemsPaginatedTest.php
+++ b/tests/scenarios/Templates/TemplatesItemsPaginatedTest.php
@@ -48,7 +48,7 @@ abstract class TemplateItemsPaginatedTestCase  extends AbstractControllerTestCas
 
     $this->_buildItems(Intonation_View_RenderTruncateList::ajaxSize() + 1);
 
-    $this->dispatch('/noticeajax/resources/id/456');
+    $this->dispatch('/noticeajax/resources/id/456/inspector_gadget/1');
   }
 
 
@@ -98,7 +98,7 @@ abstract class TemplateItemsPaginatedTestCase  extends AbstractControllerTestCas
 
   /** @test */
   public function shouldHaveLinkToNextPage() {
-    $this->assertXPath('//a[contains(@href,"/index/ajax-paginated-list/id_profil/1/id/")][contains(@href,"/size/10/page/2")]');
+    $this->assertXPath('//a[contains(@href,"/index/ajax-paginated-list/id_profil/1/id/")][contains(@href,"/size/10/page/2/inspector_gadget/1")]');
   }
 
 
diff --git a/tests/scenarios/Templates/TemplatesWidgetCarouselTest.php b/tests/scenarios/Templates/TemplatesWidgetCarouselTest.php
index 62b8fb83e48777ec20a570e2a132b9a979d54da6..ab99ac7ba7bd38a07b1e7dbc9da9f15184035fbb 100644
--- a/tests/scenarios/Templates/TemplatesWidgetCarouselTest.php
+++ b/tests/scenarios/Templates/TemplatesWidgetCarouselTest.php
@@ -152,4 +152,131 @@ class TemplatesWidgetCarouselLoaderTestTest extends ArticleLoaderGetArticlesByPr
     $article = $this->getArticles(['filter_by_local' => true]);
     $this->assertQueryEquals('SELECT `cms_article`.* FROM `cms_article` WHERE ((DEBUT IS NULL) OR (DEBUT <= CURDATE())) AND ((FIN IS NULL) OR (FIN >= CURDATE())) AND (PARENT_ID=0) AND ((cms_article.langue = "fr" or exists (select \'x\' from cms_article as translation where translation.PARENT_ID = cms_article.ID_ARTICLE and trim(translation.langue) = "fr") or cms_article.langue = "" or cms_article.langue is null)) ORDER BY `DATE_CREATION` DESC');
   }
-}
\ No newline at end of file
+}
+
+
+
+
+/* hotline: #143811 */
+class TemplatesWidgetCarouselRecordNewsTest extends AbstractControllerTestCase {
+
+  protected $_storm_default_to_volatile = true;
+
+  public function setUp() {
+    parent::setUp();
+
+    $this->_buildTemplateProfil(['id' => 1,
+                                 'libelle' => 'Randomize carousel'])
+         ->setBoiteOfTypeInDivision(2,
+                                    Intonation_Library_Widget_Carousel_Record_Definition::CODE,
+                                    ['layout' => 'horizontal_list',
+                                     'rendering' => 'card-overlay',
+                                     'size' => 2,
+                                     'rss' => 0,
+                                     'embeded_code' => 0,
+                                     'link_to_all' => 1,
+                                     'all_layout' => 'list',
+                                     'all_rendering' => 'card-horizontal',
+                                     'description_length' => 20,
+                                     'titre' => 'Toutes les nouveautés',
+                                     'order' => 'RAND()',
+                                     'id_catalogue' => 10,
+                                     'type_module' => 'KIOSQUE'
+                                    ])
+         ->assertSave();
+
+    $this->fixture(Class_Catalogue::class,
+                   ['id' => 10,
+                    'libelle' => 'Catalogue nouveauté',
+                    'type_doc' => 1
+                   ]);
+
+    for ($i = 1; $i <= 100; $i++)
+      $this->fixture(Class_Notice::class,
+                     ['id' => $i]);
+
+    Zend_Registry::set('sql', null);
+    Storm_Cache::beVolatile();
+
+    $cache = (new Storm_Cache)
+      ->memoize(['select notices.id_notice from notices Where (notices.type_doc=\'1\') and type=1 order by url_image="no", url_image=""  LIMIT 0,100',
+                 'NoticeLoader',
+                 'getNoticeIdsByRequeteRecherche'],
+                function () {return range(1, 100);});
+
+    $this->dispatch('/index');
+  }
+
+
+  /** @test */
+  public function carouselShouldBePresentWithTwoRandomRecords() {
+    $this->assertXpathCount(2, '//div[contains(@class, "boite kiosque")]//div[contains(@class, "card-deck")]//div[contains(@class, "card_with_overlay")]');
+  }
+
+
+  /** @test */
+  public function carouselRecordsShouldBeShuffled() {
+    $this->assertRegExp('/.*(\/recherche\/viewnotice\/id\/(([^1-2]\/)|([0-9][0-9]0?\/)))./',
+                        $this->_response->getBody());
+  }
+}
+
+
+
+
+/* hotline : #137145 */
+class TemplatesWidgetCarouselNoticeReviewTest
+  extends AbstractControllerTestCase {
+
+  protected $_storm_default_to_volatile = true;
+
+  public function setUp() {
+    parent::setUp();
+
+    $this->fixture(Class_Users::class,
+                   ['id' => 1,
+                    'password' => 'pass',
+                    'login' => 'totoro'
+                   ]);
+
+    for ($i = 2; $i < 27; $i++) {
+      $this->fixture(Class_Notice::class,
+                     ['id' => $i,
+                     ]);
+      $this->fixture(Class_AvisNotice::class,
+                     ['id' => $i + 1,
+                      'id_notice' => $i,
+                      'entete' => 'Avis roman ' . $i,
+                      'avis' => 'Roman de cape et d\'épée ' . $i,
+                      'id_user' => 1
+                     ]);
+    }
+
+    $this->_buildTemplateProfil(['id' => 1,
+                                 'libelle' => 'Carousel Avis Notices'])
+         ->setBoiteOfTypeInDivision(2,
+                                    Intonation_Library_Widget_Carousel_Review_Definition::CODE,
+                                    [
+                                     'layout' => Intonation_Library_Widget_Carousel_Definition::LISTING_WITH_OPTIONS,
+                                     'rendering' => 'card-horizontal',
+                                     'size' => 1000,
+                                     'rss' => 0,
+                                     'embeded-code' => 0,
+                                     'link-to-all' => 1,
+                                     'all-layout' => 'list_with_options',
+                                     'all-rendering' => 'card-horizontal',
+                                     'description-length' => 20,
+                                     'titre' => 'Avis et commentaires',
+                                     'order' => 'review_date'
+                                    ])
+         ->assertSave();
+
+    $this->dispatch('/index');
+  }
+
+
+  /** @test */
+  public function romanDeCapeEtEpee3ShouldNotBeDuplicated() {
+    $this->assertXpathCount('//p[@class="model_description_Class_AvisNotice"][text()="Roman de cape et d\'épée 3"]', 1);
+  }
+}
diff --git a/tests/scenarios/Templates/TemplatesWidgetTest.php b/tests/scenarios/Templates/TemplatesWidgetTest.php
index 86647fdf29e6f4cdc97e36597c64ef7da0a908c1..c4efc2610ee691a60f27e20fff09313d87f57ca0 100644
--- a/tests/scenarios/Templates/TemplatesWidgetTest.php
+++ b/tests/scenarios/Templates/TemplatesWidgetTest.php
@@ -549,9 +549,17 @@ class TemplatesWidgetSearchInlineStyleTest extends TemplatesIntonationTestCase {
                ->setProfileId(72)
                ->load());
     $widget
-      ->setNewDatas(['IntonationFormStyle' => 'inline'])
+      ->setNewDatas(['IntonationFormStyle' => 'inline',
+                     'select_bib' => 1,
+                     'library_selection_label' => 'Bibliothèques du réseau',
+                     ])
       ->updateProfile();
 
+    $this->fixture(Class_Bib::class,
+                   [ 'id' => 2,
+                    'libelle' => 'Lothlorien',
+                    'visibilite' => Class_Bib::V_DATA]);
+
     $this->dispatch('/opac/index/index/id_profil/72', true);
   }
 
@@ -568,6 +576,24 @@ class TemplatesWidgetSearchInlineStyleTest extends TemplatesIntonationTestCase {
   }
 
 
+  /** @test */
+  public function searchLabelForBibShoulNotBePresent() {
+    $this->assertNotXPath('//div[contains(@class,"boite rech_simple")]//form//label[contains(@for,"custom_multifacets_bib")]');
+  }
+
+
+  /** @test */
+  public function searchOptionBibliothequeShouldDisplayFirstEntryBibliotheque() {
+    $this->assertXPath('//div[contains(@class,"boite rech_simple")]//form//select[@name="custom_multifacets_bib"]//option[1][@value="0"][@label="Bibliothèques du réseau"]');
+  }
+
+
+  /** @test */
+  public function searchOptionBibliothequeShouldDisplaySecondEntryLothlorien() {
+    $this->assertXPath('//div[contains(@class,"boite rech_simple")]//form//select[@name="custom_multifacets_bib"]//option[2][@value="B2"][@label="Lothlorien"]');
+  }
+
+
   /** @test */
   public function advancedSearchButtonShouldHaveClassDropdownToggle() {
     $this->assertXPath('//div[contains(@class,"boite rech_simple")]//div[@class="dropdown"]/button[contains(@class, "dropdown-toggle")]');
@@ -610,6 +636,114 @@ class TemplatesWidgetSearchToggleStyleTest extends TemplatesIntonationTestCase {
 
 
 
+class TemplatesWidgetSearchDefaultStyleTest extends TemplatesIntonationTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    $widget = ((new Class_Systeme_Widget_Widget)
+               ->setId(8)
+               ->setProfileId(72)
+               ->load());
+
+    $widget
+      ->setNewDatas(['IntonationFormStyle' => '',
+                     'select_bib' => 1,
+                     'library_selection_label' => 'Bibliothèques du réseau',
+                     'doc_type_selection_label' => 'Typologie',
+                     'select_doc' => '5;2',
+                     'select_annexe' => 1,
+                     'domains_ids' => '1-2'])
+      ->updateProfile();
+
+    $this->fixture(Class_Bib::class,
+                   [ 'id' => 2,
+                    'libelle' => 'Lothlorien',
+                    'visibilite' => Class_Bib::V_DATA]);
+
+    $this->fixture(Class_CodifAnnexe::class,
+                   [ 'id' => 3,
+                    'libelle'=> 'Principal',
+                   'invisible' => 0]);
+
+    $this->fixture(Class_Notice::class,
+                   ['id' => 42,
+                    'type_doc' => Class_TypeDoc::LIVRE]);
+
+    $this->fixture(Class_TypeDoc::class,
+                   [ 'id' => Class_TypeDoc::LIVRE,
+                    'label'=> 'Livre']);
+
+    Class_TypeDoc::setUsedIdsCache([Class_TypeDoc::LIVRE]);
+
+    Class_Profil::getCurrentProfil()->setSelTypeDoc(Class_TypeDoc::LIVRE)->save();
+
+
+    $this->dispatch('/opac/index/index/id_profil/72', true);
+  }
+
+  public function tearDown() {
+    Class_TypeDoc::reset();
+    parent::tearDown();
+  }
+
+
+  /** @test */
+  public function searchWidgetShouldBeDefaultForm() {
+    $this->assertXPath('//div[contains(@class,"boite rech_simple")]//form');
+  }
+
+
+  /** @test */
+  public function searchLabelForBibShoulDisplayBibliothequesDuReseau() {
+    $this->assertXPathContentContains('//div[contains(@class,"boite rech_simple")]//form//label[contains(@for,"custom_multifacets_bib")]', 'Bibliothèques du réseau');
+  }
+
+
+  /** @test */
+  public function searchOptionBibliothequeShouldDisplayToutes() {
+    $this->assertXPath('//div[contains(@class,"boite rech_simple")]//form//select[@name="custom_multifacets_bib"]//option[1][@value="0"][@label="toutes"]');
+  }
+
+
+  /** @test */
+  public function searchOptionBibliothequeShouldDisplayOptionLothlorien() {
+    $this->assertXPath('//div[contains(@class,"boite rech_simple")]//form//select[@name="custom_multifacets_bib"]//option[2][@value="B2"][@label="Lothlorien"]');
+  }
+
+
+  /** @test */
+  public function searchLabelForAnnexeShouldDisplaySite() {
+    $this->assertXPathContentContains('//div[contains(@class,"boite rech_simple")]//form//label[contains(@for,"custom_multifacets_annexe")]', 'Site');
+  }
+
+
+  /** @test */
+  public function searchOptionForAnnexeShouldDisplayTous() {
+    $this->assertXPath('//div[contains(@class,"boite rech_simple")]//form//select[@name="custom_multifacets_annexe"]//option[1][@value="0"][@label="tous"]');
+  }
+
+
+  /** @test */
+  public function searchLabelForTypedocShoulDisplayTypeDoc() {
+    $this->assertXPathContentContains('//div[contains(@class,"boite rech_simple")]//form//label[contains(@for,"custom_multifacets_doctype")]', 'Typologie');
+  }
+
+
+  /** @test */
+  public function searchOptionTypeDocShoulDisplayTous() {
+    $this->assertXPath('//div[contains(@class,"boite rech_simple")]//form//select[contains(@name,"custom_multifacets_doctype")]//option[1][@value="0"][@label="tous"]');
+  }
+
+
+  /** @test */
+  public function searchOptionTypeDocShoulDisplayDocTypeLivre() {
+    $this->assertXPath('//div[contains(@class,"boite rech_simple")]//form//select[contains(@name,"custom_multifacets_doctype")]//option[2][@value="T1"][@label="Livre"]');
+  }
+}
+
+
+
+
 class TemplatesImageWidgetTest extends TemplatesIntonationTestCase {
 
   /** @test */
diff --git a/tests/scenarios/Templates/TemplatesWidgetsMultipleSelectionTest.php b/tests/scenarios/Templates/TemplatesWidgetsMultipleSelectionTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..38a16cf4ec1b2a19d632c7478b2a16099b06ab92
--- /dev/null
+++ b/tests/scenarios/Templates/TemplatesWidgetsMultipleSelectionTest.php
@@ -0,0 +1,260 @@
+<?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
+ */
+
+
+abstract class TemplatesWidgetsMultipleSelectionAdminTestCase extends Admin_AbstractControllerTestCase {
+
+  protected $_storm_default_to_volatile = true;
+
+
+  public function setUp() {
+    parent::setUp();
+    $parent_profile = $this->_buildTemplateProfil(['id' => 13]);
+
+    $parent_profile_patcher = (new Class_Template_ProfilePatcher(null))
+      ->setProfile($parent_profile);
+
+    $parent_profile_patcher
+      ->addWidget(Intonation_Library_Widget_Carousel_Library_Definition::CODE,
+                  Class_Profil::DIV_BANNIERE,
+                  ['titre' => 'Réseau',
+                   'rendering' => 'card-description',
+                   'layout' => 'multiple_carousel_plus',
+                   'size' => 10]);
+
+    $profile = $this->_buildTemplateProfil(['id' => 16,
+                                            'parent_id' => 13]);
+
+    $profile_patcher = (new Class_Template_ProfilePatcher(null))
+      ->setProfile($profile);
+
+    $profile_patcher
+      ->addWidget(Intonation_Library_Widget_Carousel_Record_Definition::CODE,
+                  Class_Profil::DIV_MAIN,
+                  ['titre' => 'Nouveauté',
+                   'rendering' => 'card-description',
+                   'layout' => 'multiple_carousel_plus',
+                   'size' => 10])
+
+      ->addWidget(Intonation_Library_Widget_Carousel_Article_Definition::CODE,
+                  Class_Profil::DIV_MAIN,
+                  ['titre' => 'Agenda',
+                   'rendering' => 'card-description',
+                   'layout' => 'multiple_carousel_plus',
+                   'size' => 10])
+
+      ->addWidget(Intonation_Library_Widget_Image_Definition::CODE,
+                  Class_Profil::DIV_MAIN,
+                  ['titre' => 'Logo']);
+  }
+}
+
+
+class TemplatesWidgetsMultipleSelectionProfilAccueilTest extends TemplatesWidgetsMultipleSelectionAdminTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    $this->dispatch('admin/profil/accueil/id_profil/16');
+  }
+
+  /** @test */
+  public function anchorToSelectWidgetInBannerShouldBePresent() {
+    $this->assertXPath('//div[@data-division="4"]//a[@href="/admin/profil/add-model-to-selection/id_profil/16/select_id/13-1"]');
+  }
+
+
+  /** @test */
+  public function anchorToSelectWidgetInMainSectionShouldBePresent() {
+    $this->assertXPath('//div[@data-division="2"]//a[@href="/admin/profil/add-model-to-selection/id_profil/16/select_id/16-3"]');
+  }
+}
+
+
+
+
+class TemplatesWidgetsMultipleSelectionProfilSessionSelectionTest extends TemplatesWidgetsMultipleSelectionAdminTestCase {
+
+  /** @test */
+  public function shouldAddWidgetOneToSelection() {
+    $this->dispatch('/admin/profil/add-model-to-selection/id_profil/16/select_id/16-1');
+    $this->assertEquals(['widget' => ['16-1']], Zend_Registry::get('session')->selected_items);
+  }
+
+
+  /** @test */
+  public function shouldRemoveWidgetOneFromSelection() {
+    Zend_Registry::get('session')->selected_items = ['widget' => ['16-1']];
+    $this->dispatch('/admin/profil/remove-model-from-selection/id_profil/16/select_id/16-1');
+    $this->assertEquals(['widget' => []], Zend_Registry::get('session')->selected_items);
+  }
+
+
+
+  /** @test */
+  public function shouldRemoveWidgetFromProfile() {
+    Zend_Registry::get('session')->selected_items = ['widget' => ['13-1', '16-3']];
+    $this->dispatch('/admin/profil/delete-multiple');
+    $this->assertNull((new Class_Systeme_Widget_Widget)
+                      ->setId(1)
+                      ->setProfileId(13)
+                      ->load());
+  }
+}
+
+
+
+
+class TemplatesWidgetsMultipleSelectionProfilAccueilWithSelectedWidgetsTest extends TemplatesWidgetsMultipleSelectionAdminTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    Zend_Registry::get('session')->selected_items = ['widget' => ['16-2', '13-1']];
+    $this->dispatch('admin/profil/accueil/id_profil/16');
+  }
+
+
+  /** @test */
+  public function reseauTitleShouldBePresentInContainerListLi() {
+    $this->assertXPathContentContains('//ul[@class="container_list"]/li',
+                                      'Boite des bibliothèques : Réseau');
+  }
+
+
+  /** @test */
+  public function twoWidgetsSelectedMessageShouldBeDisplay() {
+    $this->assertXpathContentContains('//div[@class="selected_items_widget show"]',
+                                      'Vous avez sélectionné 2 boites');
+  }
+
+
+  /** @test */
+  public function widgetLibraryInBannerSelectedMessageShouldBeDisplay() {
+    $this->assertXPathContentContains('//div[@class="selected_items_widget show"]//table//td[contains(text(), "Réseau")]/following-sibling::td',
+                                      'Boite des bibliothèques de la division bannière du profil 13');
+  }
+
+
+  /** @test */
+  public function widgetRecordInMainSelectedMessageShouldBeDisplay() {
+    $this->assertXPathContentContains('//div[@class="selected_items_widget show"]//table//td[contains(text(), "Nouveauté")]/following-sibling::td',
+                                      'Boite de notices de la division principale du profil 16');
+  }
+}
+
+
+
+
+class TemplatesWidgetsMultipleSelectionProfilSessionEditSelectionTest extends TemplatesWidgetsMultipleSelectionAdminTestCase {
+
+
+  public function setUp() {
+    parent::setUp();
+    Zend_Registry::get('session')->selected_items = ['widget' => ['13-1', '16-3']];
+    $this->dispatch('/admin/profil/edit-multiple');
+  }
+
+  /** @test */
+  public function shouldRenderEditForm() {
+    $this->assertXPath('//form');
+  }
+
+
+  /** @test */
+  public function editFormShouldNotContainsSelectionTab() {
+    $this->assertNotXPath('//form//li', 'Sélection');
+  }
+
+
+  /** @test */
+  public function shouldContainsHiddenMode() {
+    $this->assertXPath('//form//select[contains(@id, "HiddenMode")]');
+  }
+
+
+  /** @test */
+  public function shouldContainsLayout() {
+    $this->assertXPath('//form//select[contains(@id, "layout")]');
+  }
+}
+
+
+
+
+class TemplatesWidgetsMultipleSelectionProfilSessionPostEditTest extends TemplatesWidgetsMultipleSelectionAdminTestCase {
+
+
+  public function setUp() {
+    parent::setUp();
+    $_SERVER['HTTP_REFERER'] = 'admin/profil/accueil/id_profil/16';
+    Zend_Registry::get('session')->selected_items = ['widget' => ['13-1', '16-3']];
+    $this->postDispatch('/admin/profil/edit-multiple',
+                        ['IntonationShowHeader' => 0,
+                         'titre' => 'boites modifiées par lots']);
+  }
+
+  /** @test */
+  public function shouldRedirectToReferer() {
+    $this->assertRedirectTo('/admin/profil/accueil/id_profil/16');
+  }
+
+
+  /** @test */
+  public function shouldNotifySuccess() {
+    $this->assertFlashMessengerContentContains('Les 2 boites sélectionné(e)s ont bien été sauvegardé(e)s');
+  }
+
+
+  /** @test */
+  public function widgetTitreShouldHaveBeenModified() {
+    $this->assertEquals('boites modifiées par lots', (new Class_Systeme_Widget_Widget)
+                        ->setId(1)
+                        ->setProfileId(13)
+                        ->load()->gettitre());
+  }
+
+
+  /** @test */
+  public function widgetLayoutShouldNotHaveBeenModified() {
+    $this->assertEquals('multiple_carousel_plus', (new Class_Systeme_Widget_Widget)
+                        ->setId(1)
+                        ->setProfileId(13)
+                        ->load()->getlayout());
+  }
+}
+
+
+
+
+
+class TemplatesWidgetsMultipleSelectionProfilSessionEditMixedSelectionTest extends TemplatesWidgetsMultipleSelectionAdminTestCase {
+
+
+  public function setUp() {
+    parent::setUp();
+    Zend_Registry::get('session')->selected_items = ['widget' => ['13-1', '16-3', '16-4']];
+    $this->dispatch('/admin/profil/edit-multiple');
+  }
+
+  /** @test */
+  public function shouldNotContainsLayout() {
+    $this->assertNotXPath('//form//select[contains(@id, "layout")]');
+  }
+}
\ No newline at end of file
diff --git a/tests/scenarios/Templates/TerreDuMilieuTemplateTest.php b/tests/scenarios/Templates/TerreDuMilieuTemplateTest.php
index 1b4870ee3e984dcc6aca40b747b2d9e54fb0bfb0..743ab29b36633210f7505bd4c282a0fb3477680c 100644
--- a/tests/scenarios/Templates/TerreDuMilieuTemplateTest.php
+++ b/tests/scenarios/Templates/TerreDuMilieuTemplateTest.php
@@ -33,7 +33,6 @@ abstract class TerreDuMilieuTemplateTestCase extends AbstractControllerTestCase
 
     (new TerreDuMilieu_Template)->tryOn($profile);
     (new Class_Profil_Promoter())->promote($profile);
-
   }
 
 
@@ -125,6 +124,8 @@ class TerreDuMilieuTemplateOpacIndexWithUserAgentTest extends TerreDuMilieuTempl
                     'id_cat' => 34
                    ]);
 
+    Class_Article_Loader::setSelectTool(new Class_Article_SelectForTest);
+
     $this->dispatch('/opac/index/index');
   }