diff --git a/VERSIONS b/VERSIONS
index 247c7b3fca6611eafe682b44c661a72941b98ef9..88387ed2ff3014be77928fe3608050e45c621206 100644
--- a/VERSIONS
+++ b/VERSIONS
@@ -1,3 +1,19 @@
+11/10/2021 - v8.0.141
+
+ - ticket #140071 : Magasin de thèmes : correction des liens de consultation et d'emprunt des prêts numériques en bibliothèque.
+
+ - ticket #138416 : Magasin de thèmes : Correction du lien d'accès aux domaines qui sont dans des carrousels.
+ 
+ - ticket #140446 : Article : Le message Dates et horaires n'est pas affiché si il n'y a pas d'évenement.
+
+ - ticket #142642 : Indexation des pages : création d'une clé œuvre pour la notice générée.
+
+ - ticket #135229 : Cosmogramme : Correction du paramétrage des codifications de Genres, Sections, Emplacements permettre la gestion du séparateur "espace"
+
+ - ticket #139527 : Cosmogramme : Lorsque une notice de fascicule et les notices de dépouillement liées sont intégrées, les facettes auteurs et matières de l'ensemble des notices sont conservées.
+
+
+
 05/10/2021 - v8.0.140
 
  - ticket #133971 : Magasin de thèmes : Les sauts de lignes dans les avis sont maintenant visibles.
diff --git a/cosmogramme/cosmozend/tests/application/modules/cosmo/controllers/EmplacementControllerTest.php b/cosmogramme/cosmozend/tests/application/modules/cosmo/controllers/EmplacementControllerTest.php
index 7ea7d3164d2cd53088e55226961ba981923d409c..ca6c24aa9fcc017927cb22c7e524618c4dc9248d 100644
--- a/cosmogramme/cosmozend/tests/application/modules/cosmo/controllers/EmplacementControllerTest.php
+++ b/cosmogramme/cosmozend/tests/application/modules/cosmo/controllers/EmplacementControllerTest.php
@@ -24,19 +24,24 @@ abstract class Cosmo_EmplacementControllerTestCase extends CosmoControllerTestCa
   public function setUp() {
     parent::setUp();
 
-    $this->fixture('Class_CodifEmplacement', ['id' => 18,
-                                              'libelle' => 'Albums',
-                                              'regles' => '995$x=55',
-                                              'ne_pas_afficher' => 0]);
+    $this->fixture(Class_CodifEmplacement::class,
+                   ['id' => 18,
+                    'libelle' => 'Albums',
+                    'regles' =>
+                    '995$x=' . Class_Codification_Rule::REMOVE_SPACE_PREFIX . '55',
+                    'ne_pas_afficher' => 0]);
   }
 }
 
 
 
-class Cosmo_EmplacementControllerIndexTest extends Cosmo_EmplacementControllerTestCase {
+
+class Cosmo_EmplacementControllerIndexTest
+  extends Cosmo_EmplacementControllerTestCase {
+
   public function setUp() {
     parent::setUp();
-    $this->dispatch('/cosmo/emplacement/index', true);
+    $this->dispatch('/cosmo/emplacement/index');
   }
 
 
@@ -54,7 +59,8 @@ class Cosmo_EmplacementControllerIndexTest extends Cosmo_EmplacementControllerTe
 
   /** @test */
   public function albumRuleShouldBePresent() {
-    $this->assertXPathContentContains('//td', '995$x=55');
+    $this->assertXPathContentContains('//table//tr//td/ul//li',
+                                      '995$x égal 55 sans espace(s)');
   }
 
 
@@ -78,10 +84,13 @@ class Cosmo_EmplacementControllerIndexTest extends Cosmo_EmplacementControllerTe
 
 
 
-class Cosmo_EmplacementControllerEditTest extends Cosmo_EmplacementControllerTestCase {
+
+class Cosmo_EmplacementControllerEditTest
+  extends Cosmo_EmplacementControllerTestCase {
+
   public function setUp() {
     parent::setUp();
-    $this->dispatch('/cosmo/emplacement/edit/id/18', true);
+    $this->dispatch('/cosmo/emplacement/edit/id/18');
   }
 
 
@@ -106,7 +115,10 @@ class Cosmo_EmplacementControllerEditTest extends Cosmo_EmplacementControllerTes
 
 
 
-class Cosmo_EmplacementControllerEditPostTest extends Cosmo_EmplacementControllerTestCase {
+
+class Cosmo_EmplacementControllerEditPostTest
+  extends Cosmo_EmplacementControllerTestCase {
+
   public function setUp() {
     parent::setUp();
     $this->postDispatch('/cosmo/emplacement/edit/id/18',
@@ -116,6 +128,7 @@ class Cosmo_EmplacementControllerEditPostTest extends Cosmo_EmplacementControlle
                          'rule_field' => ['x'],
                          'rule_sign' => ['='],
                          'rule_values' => ['07'],
+                         'rule_space' => ['0'],
                          'ne_pas_afficher' => 1]);
 
     $this->model = Class_CodifEmplacement::find(18);
@@ -135,8 +148,10 @@ class Cosmo_EmplacementControllerEditPostTest extends Cosmo_EmplacementControlle
 
 
   /** @test */
-  public function rulesShouldBeDollarXEqualsZeroSeven() {
-    $this->assertEquals('995$x=07', $this->model->getRegles());
+  public function rulesShouldBeDollarXEqualsRemoveSpaceZeroSeven() {
+    $this->assertEquals('995$x='
+                        . Class_Codification_Rule::REMOVE_SPACE_PREFIX . '07',
+                        $this->model->getRegles());
   }
 
 
@@ -148,16 +163,18 @@ class Cosmo_EmplacementControllerEditPostTest extends Cosmo_EmplacementControlle
 
 
 
+
 class Cosmo_EmplacementControllerAddTest extends Cosmo_EmplacementControllerTestCase {
+
   public function setUp() {
     parent::setUp();
-    $this->dispatch('/cosmo/emplacement/add', true);
+    $this->dispatch('/cosmo/emplacement/add');
   }
 
 
   /** @test */
   public function defaultRuleShouldBePresent() {
-    $this->assertXpathContentContains('//script', 'values:{"rule_zone":["995"],"rule_field":["k"],"rule_sign":["\/"],"rule_values":["R"]}');
+    $this->assertXpathContentContains('//script', 'values:{"rule_zone":["995"],"rule_field":["k"],"rule_sign":["\/"],"rule_values":["R"],"rule_space":["0"]}');
   }
 
 
@@ -171,6 +188,7 @@ class Cosmo_EmplacementControllerAddTest extends Cosmo_EmplacementControllerTest
 
 
 
+
 class Cosmo_EmplacementControllerAlbumsValidatePostWithErrorsTest
   extends Cosmo_EmplacementControllerTestCase {
 
diff --git a/cosmogramme/cosmozend/tests/application/modules/cosmo/controllers/GenreControllerTest.php b/cosmogramme/cosmozend/tests/application/modules/cosmo/controllers/GenreControllerTest.php
index df6ab476bfcf73fa6320c9d1469473b08abed9a2..0df94d65b0a92fb1c6524e203ed74ee0baba82f9 100644
--- a/cosmogramme/cosmozend/tests/application/modules/cosmo/controllers/GenreControllerTest.php
+++ b/cosmogramme/cosmozend/tests/application/modules/cosmo/controllers/GenreControllerTest.php
@@ -26,12 +26,19 @@ abstract class Cosmo_GenreControllerTestCase extends CosmoControllerTestCase {
     $this->fixture('Class_CodifGenre',
                    ['id' => 5,
                     'libelle' => '01 Energie Enjeux',
-                    'regles' => '995$q=98']);
+                    'regles' => '995$q=0@@98']);
 
     $this->fixture('Class_CodifGenre',
                    ['id' => 7,
                     'libelle' => '01 Energie Politique',
-                    'regles' => '995$q=99']);
+                    'regles' => '995$q=0@@99']);
+
+    $this->fixture('Class_CodifGenre',
+                   ['id' => 9,
+                    'libelle' => '01 Energie Vertes',
+                    'regles' => '997$a=0@@11
+998$b/1@@22;33
+999$c*1@@44']);
   }
 }
 
@@ -41,7 +48,7 @@ abstract class Cosmo_GenreControllerTestCase extends CosmoControllerTestCase {
 class Cosmo_GenreControllerIndexTest extends Cosmo_GenreControllerTestCase {
   public function setUp() {
     parent::setUp();
-    $this->dispatch('/cosmo/genre', true);
+    $this->dispatch('/cosmo/genre');
   }
 
 
@@ -65,8 +72,13 @@ class Cosmo_GenreControllerIndexTest extends Cosmo_GenreControllerTestCase {
 
 
   /** @test */
-  public function energiePolitiqueReglesShouldBeInTable() {
-    $this->assertXPathContentContains('//table//tr//td', '995$q=99');
+  public function energieVertesReglesShouldBeInTable() {
+    $this->assertXPathContentContains('//table//tr//td/ul//li',
+                                      '997$a égal 11 sans espace(s)');
+    $this->assertXPathContentContains('//table//tr//td/ul//li',
+                                      '998$b commence par 22 où 33 avec espace(s)');
+    $this->assertXPathContentContains('//table//tr//td/ul//li',
+                                      '999$c contient 44 avec espace(s)');
   }
 
 
@@ -86,9 +98,10 @@ class Cosmo_GenreControllerIndexTest extends Cosmo_GenreControllerTestCase {
 
 
 class Cosmo_GenreControllerEditTest extends Cosmo_GenreControllerTestCase {
+
   public function setUp() {
     parent::setUp();
-    $this->dispatch('/cosmo/genre/edit/id/7', true);
+    $this->dispatch('/cosmo/genre/edit/id/7');
   }
 
 
@@ -99,8 +112,8 @@ class Cosmo_GenreControllerEditTest extends Cosmo_GenreControllerTestCase {
 
 
   /** @test */
-  public function reglesValueShouldBe995DollarQEquals99() {
-    $this->assertXpathContentContains('//script', '"rule_zone":["995"],"rule_field":["q"],"rule_sign":["="],"rule_values":["99"]', $this->_response->getBody());
+  public function reglesValueShouldBe995DollarQEquals99WithoutSpace() {
+    $this->assertXpathContentContains('//script', '"rule_zone":["995"],"rule_field":["q"],"rule_sign":["="],"rule_values":["99"],"rule_space":["0"]');
   }
 }
 
@@ -117,7 +130,8 @@ class Cosmo_GenreControllerEditPostTest extends Cosmo_GenreControllerTestCase {
                          'rule_zone' => ['995'],
                          'rule_field' => ['Q'],
                          'rule_sign' => ['*'],
-                         'rule_values' => ['99']]);
+                         'rule_values' => ['99'],
+                         'rule_space' => ['0']]);
 
     $this->_model = Class_CodifGenre::find(7);
   }
@@ -137,7 +151,63 @@ class Cosmo_GenreControllerEditPostTest extends Cosmo_GenreControllerTestCase {
 
   /** @test */
   public function reglesShouldBe995DollarQStar99() {
-    $this->assertEquals('995$Q*99', $this->_model->getRegles());
+    $this->assertEquals('995$Q*0@@99', $this->_model->getRegles());
+  }
+}
+
+
+
+
+abstract class Cosmo_GenreControllerEditPostFormatedTestCase
+  extends Cosmo_GenreControllerTestCase {
+
+  protected $_model;
+
+  public function setUp() {
+    parent::setUp();
+    $this->postDispatch('/cosmo/genre/edit/id/7',
+                        ['libelle' => '02 Energie Politique',
+                         'rule_zone' => ['995'],
+                         'rule_field' => ['Q'],
+                         'rule_sign' => ['='],
+                         'rule_values' => ['11;  22  ;  3   3   ;;  '],
+                         'rule_space' => [$this->_isRuleSpace() ? '1' : '0']]);
+
+    $this->_model = Class_CodifGenre::find(7);
+  }
+
+
+  protected function _isRuleSpace() {
+    return true;
+  }
+}
+
+
+
+
+class Cosmo_GenreControllerEditPostWithSpaceFormatedTest
+  extends Cosmo_GenreControllerEditPostFormatedTestCase {
+
+  /** @test */
+  public function reglesShouldBe995DollarQStar11_22_33WithSpace() {
+    $this->assertEquals('995$Q=1@@11; 22 ; 3 3 ', $this->_model->getRegles());
+  }
+}
+
+
+
+
+class Cosmo_GenreControllerEditPostWithoutSpaceFormatedTest
+  extends Cosmo_GenreControllerEditPostFormatedTestCase {
+
+  protected function _isRuleSpace() {
+    return false;
+  }
+
+
+  /** @test */
+  public function reglesShouldBe993DollarVEqual11_22_33WithoutSpace() {
+    $this->assertEquals('995$Q=0@@11;22;33', $this->_model->getRegles());
   }
 }
 
@@ -152,7 +222,8 @@ class Cosmo_GenreControllerEditInvalidPostTest extends Cosmo_GenreControllerTest
                          'rule_zone' => ['99588'],
                          'rule_field' => ['Q'],
                          'rule_sign' => ['='],
-                         'rule_values' => ['99']
+                         'rule_values' => ['99'],
+                         'rule_space' => ['0']
                          ]);
   }
 
@@ -166,7 +237,7 @@ class Cosmo_GenreControllerEditInvalidPostTest extends Cosmo_GenreControllerTest
   /** @test */
   public function pageShouldDisplayMarcZoneError() {
     $this->assertXPathContentContains('//script',
-                                      'line_errors:[["\"99588\" n\'est pas une zone marc valide (de 001 \u00e0 999)"]]',$this->_response->getBody());
+                                      'line_errors:[["\"99588\" n\'est pas une zone marc valide (de 001 \u00e0 999)"]]');
   }
 }
 
@@ -176,7 +247,7 @@ class Cosmo_GenreControllerEditInvalidPostTest extends Cosmo_GenreControllerTest
 class Cosmo_GenreControllerDeleteTest extends Cosmo_GenreControllerTestCase {
   public function setUp() {
     parent::setUp();
-    $this->dispatch('/cosmo/genre/delete/id/7', true);
+    $this->dispatch('/cosmo/genre/delete/id/7');
   }
 
 
@@ -190,4 +261,4 @@ class Cosmo_GenreControllerDeleteTest extends Cosmo_GenreControllerTestCase {
   public function shouldRedirectToGenreIndex() {
     $this->assertRedirectTo('/cosmo/genre/index');
   }
-}
\ No newline at end of file
+}
diff --git a/cosmogramme/cosmozend/tests/application/modules/cosmo/controllers/IntegrationControllerTest.php b/cosmogramme/cosmozend/tests/application/modules/cosmo/controllers/IntegrationControllerTest.php
index 4699305970a55e7cc9ccab9ad2573a0fa23061da..dd65b0fe78effdbceee0dd7df744856edca6c84f 100644
--- a/cosmogramme/cosmozend/tests/application/modules/cosmo/controllers/IntegrationControllerTest.php
+++ b/cosmogramme/cosmozend/tests/application/modules/cosmo/controllers/IntegrationControllerTest.php
@@ -550,21 +550,27 @@ class Cosmo_IntegrationControllerGenerateActionNanookPostTest
   /** @test */
   public function adulteSectionShouldBeCreated() {
     $this->assertNotNull(Class_CodifSection::findFirstBy(['libelle' => 'Adulte',
-                                                          'regles' => '995$9=1']));
+                                                          'regles' => '995$9='
+                                                          . Class_Codification_Rule::REMOVE_SPACE_PREFIX
+                                                          . '1']));
   }
 
 
   /** @test */
   public function coinLocationShouldBeCreated() {
     $this->assertNotNull(Class_CodifEmplacement::findFirstBy(['libelle' => 'Coin des tout-petits',
-                                                              'regles' => '995$6=4']));
+                                                              'regles' => '995$6='
+                                                              . Class_Codification_Rule::REMOVE_SPACE_PREFIX
+                                                              . '4']));
   }
 
 
   /** @test */
   public function bdKindShouldBeCReated() {
     $this->assertNotNull(Class_CodifGenre::findFirstBy(['libelle' => 'Biographie',
-                                                        'regles' => '995$7=3']));
+                                                        'regles' => '995$7='
+                                                        . Class_Codification_Rule::REMOVE_SPACE_PREFIX
+                                                        . '3']));
   }
 
 
