From cfaf348430df677642c41a3546d2cef30be5ab1b Mon Sep 17 00:00:00 2001
From: Laurent Laffont <llaffont@afi-sa.fr>
Date: Tue, 15 Jan 2019 11:41:35 +0100
Subject: [PATCH] dev #80658 : Now you can build dynamic facets on part of a
 marc field.

Add advanced form element 'selectDynamicFacet' that builds a selector on children of a CodifThesaurus entry
---
 FEATURES/80658                                | 10 ++
 VERSIONS_WIP/80658                            |  1 +
 .../DynamicFacetsControllerTest.php           | 64 +++++++++++--
 .../php/classes/KohaRecordIntegrationTest.php | 80 +++++++++++++++-
 library/Class/CodifThesaurus.php              | 31 +++++-
 library/Class/CodifThesaurus/Rules.php        | 28 +++++-
 .../Record/BibliographicDynamicFacets.php     |  4 +
 library/ZendAfi/Form/Admin/DynamicFacet.php   | 15 +++
 library/ZendAfi/Form/Element/Number.php       | 35 +++++++
 .../Form/Element/SelectDynamicFacet.php       | 54 +++++++++++
 library/ZendAfi/View/Helper/FormEmail.php     | 20 ++--
 library/ZendAfi/View/Helper/FormHTML5.php     | 42 ++++++++
 library/ZendAfi/View/Helper/FormNumber.php    | 31 ++++++
 library/ZendAfi/View/Helper/FormUrl.php       | 19 +---
 .../controllers/RechercheControllerTest.php   | 18 ++--
 tests/library/Class/CodifThesaurusTest.php    |  3 +-
 .../AdvancedSearch/AdvancedSearchTest.php     | 96 ++++++++++++++++---
 .../SearchResult/SearchResultTest.php         |  6 +-
 tests/scenarios/Thesauri/ThesauriTest.php     |  4 +-
 19 files changed, 488 insertions(+), 73 deletions(-)
 create mode 100644 FEATURES/80658
 create mode 100644 VERSIONS_WIP/80658
 create mode 100644 library/ZendAfi/Form/Element/Number.php
 create mode 100644 library/ZendAfi/Form/Element/SelectDynamicFacet.php
 create mode 100644 library/ZendAfi/View/Helper/FormHTML5.php
 create mode 100644 library/ZendAfi/View/Helper/FormNumber.php

diff --git a/FEATURES/80658 b/FEATURES/80658
new file mode 100644
index 00000000000..600040eee49
--- /dev/null
+++ b/FEATURES/80658
@@ -0,0 +1,10 @@
+        '80658' =>
+            ['Label' => $this->_('Facettes dynamiques sur des sous-chaînes de champs unimarc'),
+             'Desc' => $this->_('Vous pouvez créer des facettes sur des portions d\'un champ unimarc. Par exemple, sur un champ 993$w formatté comme ceci: 2018-05-04, vous pouvez créer trois facettes sur l\'année, le mois et le jour.')',
+             'Image' => 'http://wiki.bokeh-library-portal.org/images/4/41/Dynamic_facet_public.png',
+             'Video' => '',
+             'Category' => '',
+             'Right' => function($feature_description, $user) {return true;},
+             'Wiki' => 'http://wiki.bokeh-library-portal.org/index.php?title=Facettes_dynamiques#A_partir_d.27une_partie_du_libell.C3.A9_d.27un_champ_unimarc',
+             'Test' => '',
+             'Date' => '2019-01-18'],
\ No newline at end of file
diff --git a/VERSIONS_WIP/80658 b/VERSIONS_WIP/80658
new file mode 100644
index 00000000000..dbe62c644a6
--- /dev/null
+++ b/VERSIONS_WIP/80658
@@ -0,0 +1 @@
+ - ticket #80658 : Facettes dynamiques : possibilité de créer des facettes dynamiques sur des dates
\ No newline at end of file
diff --git a/cosmogramme/cosmozend/tests/application/modules/cosmo/controllers/DynamicFacetsControllerTest.php b/cosmogramme/cosmozend/tests/application/modules/cosmo/controllers/DynamicFacetsControllerTest.php
index 82ff9ff8301..029e4d57336 100644
--- a/cosmogramme/cosmozend/tests/application/modules/cosmo/controllers/DynamicFacetsControllerTest.php
+++ b/cosmogramme/cosmozend/tests/application/modules/cosmo/controllers/DynamicFacetsControllerTest.php
@@ -72,6 +72,7 @@ class Cosmo_DynamicFacetsControllerIndexTest extends CosmoControllerTestCase {
 
 
 
+
 class Cosmo_DynamicFacetsControllerAddTest extends CosmoControllerTestCase {
   public function setUp() {
     parent::setUp();
@@ -113,6 +114,18 @@ class Cosmo_DynamicFacetsControllerAddTest extends CosmoControllerTestCase {
   public function labelForBokehShouldBePresent() {
     $this->assertXPath('//input[@name="libelle_facette"][@value="** nouvelle facette dynamique **"]');
   }
+
+
+  /** @test */
+  public function inputLabelStartPosShouldBeNumberWithValueOne() {
+    $this->assertXPath('//input[@name="rule_label_start_pos"][@type="number"][@min="1"][@value="1"]');
+  }
+
+
+  /** @test */
+  public function inputLabelLengthShouldBeNumberWithValueZero() {
+    $this->assertXPath('//input[@name="rule_label_length"][@type="number"][@min="0"][@value="0"]');
+  }
 }
 
 
@@ -126,7 +139,9 @@ class Cosmo_DynamicFacetsControllerAddPostValidTest extends CosmoControllerTestC
     $this->postDispatch('/cosmo/facets/add', ['libelle'          => 'New facet name',
                                               'rule_zone'        => '609',
                                               'rule_label_field' => 'a',
-                                              'rule_id_field'    => '9']);
+                                              'rule_id_field'    => '9',
+                                              'rule_label_start_pos' => '2',
+                                              'rule_label_length' => '4']);
 
     Class_CodifThesaurus::clearCache();
     $this->model = Class_CodifThesaurus::findFirstBy(['libelle' => 'New facet name']);
@@ -161,6 +176,18 @@ class Cosmo_DynamicFacetsControllerAddPostValidTest extends CosmoControllerTestC
   public function idFieldShouldBe9() {
     $this->assertEquals('9', $this->model->getRuleIdField());
   }
+
+
+  /** @test */
+  public function rulesLabelStartPosShouldBeTwo() {
+    $this->assertEquals('2', $this->model->getRulesAsArray()['rule_label_start_pos']);
+  }
+
+
+  /** @test */
+  public function rulesLabelLengthShouldBeFour() {
+    $this->assertEquals('4', $this->model->getRulesAsArray()['rule_label_length']);
+  }
 }
 
 
@@ -171,8 +198,9 @@ class Cosmo_DynamicFacetsControllerValidateTest extends CosmoControllerTestCase
   public function idThesaurusShouldBeDOC0() {
     $this->postDispatch('/cosmo/facets/add', ['libelle' => 'doc',
                                               'rule_zone' => '033',
-                                              'rule_label_field' => '8']);
-
+                                              'rule_label_field' => '8',
+                                              'rule_label_start_pos' => '1',
+                                              'rule_label_length' => '0']);
     $this->assertEquals('DOC0',
                         Class_CodifThesaurus::findFirstBy(['libelle'=>'doc'])->getIdThesaurus());
   }
@@ -183,11 +211,16 @@ class Cosmo_DynamicFacetsControllerValidateTest extends CosmoControllerTestCase
     $this->fixture('Class_CodifThesaurus',
                    ['libelle' => 'doc that i love',
                     'id_thesaurus' => 'DOC0',
-                    'rules' => '{"Zone":"345", "LabelField":"t"}']);
+                    'rule_zone' => '345',
+                    'rule_label_field' => 't',
+                    'rule_label_start_pos' => '1',
+                    'rule_label_length' => '0']);
 
     $this->postDispatch('/cosmo/facets/add', ['libelle' => 'doc',
                                               'rule_zone' => '345',
-                                              'rule_label_field' => '3']);
+                                              'rule_label_field' => '3',
+                                              'rule_label_start_pos' => '1',
+                                              'rule_label_length' => '0']);
 
     $this->assertEquals('DOC1',
                         Class_CodifThesaurus::findFirstBy(['libelle'=>'doc'])->getIdThesaurus());
@@ -200,11 +233,16 @@ class Cosmo_DynamicFacetsControllerValidateTest extends CosmoControllerTestCase
     $this->fixture('Class_CodifThesaurus',
                    ['libelle' => 'doc that i love',
                     'id_thesaurus' => 'DOCU',
-                    'rules' => '{"Zone":"345", "LabelField":"t"}']);
+                    'rule_zone' => '345',
+                    'rule_label_field' => 't',
+                    'rule_label_start_pos' => '1',
+                    'rule_label_length' => '0']);
 
     $this->postDispatch('/cosmo/facets/add', ['libelle' => 'docu',
                                               'rule_zone' => '345',
-                                              'rule_label_field' => '3']);
+                                              'rule_label_field' => '3',
+                                              'rule_label_start_pos' => '1',
+                                              'rule_label_length' => '0']);
 
     $this->assertEquals('DOC0',
                         Class_CodifThesaurus::findFirstBy(['libelle'=>'docu'])->getIdThesaurus());
@@ -216,7 +254,9 @@ class Cosmo_DynamicFacetsControllerValidateTest extends CosmoControllerTestCase
   public function withSpecialCharsNewIdThesaurusShouldBeFourLetters() {
     $this->postDispatch('/cosmo/facets/add', ['libelle' => 'Année top 5',
                                               'rule_zone' => '066',
-                                              'rule_label_field' => '6']);
+                                              'rule_label_field' => '6',
+                                              'rule_label_start_pos' => '1',
+                                              'rule_label_length' => '0']);
 
     $this->assertEquals('ANNE',
                         Class_CodifThesaurus::findFirstBy(['libelle'=>'Année top 5'])->getIdThesaurus());