diff --git a/cosmogramme/cosmozend/tests/application/modules/cosmo/controllers/SectionControllerTest.php b/cosmogramme/cosmozend/tests/application/modules/cosmo/controllers/SectionControllerTest.php
index ddeb4454c4fdd96c003979d9080332ff4f0e5060..86fb916a7db9d05c4e2c4460866432735850ddfd 100644
--- a/cosmogramme/cosmozend/tests/application/modules/cosmo/controllers/SectionControllerTest.php
+++ b/cosmogramme/cosmozend/tests/application/modules/cosmo/controllers/SectionControllerTest.php
@@ -85,6 +85,7 @@ class Cosmo_SectionControllerEditPostTest extends Cosmo_SectionControllerTestCas
                          'rule_field' => ['x'],
                          'rule_sign' => ['='],
                          'rule_values' => ['07'],
+                         'rule_space' => ['0']
                         ]);
 
     $this->_model = Class_CodifSection::find(7);
@@ -105,7 +106,9 @@ class Cosmo_SectionControllerEditPostTest extends Cosmo_SectionControllerTestCas
 
   /** @test */
   public function rulesShouldBeDollarXEqualsZeroSeven() {
-    $this->assertEquals('995$x=07', $this->_model->getRegles());
+    $this->assertEquals('995$x='
+                        . Class_Codification_Rule::REMOVE_SPACE_PREFIX
+                        . '07', $this->_model->getRegles());
   }
 }
 
@@ -132,4 +135,4 @@ class Cosmo_SectionControllerActionsTest extends Cosmo_SectionControllerTestCase
     $this->dispatch('/cosmo/section/delete/id/7', true);
     $this->assertRedirectTo('/cosmo/section/index');
   }
-}
\ No newline at end of file
+}
diff --git a/cosmogramme/php/classes/classe_unimarc.php b/cosmogramme/php/classes/classe_unimarc.php
index 1cae798d5b0ac0e181063c72490baacfe5a2817c..dec124117069d2b1665f8605cc2859844f37a3d0 100644
--- a/cosmogramme/php/classes/classe_unimarc.php
+++ b/cosmogramme/php/classes/classe_unimarc.php
@@ -26,6 +26,7 @@ require_once 'classe_pcdm4.php';
 require_once 'classe_indexation.php';
 require_once("Class/Isbn.php");
 require_once 'classe_codif_cache.php';