@@ -296,14 +336,18 @@ class Cosmo_DynamicFacetsControllerEditPostTest extends CosmoControllerTestCase
                     'code' => 'DOCU',
                     'rule_zone' => '345',
                     'rule_label_field' => 't',
-                    'rule_id_field' => '9']);
+                    'rule_id_field' => '9',
+                    'rule_label_start_pos' => '1',
+                    'rule_label_length' => '0']);
 
     $this->postDispatch('/cosmo/facets/edit/id/3',
                         ['libelle' => 'Document',
                          'libelle_facette' => 'Document',
                          'rule_zone' => '609',
                          'rule_label_field' => 'a',
-                         'rule_id_field' => '3']);
+                         'rule_id_field' => '3',
+                         'rule_label_start_pos' => '1',
+                         'rule_label_length' => '0']);
   }
 
 
diff --git a/cosmogramme/tests/php/classes/KohaRecordIntegrationTest.php b/cosmogramme/tests/php/classes/KohaRecordIntegrationTest.php
index 6ae3c6a7bab..effbfc65605 100644
--- a/cosmogramme/tests/php/classes/KohaRecordIntegrationTest.php
+++ b/cosmogramme/tests/php/classes/KohaRecordIntegrationTest.php
@@ -134,6 +134,43 @@ class KohaRecordIntegrationFacetPresseTest extends KohaRecordIntegrationTestCase
 
   public function setUp() {
     parent::setUp();
+    $this->fixture('Class_CodifThesaurus',
+                   ['id' => 1,
+                    'libelle' => 'Année de publication',
+                    'id_thesaurus' => 'ANNE',
+                    'id_origine' => null,
+                    'code' => 'annee',
+                    'rule_zone' => '995',
+                    'rule_label_field' => 'w',
+                    'rule_label_start_pos' => 1,
+                    'rule_label_length' => 4
+                   ]);
+
+
+    $this->fixture('Class_CodifThesaurus',
+                   ['id' => 2,
+                    'libelle' => 'Mois de publication',
+                    'id_thesaurus' => 'MOIS',
+                    'id_origine' => null,
+                    'code' => 'mois',
+                    'rule_zone' => '995',
+                    'rule_label_field' => 'w',
+                    'rule_label_start_pos' => 6,
+                    'rule_label_length' => 2
+                   ]);
+
+    $this->fixture('Class_CodifThesaurus',
+                   ['id' => 3,
+                    'libelle' => 'Jour de publication',
+                    'id_thesaurus' => 'JOUR',
+                    'id_origine' => null,
+                    'code' => 'jour',
+                    'rule_zone' => '995',
+                    'rule_label_field' => 'w',
+                    'rule_label_start_pos' => 9,
+                    'rule_label_length' => 2
+                   ]);
+
 
     Class_CosmoVar::setValueOf('mode_doublon', '1');
     $this->loadNotice('unimarc_koha_okapi');
@@ -147,14 +184,52 @@ class KohaRecordIntegrationFacetPresseTest extends KohaRecordIntegrationTestCase
 
 
   /** @test */
-  public function facettesShouldContainsT2() {
-    $this->assertEquals('Lfre T2 B1 YMEDSTMAR',
+  public function thesaurusAnne001ShouldHaveIdOrigine2014() {
+    $this->assertEquals('2014',
+                        Class_CodifThesaurus::findFirstBy(['id_thesaurus' => 'ANNE0001'])->getIdOrigine());
+  }
+
+
+  /** @test */
+  public function thesaurusMois0001ShouldHaveIdOrigine09() {
+    $this->assertEquals('06',
+                        Class_CodifThesaurus::findFirstBy(['id_thesaurus' => 'MOIS0001'])->getIdOrigine());
+  }
+
+
+  /** @test */
+  public function thesaurusJour0001ShouldHaveIdOrigine09() {
+    $this->assertEquals('03',
+                        Class_CodifThesaurus::findFirstBy(['id_thesaurus' => 'JOUR0001'])->getIdOrigine());
+  }
+
+
+  /** @test */
+  public function thesaurusAnne001ShouldHaveLabel2014() {
+    $this->assertEquals('2014',
+                        Class_CodifThesaurus::findFirstBy(['id_thesaurus' => 'ANNE0001'])->getLibelle());
+  }
+
+
+
+  /** @test */
+  public function facettesShouldContainsT2AndHANNE0001AndHMOIS0001AndJOUR0001() {
+    $this->assertEquals('HANNE0001 HMOIS0001 HJOUR0001 Lfre T2 B1 YMEDSTMAR',
                         Class_Notice::find(1)
                         ->updateFacetsFromExemplaires()
                         ->getFacettes());
   }
 
 
+    /** @test */
+  public function facettesForRecord25ShouldContainsT2AndHANNE0001AndHMOIS0001() {
+    $this->assertEquals('HANNE0001 HMOIS0007 HJOUR0006 Lfre T2 B1 YMEDSTMAR',
+                        Class_Notice::find(25)
+                        ->updateFacetsFromExemplaires()
+                        ->getFacettes());
+  }
+
+
     /** @test */
   public function okapi983ShouldHaveT2Facette() {
     $okapi_983 = Class_Notice::findFirstBy(['tome_alpha' => '983']);
@@ -356,6 +431,7 @@ class KohaRecordIntegrationVagabondWithTooMany610aTest extends KohaRecordIntegra
                     'code' => 'TEST',
                     'rules' => '{"label":"610$a"}']);
 
+
     $this->fixture('Class_CodifThesaurus',
                    ['id_thesaurus' => 'TEST9999',
                     'id_origine' => 'RIME',
diff --git a/library/Class/CodifThesaurus.php b/library/Class/CodifThesaurus.php
index 15d31a1958b..d132f694d38 100644
--- a/library/Class/CodifThesaurus.php
+++ b/library/Class/CodifThesaurus.php
@@ -584,10 +584,39 @@ class Class_CodifThesaurus extends Storm_Model_Abstract {
   }
 
 
+  public function setRuleLabelStartPos($value) {
+    $this->_rules->setLabelStartPos($value);
+    return $this;
+  }
+
+
+  public function setRuleLabelLength($value) {
+    $this->_rules->setLabelLength($value);
+    return $this;
+  }
+
+
+  public function getRuleLabelLength() {
+    return $this->_rules->getLabelLength();
+  }
+
+
+  public function getRuleLabelStartPos() {
+    return $this->_rules->getLabelStartPos();
+  }
+
+
+  public function rulesTruncateLabels($labels) {
+    return $this->_rules->truncateLabels($labels);
+  }
+
+
   public function getRulesAsArray() {
     return ['rule_zone' => $this->_rules->getZone(),
             'rule_label_field' => $this->_rules->getLabelField(),
-            'rule_id_field' => $this->_rules->getIdField()];
+            'rule_id_field' => $this->_rules->getIdField(),
+            'rule_label_start_pos' => $this->_rules->getLabelStartPos(),
+            'rule_label_length' => $this->_rules->getLabelLength()];
   }
 
 
diff --git a/library/Class/CodifThesaurus/Rules.php b/library/Class/CodifThesaurus/Rules.php
index 04da51c7aa2..4315312db7d 100644
--- a/library/Class/CodifThesaurus/Rules.php
+++ b/library/Class/CodifThesaurus/Rules.php
@@ -23,7 +23,14 @@
 class Class_CodifThesaurus_Rules extends Class_Entity {
   use Trait_Translator;
 
-  protected $_persistent_attribs = ['Zone', 'LabelField', 'IdField'];
+  protected
+    $_attribs = ['LabelStartPos' => 1,
+                 'LabelLength' => 0],
+    $_persistent_attribs = ['Zone',
+                                    'LabelField',
+                                    'IdField',
+                                    'LabelStartPos',
+                                    'LabelLength'];
 
 
   public function initialize($datas) {
@@ -87,6 +94,25 @@ class Class_CodifThesaurus_Rules extends Class_Entity {
   }
 
 
+  public function truncateLabels($labels) {
+    return array_filter(
+                        array_map([$this, 'truncateLabel'], $labels),
+                        [$this, 'isValidLabel']);
+  }
+
+
+  public function truncateLabel($label) {
+    return substr($label,
+                  max(0, $this->getLabelStartPos()-1),
+                  $this->getLabelLength());
+  }
+
+
+  public function isValidLabel($label) {
+    return is_string($label) && $label > '';
+  }
+
+
   public function withLabelAndIdDo($closure) {
     if (!$this->isEmpty())
       return $closure($this->getZonePadded(), trim($this->getLabelField()), trim($this->getIdField()));
diff --git a/library/Class/Cosmogramme/Integration/Record/BibliographicDynamicFacets.php b/library/Class/Cosmogramme/Integration/Record/BibliographicDynamicFacets.php
index e4bb0d984e9..4ec66244804 100644
--- a/library/Class/Cosmogramme/Integration/Record/BibliographicDynamicFacets.php
+++ b/library/Class/Cosmogramme/Integration/Record/BibliographicDynamicFacets.php
@@ -40,6 +40,7 @@ class Class_Cosmogramme_Integration_Record_BibliographicDynamicFacets {
 
 
 
+
 class Class_Cosmogramme_Integration_Record_BibliographicDynamicFacets_Simple {
   protected $_thesaurus;
 
@@ -54,6 +55,8 @@ class Class_Cosmogramme_Integration_Record_BibliographicDynamicFacets_Simple {
                          function($field, $subfield) use($reader) {
                            return $reader->get_subfield($field, $subfield);
                          });
+    if ($this->_thesaurus->getRuleLabelLength())
+      $labels = $this->_thesaurus->rulesTruncateLabels($labels);
 
     return $this->_thesaurus->getOrCreateChildren($labels);
   }
@@ -61,6 +64,7 @@ class Class_Cosmogramme_Integration_Record_BibliographicDynamicFacets_Simple {
 
 
 
+
 class Class_Cosmogramme_Integration_Record_BibliographicDynamicFacets_Authority
   extends Class_Cosmogramme_Integration_Record_BibliographicDynamicFacets_Simple {
 
diff --git a/library/ZendAfi/Form/Admin/DynamicFacet.php b/library/ZendAfi/Form/Admin/DynamicFacet.php
index 592532fe2ea..08a3791067d 100644
--- a/library/ZendAfi/Form/Admin/DynamicFacet.php
+++ b/library/ZendAfi/Form/Admin/DynamicFacet.php
@@ -57,6 +57,21 @@ class ZendAfi_Form_Admin_DynamicFacet extends ZendAfi_Form {
                     'required' => true,
                     'allowEmpty' => false])
 
+      ->addElement('number',
+                   'rule_label_start_pos',
+                   ['label' => $this->_('Position du premier caractère du libellé'),
+                    'required' => true,
+                    'allowEmpty' => false,
+                    'min' => 1])
+
+
+      ->addElement('number',
+                   'rule_label_length',
+                   ['label' => $this->_('Longueur du libellé (0 pour prendre totalité du libellé)'),
+                    'required' => true,
+                    'allowEmpty' => false,
+                    'min' => 0])
+
       ->addElement('select',
                    'rule_id_field',
                    ['label' => $this->_('Prendre l\'identifiant en'),
diff --git a/library/ZendAfi/Form/Element/Number.php b/library/ZendAfi/Form/Element/Number.php
new file mode 100644
index 00000000000..579936c7dcb
--- /dev/null
+++ b/library/ZendAfi/Form/Element/Number.php
@@ -0,0 +1,35 @@
+<?php
+/**
+ * Copyright (c) 2012, 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_Form_Element_Number extends Zend_Form_Element_Text {
+  /**
+   * HTML5 input type = number
+   * @var string
+   */
+  public $helper = 'formNumber';
+
+
+  public function init() {
+    $this->addValidator(new Zend_Validate_Int());
+  }
+}
+
+?>
\ No newline at end of file
diff --git a/library/ZendAfi/Form/Element/SelectDynamicFacet.php b/library/ZendAfi/Form/Element/SelectDynamicFacet.php
new file mode 100644
index 00000000000..6f9ec980e9c
--- /dev/null
+++ b/library/ZendAfi/Form/Element/SelectDynamicFacet.php
@@ -0,0 +1,54 @@
+<?php
+/**
+ * Copyright (c) 2012-2017, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class ZendAfi_Form_Element_SelectDynamicFacet extends Zend_Form_Element_Select {
+  public function init() {
+    if (!$thesaurus = Class_CodifThesaurus::findFirstBy(['rules not' => null,
+                                                         'code' => $this->_getCodeThesaurus(),
+                                                         'id_thesaurus not' => null]))
+      return;
+
+    $this->setMultiOptions($this->_buildThesaurusChildrenEntries($thesaurus));
+  }
+
+
+  protected function _getCodeThesaurus() {
+    return str_replace('rech_' . Class_CodifThesaurus::CODE_FACETTE, '', $this->getName());
+  }
+
+
+  protected function _buildThesaurusChildrenEntries($thesaurus) {
+    $options = ['' => ''];
+
+    foreach (Class_CodifThesaurus::findChildrenOfWith($thesaurus->getIdThesaurus(),
+                                                      100,
+                                                      'libelle')
+             as $child)
+      $options[$child->getLibelle()] = $child->getLibelle();
+
+    return $options;
+  }
+
+
+  public function loadDefault($datas) {
+  }
+}
diff --git a/library/ZendAfi/View/Helper/FormEmail.php b/library/ZendAfi/View/Helper/FormEmail.php
index de826f83315..dbb06cfc47b 100644
--- a/library/ZendAfi/View/Helper/FormEmail.php
+++ b/library/ZendAfi/View/Helper/FormEmail.php
@@ -19,21 +19,13 @@
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
 
-class ZendAfi_View_Helper_FormEmail extends Zend_View_Helper_FormText {
+class ZendAfi_View_Helper_FormEmail extends ZendAfi_View_Helper_FormHTML5 {
   public function formEmail($name, $value = null, $attribs = null)   {
-    $info = $this->_getInfo($name, $value, $attribs);
-    extract($info);
-    $disabled = [];
-    if ($disable)
-      $disabled ['disabled'] = 'disabled';
+    return $this->renderElement($name, $value, $attribs);
+  }
+
 
-    return $this->view->tag('input',
-                            null,
-                            array_merge(['type' => 'email',
-                                         'name' => $this->view->escape($name),
-                                         'id' => $this->view->escape($id),
-                                         'value' => $value],
-                                        $attribs,
-                                        $disabled));
+  public function inputType() {
+    return 'email';
   }
 }
\ No newline at end of file
diff --git a/library/ZendAfi/View/Helper/FormHTML5.php b/library/ZendAfi/View/Helper/FormHTML5.php
new file mode 100644
index 00000000000..864187dde9f
--- /dev/null
+++ b/library/ZendAfi/View/Helper/FormHTML5.php
@@ -0,0 +1,42 @@
+<?php
+/**
+ * Copyright (c) 2012, 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 ZendAfi_View_Helper_FormHTML5 extends Zend_View_Helper_FormText {
+  public abstract function inputType();
+
+
+  public function renderElement($name, $value = null, $attribs = null)   {
+    $info = $this->_getInfo($name, $value, $attribs);
+    extract($info);
+    $disabled = [];
+    if ($disable)
+      $disabled ['disabled'] = 'disabled';
+
+    return $this->view->tag('input',
+                            null,
+                            array_merge(['type' => $this->inputType(),
+                                         'name' => $this->view->escape($name),
+                                         'id' => $this->view->escape($id),
+                                         'value' => $value],
+                                        $attribs,
+                                        $disabled));
+  }
+}
\ No newline at end of file
diff --git a/library/ZendAfi/View/Helper/FormNumber.php b/library/ZendAfi/View/Helper/FormNumber.php
new file mode 100644
index 00000000000..4b276b96843
--- /dev/null
+++ b/library/ZendAfi/View/Helper/FormNumber.php
@@ -0,0 +1,31 @@
+<?php
+/**
+ * Copyright (c) 2012, 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_View_Helper_FormNumber extends ZendAfi_View_Helper_FormHTML5 {
+  public function formNumber($name, $value = null, $attribs = null)   {
+    return $this->renderElement($name, $value, $attribs);
+  }
+
+
+  public function inputType() {
+    return 'number';
+  }
+}
\ No newline at end of file
diff --git a/library/ZendAfi/View/Helper/FormUrl.php b/library/ZendAfi/View/Helper/FormUrl.php
index ce8b7fa981a..a9f7f678ff0 100644
--- a/library/ZendAfi/View/Helper/FormUrl.php
+++ b/library/ZendAfi/View/Helper/FormUrl.php
@@ -19,22 +19,13 @@
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
 
-class ZendAfi_View_Helper_FormUrl extends Zend_View_Helper_FormText {
+class ZendAfi_View_Helper_FormUrl extends ZendAfi_View_Helper_FormHTML5 {
   public function formUrl($name, $value = null, $attribs = null)   {
-    $info = $this->_getInfo($name, $value, $attribs);
-    extract($info);
+    return $this->renderElement($name, $value, $attribs);
+  }
 
-    $disabled = [];
-    if ($disable)
-      $disabled ['disabled'] = 'disabled';
 
-    return $this->view->tag('input',
-                            null,
-                            array_merge(['type' => 'url',
-                                         'name' => $this->view->escape($name),
-                                         'id' => $this->view->escape($id),
-                                         'value' => $value],
-                                        $attribs,
-                                        $disabled));
+  public function inputType() {
+    return 'url';
   }
 }
\ No newline at end of file
diff --git a/tests/application/modules/opac/controllers/RechercheControllerTest.php b/tests/application/modules/opac/controllers/RechercheControllerTest.php
index db8d93692ed..52b31dd8052 100644
--- a/tests/application/modules/opac/controllers/RechercheControllerTest.php
+++ b/tests/application/modules/opac/controllers/RechercheControllerTest.php
@@ -3128,8 +3128,7 @@ class RechercheControlleSimpleActionWithMultifacetsThesauriTest extends Recherch
                     'libelle_facette' => 'Document',
                     'id_thesaurus' => 'DOCU',
                     'id_origine' => null,
-                    'code' => 'DOCU',
-                    'rules' => '{"label":" 99$t "}']);
+                    'code' => 'DOCU']);
 
       $this->fixture('Class_CodifThesaurus',
                    ['id' => 30,
@@ -3137,32 +3136,28 @@ class RechercheControlleSimpleActionWithMultifacetsThesauriTest extends Recherch
                     'libelle_facette' => 'Musique',
                     'id_thesaurus' => 'MUSI',
                     'id_origine' => null,
-                    'code' => 'MUSI',
-                    'rules' => '{"label":" 95$t "}']);
+                    'code' => 'MUSI']);
 
     $this->fixture('Class_CodifThesaurus',
                    ['id' => 4,
                     'libelle' => 'SIFI',
                     'libelle_facette' => 'Science fiction',
                     'id_thesaurus' => 'DOCU0001',
-                    'code' => 'SIFI',
-                    'rules' => '{"label":" 99$t "}']);
+                    'code' => 'SIFI']);
 
     $this->fixture('Class_CodifThesaurus',
                    ['id' => 5,
                     'libelle' => 'BD',
                     'libelle_facette' => 'Bande dessinee',
                     'id_thesaurus' => 'DOCU0002',
-                    'code' => 'BD',
-                    'rules' => '{"label":" 99$t "}']);
+                    'code' => 'BD']);
 
     $this->fixture('Class_CodifThesaurus',
                    ['id' => 6,
                     'libelle' => 'BD',
                     'libelle_facette' => 'Manga',
                     'id_thesaurus' => 'DOCU00020001',
-                    'code' => 'Manga',
-                    'rules' => '{"label":" 90$t "}']);
+                    'code' => 'Manga']);
 
     $this->fixture('Class_CodifThesaurus',
                    ['id' => 31,
@@ -3170,8 +3165,7 @@ class RechercheControlleSimpleActionWithMultifacetsThesauriTest extends Recherch
                     'libelle_facette' => 'Experimentale',
                     'id_origine' => 30,
                     'id_thesaurus' => 'MUSI0001',
-                    'code' => 'EXPE',
-                    'rules' => '{"label":" 95$t "}']);
+                    'code' => 'EXPE']);
 
     $this->fixture('Class_Article',
                    ['id' => 1,
diff --git a/tests/library/Class/CodifThesaurusTest.php b/tests/library/Class/CodifThesaurusTest.php
index 6e9a199bafe..9cb92269d4c 100644
--- a/tests/library/Class/CodifThesaurusTest.php
+++ b/tests/library/Class/CodifThesaurusTest.php
@@ -200,7 +200,7 @@ class CodifThesaurusTooManyValuesTest extends ModelTestCase {
                     'code' => 'TEST',
                     'id' => 723,
                     'libelle_facette' => 'test',
-                    'rules' => '{"label":"610$a"}']);
+                    'rules' => null]);
 
     $this->fixture('Class_CodifThesaurus',
                    ['id_thesaurus' => 'TEST9999',
@@ -213,6 +213,7 @@ class CodifThesaurusTooManyValuesTest extends ModelTestCase {
   }
 
 
+
   /** @test */
   public function shouldNotInsertMoreThan9999Child() {
     Class_CodifThesaurus::find(723)
diff --git a/tests/scenarios/AdvancedSearch/AdvancedSearchTest.php b/tests/scenarios/AdvancedSearch/AdvancedSearchTest.php
index 80463ec061e..2f6b309de68 100644
--- a/tests/scenarios/AdvancedSearch/AdvancedSearchTest.php
+++ b/tests/scenarios/AdvancedSearch/AdvancedSearchTest.php
@@ -414,8 +414,7 @@ class AdvancedSearchForbiddenCallCustomFormSelectedTest
 
 
 
-class AdvancedSearchValidCustomFormSelectedNotPublishedTest
-  extends AdvancedSearchCustomFormSelectedTestCase {
+class AdvancedSearchFormWithDateSelectorsTest extends AdvancedSearchCustomFormSelectedTestCase {
 
   protected function _prepareFixtures() {
     parent::_prepareFixtures();
@@ -423,6 +422,61 @@ class AdvancedSearchValidCustomFormSelectedNotPublishedTest
     $form_filename = 'userfiles/forms/form.php';
     $this->fixture('Class_SearchForm', ['id' => 3, 'filename' => $form_filename]);
 
+    $this->fixture('Class_CodifThesaurus',
+                   ['id' => 1,
+                    'libelle' => 'Année de publication',
+                    'id_thesaurus' => 'APUB',
+                    'id_origine' => null,
+                    'code' => 'APUB',
+                    'rule_zone' => '995',
+                    'rule_label_field' => 'w',
+                    'rule_label_start_pos' => 1,
+                    'rule_label_length' => 4
+                   ]);
+
+
+    $this->fixture('Class_CodifThesaurus',
+                   ['id' => 2,
+                    'libelle' => 'Mois de publication',
+                    'id_thesaurus' => 'MPUB',
+                    'id_origine' => null,
+                    'code' => 'MPUB',
+                    'rule_zone' => '995',
+                    'rule_label_field' => 'w',
+                    'rule_label_start_pos' => 6,
+                    'rule_label_length' => 2
+                   ]);
+
+    $this->fixture('Class_CodifThesaurus',
+                   ['id' => 3,
+                    'libelle' => 'Jour de publication',
+                    'id_thesaurus' => 'JPUB',
+                    'id_origine' => null,
+                    'code' => 'JBUB',
+                    'rule_zone' => '995',
+                    'rule_label_field' => 'w',
+                    'rule_label_start_pos' => 9,
+                    'rule_label_length' => 2
+                   ]);
+
+
+    $this->fixture('Class_CodifThesaurus',
+                   ['id' => 34,
+                    'libelle' => '3',
+                    'id_thesaurus' => 'JPUB0003',
+                    'id_origine' => null,
+                    'code' => 'JPUB',
+                   ]);
+
+    $this->fixture('Class_CodifThesaurus',
+                   ['id' => 35,
+                    'libelle' => '1',
+                    'id_thesaurus' => 'JPUB0001',
+                    'id_origine' => null,
+                    'code' => 'JPUB',
+                   ]);
+
+
     $file_system = $this->mock()
                         ->whenCalled('directoryAt')->with($form_filename)->answers(false)
                         ->whenCalled('fileAt')->with($form_filename)
@@ -435,11 +489,7 @@ class AdvancedSearchValidCustomFormSelectedNotPublishedTest
                                   ->setParentPath('userfiles/forms')
                                   ->setWritable(true))
                         ->whenCalled('getContent')->with($form_filename)
-                        ->answers('<?php
-$form
-  ->addElement(\'text\', \'my_super_input\', [\'label\' => \'so good\'])
-  ->addUniqDisplayGroup(\'yeah\')
-  ;')
+                        ->answers('<?php ?>')
       ;
 
     Class_FileManager::setFileSystem($file_system);
@@ -447,21 +497,37 @@ $form
                                   function($path, $form) {
                                     $form
                                       ->addElement('text', 'my_super_input', ['label' => 'so good'])
-                                      ->addUniqDisplayGroup('yeah')
-                                      ;
+                                      ->addElement('selectDynamicFacet', 'annee', ['label' => 'this should not break'])
+                                      ->addElement('selectDynamicFacet', 'rech_HAPUB', ['label' => 'année de publication'])
+                                      ->addElement('selectDynamicFacet', 'rech_HMPUB', ['label' => 'mois de publication'])
+                                      ->addElement('selectDynamicFacet', 'rech_HJPUB', ['label' => 'jour de publication'])
+                                      ->addUniqDisplayGroup('yeah');
                                   });
   }
 
 
   /** @test */
   public function mySuperInputShouldBePresent() {
-    $this->assertXPath('//input[@name="my_super_input"]', $this->_response->getBody());
+    $this->assertXPath('//input[@name="my_super_input"]');
+  }
+
+
+  /** @test */
+  public function daySelectorFirstOptionShouldBeEmpty() {
+    $this->assertXPath('//select[@name="rech_HJPUB"]/option[@value=""]');
+  }
+
+
+  /** @test */
+  public function daySelectorShouldIncludeOptionForDayOne() {
+    $this->assertXPath('//select[@name="rech_HJPUB"]/option[@value="1"]');
   }
 
 
   /** @test */
-  public function notPublishedMessageShouldBePresent() {
-    $this->assertXPathContentContains('//p[@class="error"]', 'Ce formulaire n\'est pas visible.');
+  public function dayThreeShouldBePresentAfterDayOne() {
+    $this->assertXPathContentContains('//select[@name="rech_HJPUB"]/option[@value="3"][preceding-sibling::option[@value="1"]]',
+                                      '3');
   }
 }
 
@@ -717,7 +783,8 @@ class AdvancedSearchMultiFacetsPostDispatchTest extends Admin_AbstractController
                     'id_thesaurus' => 'DOCU',
                     'id_origine' => null,
                     'code' => 'DOCU',
-                    'rules' => '{"label":" 99$t "}']);
+                    'rule_zone' => '995',
+                    'rule_label_field' => 't']);
 
     $this->fixture('Class_CodifThesaurus',
                    ['id' => 4,
@@ -726,7 +793,8 @@ class AdvancedSearchMultiFacetsPostDispatchTest extends Admin_AbstractController
                     'id_thesaurus' => 'DOCU0001',
                     'id_origine' => null,
                     'code' => 'DOCU',
-                    'rules' => '{"label":" 99$t "}']);
+                    'rule_zone' => '999',
+                    'rule_label_field' => 't']);
   }
 
 
diff --git a/tests/scenarios/SearchResult/SearchResultTest.php b/tests/scenarios/SearchResult/SearchResultTest.php
index 007fe253fc6..f68ffb45827 100644
--- a/tests/scenarios/SearchResult/SearchResultTest.php
+++ b/tests/scenarios/SearchResult/SearchResultTest.php
@@ -252,7 +252,8 @@ class SearchResultWithDynamicFacetTest extends AbstractControllerTestCase {
                     'id_thesaurus' => 'DOCU',
                     'id_origine' => null,
                     'code' => 'DOCU',
-                    'rules' => '{"label":" 99$t "}']);
+                    'rule_zone' => '995',
+                    'rule_label_field' => 't']);
 
     $this->fixture('Class_CodifThesaurus',
                    ['id' => 4,
@@ -261,7 +262,8 @@ class SearchResultWithDynamicFacetTest extends AbstractControllerTestCase {
                     'id_thesaurus' => 'DOCU0001',
                     'id_origine' => null,
                     'code' => 'DOCU',
-                    'rules' => '{"label":" 99$t "}']);
+                    'rule_zone' => '995',
+                    'rule_label_field' => 't']);
 
 
   }
diff --git a/tests/scenarios/Thesauri/ThesauriTest.php b/tests/scenarios/Thesauri/ThesauriTest.php
index f3beed11ed8..3a5b4799466 100644
--- a/tests/scenarios/Thesauri/ThesauriTest.php
+++ b/tests/scenarios/Thesauri/ThesauriTest.php
@@ -87,7 +87,7 @@ class ThesauriIndexTest extends ThesauriTestCase {
 
   /** @test */
   public function rulesShouldBeDisplay() {
-    $this->assertXpathContentContains('//td', '{"Zone":"099","LabelField":"t"}');
+    $this->assertXpathContentContains('//td', '{"LabelStartPos":1,"LabelLength":0,"Zone":"099","LabelField":"t"}');
   }
 
 
@@ -134,7 +134,7 @@ class ThesauriIndexChildrenTest extends ThesauriTestCase {
 
   /** @test */
   public function parentRulesShouldBeDisplay() {
-    $this->assertXpathContentContains('//table', '{"Zone":"099","LabelField":"t"');
+    $this->assertXpathContentContains('//table', '{"LabelStartPos":1,"LabelLength":0,"Zone":"099","LabelField":"t"',$this->_response->getBody());
   }
 }
 
-- 
GitLab