+require_once 'Class/Codification/RulesHelper.php';
 
 
 class notice_unimarc extends iso2709_record {
@@ -52,12 +53,13 @@ class notice_unimarc extends iso2709_record {
   private $copyright;              // Mots clefs pour les notices non libres de droits (801$b)
   private $sigb;                   // Pour traitements specifiques
   private $ean345;                 // Reconnaissance des ean par la zone 345$b
-  private $regles_sections_genres; // Règles de reconnaissance des sections et des genres
+
   private $id_genre_documentaire;  // Identifiant pour le genre "documentaire"
   private $controle_codes_barres;  // Exception de filtrage des codes-barres
   protected $_id_bib;              // Bibliotheque d'intégration
   protected $_fluent; // Class_NoticeUnimarc_Fluent
   protected $_type_doc; //type do document
+  protected $_codifs_rules; // Règles de reconnaissance des sections et des genres
 
   public function __construct($id_bib=null) {
     $this->_id_bib = $id_bib;
@@ -69,7 +71,7 @@ class notice_unimarc extends iso2709_record {
     $this->ean345 = getVariable('ean_345');
 
     // Règles sections, genres, emplacements
-    $this->regles_sections_genres = $this->extractRegles();
+    $this->_codif_rules = new Class_Codification_RulesHelper();
     parent::__construct();
   }
 
@@ -237,10 +239,15 @@ class notice_unimarc extends iso2709_record {
     // Analyse sections, genres et emplacements
     $notice['genre'] = $notice["dewey"] ? $this->id_genre_documentaire : 0;
 
-    $ret = $this->getSectionGenre($notice["genre"]);
+    $ret['genre'] = ($genre = $notice['genre'])
+      ? $genre
+      : [];
+
+    $ret['genre'] = array_merge($ret['genre'],
+                                $this->_codif_rules->getCodifIdByGenre($this));
 
-    if (!empty($ret["genre"]))
-      $notice["genres"] = $ret["genre"];
+    if (!empty($ret['genre']))
+      $notice['genres'] = $ret['genre'];
 
     if (in_array((string)$notice["type_doc"],
                  [(string)Class_TypeDoc::PERIODIQUE,
@@ -1640,101 +1647,9 @@ class notice_unimarc extends iso2709_record {
   }
 
 
-  private function extractRegles() {
-    $ret = [];
-    foreach(['section', 'genre', 'emplacement'] as $type) {
-      $classname = 'Class_Codif'.ucfirst($type);
-      if (!$enregs = $classname::findAll())
-        continue;
-
-      foreach ($enregs as $enreg) {
-        if (!$regles = $enreg->getRegles())
-          continue;
-
-        $regles = explode("\n", $regles);
-        foreach ($regles as $regle) {
-          $zone = substr($regle, 0, 5);
-          $signe = substr($regle, 5, 1);
-          $valeurs = explode(';', strToUpper(substr($regle, 6)));
-          foreach ($valeurs as $valeur) {
-            $valeur = trim(str_replace(' ', '', $valeur));
-            if ('' != $valeur)
-              $ret[$zone][$type][$signe][$valeur] = $enreg->getId();
-          }
-        }
-      }
-    }
-    return $ret;
-  }
-
-
-  private function getSectionGenre($genre) {
-    $ret = [
-      'section' => [],
-      'emplacement' => [],
-      'genre' => [],
-    ];
-
-    if ($genre) {
-      $ret['genre'] = $genre;
-    }
-
-
-    foreach ($this->regles_sections_genres as $zone_champ => $types) {
-      $zone = substr($zone_champ, 0, 3);
-      $champ = substr($zone_champ, 4, 1);
-      $data = $this->get_subfield($zone, $champ);
-      if (!count($data))
-        continue;
-
-      foreach($data as $valeur_notice) {
-        $valeur_notice = strToUpper($valeur_notice);
-        $valeur_notice = str_replace(' ', '', $valeur_notice);
-        if (!$valeur_notice)
-          continue;
-
-        foreach($types as $type => $signes) {
-          foreach($signes as $signe => $valeurs) {
-            $retenu = '';
-            foreach($valeurs as $valeur => $code) {
-              if($signe == "=") {
-                if($valeur == $valeur_notice)
-                  $retenu=$code;
-              } elseif($signe == "/") {
-                if($valeur == substr($valeur_notice,0,strlen($valeur)))
-                  $retenu=$code;}
-              elseif($signe == "*") {
-                if( strpos($valeur_notice,$valeur) !== false)
-                  $retenu=$code;
-              }
-
-              if($retenu)
-                break;
-            }
-
-            if($retenu) {
-              $ret[$type][] = $retenu;
-              break;
-            }
-          }
-        }
-      }
-    }
-
-    return $ret;
-  }
-
-
   private function getIdCodeExemplaire($type, $champ, $sous_champ, $valeur) {
-    $valeur = trim(str_replace(' ', '', strtoupper($valeur)));
     $champ = $champ . '$' . $sous_champ;
-    if (!isset($this->regles_sections_genres[$champ][$type]))
-      return '';
-
-    $id = $this->regles_sections_genres[$champ][$type]['='][$valeur];
-    if(!$id) $id = $this->regles_sections_genres['995$0'][$type]['='][$valeur];
-    if(!$id) $id = '';
-    return $id;
+    return $this->_codif_rules->getCodifId($type, $champ, $valeur);
   }
 
 
@@ -1749,4 +1664,4 @@ class notice_unimarc extends iso2709_record {
     return Class_Notice_ThumbnailFields::newForDataProfile($this->profil_unimarc->getDataProfile())
       ->firstImageIn($this);
   }
-}
\ No newline at end of file
+}
diff --git a/cosmogramme/tests/php/classes/135229_rules_with_space.mrc b/cosmogramme/tests/php/classes/135229_rules_with_space.mrc
new file mode 100644
index 0000000000000000000000000000000000000000..d049347cba539bf642e1d2e8b8bb15167ba3717f
--- /dev/null
+++ b/cosmogramme/tests/php/classes/135229_rules_with_space.mrc
@@ -0,0 +1 @@
+00238     2200109   45000010005000000900009000051000041000142000042000551010008000979020010001059020013001155063  a5063  a20140806d        u||y0frey50      ba  aTest genre multiple 902bLIVRfAuteur  afre  aR PER  aRx   902
\ No newline at end of file
diff --git a/cosmogramme/tests/php/classes/NoticeIntegrationTest.php b/cosmogramme/tests/php/classes/NoticeIntegrationTest.php
index 75ce412ec17eaac86d99df95937302a9f1dd1d7d..fde6f03b538f4bb39e16f93178663f03fb49945a 100644
--- a/cosmogramme/tests/php/classes/NoticeIntegrationTest.php
+++ b/cosmogramme/tests/php/classes/NoticeIntegrationTest.php
@@ -988,6 +988,7 @@ class NoticeIntegrationDossier64Test extends NoticeIntegrationTestCase {
 
 
 class NoticeIntegrationGenreMultiple902Test extends NoticeIntegrationTestCase {
+
   public function setUp() {
     parent::setUp();
 
@@ -1019,6 +1020,7 @@ class NoticeIntegrationGenreMultiple902Test extends NoticeIntegrationTestCase {
     $this->assertContains(83, $this->notice_data['genres']);
   }
 
+
   /** @test */
   public function facettesShouldContainThreeGenres() {
     $this->assertContains(' G81', $this->notice_data['facettes']);
@@ -1030,6 +1032,52 @@ class NoticeIntegrationGenreMultiple902Test extends NoticeIntegrationTestCase {
 
 
 
+/* hotline: #135229 */
+class NoticeIntegrationGenreRulesKeepSpaceTest extends NoticeIntegrationTestCase {
+
+  public function setUp() {
+    parent::setUp();
+
+    $this->fixture(Class_CodifGenre::class,
+                   ['id' => 99,
+                    'libelle' => 'Policier',
+                    'regles' => '902$a/'
+                    . Class_Codification_Rule::KEEP_SPACE_PREFIX
+                    . 'RX']);
+
+    $this->fixture(Class_CodifGenre::class,
+                   ['id' => 88,
+                    'libelle' => 'Detective',
+                    'regles' => '902$a/'
+                    . Class_Codification_Rule::KEEP_SPACE_PREFIX
+                    . 'RP']);
+
+    $this->loadNoticeFromString(file_get_contents(__DIR__
+                                                  . '/135229_rules_with_space.mrc'));
+  }
+
+
+  /** @test */
+  public function noticeUnimarcShouldHaveOnlyOneGenre() {
+    $this->assertCount(1, $this->notice_data['genres']);
+  }
+
+
+  /** @test */
+  public function noticeUnimarcShouldContainCodif99() {
+    $this->assertContains(99, $this->notice_data['genres']);
+  }
+
+
+  /** @test */
+  public function facettes88ShouldNotExist() {
+    $this->assertNotContains(' G88', $this->notice_data['facettes']);
+  }
+}
+
+
+
+
 class NoticeIntegrationUpdateExistingNoticeTest  extends NoticeIntegrationTestCase {
   protected $_profil_donnees = [
                                 'id_profil' => 100,
diff --git a/library/Class/CodifEmplacement.php b/library/Class/CodifEmplacement.php
index f5231f061bb57d17ea39e0f3de7ca3b8944765fe..1e1c84c6bcb0f9e1f49c1ac599eee024e000559d 100644
--- a/library/Class/CodifEmplacement.php
+++ b/library/Class/CodifEmplacement.php
@@ -59,4 +59,9 @@ class Class_CodifEmplacement extends Storm_Model_Abstract {
   public function getCategorie() {
     return;
   }
-}
\ No newline at end of file
+
+
+  public function getRulesLabel() {
+    return (new Class_Codification_Rules($this))->asHumanRead();
+  }
+}
diff --git a/library/Class/CodifGenre.php b/library/Class/CodifGenre.php
index 7103dd7f2aab10ba87f956e3a6bce6a97176e810..4e7cf96d405b2741ee9a17944aadecaec7e6e6d5 100644
--- a/library/Class/CodifGenre.php
+++ b/library/Class/CodifGenre.php
@@ -80,4 +80,9 @@ class Class_CodifGenre extends Storm_Model_Abstract {
   public function validate() {
     $this->checkAttribute('libelle', '' != $this->getLibelle(), $this->_('Vous devez définir le libellé'));
   }
+
+
+  public function getRulesLabel() {
+    return (new Class_Codification_Rules($this))->asHumanRead();
+  }
 }
diff --git a/library/Class/CodifSection.php b/library/Class/CodifSection.php
index 1ffdfc74b4e34100c7ca849efe63b6c6ac4abb71..895eef3f885c7665c043d84e956633abeb09ebf8 100644
--- a/library/Class/CodifSection.php
+++ b/library/Class/CodifSection.php
@@ -89,4 +89,9 @@ class Class_CodifSection extends Storm_Model_Abstract {
   public function validate() {
     $this->checkAttribute('libelle', '' != $this->getLibelle(), $this->_('Vous devez définir le libellé'));
   }
+
+
+  public function getRulesLabel() {
+    return (new Class_Codification_Rules($this))->asHumanRead();
+  }
 }
diff --git a/library/Class/Codification/Rule.php b/library/Class/Codification/Rule.php
index b25e3d4a023a8c542869b507ed19af7e7152a9fa..056698c7d0ec1ec03d94a9b8faaf636be836205d 100644
--- a/library/Class/Codification/Rule.php
+++ b/library/Class/Codification/Rule.php
@@ -21,32 +21,56 @@
 
 
 class Class_Codification_Rule {
-  const VALUE_SEPARATOR = ';';
+  use Trait_Translator;
+
+  const
+    VALUE_SEPARATOR = ';',
+    KEEP_SPACE_PREFIX = '1@@',
+    REMOVE_SPACE_PREFIX = '0@@';
 
   protected
     $_zone,
     $_operator,
-    $_values = [];
+    $_with_space,
+    $_values = [],
+    $_type,
+    $_codif;
 
   public static function newFrom($rule) {
     $zone = substr($rule, 0, 5);
     $operator = substr($rule, 5, 1);
-    $values = array_filter(array_map(
-                                     function($value) {
-                                       return trim(str_replace(' ', '', $value));
-                                     },
-                                     explode(static::VALUE_SEPARATOR, substr($rule, 6))));
+    $values = substr($rule, 6);
+    $pos = strpos($values, static::KEEP_SPACE_PREFIX);
+    $with_space = (false !== $pos && 0 === $pos) ? '1' : '0';
+    $values = $with_space
+      ? str_replace(static::KEEP_SPACE_PREFIX, '', $values)
+      : str_replace(static::REMOVE_SPACE_PREFIX, '', $values);
+
+    $values = array_unique(array_filter(explode(static::VALUE_SEPARATOR,
+                                                static::_formatValue($with_space,
+                                                                     $values)),
+                                        function ($value) {
+                                          return '' !== $value && ' ' !== $value;
+                                        }));
 
     return $values
-      ? new static($zone, $operator, $values)
+      ? new static($zone, $operator, $values, $with_space)
       : null;
   }
 
 
-  public function __construct($zone, $operator, $values) {
+  public function __construct($zone, $operator, $values, $with_space) {
     $this->_zone = $zone;
     $this->_operator = $operator;
     $this->_values = $values;
+    $this->_with_space = $with_space;
+  }
+
+
+  public static function getSpacePrefix($form_value) {
+    return '1' == $form_value
+      ? static::KEEP_SPACE_PREFIX
+      : static::REMOVE_SPACE_PREFIX;
   }
 
 
@@ -65,6 +89,59 @@ class Class_Codification_Rule {
   }
 
 
+  public function getWithSpace() {
+    return $this->_with_space;
+  }
+
+
+  public function setWithSpace($with_space) {
+    $this->_with_space = $with_space;
+    return $this;
+  }
+
+
+  public function getType() {
+    return $this->_type;
+  }
+
+
+  public function setType($type) {
+    $this->_type = $type;
+    return $this;
+  }
+
+
+  public function getCodif() {
+    return $this->_codif;
+  }
+
+
+  public function setCodif($codif) {
+    $this->_codif = $codif;
+    return $this;
+  }
+
+
+  public function getCodifIdByField($field) {
+    return $this->_isFieldValid(static::_formatValue($this->_with_space, $field))
+      ? $this->getCodif()->getId()
+      : null;
+  }
+
+
+  public function isZoneContainsValue($zone, $value) {
+    return $zone === $this->_zone
+      && '=' === $this->_operator
+      && in_array(static::_formatValue($this->_with_space, $value),
+                  $this->_values, true);
+  }
+
+
+  public function isGenre() {
+    return Class_Codification_RulesHelper::TYPE_GENRE === $this->getType();
+  }
+
+
   public function getValuesAsString() {
     return implode(static::VALUE_SEPARATOR, array_unique($this->_values));
   }
@@ -108,6 +185,70 @@ class Class_Codification_Rule {
   public function asString() {
     return $this->_zone
       . $this->_operator
+      . static::getSpacePrefix($this->_with_space)
       . $this->getValuesAsString();
   }
-}
\ No newline at end of file
+
+
+  public function asHumanRead() {
+    return $this->_zone
+      . $this->_getOperatorAsHumanRead()
+      . $this->_getValuesAsHumanRead()
+      . ('1' === $this->getWithSpace()
+         ? $this->_(' avec espace(s)')
+         : $this->_(' sans espace(s)'));
+  }
+
+
+  protected static function _formatValue($with_space, $value) {
+    $formated = $with_space
+      ? preg_replace('/\s+/', ' ', strToUpper($value))
+      : str_replace(' ', '', strToUpper($value));
+
+    return (' ' === $formated)
+      ? ''
+      : $formated;
+  }
+
+
+  protected function _getOperatorAsHumanRead() {
+    if ('=' == $this->_operator)
+      return $this->_(' égal ');
+
+    if ('/' == $this->_operator)
+      return $this->_(' commence par ');
+
+    if ('*' == $this->_operator)
+      return $this->_(' contient ');
+
+    return '';
+  }
+
+
+  protected function _getValuesAsHumanRead() {
+    return implode($this->_(' où '), array_unique($this->_values));
+  }
+
+
+  protected function _isFieldValid($field) {
+    foreach ($this->_values as $value)
+      if ($this->_isFieldValidByValue($value, $field))
+        return true;
+
+    return false;
+  }
+
+
+  protected function _isFieldValidByValue($value, $field) {
+    if ('=' == $this->_operator)
+      return $value == $field;
+
+    if ('/' == $this->_operator)
+      return $value == substr($field, 0, strlen($value));
+
+    if ('*' == $this->_operator)
+      return false !== strpos($field, $value);
+
+    return false;
+  }
+}
diff --git a/library/Class/Codification/Rules.php b/library/Class/Codification/Rules.php
index 8f42919172e0aa4ac7f23a7351655311c617664b..d501b278c9480ab49d399ee1684865e117656f3d 100644
--- a/library/Class/Codification/Rules.php
+++ b/library/Class/Codification/Rules.php
@@ -27,6 +27,7 @@ class Class_Codification_Rules {
     ZONE_NAME = 'rule_zone',
     FIELD_NAME = 'rule_field',
     SIGN_NAME = 'rule_sign',
+    WITH_SPACE = 'rule_space',
     VALUES_NAME = 'rule_values';
 
   protected $_rules;
@@ -55,7 +56,9 @@ class Class_Codification_Rules {
                                '$',
                                $post[static::FIELD_NAME][$position],
                                $post[static::SIGN_NAME][$position],
-                               $post[static::VALUES_NAME][$position]]));
+                               Class_Codification_Rule::getSpacePrefix($post[static::WITH_SPACE][$position]),
+                               $post[static::VALUES_NAME][$position],
+                               ]));
   }
 
 
@@ -63,7 +66,8 @@ class Class_Codification_Rules {
     $values = [static::ZONE_NAME => [],
                static::FIELD_NAME => [],
                static::SIGN_NAME => [],
-               static::VALUES_NAME => []];
+               static::VALUES_NAME => [],
+               static::WITH_SPACE => []];
 
     static::newFromString($rules)
       ->eachDo(function($rule) use(&$values)
@@ -72,6 +76,7 @@ class Class_Codification_Rules {
                  $values[static::FIELD_NAME][] = $rule->getField();
                  $values[static::SIGN_NAME][] = $rule->getOperator();
                  $values[static::VALUES_NAME][] = $rule->getValuesAsString();
+                 $values[static::WITH_SPACE][] = $rule->getWithSpace();
                });
 
     return $values;
@@ -115,7 +120,8 @@ class Class_Codification_Rules {
   public function multiInputFields() {
     $possible_fields = Class_IntProfilDonnees::getAllZoneAndFields();
 
-    return [['name' => static::ZONE_NAME,
+    return [
+            ['name' => static::ZONE_NAME,
              'label' => $this->_('Zone (001 à 999)'),
              'attribs' => ['maxlength' => 3, 'size' => 3]],
 
@@ -135,7 +141,13 @@ class Class_Codification_Rules {
              'attribs' => ['style' => 'width:auto'],],
 
             ['name' => static::VALUES_NAME,
-             'label' => $this->_('Valeur(s), séparées par ;')]];
+             'label' => $this->_('Valeur(s), séparées par ;')],
+
+            ['name' => static::WITH_SPACE,
+             'label' => $this->_('Avec espace'),
+             'type' => 'checkbox',
+             'attribs' => ['style' => 'width:auto']]
+    ];
   }
 
 
@@ -196,8 +208,29 @@ class Class_Codification_Rules {
   }
 
 
+  public function asHumanRead() {
+    if ($this->isEmpty())
+      return '';
+
+    $rules_as_string = '';
+    foreach ($this->_rulesAsHumanRead() as $rule_as_string)
+      $rules_as_string .= sprintf('<li>%s</li>', $rule_as_string);
+
+    return sprintf('<ul>%s</ul>', $rules_as_string);
+  }
+
+
   protected function _rulesAsString() {
     return $this->_rules
       ->collect(function($rule) { return $rule->asString(); });
   }
-}
\ No newline at end of file
+
+
+  protected function _rulesAsHumanRead() {
+    return $this->_rules
+      ->collect(function ($rule) {
+        return $rule->asHumanRead();
+      })
+      ->getArrayCopy();
+  }
+}
diff --git a/library/Class/Codification/RulesHelper.php b/library/Class/Codification/RulesHelper.php
new file mode 100644
index 0000000000000000000000000000000000000000..151e169a47dde20536ce13e93f8ba085d867561c
--- /dev/null
+++ b/library/Class/Codification/RulesHelper.php
@@ -0,0 +1,123 @@
+<?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_Codification_RulesHelper {
+
+  const
+    TYPE_GENRE = 'genre',
+    TYPE_SECTION = 'section',
+    TYPE_EMPLACEMENT = 'emplacement';
+
+  protected
+    $_list_rules;
+
+  public function __construct() {
+    $this->_list_rules = new Storm_Collection();
+    $this->_init();
+  }
+
+
+  public function getCodifId($type, $zone_field, $value) {
+    $detect = $this->_list_rules
+      ->detect(function ($codif_rule) use ($zone_field, $value)
+      {
+        return $codif_rule->isZoneContainsValue($zone_field, $value)
+          || $codif_rule->isZoneContainsValue('995$0', $value);
+      });
+
+    return $detect
+      ? $detect->getCodif()->getId()
+      : '';
+  }
+
+
+  public function getCodifIdByGenre($notice_unimarc) {
+    $list_result = $this->_list_rules->select(function ($codif_rule)
+    {
+      return $codif_rule->isGenre();
+    })->injectInto([], function ($next_result, $codif_rule) use ($notice_unimarc)
+    {
+      return $this->_extractFieldsValues($next_result, $codif_rule, $notice_unimarc);
+    });
+
+    return array_filter($list_result);
+  }
+
+
+  protected function _init() {
+    foreach ([static::TYPE_SECTION, static::TYPE_GENRE, static::TYPE_EMPLACEMENT]
+             as $type)
+      $this->_extractForType($type);
+
+    return $this;
+  }
+
+
+  protected function _extractFieldsValues($list_result,
+                                          $codif_rule,
+                                          $notice_unimarc) {
+    $datas = $notice_unimarc->get_subfield($codif_rule->getZone(),
+                                           $codif_rule->getField());
+    if (!count($datas))
+      return $list_result;
+
+    foreach ($datas as $field_value)
+      $list_result [] = $codif_rule->getCodifIdByField($field_value);
+
+    return $list_result;
+  }
+
+
+  protected function _extractForType($type) {
+    $classname = 'Class_Codif' . ucfirst($type);
+    if (!$list_codif = $classname::findAll())
+      return $this;
+
+    foreach ($list_codif as $codif)
+      $this->_extractForCodif($type, $codif);
+
+    return $this;
+  }
+
+
+  protected function _extractForCodif($type, $codif) {
+    if (!$rules = $codif->getRegles())
+      return $this;
+
+    foreach (explode("\n", $rules) as $rule)
+      $this->_extractForRule($type, $codif, $rule);
+
+    return $this;
+  }
+
+
+  protected function _extractForRule($type, $codif, $rule) {
+    if (!($codif_rule = Class_Codification_Rule::newFrom($rule)))
+      return $this;
+
+    $this->_list_rules->add($codif_rule
+                            ->setType($type)
+                            ->setCodif($codif));
+
+    return $this;
+  }
+}
diff --git a/library/Class/Cosmogramme/Generator/KindsTask.php b/library/Class/Cosmogramme/Generator/KindsTask.php
index 7a8a7ae78a17a41fa67bc72f358ee0b7afd15d75..78c23611f203aab6fbc1aeb0a973e4f0367d29fa 100644
--- a/library/Class/Cosmogramme/Generator/KindsTask.php
+++ b/library/Class/Cosmogramme/Generator/KindsTask.php
@@ -50,7 +50,7 @@ class Class_Cosmogramme_Generator_KindsTask extends Class_Cosmogramme_Generator_
       $field = '930$4';
 
     if ($field)
-      return $field . '=' . $codes;
+      return $field . '=' . Class_Codification_Rule::REMOVE_SPACE_PREFIX . $codes;
   }
 
 
diff --git a/library/Class/Cosmogramme/Generator/LocationsTask.php b/library/Class/Cosmogramme/Generator/LocationsTask.php
index 43fc31082ea4cc5df9423ca8a747f55600ddb5aa..aabce576b302507a160da731b6443d982210d91d 100644
--- a/library/Class/Cosmogramme/Generator/LocationsTask.php
+++ b/library/Class/Cosmogramme/Generator/LocationsTask.php
@@ -33,7 +33,7 @@ class Class_Cosmogramme_Generator_LocationsTask extends Class_Cosmogramme_Genera
 
 
   protected function _getRuleFor($code) {
-    return '995$6=' . $code;
+    return '995$6=' . Class_Codification_Rule::REMOVE_SPACE_PREFIX . $code;
   }
 
 
diff --git a/library/Class/Cosmogramme/Generator/SectionsTask.php b/library/Class/Cosmogramme/Generator/SectionsTask.php
index e9a24091675487daea01fb464d8e86e437c73a2f..04f9867021ea36b9c92396912b21a7b21ae6f29d 100644
--- a/library/Class/Cosmogramme/Generator/SectionsTask.php
+++ b/library/Class/Cosmogramme/Generator/SectionsTask.php
@@ -41,11 +41,12 @@ class Class_Cosmogramme_Generator_SectionsTask extends Class_Cosmogramme_Generat
       $field = 'q';
 
     if ($field)
-      return '995$' . $field . '=' . strtolower($code);
+      return '995$' . $field . '=' . Class_Codification_Rule::REMOVE_SPACE_PREFIX
+        . strtolower($code);
   }
 
 
   protected function getItemField() {
     return 'section';
   }
-}
\ No newline at end of file
+}
diff --git a/library/Class/Cosmogramme/Integration/PhaseSerialArticlesIndex.php b/library/Class/Cosmogramme/Integration/PhaseSerialArticlesIndex.php
index 945f0fbed1d291b18924a90f4dcd52c072488def..17ce3171c26426cd84b23d04abbf4511a8f65031 100644
--- a/library/Class/Cosmogramme/Integration/PhaseSerialArticlesIndex.php
+++ b/library/Class/Cosmogramme/Integration/PhaseSerialArticlesIndex.php
@@ -187,13 +187,7 @@ class Class_Cosmogramme_Integration_PhaseSerialArticlesIndex
 
     $this->_setSeen($issue);
 
-    return $facets
-      ->select(function($facet)
-               {
-                 return !in_array(substr($facet, 0, 1),
-                                  [Class_CodifAuteur::CODE_FACETTE,
-                                   Class_CodifMatiere::CODE_FACETTE]);
-               });
+    return $facets;
   }
 
 
diff --git a/library/Class/Indexation/PseudoNotice/WorkKey.php b/library/Class/Indexation/PseudoNotice/WorkKey.php
index c2d169eef09336e2972825152c9c9c00070ad4dd..ae59d3dda45daa5aa2c1d6501fadf201c44b87ed 100644
--- a/library/Class/Indexation/PseudoNotice/WorkKey.php
+++ b/library/Class/Indexation/PseudoNotice/WorkKey.php
@@ -37,7 +37,8 @@ class Class_Indexation_PseudoNotice_WorkKey {
     $map = [Class_Album::class      => Class_Indexation_PseudoNotice_WorkKeyAlbum::class,
             Class_Sitotheque::class => Class_Indexation_PseudoNotice_WorkKeyTitre::class,
             Class_Article::class    => Class_Indexation_PseudoNotice_WorkKeyCms::class,
-            Class_Rss::class        => Class_Indexation_PseudoNotice_WorkKeyTitre::class];
+            Class_Rss::class        => Class_Indexation_PseudoNotice_WorkKeyTitre::class,
+            Class_Profil::class     => Class_Indexation_PseudoNotice_WorkKeyPage::class];
 
     foreach($map as $model_class => $key_class)
       if (is_a($model, $model_class))
@@ -99,6 +100,19 @@ class Class_Indexation_PseudoNotice_WorkKeyTitre extends Class_Indexation_Pseudo
 
 
 
+class Class_Indexation_PseudoNotice_WorkKeyPage extends Class_Indexation_PseudoNotice_WorkKey {
+  public function _record() {
+    return new Class_Entity(['TitrePrincipal' => $this->_model->getTitre(),
+                             'ComplementTitre' => '',
+                             'AuteurClefAlpha' => $this->_model->isInPortail()
+                                                  ? 'Portal'
+                                                  : $this->_model->getBibLibelle(),
+                             'TomeAlpha' => $this->_model->getId()]);
+  }
+}
+
+
+
 
 class Class_Indexation_PseudoNotice_WorkKeyCms extends Class_Indexation_PseudoNotice_WorkKey {
   public function _record() {
@@ -107,4 +121,4 @@ class Class_Indexation_PseudoNotice_WorkKeyCms extends Class_Indexation_PseudoNo
                              'AuteurClefAlpha' => $this->_model->getAuthorName(),
                              'TomeAlpha' => '']);
   }
-}
\ No newline at end of file
+}
diff --git a/library/Class/TableDescription/CosmoCodification.php b/library/Class/TableDescription/CosmoCodification.php
index 6da49b9b385047a64abc00c0132fb41bc1703cd4..a43d0521ae6b50f876070e713793be4b905c2a2b 100644
--- a/library/Class/TableDescription/CosmoCodification.php
+++ b/library/Class/TableDescription/CosmoCodification.php
@@ -23,7 +23,7 @@
 class Class_TableDescription_CosmoCodification extends Class_TableDescription {
   public function init() {
     $this->addColumn($this->_('Libellé'), 'libelle')
-         ->addColumn($this->_('Règles de reconnaissance'), 'regles')
+         ->addColumn($this->_('Règles de reconnaissance'), 'rulesLabel')
          ->_addSpecifics()
          ->addRowPluginsActions();
   }
diff --git a/library/ZendAfi/View/Helper/Article/RenderEventTimings.php b/library/ZendAfi/View/Helper/Article/RenderEventTimings.php
index 2162b48f89b24e728dbdf94a789c37c4a22949f0..b45919ba8d422dfdee4c9b3c7978386396cb2a8d 100644
--- a/library/ZendAfi/View/Helper/Article/RenderEventTimings.php
+++ b/library/ZendAfi/View/Helper/Article/RenderEventTimings.php
@@ -28,6 +28,9 @@ class ZendAfi_View_Helper_Article_RenderEventTimings extends ZendAfi_View_Helper
                                                  return !$timing->isPast();
                                                });
 
+    if (!$current_and_future_timings)
+      return '';
+
     $human_timings = array_map(
                                function($timing)
                                {
diff --git a/library/startup.php b/library/startup.php
index 2de3623ea9d391576eec6cade61fcece58dfc19a..0055c88b5b33b9d620b3cdc084014441b9731526 100644
--- a/library/startup.php
+++ b/library/startup.php
@@ -111,7 +111,7 @@ class Bokeh_Engine {
 
   function setupConstants() {
     defineConstant('BOKEH_MAJOR_VERSION','8.0');
-    defineConstant('BOKEH_RELEASE_NUMBER', BOKEH_MAJOR_VERSION . '.140');
+    defineConstant('BOKEH_RELEASE_NUMBER', BOKEH_MAJOR_VERSION . '.141');
 
     defineConstant('BOKEH_REMOTE_FILES', 'https://git.afi-sa.net/afi/opacce/');
 
diff --git a/library/templates/Intonation/Library/View/Wrapper/Domain.php b/library/templates/Intonation/Library/View/Wrapper/Domain.php
index e2a0daa8818025d62cde40186eb03eb93bf8be76..00baef2495e6f63aebc3f9c041677fd76c81e082 100644
--- a/library/templates/Intonation/Library/View/Wrapper/Domain.php
+++ b/library/templates/Intonation/Library/View/Wrapper/Domain.php
@@ -51,7 +51,7 @@ class Intonation_Library_View_Wrapper_Domain extends Intonation_Library_View_Wra
       $url ['id_module'] = $this->_widget_context->getId();
 
     return (new Intonation_Library_Link)
-      ->setUrl($this->_view->url($url))
+      ->setUrl($this->_view->url($url, null, true))
       ->setImage($this->getIco('read-document', 'library'))
       ->setText($this->_('Voir les documents'))
       ->setTitle($this->_getMainLinkTitle());
diff --git a/library/templates/Intonation/Library/View/Wrapper/RichContent/Section.php b/library/templates/Intonation/Library/View/Wrapper/RichContent/Section.php
index f67c28889b00fcaf607330cc4def3be5f26960ed..b82a6d4b2d2cbac17403ed249d338ee71e7d3d2c 100644
--- a/library/templates/Intonation/Library/View/Wrapper/RichContent/Section.php
+++ b/library/templates/Intonation/Library/View/Wrapper/RichContent/Section.php
@@ -185,8 +185,8 @@ abstract class Intonation_Library_View_Wrapper_RichContent_Section {
     if ($class = $this->getClass())
       $js =
         sprintf('$(\'.loading_icon\').parent().remove();
-                 if ($(\'.%1$s [data-ajax-content] > *\').length)
-                   return $(\'.%1$s\').removeClass(\'d-none text-black-50 disabled\');',
+                 if ($(\'.%1$s [data-ajax-content] > *\').length) {
+                   return $(\'.%1$s\').removeClass(\'d-none text-black-50 disabled\');}',
                 $class);
 
     if ($html)
diff --git a/library/templates/Intonation/View/RenderAjax.php b/library/templates/Intonation/View/RenderAjax.php
index aa00ff7a024a2ca9bd706a5f8787afbe4e58b635..8dc2469e082e553787ed39566f6d8c14ef4d818d 100644
--- a/library/templates/Intonation/View/RenderAjax.php
+++ b/library/templates/Intonation/View/RenderAjax.php
@@ -28,7 +28,11 @@ class Intonation_View_RenderAjax extends ZendAfi_View_Helper_BaseHelper {
                         'action' => $action],
                        $params);
 
-    $js_callback .= 'initializePopups();setupAnchorsTarget();'.Class_AdminVar_Cookies::manager()->triggers();
+    $js_callback = 'initializePopups();setupAnchorsTarget();'
+      . Class_AdminVar_Cookies::manager()->triggers()
+      . ';'
+      . $js_callback;
+
     if (($ig = Zend_Controller_Front::getInstance()
          ->getPlugin('ZendAfi_Controller_Plugin_InspectorGadget'))
         && ($ig->isEnabled()))
diff --git a/public/admin/js/multi_inputs/multi_inputs.js b/public/admin/js/multi_inputs/multi_inputs.js
index f06a105e83a295d9687e6acc886fa9171aea66ba..0dc9e7f52beaf2d778e59aeee43206b1015394a0 100644
--- a/public/admin/js/multi_inputs/multi_inputs.js
+++ b/public/admin/js/multi_inputs/multi_inputs.js
@@ -15,7 +15,7 @@
  *
  * 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 
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
 
 
@@ -37,7 +37,7 @@
     var header_str = '<tr><th>' + header.join('</th><th>') + '</th></tr>';
 
     var id = this.attr('id');
-    this.append('<table data-rel="' + id + '">' 
+    this.append('<table data-rel="' + id + '">'
                 + '<thead>' + header_str + '</thead>'
                 + '<tbody></tbody>'
                 + '</table>');
@@ -53,7 +53,7 @@
         for (var key in options.values)
           lines_count = Math.max(options.values[key].length, lines_count);
         for (var i=0; i < lines_count; i++)
-          this.add(i);	
+          this.add(i);
         this.refresh();
 	this.onchange_values();
       },
@@ -68,10 +68,10 @@
 
         var tbody = $(table).find('tbody');
         tbody.append(inputs);
-        
+
         if (null != value_index && options.line_errors && options.line_errors[value_index])
-          tbody.append('<tr><td colspan="' + (options.fields.length + 1) +'">' 
-                       + '<ul class="errors">' 
+          tbody.append('<tr><td colspan="' + (options.fields.length + 1) +'">'
+                       + '<ul class="errors">'
                        + '<li>' + options.line_errors[value_index].join('</li><li>') + '</li></ul></td></tr>');
       },
 
@@ -88,34 +88,78 @@
       field_add: function(options, key, value_index) {
         var input = options.fields[key];
         if ('placeholder' == input.type)
-          return '<td style="vertical-align:top;" data-multiinputs-field="' + input.name + '"></td>';
+          return '<td style="vertical-align:top;" data-multiinputs-field="'
+            + input.name + '"></td>';
 
-        var value = this.field_get_value(options, value_index, input);
-        
+        input.type = input.type || 'text';
         var desc = (null != value_index
                     && null != options.descs
                     && input.name in options.descs
                     && value_index < options.descs[input.name].length)
-            ? options.descs[input.name][value_index]
-            : '';
+          ? options.descs[input.name][value_index]
+          : '';
+
+        return this.get_html(options, value_index, input, desc);
+      },
+
+      get_html: function (options, value_index, input, desc) {
+        var value = this.field_get_value(options, value_index, input);
+
+        var html;
+        if ('select' == input.type)
+          html = this.get_html_for_select(input, value);
+        if ('checkbox' == input.type)
+          html = this.get_html_for_checkbox(input, value, value_index, options);
+        if ('select' != input.type && 'checkbox' != input.type)
+          html = this.get_html_for_others(input, value);
+
+        return '<td style="vertical-align:top;">'
+          + html.append(desc).html() + '</td>';
+      },
+
+      get_html_for_select: function (input, value) {
+        var node = $('<select></select>').attr('name', input.name + '[]');
+        this.node_add_options(node, input.options, value);
+        this.node_add_attribs(node, input);
+
+        return $('<div/>').append(node);
+      },
+
+      get_html_for_checkbox: function (input, value, value_index, options) {
+        if (null === value_index)
+          value_index = options.values[input.name].length;
+
+        var elmnt_id = input.name + '_' + value_index;
+        var node_hidden = $('<input type="hidden" />')
+          .attr('name', input.name + '[]')
+          .attr('id', elmnt_id)
+          .attr('value', value || '0');
+        var node_cb = $('<input type="checkbox" />')
+          .attr('id', input.name + '_cb_' + value_index);
+
+        if ('1' == value)
+          node_cb.attr('checked', 'checked');
 
-        var html = input.type == 'select'
-            ? '<select></select>'
-            : '<input type="' + (input.type || 'text') + '" />';
-        
-        var node = $(html).attr('name', input.name + '[]');
+        this.node_add_attribs(node_cb, input);
 
-        if (input.type != 'select')
-          node.attr('value', value);
+        return $('<div/>').append(node_hidden).append(node_cb);
+      },
+
+      get_html_for_others: function (input, value) {
+        var node = $('<input type="' + input.type + '" />')
+          .attr('value', value)
+          .attr('name', input.name + '[]');
+
+        this.node_add_options(node, input.options, value);
+        this.node_add_attribs(node, input);
+
+        return $('<div/>').append(node);
+      },
 
-	this.node_add_options(node, input.options, value);
-        
+      node_add_attribs: function (node, input) {
         for (var attrib_name in input.attribs)
           if (input.attribs.hasOwnProperty(attrib_name))
             node.attr(attrib_name, input.attribs[attrib_name]);
-
-        return '<td style="vertical-align:top;">'
-          + $('<div/>').append( node ).append(desc).html() + '</td>';
       },
 
       node_add_options: function(node, options, value) {
@@ -134,15 +178,15 @@
 	    append_option_to(node, option_value, options[option_value]);
 	    return;
           }
-	  
+
           var optgroup = $('<optgroup label="' + option_value + '"></optgroup>');
           var optgroup_options = options[option_value];
           for (var optgroup_value in optgroup_options)
 	    append_option_to(optgroup, optgroup_value, optgroup_options[optgroup_value]);
-	    
+
           node.append(optgroup);
         };
-	
+
         for (var option_value in options)
 	  node_add_option(option_value);
       },
@@ -208,6 +252,7 @@
       onchanges: function() {
         for (var key in options.fields) {
           this.onchange_one(options.fields[key]);
+          this.onchange_cb(options.fields[key]);
         }
       },
 
@@ -216,12 +261,25 @@
           return;
 
         const changed = this.onchange_callback(input, false);
- 
+
         $(table).find('select[name="' + input.name + '[]"]').each(function() {
           $(this).change(changed);
         });
       },
 
+      onchange_cb: function (input) {
+        if ('checkbox' != input.type)
+          return;
+
+        $(table).find('input[type="checkbox"][id^="'
+                      + input.name + '_cb_"]').each(function () {
+          var sibling = $(this).prev('input');
+          $(this).change(function () {
+            sibling.val('1' == sibling.val() ? '0' : '1');
+          });
+        });
+      },
+
       onchange_values: function() {
 	for (var key in options.fields) {
 	  input = options.fields[key]
diff --git a/public/admin/js/multi_inputs/test.js b/public/admin/js/multi_inputs/test.js
index a1b9cae12f9a13298511d6292781b85e4e34f5e9..c0bc4299f8823ebcaabfb488c1940f3fdd3c1a59 100644
--- a/public/admin/js/multi_inputs/test.js
+++ b/public/admin/js/multi_inputs/test.js
@@ -15,7 +15,7 @@
  *
  * 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 
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
 
 var ajax_calls = [];
@@ -29,6 +29,12 @@ function call_multi_inputs_for(options) {
   return insertion_point;
 }
 
+function call_checkbox (name, values) {
+  return call_multi_inputs_for({
+    fields: [ { name: name, type:'checkbox' } ],
+    values: { testcb: values }
+  });
+}
 
 QUnit.module('multi_inputs');
 
@@ -72,6 +78,7 @@ test('one select input', function () {
   equal(insertion_point.find('select[name="testing[]"]').children('option[value="3"][selected="selected"]').length, 1, 'opt3 selected');
 });
 
+
 test('one select input with optgroups', function () {
   var insertion_point = call_multi_inputs_for({
     fields:[{name:'testing', type:'select', options:{ 'label1':{1:'opt1', 2:'opt2'}, 'label2':{3:'opt3', 4:'opt4'}}}],
@@ -86,21 +93,21 @@ test('one select input with optgroups', function () {
 
 test('one input and two values', function () {
   var insertion_point = call_multi_inputs_for({
-    fields:[{name:'testing'}], 
+    fields:[{name:'testing'}],
     values:{testing:['value1', 'value2']},
     descs:{testing:['', '<a href="testing.com">Test</a>']}
   });
   equal(insertion_point.find('input[name="testing[]"]').length, 2, '2 inputs with []');
   equal(insertion_point.find('img[src*="ico/add.gif"]').length, 1, 'one add image');
   equal(insertion_point.find('img[src*="ico/del.gif"]').length, 1, 'one delete image');
-  equal(insertion_point.find('input[value="value2"]').siblings('a[href="testing.com"]').length, 
+  equal(insertion_point.find('input[value="value2"]').siblings('a[href="testing.com"]').length,
 	1, 'one desc');
 });
 
 
 test('one input with error message', function () {
   var insertion_point = call_multi_inputs_for({
-    fields:[{name:'testing'}], 
+    fields:[{name:'testing'}],
     values:{testing:['value1', 'value2']},
     descs:{testing:['', '']},
     line_errors:[["You should not do that"]]
@@ -111,7 +118,7 @@ test('one input with error message', function () {
 
 test('one input with styles', function() {
   var insertion_point = call_multi_inputs_for({
-    fields:[{name:'testing', attribs: {style:'width:20px'}}], 
+    fields:[{name:'testing', attribs: {style:'width:20px'}}],
     values:{testing:['value1', 'value2']},
     descs:{testing:['', '']}
   });
@@ -166,7 +173,7 @@ test('onchange', function() {
 
 test('one input with type number', function() {
   var insertion_point = call_multi_inputs_for({
-    fields:[{name:'testing', type:'number'}], 
+    fields:[{name:'testing', type:'number'}],
     values:{testing:[]}
   });
   equal(insertion_point.find('input[name="testing[]"][type="number"]').length, 1, '1 input with number type');
@@ -175,7 +182,7 @@ test('one input with type number', function() {
 
 test('one input with default type text', function() {
   var insertion_point = call_multi_inputs_for({
-    fields:[{name:'testing'}], 
+    fields:[{name:'testing'}],
     values:{testing:[]}
   });
   equal(insertion_point.find('input[name="testing[]"][type="text"]').length, 1, '1 input with default type text');
@@ -184,8 +191,58 @@ test('one input with default type text', function() {
 
 test('input with default value', function() {
   var insertion_point = call_multi_inputs_for({
-    fields:[{name:'testing',default_value:'3'}], 
+    fields:[{name:'testing',default_value:'3'}],
     values:{testing:[]}
   });
   equal(insertion_point.find('input[name="testing[]"][type="text"][value="3"]').length, 1, '1 input with default value 3');
 });
+
+
+test('one input with type checkbox not checked', function () {
+  var insertion_point = call_checkbox('testcb', ['0']);
+  var checkbox = insertion_point.find('input:not(:checked)[type="checkbox"]');
+  var hidden = insertion_point.find('input[type="hidden"][name="testcb[]"]');
+
+  equal(checkbox.length, 1, 'input checkbox not checked exist');
+  equal(hidden.length, 1, 'input hidden exist for checkbox element');
+  equal(hidden[0].value, 0, 'input hidden with value 0');
+  equal(hidden[0].id, 'testcb_0', 'input hidden with id testcb_0');
+});
+
+
+test('one input with type checkbox checked', function () {
+  var insertion_point = call_checkbox('testcb', ['1']);
+  var checkbox = insertion_point.find('input:checked[type="checkbox"]');
+  var hidden = insertion_point.find('input[type="hidden"][name="testcb[]"]')[0];
+
+  equal(checkbox.length, 1, 'input checkbox checked exist');
+  equal(hidden.value, 1, 'input hidden with value 1');
+});
+
+
+test('trigger checkbox onchange should change hidden value from 0 to 1', function () {
+  var insertion_point = call_checkbox('testcb', ['0']);
+  var checkbox = insertion_point.find('input[type="checkbox"][id="testcb_cb_0"]');
+  var hidden = insertion_point.find('input[type="hidden"][id="testcb_0"]')[0];
+
+  equal(hidden.value, 0, 'input hidden with intial value 0');
+  $(checkbox).change();
+
+  hidden = insertion_point.find('input[type="hidden"][id="testcb_0"]')[0];
+  equal(hidden.value, 1, 'input hidden changed value from 0 to 1');
+});
+
+
+test('add new line input hidden should have id = testcb_1', function () {
+  var insertion_point = call_checkbox('testcb', ['0']);
+  var hidden = insertion_point.find('input[type="hidden"][id="testcb_1"]');
+  var button_add = insertion_point.find('img[src*="ico/add.gif"]');
+
+  equal(hidden.length, 0,
+        'before add line, input hidden with id testcb_1 should not exist');
+
+  $(button_add).click();
+  hidden = insertion_point.find('input[type="hidden"][id="testcb_1"]');
+  equal(hidden.length, 1,
+        'after add line input hidden with id testcb_1 should exist');
+});
diff --git a/tests/library/Class/Cosmogramme/Integration/PhasePrepareIntegrationsTest.php b/tests/library/Class/Cosmogramme/Integration/PhasePrepareIntegrationsTest.php
index 9890dbac2cf02173a8c871b6204b4a826e33fb4c..ec121f743109bb252e38aa37625bd8c5df83d8aa 100644
--- a/tests/library/Class/Cosmogramme/Integration/PhasePrepareIntegrationsTest.php
+++ b/tests/library/Class/Cosmogramme/Integration/PhasePrepareIntegrationsTest.php
@@ -472,19 +472,19 @@ abstract class PhasePrepareIntegrationsNanookStandardTestCase
     $this->fixture('Class_CodifSection',
                    ['id' => 34,
                     'libelle' => 'Game',
-                    'regles' => '995$9=3',
+                    'regles' => '995$9=0@@3',
                     'date_maj' => '']);
 
     $this->fixture('Class_CodifSection',
                    ['id' => 36,
                     'libelle' => 'Adultes',
-                    'regles' => "995$9=4",
+                    'regles' => '995$9=0@@4',
                     'date_maj' => '']);
 
     $this->fixture('Class_CodifSection',
                    ['id' => 38,
                     'libelle' => 'Jeunesse',
-                    'regles' => "995$9=7",
+                    'regles' => '995$9=0@@7',
                     'date_maj' => '']);
 
     $this->fixture('Class_Notice',
@@ -495,7 +495,7 @@ abstract class PhasePrepareIntegrationsNanookStandardTestCase
     $this->fixture('Class_CodifGenre',
                    ['id' => 34,
                     'libelle' => 'Fiction',
-                    'regles' => '995$9=3',
+                    'regles' => '995$9=0@@3',
                     'date_maj' => '']);
 
     $this->fixture('Class_Notice',
@@ -507,7 +507,7 @@ abstract class PhasePrepareIntegrationsNanookStandardTestCase
     $this->fixture('Class_CodifEmplacement',
                    ['id' => 34,
                     'libelle' => 'RDC',
-                    'regles' => '995$9=3',
+                    'regles' => '995$9=0@@3',
                     'date_maj' => '']);
 
     $this->fixture('Class_Notice',
@@ -781,14 +781,14 @@ class PhasePrepareIntegrationsNanookStandardTest
 
   /** @test */
   public function firstKindLabelShouldBeMusic() {
-    $this->assertNotNull($kind = Class_CodifGenre::findFirstBy(['regles' => '995$7=1']));
+    $this->assertNotNull($kind = Class_CodifGenre::findFirstBy(['regles' => '995$7=0@@1']));
     $this->assertEquals('Music', $kind->getLibelle());
   }
 
 
   /** @test */
   public function secondKindLabelShouldBeBandeDessinée() {
-    $this->assertNotNull($kind = Class_CodifGenre::findFirstBy(['regles' => '995$7=2;3;4']));
+    $this->assertNotNull($kind = Class_CodifGenre::findFirstBy(['regles' => '995$7=0@@2;3;4']));
     $this->assertEquals('Bande dessinée', $kind->getLibelle());
   }
 
@@ -820,7 +820,7 @@ class PhasePrepareIntegrationsNanookStandardTest
 
   /** @test */
   public function existingSectionValueShouldNotBeDuplicated() {
-    $this->assertEquals('995$9=4', Class_CodifSection::find(36)->getRegles());
+    $this->assertEquals('995$9=0@@4', Class_CodifSection::find(36)->getRegles());
   }
 
 
@@ -1008,17 +1008,17 @@ class PhasePrepareIntegrationsKindTest extends Class_Cosmogramme_Integration_Pha
     $this->fixture('Class_CodifGenre',
                    ['id' => 4,
                     'libelle' => 'ALBUM',
-                    'regles' => '995$7=1']);
+                    'regles' => '995$7=0@@1']);
 
     $this->fixture('Class_CodifGenre',
                    ['id' => 5,
                     'libelle' => 'Fiction',
-                    'regles' => '995$7=3']);
+                    'regles' => '995$7=0@@3']);
 
     $this->fixture('Class_CodifGenre',
                    ['id' => 6,
                     'libelle' => 'SYNTHETISEUR, flûte de PAN',
-                    'regles' => '995$7=51;2;3']);
+                    'regles' => '995$7=0@@51;2;3']);
 
     $datas = ['BIB_GENRES|SUPPORT|CODE|LIBELLE|DOC',
               '0|1|Music|f',
@@ -1054,7 +1054,7 @@ class PhasePrepareIntegrationsKindTest extends Class_Cosmogramme_Integration_Pha
             ['Music',
              ['picto' => '',
               'libelle' => 'Music',
-              'regles' => '995$7=1',
+              'regles' => '995$7=0@@1',
               'date_maj' => '',
               'id' => 7,
               'id_genre' => 7]],
@@ -1062,7 +1062,7 @@ class PhasePrepareIntegrationsKindTest extends Class_Cosmogramme_Integration_Pha
             ['Bande dessinée',
              ['picto' => '',
               'libelle' => 'Bande dessinée',
-              'regles' => '995$7=2;3',
+              'regles' => '995$7=0@@2;3',
               'date_maj' => '',
               'id' => 8,
               'id_genre' => 8]],
@@ -1070,7 +1070,7 @@ class PhasePrepareIntegrationsKindTest extends Class_Cosmogramme_Integration_Pha
             ['SYNTHETISEUR, flûte de PAN',
              ['picto' => '',
               'libelle' => 'SYNTHETISEUR, flûte de PAN',
-              'regles' => '995$7=51',
+              'regles' => '995$7=0@@51',
               'date_maj' => '',
               'id' => 6,
               'id_genre' => 6]],
@@ -1078,7 +1078,7 @@ class PhasePrepareIntegrationsKindTest extends Class_Cosmogramme_Integration_Pha
             ['SYNTHETISEUR',
              ['picto' => '',
               'libelle' => 'SYNTHETISEUR',
-              'regles' => '995$7=94',
+              'regles' => '995$7=0@@94',
               'date_maj' => '',
               'id' => 9,
               'id_genre' => 9]]
diff --git a/tests/library/Class/Cosmogramme/Integration/PhaseSerialArticlesIndexTest.php b/tests/library/Class/Cosmogramme/Integration/PhaseSerialArticlesIndexTest.php
index 43915d993e0aa5f197778b79a907269ac86e26ad..3adf4dc675794217c4ec244e22625d31bdc8ccf6 100644
--- a/tests/library/Class/Cosmogramme/Integration/PhaseSerialArticlesIndexTest.php
+++ b/tests/library/Class/Cosmogramme/Integration/PhaseSerialArticlesIndexTest.php
@@ -95,13 +95,13 @@ class Class_Cosmogramme_Integration_PhaseSerialArticlesIndexTest
     $this->onLoaderOfModel(Class_Notice_SerialArticles::class)
          ->whenCalled('findAllBy')
          ->willDo(function() use($xmen, $iron_man, $orphan)
-                  {
-                    if ($this->_called)
-                      return [];
+         {
+           if ($this->_called)
+             return [];
 
-                    $this->_called = true;
-                    return [$xmen, $orphan, $iron_man];
-                  });
+           $this->_called = true;
+           return [$xmen, $orphan, $iron_man];
+         });
 
     $this->onLoaderOfModel(Class_CodifAuteur::class);
     $this->_prepareAuthor(1, 'LEExSTAN')
@@ -200,3 +200,283 @@ class Class_Cosmogramme_Integration_PhaseSerialArticlesIndexTest
     $this->assertEquals('2021-05-06 17:51:45', $this->_issue->getDateMaj());
   }
 }
+
+
+
+
+/* hotline : #139527 */
+abstract class RecordPerioWithFacettesTestCase
+  extends Class_Cosmogramme_Integration_PhaseTestCase {
+
+  protected
+    $_record,
+    $_article,
+    $_author_id = 1,
+    $_subject_id = 1;
+
+  protected function _getPreviousPhase() {
+    return (new Class_Cosmogramme_Integration_Phase(2))->beCron();
+  }
+
+
+  protected function _prepareFixtures() {
+    Class_CosmoVar::setValueOf('unimarc_zone_titre', '200$a');
+
+    $this->_record = $this->fixture(Class_Notice::class,
+                                   ['id' => 222,
+                                    'clef_chapeau' => 'FEMME ACTUELLE',
+                                    'tome_alpha' => '1919',
+                                    'matieres' => '',
+                                    'auteurs' => '',
+                                    'facettes' => '',
+                                    'titres' => 'FEMME FEM ACTUELLE AKTUEL 05 JUILLET JUI 2021 11 1919',
+                                   ]);
+
+    $marc = (new Class_NoticeUnimarc_Fluent)
+      ->zoneWithChildren('200', ['a' => '15 recettes avec 5 produits de saison']);
+
+    $this->_prepareCodifsForArticle($marc);
+
+    $this->_article = $this->fixture(Class_Notice_SerialArticles::class,
+                                     ['id' => 222,
+                                      'clef_chapeau' => 'FEMME ACTUELLE',
+                                      'clef_numero' => '1919',
+                                      'unimarc' => $marc->render(),
+                                      'date_maj' => $this->_chrono->mainStartDate(),
+                                     ]);
+
+    $this->onLoaderOfModel(Class_Notice_SerialArticles::class)
+         ->whenCalled('findAllBy')
+         ->willDo(function ()
+         {
+           Class_Notice_SerialArticles::whenCalled('findAllBy')->answers([]);
+           return [$this->_article];
+         });
+
+    $time_source = new TimeSourceForTest('2021-09-27 09:00:00');
+    Class_Cosmogramme_Integration_PhaseSerialArticlesIndex::setTimeSource($time_source);
+  }
+
+
+  protected function _prepareCodifsForArticle($marc) {
+    return $this;
+  }
+
+
+  protected function _prepareAuthor($first_name, $last_name, $marc = null) {
+    $formes = $last_name . 'x' . $first_name;
+    $author = $this->fixture(Class_CodifAuteur::class,
+                             ['id' => $this->_author_id++,
+                              'formes' => $formes,
+                             ]);
+
+    $this->onLoaderOfModel(Class_CodifAuteur::class);
+    Class_CodifAuteur::whenCalled('findFirstBy')
+      ->with(['where' => "MATCH(formes) AGAINST('" . $formes . "' IN BOOLEAN MODE)"])
+      ->answers($author);
+
+    if ($marc)
+      $marc->zoneWithChildren('702', ['a' => $last_name, 'b' => $first_name]);
+
+    return $this;
+  }
+
+
+  protected function _prepareSubject($name, $marc = null) {
+    Class_CosmoVar::setValueOf('unimarc_zone_matiere', '600$a');
+
+    $this->fixture(Class_CodifMatiere::class,
+                   ['id' => $this->_subject_id++,
+                    'libelle' => $name,
+                   ]);
+
+    if ($marc)
+      $marc->zoneWithChildren('600', ['a' => $name]);
+
+    return $this;
+  }
+
+
+  public function setUp() {
+    parent::setUp();
+
+    $this->_phase = $this->_buildPhase('SerialArticlesIndex')
+                         ->setMemoryCleaner(function () {})
+                         ->setPrinter($this->_printer)
+                         ->run();
+  }
+}
+
+
+
+
+/* Record have one author and subject, Article nothing */
+class PhaseSerialArticlesIndexRecordPerioWithFacetsArticleWhitoutFacetsTest
+  extends RecordPerioWithFacettesTestCase {
+
+  protected function _prepareFixtures() {
+    parent::_prepareFixtures();
+    $this->_record
+      ->setMatieres('REVUE REVU')
+      ->setAuteurs('DESSAUVAGES DESOVAJ JULIEN JULIN')
+      ->setFacettes('A1 M1')
+      ->assertSave();
+
+    return $this;
+  }
+
+
+  protected function _prepareCodifsForArticle($marc) {
+    $this->_prepareAuthor('JULIEN', 'DESSAUVAGES')
+         ->_prepareSubject('REVUE');
+
+    return $this;
+  }
+
+
+  /** @test */
+  public function recordFacetsShouldContainsAuthorsA1andMatieresM1() {
+    $this->assertEquals('A1 M1', $this->_record->getFacettes());
+  }
+
+
+  /** @test */
+  public function recordMatieresShouldContainsSubjectsTerms() {
+    $this->assertEquals('REVUE REVU', $this->_record->getMatieres());
+  }
+
+
+  /** @test */
+  public function recordAuteursShouldContainsAuthorsTerms() {
+    $this->assertEquals('DESSAUVAGES DESOVAJ JULIEN JULIN',
+                        $this->_record->getAuteurs());
+  }
+}
+
+
+
+
+/* Record have nothing, Article have one author and subject */
+class PhaseSerialArticlesIndexRecordPerioWithoutFacetsArticleWithtFacetsTest
+  extends RecordPerioWithFacettesTestCase {
+
+  protected function _prepareCodifsForArticle($marc) {
+    $this->_prepareAuthor('JULIEN', 'DESSAUVAGES', $marc)
+         ->_prepareSubject('REVUE', $marc);
+
+    return $this;
+  }
+
+
+  /** @test */
+  public function recordFacetsShouldContainsAuthorsA1andMatieresM1() {
+    $this->assertEquals('A1 M1', $this->_record->getFacettes());
+  }
+
+
+  /** @test */
+  public function recordMatieresShouldContainsSubjectsTerms() {
+    $this->assertEquals('REVUE REVU', $this->_record->getMatieres());
+  }
+
+
+  /** @test */
+  public function recordAuteursShouldContainsAuthorsTerms() {
+    $this->assertEquals('DESSAUVAGES DESOVAJ JULIEN JULIN',
+                        $this->_record->getAuteurs());
+  }
+}
+
+
+
+
+/* Record have one author and subject, Article has the same author and subject */
+class PhaseSerialArticlesIndexRecordPerioAndArticleHaveSameFacetsTest
+  extends RecordPerioWithFacettesTestCase {
+
+  protected function _prepareFixtures() {
+    parent::_prepareFixtures();
+    $this->_record
+      ->setMatieres('REVUE REVU')
+      ->setAuteurs('DESSAUVAGES DESOVAJ JULIEN JULIN')
+      ->setFacettes('A1 M1')
+      ->assertSave();
+
+    return $this;
+  }
+
+
+  protected function _prepareCodifsForArticle($marc) {
+    $this->_prepareAuthor('JULIEN', 'DESSAUVAGES', $marc)
+         ->_prepareSubject('REVUE', $marc);
+
+    return $this;
+  }
+
+
+  /** @test */
+  public function recordFacetsShouldContainsAuthorsA1andMatieresM1() {
+    $this->assertEquals('A1 M1', $this->_record->getFacettes());
+  }
+
+
+  /** @test */
+  public function recordMatieresShouldContainsSubjectsTerms() {
+    $this->assertEquals('REVUE REVU', $this->_record->getMatieres());
+  }
+
+
+  /** @test */
+  public function recordAuteursShouldContainsAuthorsTerms() {
+    $this->assertEquals('DESSAUVAGES DESOVAJ JULIEN JULIN',
+                        $this->_record->getAuteurs());
+  }
+}
+
+
+
+
+/* Record have one author and subject, Article have different author and subject */
+class PhaseSerialArticlesIndexRecordPerioAndArticleHaveDifferentFacetsTest
+  extends RecordPerioWithFacettesTestCase {
+
+  protected function _prepareFixtures() {
+    parent::_prepareFixtures();
+    $this->_record
+      ->setMatieres('REVUE REVU')
+      ->setAuteurs('DESSAUVAGES DESOVAJ JULIEN JULIN')
+      ->setFacettes('A1 M1')
+      ->assertSave();
+
+    return $this;
+  }
+
+
+  protected function _prepareCodifsForArticle($marc) {
+    $this->_prepareAuthor('JULIEN', 'DESSAUVAGES')
+         ->_prepareSubject('REVUE')
+         ->_prepareAuthor('JAMIE', 'OLIVER', $marc)
+         ->_prepareSubject('CUISINE', $marc);
+
+    return $this;
+  }
+
+
+  /** @test */
+  public function recordFacetsShouldContainsAuthorsA1_A2andMatieresM1_M2() {
+    $this->assertEquals('A1 A2 M1 M2', $this->_record->getFacettes());
+  }
+
+
+  /** @test */
+  public function recordMatieresShouldContainsSubjectsTerms() {
+    $this->assertEquals('CUISINE KUISIN REVUE REVU', $this->_record->getMatieres());
+  }
+
+
+  /** @test */
+  public function recordAuteursShouldContainsAuthorsTerms() {
+    $this->assertEquals('OLIVER OLIV JAMIE JAMI DESSAUVAGES DESOVAJ JULIEN JULIN',
+                        $this->_record->getAuteurs());
+  }
+}
diff --git a/tests/library/Class/Migration/SigbStandardCodificationsTest.php b/tests/library/Class/Migration/SigbStandardCodificationsTest.php
index be940be2ad16039fe7a77af49b31a845282b253f..edaabc40764b99145b206a14d97cc34f39547ca7 100644
--- a/tests/library/Class/Migration/SigbStandardCodificationsTest.php
+++ b/tests/library/Class/Migration/SigbStandardCodificationsTest.php
@@ -24,26 +24,33 @@ class Class_Migration_SigbStandardCodificationsTest extends ModelTestCase {
   public function setUp() {
     parent::setUp();
 
-    $this->fixture('Class_CodifSection',
+    $this->fixture(Class_CodifSection::class,
                    ['id' => 36,
                     'libelle' => 'Adultes',
-                    'regles' => '995$9=4;4;4;4;4',
+                    'regles' => '995$9='
+                    . Class_Codification_Rule::REMOVE_SPACE_PREFIX
+                    . '4;4;4;4;4',
                     'date_maj' => '']);
 
-    $this->fixture('Class_CodifEmplacement',
+    $this->fixture(Class_CodifEmplacement::class,
                    ['id' => 33,
                     'libelle' => 'Manga',
-                    'regles' => '995$6=108;108;108;108;108',
+                    'regles' => '995$6='
+                    . Class_Codification_Rule::REMOVE_SPACE_PREFIX
+                    . '108;108;108;108;108',
                     'date_maj' => '']);
 
-    $this->fixture('Class_CodifGenre',
+    $this->fixture(Class_CodifGenre::class,
                    ['id' => 64,
                     'libelle' => 'Music',
-                    'regles' => '995$7=1;1;1;1',
+                    'regles' => '995$7='
+                    . Class_Codification_Rule::REMOVE_SPACE_PREFIX
+                    . '1;1;1;1',
                     'date_maj' => '']);
 
-    $this->fixture('Class_IntBib', ['id' => 10, 'sigb' => Class_IntBib::SIGB_NANOOK]);
-    $this->fixture('Class_IntMajAuto', ['id' => 42]);
+    $this->fixture(Class_IntBib::class,
+                   ['id' => 10, 'sigb' => Class_IntBib::SIGB_NANOOK]);
+    $this->fixture(Class_IntMajAuto::class, ['id' => 42]);
 
     $landing_directory = $this
       ->mock()
@@ -86,27 +93,35 @@ class Class_Migration_SigbStandardCodificationsTest extends ModelTestCase {
   public function withNotSingleNanookShouldNotCleanSections() {
     Class_IntBib::deleteBy([]);
     (new Class_Migration_SigbStandardCodifications())->run();
-    $this->assertEquals('995$9=4;4;4;4;4', Class_CodifSection::find(36)->getRegles());
+    $this->assertEquals('995$9='
+                        . Class_Codification_Rule::REMOVE_SPACE_PREFIX
+                        . '4;4;4;4;4', Class_CodifSection::find(36)->getRegles());
   }
 
 
   /** @test */
   public function withSingleNanookShouldCleanSections() {
     (new Class_Migration_SigbStandardCodifications())->run();
-    $this->assertEquals('995$9=4', Class_CodifSection::find(36)->getRegles());
+    $this->assertEquals('995$9='
+                        . Class_Codification_Rule::REMOVE_SPACE_PREFIX
+                        . '4', Class_CodifSection::find(36)->getRegles());
   }
 
 
   /** @test */
   public function withSingleNanookShouldCleanLocations() {
     (new Class_Migration_SigbStandardCodifications())->run();
-    $this->assertEquals('995$6=9', Class_CodifEmplacement::find(33)->getRegles());
+    $this->assertEquals('995$6='
+                        . Class_Codification_Rule::REMOVE_SPACE_PREFIX
+                        . '9', Class_CodifEmplacement::find(33)->getRegles());
   }
 
 
   /** @test */
   public function withSingleNanookShouldCleanKinds() {
     (new Class_Migration_SigbStandardCodifications())->run();
-    $this->assertEquals('995$7=1', Class_CodifGenre::find(64)->getRegles());
+    $this->assertEquals('995$7='
+                        . Class_Codification_Rule::REMOVE_SPACE_PREFIX
+                        . '1', Class_CodifGenre::find(64)->getRegles());
   }
 }
diff --git a/tests/scenarios/ArticlesMultipleTimings/ArticlesMultipleTimingsViewTest.php b/tests/scenarios/ArticlesMultipleTimings/ArticlesMultipleTimingsViewTest.php
index d729d5c85fa1a53bf30243b1c8394c43698fbfb5..80100f26893e8888007513e3e1c403cde8945199 100644
--- a/tests/scenarios/ArticlesMultipleTimings/ArticlesMultipleTimingsViewTest.php
+++ b/tests/scenarios/ArticlesMultipleTimings/ArticlesMultipleTimingsViewTest.php
@@ -145,3 +145,27 @@ class ArticlesMultipleTimingsTemplatesViewTest extends ArticlesMultipleTimingsVi
                                          '01 juin');
   }
 }
+
+
+
+
+class ArticlesMultipleTimingsViewWithEmptyTimingsTest
+  extends ArticlesMultipleTimingsViewTestCase {
+
+  public function setUp() {
+    parent::setUp();
+
+    Class_Article::find(5)
+      ->setEventTimings([])
+      ->assertSave();
+
+    $this->dispatch('/cms/articleview/id/5');
+  }
+
+
+  /** @test */
+  public function articleShouldNotContainsDivForTimings() {
+    $this->assertNotXPathContentContains('//div[@class="article_timings"]/h2',
+                                         'Dates et Horaires');
+  }
+}
diff --git a/tests/scenarios/IndexablePages/IndexablePagesPseudoRecordTest.php b/tests/scenarios/IndexablePages/IndexablePagesPseudoRecordTest.php
index 8b3d47de5486147e1680c47359e7cf1a177e5d48..d0773f15b07ab1ba14cadb4375e9a3c9ee94642c 100644
--- a/tests/scenarios/IndexablePages/IndexablePagesPseudoRecordTest.php
+++ b/tests/scenarios/IndexablePages/IndexablePagesPseudoRecordTest.php
@@ -118,6 +118,24 @@ class IndexablePagesPseudoRecordEnabledIndexableTest extends ModelTestCase {
   }
 
 
+  /** @test */
+  public function recordWorkKeyShouldBeMysuperpage_Portal_233() {
+    $this->assertEquals('MYSUPERPAGE--PORTAL-233',
+                        $this->_record->getClefOeuvre());
+  }
+
+
+  /** @test */
+  public function withPageInLibraryAnnecyShouldBeMysuperpage_Annecy_233() {
+    $this->_page->setBib($this->fixture(Class_Bib::class,
+                                        ['id' => 3,
+                                         'libelle' => 'Annecy']))
+                ->index();
+    $this->assertEquals('MYSUPERPAGE--ANNECY-233',
+                        $this->_page->getNotice()->getClefOeuvre());
+  }
+
+
   /** @test */
   public function recordTitleShouldBeMySuperPage() {
     $this->assertEquals('My super page', $this->_record->getTitrePrincipal());
diff --git a/tests/scenarios/Templates/TemplatesDigitalResourcesTest.php b/tests/scenarios/Templates/TemplatesDigitalResourcesTest.php
index 808327b6a99891d25620af8b366caa66019944f1..afbe25fbc0dadb8f990f6222f5741c5f1729b705 100644
--- a/tests/scenarios/Templates/TemplatesDigitalResourcesTest.php
+++ b/tests/scenarios/Templates/TemplatesDigitalResourcesTest.php
@@ -240,10 +240,17 @@ class TemplatesDigitalResourcesDilicomItemTest extends TemplatesDigitalResources
   public function consultLinkShouldBeDisabled() {
     $this->assertXPathContentContains('//a[@data-disabled]', 'Consulter le livre en ligne');
   }
+
+
+  /** @test */
+  public function initializePopupsScriptsAndSetupAnchorsTargetShouldBePresent() {
+    $this->assertXPathContentContains('//script', 'setTimeout(function(){initializePopups();setupAnchorsTarget();}, 5);');
+  }
 }
 
 
 
+
 /** @see http://forge.afi-sa.fr/issues/115415 */
 class TemplatesDigitalResourcesWrongIndexationTest extends AbstractControllerTestCase {
   protected $_storm_default_to_volatile = true;
@@ -515,3 +522,35 @@ class TemplatesDigitalResourcesAudioRecordTest extends AbstractControllerTestCas
     $this->assertXPathContentContains('//div[@class="list-group-item bg-transparent px-0 mb-3"]//a[@class="audio_track card-title text-secondary"][contains(@data-track-url,"bib-numerique/play-ressource/id/4.mp3")]/span[@class="track_title"]', 'The prophecy');
   }
 }
+
+
+
+
+class TemplatesDigitalResourcesDilicomRecordItemTest extends TemplatesDigitalResourcesDilicomTestCase {
+
+  public function setUp() {
+    parent::setUp();
+
+    $this->fixture('Class_Notice',
+                   ['id' => 123,
+                    'type_doc' => Class_TypeDoc::LIVRE_NUM]);
+
+    $this->fixture('Class_Exemplaire',
+                   ['id' => 8,
+                    'id_origine' => 3,
+                    'id_notice' => 123]);
+
+    (new DilicomFixtures())->albumTotemThora();
+    RessourcesNumeriquesFixtures::activateDilicom();
+
+    $this->dispatch('record/items/id/123');
+  }
+
+
+  /** @test */
+  public function initializePopupsScriptsShouldBeDoneBeforeReturn() {
+    $this->assertXPathContentContains('//script', '.load("/noticeajax/resources/id/123", function() {initializePopups();setupAnchorsTarget();;$(\'.loading_icon\').parent().remove();
+                 if ($(\'.document_items [data-ajax-content] > *\').length) {
+                   return $(\'.document_items\').removeClass(\'d-none text-black-50 disabled\');}});}');
+  }
+}
\ No newline at end of file
diff --git a/tests/scenarios/Templates/TemplatesDomainTest.php b/tests/scenarios/Templates/TemplatesDomainTest.php
index 9eb7b5f8aa9e5ac9ae87923ea5d22b9aac8fef12..cb8460d34c3efea6e4c8f2738a10834073e8689c 100644
--- a/tests/scenarios/Templates/TemplatesDomainTest.php
+++ b/tests/scenarios/Templates/TemplatesDomainTest.php
@@ -67,4 +67,10 @@ class TemplatesDomainWidgetWithDescriptionLengthTest extends AbstractControllerT
     $this->assertXPathContentContains('//div[contains(@class, "domain_browser widget")]//p[contains(@class, "model_description")]',
                                       'La description s\'arrête ici …');
   }
+
+
+  /** @test */
+  public function catalogueLinkShouldNotContainsUrlParams() {
+    $this->assertXPathContentContains('//div//a[@href="/recherche/simple/id_catalogue/2/id_module/1"]', 'BD');
+  }
 }
diff --git a/tests/scenarios/Templates/TemplatesJumbotronTest.php b/tests/scenarios/Templates/TemplatesJumbotronTest.php
index 49b4036dbd240a157a125ff15fbe9458d406be02..dd66352116350cc4c6347e0efae8397e09515edd 100644
--- a/tests/scenarios/Templates/TemplatesJumbotronTest.php
+++ b/tests/scenarios/Templates/TemplatesJumbotronTest.php
@@ -40,9 +40,9 @@ class TemplatesRecordsDispatchItemsTest extends AbstractControllerTestCase {
   /** @test */
   public function shouldJsLoadItemsWithInitializePopupAndSetupAnchorTarget() {
     $this->dispatch('/record/items/id/1');
-    $this->assertXPathContentContains('//script', '.load("/noticeajax/resources/id/1", function() {$(\'.loading_icon\').parent().remove();
-                 if ($(\'.document_items [data-ajax-content] > *\').length)
-                   return $(\'.document_items\').removeClass(\'d-none text-black-50 disabled\');initializePopups();setupAnchorsTarget();});}, 5);');
+    $this->assertXPathContentContains('//script', '.load("/noticeajax/resources/id/1", function() {initializePopups();setupAnchorsTarget();;$(\'.loading_icon\').parent().remove();
+                 if ($(\'.document_items [data-ajax-content] > *\').length) {
+                   return $(\'.document_items\').removeClass(\'d-none text-black-50 disabled\');}});}, 5);');
   }
 
 
@@ -50,8 +50,8 @@ class TemplatesRecordsDispatchItemsTest extends AbstractControllerTestCase {
   public function shouldJsLoadHasAuthor() {
     $this->dispatch('/record/items/id/1');
     $this->assertXPathContentContains('//script', '.load("/noticeajax/has-author/id/1", function() {$(\'.loading_icon\').parent().remove();
-                 if ($(\'.document_author [data-ajax-content] > *\').length)
-                   return $(\'.document_author\').removeClass(\'d-none text-black-50 disabled\');});}, 5);');
+                 if ($(\'.document_author [data-ajax-content] > *\').length) {
+                   return $(\'.document_author\').removeClass(\'d-none text-black-50 disabled\');}});}, 5);');
   }
 
 
@@ -59,8 +59,8 @@ class TemplatesRecordsDispatchItemsTest extends AbstractControllerTestCase {
   public function shouldJsLoadHasMedia() {
     $this->dispatch('/record/items/id/1');
     $this->assertXPathContentContains('//script', '.load("/noticeajax/has-media/id/1", function() {$(\'.loading_icon\').parent().remove();
-                 if ($(\'.document_media [data-ajax-content] > *\').length)
-                   return $(\'.document_media\').removeClass(\'d-none text-black-50 disabled\');});}, 5);');
+                 if ($(\'.document_media [data-ajax-content] > *\').length) {
+                   return $(\'.document_media\').removeClass(\'d-none text-black-50 disabled\');}});}, 5);');
   }
 
 
@@ -118,8 +118,8 @@ class TemplatesRecordsDispatchItemsTest extends AbstractControllerTestCase {
 
     $this->dispatch('/abonne/selections/id/2');
     $this->assertXPathContentContains('//script', 'load("/abonne/has-loan/id/2", function() {$(\'.loading_icon\').parent().remove();
-                 if ($(\'.user_loans [data-ajax-content] > *\').length)
-                   return $(\'.user_loans\').removeClass(\'d-none text-black-50 disabled\');});});');
+                 if ($(\'.user_loans [data-ajax-content] > *\').length) {
+                   return $(\'.user_loans\').removeClass(\'d-none text-black-50 disabled\');}});});');
   }