From 721993928e4eac1464e3e5a307ac45564dadf4b2 Mon Sep 17 00:00:00 2001
From: efalcy <efalcy@afi-sa.fr>
Date: Tue, 4 Feb 2020 12:16:42 +0100
Subject: [PATCH] ccdev #104950 add age criteria on search group

---
 VERSIONS_WIP/104950                           |   1 +
 cosmogramme/sql/patch/patch_384.php           |   2 +
 .../php/classes/AbonneIntegrationTest.php     | 392 ++++--------------
 library/Class/AdminVar.php                    |   3 +-
 library/Class/Cosmogramme/Integration.php     |   6 +
 .../Integration/PhaseAuthority.php            |   6 +-
 .../Integration/PhaseDeleteItem.php           |   6 +-
 .../Cosmogramme/Integration/PhaseNotice.php   |   6 +-
 .../Integration/PhaseOnDataSource.php         |   4 +-
 .../Cosmogramme/Integration/PhasePanier.php   |   5 +-
 .../Cosmogramme/Integration/PhasePatrons.php  |  41 +-
 .../Integration/PhaseReservation.php          |   5 +-
 .../Cosmogramme/Integration/Record/Patron.php | 149 ++++---
 .../Migration/BirthdateFormatChanges.php      |  58 +++
 library/Class/User/SearchCriteria.php         |  28 +-
 .../Controller/Plugin/Manager/User.php        |   3 +
 library/ZendAfi/Form/Admin/User.php           |   4 +-
 .../controllers/UserGroupControllerTest.php   |  10 +-
 .../admin/controllers/UsersControllerTest.php |   8 +-
 tests/db/UpgradeDBTest.php                    |  69 +++
 .../Integration/PhasePatronsTest.php          | 296 ++++++++++++-
 21 files changed, 671 insertions(+), 431 deletions(-)
 create mode 100644 VERSIONS_WIP/104950
 create mode 100644 cosmogramme/sql/patch/patch_384.php
 rename cosmogramme/php/classes/classe_abonne.php => library/Class/Cosmogramme/Integration/Record/Patron.php (54%)
 create mode 100644 library/Class/Migration/BirthdateFormatChanges.php

diff --git a/VERSIONS_WIP/104950 b/VERSIONS_WIP/104950
new file mode 100644
index 00000000000..85380b05523
--- /dev/null
+++ b/VERSIONS_WIP/104950
@@ -0,0 +1 @@
+ - ticket #104950 : Groupes recherche d'utilisateurs : ajout du critère âge
\ No newline at end of file
diff --git a/cosmogramme/sql/patch/patch_384.php b/cosmogramme/sql/patch/patch_384.php
new file mode 100644
index 00000000000..c6f00a9619a
--- /dev/null
+++ b/cosmogramme/sql/patch/patch_384.php
@@ -0,0 +1,2 @@
+<?php
+(new Class_Migration_BirthdateFormatChanges())->run();
\ No newline at end of file
diff --git a/cosmogramme/tests/php/classes/AbonneIntegrationTest.php b/cosmogramme/tests/php/classes/AbonneIntegrationTest.php
index 89be961e139..28be431c49d 100644
--- a/cosmogramme/tests/php/classes/AbonneIntegrationTest.php
+++ b/cosmogramme/tests/php/classes/AbonneIntegrationTest.php
@@ -19,7 +19,6 @@
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
 
-require_once 'classe_abonne.php';
 require_once 'ModelTestCase.php';
 
 
@@ -35,10 +34,24 @@ abstract class AbonneIntegrationTestCase extends ModelTestCase {
                    ['id' => 2,
                     'id_bib' => 2]);
 
-    $this->abon_config = new abonne();
-    $this->abon_config->setIdIntBib(2);
+    $this->abon_config = new Class_Cosmogramme_Integration_Record_Patron($this->getIntegration());
   }
 
+  public function getIntegration() {
+    $this->fixture('Class_IntProfilDonnees',
+                   ['id' => 101,
+                    'libelle' => 'Nanook Abonne',
+                    'accents' => Class_IntProfilDonnees::ENCODING_UTF8,
+                    'type_fichier' => Class_IntProfilDonnees::FT_PATRONS,
+                    'format' => Class_IntProfilDonnees::FORMAT_PIPED_ASCII,
+                    'attributs' => []
+                   ]);
+
+    $integration = new Class_Cosmogramme_Integration();
+    $integration->setBib(Class_IntBib::find(2));
+    $integration->setProfilDonnees(Class_IntProfilDonnees::find(101));
+    return $integration;
+  }
 
   public function tearDown() {
     Storm_Model_Loader::defaultToDb();
@@ -56,14 +69,6 @@ abstract class AbonneIntegrationASCIITestCase extends AbonneIntegrationTestCase
     Storm_Test_ObjectWrapper::onLoaderOfModel('Class_Users')
       ->whenCalled('save')
       ->answers(true);
-
-
-    $this->abon_config->setChamps(['IDABON',
-                                   'ID_SIGB',
-                                   'NOM',
-                                   'PRENOM',
-                                   'PASSWORD',
-                                   'MAIL']);
   }
 }
 
@@ -74,41 +79,15 @@ abstract class AbonneIntegrationXMLTestCase extends AbonneIntegrationTestCase {
   public function setup(){
     parent::setup();
 
-
-    $this->abon_config->setChamps(['IDABON'=>'LECTNUM',
-                                   'ID_SIGB'=>'LECTNUM',
-                                   'NOM' => 'LASTNAME',
-                                   'PRENOM' => 'FIRSTNAME',
-                                   'PASSWORD'=> 'MOT_DE_PASSE',
-                                   'MAIL'=> 'EMAIL',
-                                   'NAISSANCE'=>'DATEBIRTH']);
   }
 }
 
 
 
 
-class AbonneIntegrationASCIIWithNoUsersInDbTest extends AbonneIntegrationASCIITestCase {
-
-  public function setup(){
-    parent::setup();
-    $champs_sigb = ['12  3 4','12','2','Tom','paul','ici','tp@afi-sa.fr'];
-    $champs_sigb_ascii = implode(chr(9), $champs_sigb);
-    $this->abon_config->importFicheAscii($champs_sigb_ascii);
-    $this->user_tom = Class_Users::getFirstAttributeForLastCallOn('save');
-
-  }
-
-
-  /** @test **/
-  public function abonneSigbTomIdShouldBe1234() {
-    $this->assertEquals('12  3 4', $this->user_tom->getIdabon());
-  }
-}
-
-
-
 class AbonneIntegrationPipedASCIITest extends AbonneIntegrationASCIITestCase {
+  protected $integration;
+
   public function setup() {
     parent::setup();
     $this->fixture('Class_CodifAnnexe',
@@ -117,209 +96,57 @@ class AbonneIntegrationPipedASCIITest extends AbonneIntegrationASCIITestCase {
                     'code' => 'CHAMWOIS',
                     'id_bib' => 42]);
 
-    $this->abon_config->setChamps(['IDABON',
-                                   'NUM_CARTE',
-                                   'NOM',
-                                   'PRENOM',
-                                   'NAISSANCE',
-                                   'DATE_DEBUT',
-                                   'DATE_FIN',
-                                   'MAIL',
-                                   'PASSWORD',
-                                   'LIBRARY_CODE']);
-
-    $this->abon_config->importFicheAscii(str_replace('|', chr(9), 'francoise.charbonnier|test|CHARBONNIER|Francoise|2013-01-04|2013-01-04|2015-04-29|sophie.meynieux@biblibre.com|test|CHAMOIS'));
-    $this->user = Class_Users::getFirstAttributeForLastCallOn('save');
-  }
-
-
-  /** @test */
-  public function idAbonShouldBeTest() {
-    $this->assertEquals("test", $this->user->getIdabon());
-  }
-
-
-  /** @test */
-  public function idIntBibShouldBe2() {
-    $this->assertEquals(2, $this->user->getIdIntBib());
-  }
 
 
-  /** @test */
-  public function idSiteShouldBe44() {
-    $this->assertEquals(42, $this->user->getIdSite());
-  }
+    $data = explode('|','francoise.charbonnier|test|CHARBONNIER|Francoise|2013-01-04|2013-01-04|2015-04-29|sophie.meynieux@biblibre.com|test|CHAMOIS');
 
+    (new Class_Cosmogramme_Integration_PhasePatrons(7,'',''))->importPatronRecord($data, $this->getIntegration());
 
-  /** @test */
-  public function unknownLibraryCodeShouldSetIdSiteTo2() {
-    $this->abon_config->importFicheAscii(str_replace('|', chr(9), 'francoise.charbonnier|test|CHARBONNIER|Francoise|2013-01-04|2013-01-04|2015-04-29|sophie.meynieux@biblibre.com|test|CHAT'));
-    $user = Class_Users::getFirstAttributeForLastCallOn('save');
-    $this->assertEquals(2, $user->getIdSite());
-  }
-}
-
-
-
-class AbonneIntegrationTabbedASCIIWithNoUsersInDbTest extends AbonneIntegrationASCIITestCase {
-  public function setup(){
-    parent::setup();
-    $this->abon_config->setChamps(['NULL',
-                                   'IDABON',
-                                   'NOM',
-                                   'PRENOM',
-                                   'NAISSANCE',
-                                   'DATE_FIN',
-                                   'NUM_CARTE',
-                                   'PASSWORD',
-                                   'NULL',
-                                   'NULL',
-                                   'NULL',
-                                   'NULL',
-                                   'NULL',
-                                   'NULL',
-                                   'NULL',
-                                   'NULL',
-                                   'NULL',
-                                   'MAIL']);
-    $this->abon_config->importFicheAscii('A	50	ANIMATION JEUNESSE			24/11/2013	50	jsecret8						5	14	10	1');
     $this->user = Class_Users::getFirstAttributeForLastCallOn('save');
-
-  }
-
-  /** @test **/
-  public function abonneSigbMailShouldNotBeNull() {
-    $this->assertNotNull($this->user->getMail());
-  }
-
-
-  /** @test **/
-  public function idAbonShouldNotBe50() {
-    $this->assertEquals(50, $this->user->getIdabon());
   }
 
+  public function getIntegration() {
+    $integration = parent::getIntegration();
+    $champs = "IDABON;NUM_CARTE;NOM;PRENOM;NAISSANCE;DATE_DEBUT;DATE_FIN;MAIL;PASSWORD;LIBRARY_CODE";
+    $integration->getProfilDonnees()->setAttributs([1 => ['champs' => $champs]] );
 
-  /** @test */
-  public function nomShouldBeAnimationJeunesse() {
-    $this->assertEquals('ANIMATION JEUNESSE', $this->user->getNom());
+    return $integration;
   }
 
 
   /** @test */
-  public function prenomShouldBeEmptyString() {
-    $this->assertSame('', $this->user->getPrenom());
-  }
-
-
-  /** @test */
-  public function naissanceShouldBeEmptyString() {
-    $this->assertSame('', $this->user->getNaissance());
-  }
-
-
-  /** @test */
-  public function dateFinShouldBe2013_11_24() {
-    $this->assertEquals('2013-11-24', $this->user->getDateFin());
-  }
-
-
-  /** @test */
-  public function loginShould50() {
-    $this->assertEquals('50', $this->user->getLogin());
+  public function idAbonShouldBeTest() {
+    $this->assertEquals("test", $this->user->getIdabon());
   }
 
 
   /** @test */
-  public function passwordShouldBejsecret8() {
-    $this->assertEquals('jsecret8', $this->user->getPassword());
-  }
-}
-
-
-
-
-class AbonneIntegrationASCIIWithRoutoInDbTest extends AbonneIntegrationTestCase {
-  protected $user_routo;
-  protected $routo_after_import;
-
-  public function setup(){
-    parent::setup();
-
-    $this->abon_config->setChamps(['IDABON',
-                                   'ID_SIGB',
-                                   'NOM',
-                                   'PRENOM',
-                                   'PASSWORD',
-                                   'MAIL']);
-
-    $this->user_routo = $this->fixture('Class_Users',
-                                       ['id' => 5,
-                                        'nom'=>'Routo',
-                                        'prenom'=>'Pierre',
-                                        'role_level' => ZendAfi_Acl_AdminControllerRoles::ABONNE_SIGB,
-                                        'idabon' => '123456',
-                                        'login'=>'5  5',
-                                        'id_site' => 2,
-                                        'id_int_bib' => 2,
-                                        'pseudo' => 'riri',
-                                        'password'=>'old']);
-
-    $champs_sigb_ascii = implode(chr(9), ['5  5',
-                                          '12',
-                                          'Routo',
-                                          'Pierre',
-                                          'la',
-                                          '"rp@afi-sa.fr "']);
-
-
-    $this->abon_config->importFicheAscii($champs_sigb_ascii);
-
-    $this->users = Class_Users::findAllBy(['login' => '5  5']);
+  public function idIntBibShouldBe2() {
+    $this->assertEquals(2, $this->user->getIdIntBib());
   }
 
 
   /** @test */
-  public function shouldNotBeSingleNanookPatron() {
-    $this->assertFalse(Class_IntBib::isSingleNanookPatrons());
+  public function idSiteShouldBe42() {
+    $this->assertEquals(42, $this->user->getIdSite());
   }
 
 
-
   /** @test */
-  public function onlyOneUserShouldHaveLogin55() {
-    $this->assertEquals(1, count($this->users));
-  }
-
-  /** @test */
-  public function mailShouldBeCleaned() {
-    $this->assertEquals('rp@afi-sa.fr',$this->users[0]->getMail());
-  }
-
-  /** @test **/
-  public function withRoutoInDbImportShouldUpdateRoutoWithIdabon55() {
-    $this->assertEquals('5  5',$this->users[0]->getIdabon());
-  }
+  public function unknownLibraryCodeShouldSetIdSiteTo2() {
+    $data = explode('|','francoise.charbonnier|test|CHARBONNIER|Francoise|2013-01-04|2013-01-04|2015-04-29|sophie.meynieux@biblibre.com|test|CHAT');
+    (new Class_Cosmogramme_Integration_PhasePatrons(7,'',''))->importPatronRecord($data, $this->getIntegration());
 
-  /** @test **/
-  public function pseudoShouldBeRiri() {
-    $this->assertEquals('riri',$this->users[0]->getPseudo());
+    $user = Class_Users::getFirstAttributeForLastCallOn('save');
+    $this->assertEquals(2, $user->getIdSite());
   }
 }
 
 
 
-
 class AbonneIntegrationASCIIWithTwoUsersSameLoginDifferentPasswordAndOrdreAbonTest extends AbonneIntegrationTestCase {
   public function setup(){
     parent::setup();
-    $this->abon_config->setChamps(['IDABON',
-                                   'ORDREABON',
-                                   'NOM',
-                                   'PRENOM',
-                                   'NAISSANCE',
-                                   'DATE_DEBUT',
-                                   'DATE_FIN',
-                                   'MAIL']);
 
     $this->fixture('Class_Users', ['id' => '4',
                                    'login' => 'jsmith',
@@ -330,18 +157,25 @@ class AbonneIntegrationASCIIWithTwoUsersSameLoginDifferentPasswordAndOrdreAbonTe
   }
 
 
-  /** @test */
-  public function userWithOrdreAbon2ShouldBeCreated() {
-    $champs_sigb_ascii = implode(chr(9), ['jsmith',
-                                          '2',
-                                          'john',
-                                          'smith',
-                                          '1977-06-25',
-                                          '2012-03-25',
-                                          '2016-03-25',
-                                          'js@afi-sa.fr']);
+  public function getIntegration() {
+    $integration = parent::getIntegration();
+    $champs = "IDABON;ORDREABON;NOM;PRENOM;NAISSANCE;DATE_DEBUT;DATE_FIN;MAIL";
+    $integration->getProfilDonnees()->setAttributs([1 => ['champs' => $champs]] );
+    $integration->setBib(Class_IntBib::find(2));
+    return $integration;
+  }
 
-    $this->abon_config->importFicheAscii($champs_sigb_ascii);
+  /** @test
+  public function userWithOrdreAbon2ShouldBeCreated() {
+    $champs_sigb_ascii = ['jsmith',
+                          '2',
+                          'john',
+                          'smith',
+                          '1977-06-25',
+                          '2012-03-25',
+                          '2016-03-25',
+                          'js@afi-sa.fr'];
+    (new Class_Cosmogramme_Integration_PhasePatrons(7,'',''))->importPatronRecord($champs_sigb_ascii, $this->getIntegration());
 
     $jsmith2 = Class_Users::findFirstBy(['login' => 'jsmith',
                                          'ordreabon' => 2]);
@@ -354,16 +188,15 @@ class AbonneIntegrationASCIIWithTwoUsersSameLoginDifferentPasswordAndOrdreAbonTe
 
   /** @test */
   public function userWithOrdreAbon1ShouldBeUpdated() {
-    $champs_sigb_ascii = implode(chr(9), ['jsmith',
-                                          '1',
-                                          'john',
-                                          'smith',
-                                          '1977-06-25',
-                                          '2012-03-25',
-                                          '2016-03-25',
-                                          'js@afi-sa.fr']);
-
-    $this->abon_config->importFicheAscii($champs_sigb_ascii);
+    $champs_sigb_ascii =  ['jsmith',
+                           '1',
+                           'john',
+                           'smith',
+                           '1977-06-25',
+                           '2012-03-25',
+                           '2016-03-25',
+                           'js@afi-sa.fr'];
+    (new Class_Cosmogramme_Integration_PhasePatrons(7,'',''))->importPatronRecord($champs_sigb_ascii, $this->getIntegration());
 
     $users = Class_Users::findAllBy(['login' => 'jsmith']);
     $this->assertEquals(1, count($users));
@@ -374,96 +207,29 @@ class AbonneIntegrationASCIIWithTwoUsersSameLoginDifferentPasswordAndOrdreAbonTe
 
 
 
+class AbonneIntegrationXMLWithNoUsersInDbTest extends AbonneIntegrationXMLTestCase {
 
-class AbonneIntegrationASCIIWithIDSigbAndLoginChangeTest extends AbonneIntegrationTestCase {
-  public function setup(){
-    parent::setup();
-
-    $this->fixture('Class_Users', ['id' => '4',
-                                   'login' => 'jsmith',
-                                   'password' => '1234',
-                                   'ordreabon' => 1,
-                                   'id_site' => 2,
-                                   'id_int_bib' => 2,
-                                   'id_sigb' => 24]);
-
-    $this->abon_config->importFicheArray(['IDABON' => 'johnny',
-                                          'ID_SIGB' => 24]);
-
-    $this->johny = Class_Users::findFirstBy(['id_sigb' => 24]);
-  }
-
-
-  /** @test */
-  public function loginShouldBeJohny() {
-    $this->assertEquals('johnny', $this->johny->getLogin());
-  }
-
-  /** @test */
-  public function onlyOneUserShouldHaveIDSigb24() {
-    $this->assertEquals(1, count(Class_Users::findAllBy(['id_sigb' => 24])));
-  }
-}
-
-
-
-class AbonneIntegrationASCIIWithIDSigbInDataAndNotInBaseTest extends AbonneIntegrationTestCase {
   public function setup(){
     parent::setup();
 
-    $this->fixture('Class_Users', ['id' => '4',
-                                   'login' => 'jsmith',
-                                   'password' => '1234',
-                                   'id_site' => 2,
-                                   'id_int_bib' => 2,
-                                   'id_sigb' => 0]);
-
-    $this->abon_config->importFicheArray(['IDABON' => 'jsmith',
-                                          'NOM' => 'johnny',
-                                          'ID_SIGB' => 24,
-                                          'MAIL' => ' "  jsmith!@nomail.org "']);
-
-    $this->johny = Class_Users::findFirstBy(['id_sigb' => 24]);
-  }
-
+    $champs_sigb_xml = '<Item><TYPE_ABONNE>I</TYPE_ABONNE><LECTNUM>00003090</LECTNUM><LASTNAME>Pirly</LASTNAME><FIRSTNAME>Coco</FIRSTNAME><FULLNAME>Pirly, Coco</FULLNAME><DATEBIRTH>12/31/1982</DATEBIRTH><MOT_DE_PASSE>zoom</MOT_DE_PASSE></Item>';
 
-  /** @test */
-  public function loginShouldBeJohny() {
-    $this->assertEquals('jsmith', $this->johny->getLogin());
-  }
+     $fields = ['IDABON'=>'LECTNUM',
+                'ID_SIGB'=>'LECTNUM',
+                'NOM' => 'LASTNAME',
+                'PRENOM' => 'FIRSTNAME',
+                'PASSWORD'=> 'MOT_DE_PASSE',
+                'MAIL'=> 'EMAIL',
+                'NAISSANCE'=>'DATEBIRTH'];
 
-  /** @test */
-  public function onlyOneUserShouldHaveIDSigb24() {
-    $this->assertEquals(1, count(Class_Users::findAllBy(['id_sigb' => 24])));
-  }
 
-  /** @test */
-  public function userWithIdFourShouldBeJohnny() {
-    $this->assertEquals('johnny', Class_Users::find(4)->getNom());
-  }
+    $this->abon_config->import($champs_sigb_xml, $fields, 1);
+    $this->user_tom = Class_Users::findFirstBy(['nom'=> 'Pirly']);
 
-  /** @test */
-  public function mailJohnnyShouldBeCleaned() {
-    $this->assertEquals('jsmith@nomail.org',$this->johny->getMail());
   }
 
-}
-
-
 
 
-class AbonneIntegrationXMLWithNoUsersInDbTest extends AbonneIntegrationXMLTestCase {
-
-  public function setup(){
-    parent::setup();
-
-    $champs_sigb_xml = '<Item><TYPE_ABONNE>I</TYPE_ABONNE><LECTNUM>00003090</LECTNUM><LASTNAME>Pirly</LASTNAME><FIRSTNAME>Coco</FIRSTNAME><FULLNAME>Pirly, Coco</FULLNAME><DATEBIRTH>12/31/1982</DATEBIRTH><MOT_DE_PASSE>zoom</MOT_DE_PASSE></Item>';
-
-    $this->abon_config->importFicheXml($champs_sigb_xml);
-    $this->user_tom = Class_Users::findFirstBy(['nom'=> 'Pirly']);
-
-  }
-
   /** @test **/
   public function abonneSigbTomIdShouldBe1234() {
     $this->assertEquals('00003090', $this->user_tom->getIdabon());
@@ -489,8 +255,16 @@ class AbonneIntegrationXMLWithRoutoInDbTest extends AbonneIntegrationXMLTestCase
 
 
     $champs_sigb_xml = '<Item><TYPE_ABONNE>I</TYPE_ABONNE><LECTNUM>0100 3080</LECTNUM><LASTNAME>poirreau</LASTNAME><FIRSTNAME>Zozio</FIRSTNAME><FULLNAME>Piou Piou</FULLNAME><DATEBIRTH>12/31/2012</DATEBIRTH><MOT_DE_PASSE>poum</MOT_DE_PASSE><EMAIL>" ##zozio@nomail.org "</EMAIL></Item>';
+     $fields = ['IDABON'=>'LECTNUM',
+                'ID_SIGB'=>'LECTNUM',
+                'NOM' => 'LASTNAME',
+                'PRENOM' => 'FIRSTNAME',
+                'PASSWORD'=> 'MOT_DE_PASSE',
+                'MAIL'=> 'EMAIL',
+                'NAISSANCE'=>'DATEBIRTH'];
+
 
-    $this->abon_config->importFicheXml($champs_sigb_xml);
+    $this->abon_config->import($champs_sigb_xml, $fields, 1);
 
     $this->zozio_after_import = Class_Users::findFirstby(['login'=>'0100 3080',
                                                           'id_site' => 2]);
diff --git a/library/Class/AdminVar.php b/library/Class/AdminVar.php
index 2ebf0a7a905..e553f043fb5 100644
--- a/library/Class/AdminVar.php
+++ b/library/Class/AdminVar.php
@@ -425,7 +425,8 @@ class Class_AdminVarLoader extends Storm_Model_Loader {
             'CUSTOM_FIELDS_REPORT' => Class_AdminVar_Meta::newOnOff($this->_('Activation des rapports statistiques sur les champs personnalisés')),
             'WEBSERVICE_TEST' => Class_AdminVar_Meta::newOnOff($this->_('Activation des tests de webservices')),
             'IMPORT_AVIS_OPAC2' => Class_AdminVar_Meta::newOnOff($this->_('Activation de l\'import des avis de l\'opac2')),
-            'AGENDA_KEEP_LOCAL_CONTENT' => Class_AdminVar_Meta::newOnOff($this->_('Agenda externe : conserver les évenements modifiés localement'))
+            'AGENDA_KEEP_LOCAL_CONTENT' => Class_AdminVar_Meta::newOnOff($this->_('Agenda externe : conserver les évenements modifiés localement')),
+            'ENABLED_SEARCH_USER_AGE' => Class_AdminVar_Meta::newOnOff($this->_('Recherche utilisateur : ajouter le filtre d\'age')),
 ];
   }
 
diff --git a/library/Class/Cosmogramme/Integration.php b/library/Class/Cosmogramme/Integration.php
index 65bf6d6cd48..8ff28e0cf5f 100644
--- a/library/Class/Cosmogramme/Integration.php
+++ b/library/Class/Cosmogramme/Integration.php
@@ -88,6 +88,11 @@ class Class_Cosmogramme_Integration extends Storm_Model_Abstract {
   }
 
 
+  public function isFormatXml() {
+    return $this->getProfilDonnees()->getFormat() == Class_IntProfilDonnees::FORMAT_XML;
+  }
+
+
   public function isBibliondemand() {
     return $this->getProfilDonnees()->isBibliondemand();
   }
@@ -164,4 +169,5 @@ class Class_Cosmogramme_Integration extends Storm_Model_Abstract {
             $this->getTypeDoc(),
             $this->getNomFichier()];
   }
+
 }
\ No newline at end of file
diff --git a/library/Class/Cosmogramme/Integration/PhaseAuthority.php b/library/Class/Cosmogramme/Integration/PhaseAuthority.php
index 2a988f84f5d..8315482b0c6 100644
--- a/library/Class/Cosmogramme/Integration/PhaseAuthority.php
+++ b/library/Class/Cosmogramme/Integration/PhaseAuthority.php
@@ -86,8 +86,10 @@ class Class_Cosmogramme_Integration_PhaseAuthority
 
 
   /** return true if given profil parameters are correct for this phase */
-  protected function _validateProfil($profil) {
-    return $profil->isAuthorityRecords();
+  protected function _validateProfil($integration) {
+    return ($profil = $integration->getProfilDonnees())
+      ? $profil->isAuthorityRecords()
+      : false;
   }
 
 
diff --git a/library/Class/Cosmogramme/Integration/PhaseDeleteItem.php b/library/Class/Cosmogramme/Integration/PhaseDeleteItem.php
index 39ee386f512..6d57fa23377 100644
--- a/library/Class/Cosmogramme/Integration/PhaseDeleteItem.php
+++ b/library/Class/Cosmogramme/Integration/PhaseDeleteItem.php
@@ -59,8 +59,10 @@ class Class_Cosmogramme_Integration_PhaseDeleteItem extends Class_Cosmogramme_In
   protected function _shouldIgnoreLine($line, $integration){}
 
   /** return true if given profil parameters are correct for this phase */
-  protected function _validateProfil($profil){
-    return $profil->isBiblioRecords();
+  protected function _validateProfil($integration){
+        return ($profil = $integration->getProfilDonnees())
+      ? $profil->isBiblioRecords()
+      : false;
   }
 
   /** hooked called after the file has been fully processed */
diff --git a/library/Class/Cosmogramme/Integration/PhaseNotice.php b/library/Class/Cosmogramme/Integration/PhaseNotice.php
index 967541dae04..c1fc5cb573f 100644
--- a/library/Class/Cosmogramme/Integration/PhaseNotice.php
+++ b/library/Class/Cosmogramme/Integration/PhaseNotice.php
@@ -98,8 +98,10 @@ class Class_Cosmogramme_Integration_PhaseNotice
   protected function _shouldIgnoreLine($line, $integration) {}
 
   /** return true if given profil parameters are correct for this phase */
-  protected function _validateProfil($profil) {
-    return $profil->isBiblioRecords();
+  protected function _validateProfil($integration) {
+    return ($profil = $integration->getProfilDonnees())
+      ? $profil->isBiblioRecords()
+      : false;
   }
 
 
diff --git a/library/Class/Cosmogramme/Integration/PhaseOnDataSource.php b/library/Class/Cosmogramme/Integration/PhaseOnDataSource.php
index 79f139c8faa..4d4157f482d 100644
--- a/library/Class/Cosmogramme/Integration/PhaseOnDataSource.php
+++ b/library/Class/Cosmogramme/Integration/PhaseOnDataSource.php
@@ -45,7 +45,7 @@ abstract class Class_Cosmogramme_Integration_PhaseOnDataSource extends Class_Cos
 
 
   protected function _runOne($integration) {
-    if (!($profil = $integration->getProfilDonnees()) || !$this->_validateProfil($profil))
+    if (!$this->_validateProfil($integration))
       return;
 
     $this->_headerOf($integration);
@@ -204,7 +204,7 @@ abstract class Class_Cosmogramme_Integration_PhaseOnDataSource extends Class_Cos
   abstract protected function _shouldIgnoreLine($line, $integration);
 
   /** return true if given profil parameters are correct for this phase */
-  abstract protected function _validateProfil($profil);
+  abstract protected function _validateProfil($integration);
 
   /** hooked called after the file has been fully processed */
   abstract protected function _afterFileProcessed($integration);
diff --git a/library/Class/Cosmogramme/Integration/PhasePanier.php b/library/Class/Cosmogramme/Integration/PhasePanier.php
index 7aaf7cf74cf..a9b9358f491 100644
--- a/library/Class/Cosmogramme/Integration/PhasePanier.php
+++ b/library/Class/Cosmogramme/Integration/PhasePanier.php
@@ -29,7 +29,10 @@ class Class_Cosmogramme_Integration_PhasePanier
 
   protected function _init($new_phase) {}
 
-  protected function _validateProfil($profil) {
+  protected function _validateProfil($integration) {
+    if (!$profil = $integration->getProfilDonnees())
+      return false;
+
     if (!$profil->isBaskets())
       return false;
 
diff --git a/library/Class/Cosmogramme/Integration/PhasePatrons.php b/library/Class/Cosmogramme/Integration/PhasePatrons.php
index e5d41610233..144e878bc0c 100644
--- a/library/Class/Cosmogramme/Integration/PhasePatrons.php
+++ b/library/Class/Cosmogramme/Integration/PhasePatrons.php
@@ -19,7 +19,6 @@
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
 
-require_once('cosmogramme/php/classes/classe_abonne.php');
 
 class Class_Cosmogramme_Integration_PhasePatrons extends Class_Cosmogramme_Integration_PhaseOnDataSource {
   const MY_ID = 8;
@@ -38,25 +37,21 @@ class Class_Cosmogramme_Integration_PhasePatrons extends Class_Cosmogramme_Integ
   }
 
 
-  protected function importPatronRecord($data, $integration) {
-    $integrator = new abonne();
-    $integrator->setParamsIntegration($integration->getIdBib(),
-                                      $integration->getTypeOperation(),
-                                      $integration->getProfil());
+  public function importPatronRecord($data, $integration) {
+    $patron = new Class_Cosmogramme_Integration_Record_Patron($integration);
 
+    return $patron->import( $integration->isFormatXml()
+                           ? $data
+                           : $this->mapRecordColumns($integration, $data),
+                           $this->getFields($integration),
+                           $integration->isFormatXml()
+    );
 
-    $profil = $integration->getProfilDonnees();
-    $integrator->importFiche(($profil->getFormat() == Class_IntProfilDonnees::FORMAT_XML)
-                             ? $data
-                             : $this->mapRecordColumns($integration, $data),
-
-                             $profil->getFormat());
   }
 
 
-
   protected function mapRecordColumns($integration, $datas) {
-    $fields = $this->getFields($integration->getProfilDonnees());
+    $fields = $this->getFields($integration);
     $map = [];
     foreach($fields as $k => $name)
       $map[strtoupper($name)] = $datas[$k];
@@ -65,14 +60,16 @@ class Class_Cosmogramme_Integration_PhasePatrons extends Class_Cosmogramme_Integ
   }
 
 
-  protected function getFields($profil_donnees) {
-    $attribs = unserialize($profil_donnees->getAttributs());
-    return ($profil_donnees->getFormat() == Class_IntProfilDonnees::FORMAT_XML)
-      ? $attribs[5]['xml_champs_abonne']
-      : array_map('trim', explode(';', $attribs[1]['champs']));
+  protected function getFields($integration) {
+    $attribs = unserialize($integration->getProfilDonnees()->getAttributs());
+    return ($integration->isFormatXml()
+            ? $attribs[5]['xml_champs_abonne']
+            : array_map('trim', explode(';', $attribs[1]['champs'])));
   }
 
 
+
+
   protected function _clean($integration) {
     $this->_markUsersForDeletion($integration);
     return $this;
@@ -105,12 +102,14 @@ class Class_Cosmogramme_Integration_PhasePatrons extends Class_Cosmogramme_Integ
   }
 
 
-  protected function _validateProfil($profil) {
+  protected function _validateProfil($integration) {
+    if (!$profil = $integration->getProfilDonnees())
+      return false;
     if (!$profil->isPatrons())
       return false;
 
     $errors = [];
-    $fields = $this->getFields($profil);
+    $fields = $this->getFields($integration);
 
     if ($profil->getFormat() == Class_IntProfilDonnees::FORMAT_XML)
       $fields = array_keys(array_filter($fields));
diff --git a/library/Class/Cosmogramme/Integration/PhaseReservation.php b/library/Class/Cosmogramme/Integration/PhaseReservation.php
index e4b9a67cb7e..efe61704664 100644
--- a/library/Class/Cosmogramme/Integration/PhaseReservation.php
+++ b/library/Class/Cosmogramme/Integration/PhaseReservation.php
@@ -33,7 +33,10 @@ class Class_Cosmogramme_Integration_PhaseReservation extends Class_Cosmogramme_I
   protected function _afterFileProcessed($integration) {}
 
 
-  protected function _validateProfil($profil) {
+  protected function _validateProfil($integration) {
+    if (!$profil = $integration->getProfilDonnees())
+      return false;
+
     return $profil->isHolds();
   }
 
diff --git a/cosmogramme/php/classes/classe_abonne.php b/library/Class/Cosmogramme/Integration/Record/Patron.php
similarity index 54%
rename from cosmogramme/php/classes/classe_abonne.php
rename to library/Class/Cosmogramme/Integration/Record/Patron.php
index 45fe27f1ca2..8ee432d7b16 100644
--- a/cosmogramme/php/classes/classe_abonne.php
+++ b/library/Class/Cosmogramme/Integration/Record/Patron.php
@@ -1,6 +1,6 @@
 <?php
 /**
- * Copyright (c) 2012, Agence Française Informatique (AFI). All rights reserved.
+ * Copyright (c) 2012-2020, 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
@@ -19,40 +19,20 @@
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
 
-class abonne {
-  private $id_int_bib;
-  private $type_operation;
-  private $champs;
-  private $type_accents;
-
 
-  public function setParamsIntegration($id_int_bib,$type_operation,$id_profil) {
-    $this->id_int_bib = $id_int_bib;
-    $this->type_operation = $type_operation;
-    $profil = Class_IntProfilDonnees::find($id_profil);
-    $this->type_accents = $profil->getAccents();
-    $attribs = unserialize($profil->getAttributs());
-
-    $this->champs = ($profil->getFormat() == Class_IntProfilDonnees::FORMAT_XML)
-      ? $attribs[5]["xml_champs_abonne"]
-      : array_map('trim', explode(";", $attribs[1]["champs"]));
-  }
-
-
-  public function setChamps($champs) {
-    $this->champs = $champs;
-    return $this;
-  }
+class Class_Cosmogramme_Integration_Record_Patron {
+  private $bib;
+  private $champs;
 
 
-  public function setIdIntBib($id_int_bib) {
-    $this->id_int_bib = $id_int_bib;
-    return $this;
+  public function __construct($integration) {
+    $this->integration = $integration;
   }
 
 
-  public function importFiche($data, $format) {
-    return ($format == Class_IntProfilDonnees::FORMAT_XML)
+  public function import($data, $fields, $is_xml) {
+    $this->champs = $fields;
+    return $is_xml
       ? $this->importFicheXml($data)
       : $this->importFicheArray($data);
   }
@@ -66,19 +46,6 @@ class abonne {
   }
 
 
-  public function importFicheAscii($data)	{
-    $data = $this->changeAccents($data);
-    $data = explode(chr(9),$data);
-
-    for ($i = 0; $i < count($this->champs); $i++) {
-      $colonne = $this->champs[$i];
-      $enreg[$colonne] = $data[$i];
-    }
-
-    return $this->importFicheArray($enreg);
-  }
-
-
   public function importFicheArray($enreg) {
     $enreg = $this->prepareData($enreg);
 
@@ -89,11 +56,11 @@ class abonne {
 
     unset($enreg["NULL"]);
 
-    if(isset($enreg["NAISSANCE"]) && (strlen($enreg["NAISSANCE"]) == 10))
-      $enreg["NAISSANCE"] = rendDate($enreg["NAISSANCE"], 0);
+    if(isset($enreg["NAISSANCE"]))
+      $enreg["NAISSANCE"] = $this->formatDate($enreg["NAISSANCE"]);
 
     if(!$enreg["PASSWORD"] and $enreg["NAISSANCE"])
-      $enreg["PASSWORD"] = rendDate($enreg["NAISSANCE"],1);
+      $enreg["PASSWORD"] = $this->formatDate($enreg["NAISSANCE"],'fr');
 
     if (isset($enreg['MAIL']))
       $enreg['MAIL'] = $this->clean_email($enreg['MAIL']);
@@ -103,13 +70,55 @@ class abonne {
       unset($enreg['LIBRARY_CODE']);
 
       $annexe = Class_CodifAnnexe::findFirstBy(['id_origine' => $library_code]);
-      $enreg['ID_SITE'] = $annexe ? $annexe->getIdBib() : $this->id_int_bib;
+      $enreg['ID_SITE'] = $annexe ? $annexe->getIdBib() : $this->integration->getBib()->getId();
     }
 
     return $this->saveOrUpdateInDB($enreg);
   }
 
 
+  protected function formatDateDynix($date, $date_format) {
+    if (strlen($date) == 8
+        && $this->integration->getBib()->getCommSigb() == Class_IntBib::COM_DYNIX)
+      return ($date_format == 'fr')
+        ? substr($date,6,2).'-'.substr($date,4,2).'-'.substr($date, 0, 4)
+        : substr($date, 0, 4).'-'.substr($date,4,2).'-'.substr($date,6,2);
+    return false;
+  }
+
+  protected function formatDate($date, $date_format = 'en') {
+    if (!$date)
+      return;
+
+    if ($date_dynix=$this->formatDateDynix($date,$date_format))
+      return $date_dynix;
+
+    if (strlen($date) != 10)
+      return ;
+
+    if ($this->integration->getBib()->isNanook()
+        || $this->integration->getBib()->isKoha())
+      return $this->formatDateIso8601($date);
+
+    $date = str_replace('/', '-', $date);
+    $parts = explode('-', $date);
+    $year_first = 4 == strlen($parts[0]);
+    $an = ($year_first) ? $parts[0] : $parts[2];
+    $jour = str_pad(($year_first) ? (int)$parts[2] : (int)$parts[0],
+                    2, '0', STR_PAD_LEFT);
+    $mois = str_pad((int)$parts[1], 2, '0', STR_PAD_LEFT);
+    return ($date_format=='fr')
+      ? $jour.'-'.$mois.'-'.$an
+      : $an .'-'.$mois .'-'. $jour;
+  }
+
+
+  public function formatDateIso8601($date) {
+    return (preg_match("/\d{4}\-\d{2}-\d{2}/", $date))
+      ?  $date : null;
+  }
+
+
   public function importFicheXml($data){
     $data = $this->changeAccents($data);
 
@@ -132,9 +141,9 @@ class abonne {
 
   protected function prepareData($enreg) {
     $enreg["MAIL"] = isset($enreg["MAIL"]) ? $enreg["MAIL"] : '';
-    $enreg["DATE_DEBUT"] = isset($enreg["DATE_DEBUT"]) ? rendDate($enreg["DATE_DEBUT"], 0) : '';
-    $enreg["DATE_FIN"] = isset($enreg["DATE_FIN"]) ? rendDate($enreg["DATE_FIN"], 0) : '';
-    $enreg["ID_SITE"] = $enreg['ID_INT_BIB'] = $this->id_int_bib;
+    $enreg["DATE_DEBUT"] = isset($enreg["DATE_DEBUT"]) ? $this->formatDate($enreg["DATE_DEBUT"]) : '';
+    $enreg["DATE_FIN"] = isset($enreg["DATE_FIN"]) ? $this->formatDate($enreg["DATE_FIN"]) : '';
+    $enreg["ID_SITE"] = $enreg['ID_INT_BIB'] = $this->integration->getBib()->getId();
     $enreg["LOGIN"] = $enreg["IDABON"];
     $enreg["ROLE"] = "abonne_sigb";
     $enreg["ROLE_LEVEL"] = ZendAfi_Acl_AdminControllerRoles::ABONNE_SIGB;
@@ -168,8 +177,9 @@ class abonne {
   private function changeAccents($chaine) {
     if (!trim($chaine))
       return $chaine;
-
-    switch($this->type_accents) {
+    $profil = $this->integration->getProfilDonnees();
+    $type_accents = $profil->getAccents();
+    switch($type_accents) {
       case 2: // Windows
         return utf8_encode($chaine);
 
@@ -186,26 +196,27 @@ class abonne {
 
   private function dosDecode($char) {
     switch($char) {
-        case 0xe9: $result = 'é';	break ;
-        case 0xe8: $result = 'è';	break ;
-        case 0xeb: $result = 'ë';	break ;
-        case 0xe4: $result = 'ä';	break ;
-        case 0xe2: $result = 'â';	break ;
-        case 0xef: $result = 'ï';	break ;
-        case 0xcf: $result = 'Ï';	break ;
-        case 0xee: $result = 'î';	break ;
-        case 0xce: $result = 'ÃŽ';	break ;
-        case 0xf4: $result = 'ô';	break ;
-        case 0xf6: $result = 'ö';	break ;
-        case 0xd6: $result = 'Ö';	break ;
-        case 0xfc: $result = 'ü';	break ;
-        case 0xdc: $result = 'Ü';	break ;
-        case 0xfb: $result = 'û';	break ;
-        case 0xe7: $result = 'ç';	break ;
-        case 0xc7: $result = 'Ç';	break ;
-        default: $result = $char;	break;
+      case 0xe9: $result = 'é';	break ;
+      case 0xe8: $result = 'è';	break ;
+      case 0xeb: $result = 'ë';	break ;
+      case 0xe4: $result = 'ä';	break ;
+      case 0xe2: $result = 'â';	break ;
+      case 0xef: $result = 'ï';	break ;
+      case 0xcf: $result = 'Ï';	break ;
+      case 0xee: $result = 'î';	break ;
+      case 0xce: $result = 'ÃŽ';	break ;
+      case 0xf4: $result = 'ô';	break ;
+      case 0xf6: $result = 'ö';	break ;
+      case 0xd6: $result = 'Ö';	break ;
+      case 0xfc: $result = 'ü';	break ;
+      case 0xdc: $result = 'Ü';	break ;
+      case 0xfb: $result = 'û';	break ;
+      case 0xe7: $result = 'ç';	break ;
+      case 0xc7: $result = 'Ç';	break ;
+      default: $result = $char;	break;
     }
 
     return $result;
   }
+
 }
diff --git a/library/Class/Migration/BirthdateFormatChanges.php b/library/Class/Migration/BirthdateFormatChanges.php
new file mode 100644
index 00000000000..2f71d577c36
--- /dev/null
+++ b/library/Class/Migration/BirthdateFormatChanges.php
@@ -0,0 +1,58 @@
+<?php
+/**
+ * Copyright (c) 2012-2020, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+class Class_Migration_BirthdateFormatChanges {
+  protected $_adapter;
+
+  public function __construct() {
+    $this->_adapter = Zend_Db_Table::getDefaultAdapter();
+
+  }
+
+  public function run() {
+    $this->_runAll();
+    $this->_cleanField();
+
+  }
+
+
+  protected function _cleanField() {
+    try {
+      $this->_adapter->query("update bib_admin_users set naissance='0000-00-00'  where naissance = '';");
+      $this->_adapter->query("ALTER TABLE bib_admin_users MODIFY naissance date, add index naissance (naissance)");
+      $this->_adapter->query("update bib_admin_users set naissance=null where naissance = '0000-00-00';");
+    } catch (Exception $e) { }
+  }
+
+
+  protected function _runAll() {
+    try {
+
+      $ids = $this->_adapter->query("select group_concat(b.id_user) from bib_admin_users b where naissance !='' and ( DATE(STR_TO_DATE(b.naissance, '%Y-%m-%d')) is null or DATE(STR_TO_DATE(b.naissance, '%Y-%m-%d')) ='0000-00-00');" )->fetchAll(Zend_Db::FETCH_NUM);
+
+      if (! $ids = current(current($ids)))
+        return false;
+
+      $this->_adapter->query("update bib_admin_users set naissance='0000-00-00' where id_user in (".$ids.");");
+    } catch (Exception $e) {}
+
+  }
+
+}
\ No newline at end of file
diff --git a/library/Class/User/SearchCriteria.php b/library/Class/User/SearchCriteria.php
index 5caadb10a04..563ac75b889 100644
--- a/library/Class/User/SearchCriteria.php
+++ b/library/Class/User/SearchCriteria.php
@@ -24,19 +24,21 @@ class Class_User_SearchCriteria extends Class_SearchCriteria {
   protected $_model_class = 'Class_Users';
 
   public function __construct($params) {
-    $this->_criteria = [new Class_User_SearchCriteriaLibrary($params),
-                        new Class_User_SearchCriteria_RoleLevel($params),
-                        new Class_User_SearchCriteriaValidSubscription($params),
-                        new Class_User_SearchCriteria_EndSubscriptionDate($params),
-                        new Class_User_SearchCriteria_DateFin($params),
-                        new Class_User_SearchCriteria_InLastSigbExport($params),
-                        new Class_User_SearchCriteria_DateMaj($params),
-                        // disabled until real birth date on database
-//                        new Class_User_SearchCriteria_Age($params),
-                        new Class_User_SearchCriteria_NumberOfReviews($params),
-                        new Class_User_SearchCriteria_NumberOfBaskets($params),
-                        new Class_User_SearchCriteriaSearchFor($params),
-                        new Class_User_SearchCriteria_Order($params)];
+    $this->_criteria = array_filter([new Class_User_SearchCriteriaLibrary($params),
+                                     new Class_User_SearchCriteria_RoleLevel($params),
+                                     new Class_User_SearchCriteriaValidSubscription($params),
+                                     new Class_User_SearchCriteria_EndSubscriptionDate($params),
+                                     new Class_User_SearchCriteria_DateFin($params),
+                                     new Class_User_SearchCriteria_InLastSigbExport($params),
+                                     new Class_User_SearchCriteria_DateMaj($params),
+                                     Class_AdminVar::get('ENABLED_SEARCH_USER_AGE')
+                                     ? new Class_User_SearchCriteria_Age($params)
+                                     : null,
+                                     new Class_User_SearchCriteria_NumberOfReviews($params),
+                                     new Class_User_SearchCriteria_NumberOfBaskets($params),
+                                     new Class_User_SearchCriteriaSearchFor($params),
+                                     new Class_User_SearchCriteria_Order($params)]
+    );
   }
 }
 
diff --git a/library/ZendAfi/Controller/Plugin/Manager/User.php b/library/ZendAfi/Controller/Plugin/Manager/User.php
index 68d1e868042..71b149ac298 100644
--- a/library/ZendAfi/Controller/Plugin/Manager/User.php
+++ b/library/ZendAfi/Controller/Plugin/Manager/User.php
@@ -87,6 +87,9 @@ class ZendAfi_Controller_Plugin_Manager_User extends ZendAfi_Controller_Plugin_M
       ? Class_Date::frToIso(trim($post['date_fin']))
       : '';
 
+    if(isset($post['naissance']))
+      $post['naissance'] = Class_Date::humanDate($post['naissance'],'yyyy-MM-dd');
+
     unset($post['id_categories']);
 
     return $post;
diff --git a/library/ZendAfi/Form/Admin/User.php b/library/ZendAfi/Form/Admin/User.php
index ac20e773a9e..9023d76bf26 100644
--- a/library/ZendAfi/Form/Admin/User.php
+++ b/library/ZendAfi/Form/Admin/User.php
@@ -146,10 +146,10 @@ class ZendAfi_Form_Admin_User extends ZendAfi_Form {
                    ['label' => $this->_('Civilité'),
                     'multiOptions' => Class_Users::getCivilitiesLabels()])
 
-      ->addElement('text',
+      ->addElement('datePicker',
                    'naissance',
                    ['label' => $this->_('Date de naissance'),
-                    'size' => 50])
+                    'dateFormat' => 'YYYY-MM-dd'])
 
       ->addElement('text',
                    'telephone',
diff --git a/tests/application/modules/admin/controllers/UserGroupControllerTest.php b/tests/application/modules/admin/controllers/UserGroupControllerTest.php
index 9e6d444d454..8755c6fb28d 100644
--- a/tests/application/modules/admin/controllers/UserGroupControllerTest.php
+++ b/tests/application/modules/admin/controllers/UserGroupControllerTest.php
@@ -502,7 +502,7 @@ class Admin_UserGroupControllerEditGroupStagiairesTest extends Admin_UserGroupCo
                     'field_type' => Class_CustomField_Meta::SELECT,
                     'options_list' => 'enabled; disabled; ',
                     'model' => 'UserGroup']);
-
+    Class_AdminVar::set('ENABLED_SEARCH_USER_AGE',1);
     $this->dispatch('admin/usergroup/edit/id/3', true);
   }
 
@@ -543,6 +543,14 @@ class Admin_UserGroupControllerEditGroupStagiairesTest extends Admin_UserGroupCo
   public function customStatusShouldBePresent() {
     $this->assertXPath('//select[@name="field_5"]/option[@value="enabled"]');
   }
+
+
+  /** @test */
+  public function ageShouldBePresent() {
+    $this->assertXPath('//input[@name="search_age_debut"]', $this->_response->getBody());
+    $this->assertXPath('//input[@name="search_age_fin"]', $this->_response->getBody());
+  }
+
 }
 
 
diff --git a/tests/application/modules/admin/controllers/UsersControllerTest.php b/tests/application/modules/admin/controllers/UsersControllerTest.php
index 5707b4fc1ff..5ee4783b93a 100644
--- a/tests/application/modules/admin/controllers/UsersControllerTest.php
+++ b/tests/application/modules/admin/controllers/UsersControllerTest.php
@@ -98,7 +98,7 @@ class UsersControllerIndexTest extends UsersControllerWithMarcusTestCase {
     parent::setUp();
     $time_source = new TimeSourceForTest('2012-02-01 14:00:00');
     Class_User_SearchCriteria_Age::setTimeSource($time_source);
-
+    Class_AdminVar::set('ENABLED_SEARCH_USER_AGE',1);
 
     Zend_Registry::set('sql', $this->mock()
                        ->whenCalled('fetchAll')
@@ -147,14 +147,14 @@ class UsersControllerIndexTest extends UsersControllerWithMarcusTestCase {
          ->whenCalled('findAllBy')
          ->with(['role_level' => 2,
                  'order' => 'nom asc',
-                 'where' => '(STR_TO_DATE(date_fin, \'%Y-%m-%d\') >= CURDATE()) AND (id_user in (2233,987398)) AND (login LIKE "%francis%" OR nom LIKE "%francis%" OR prenom LIKE "%francis%" OR pseudo LIKE "%francis%" OR mail LIKE "%francis%" OR idabon LIKE "%francis%") AND (role_level <= 7)',
+                 'where' => '(STR_TO_DATE(date_fin, \'%Y-%m-%d\') >= CURDATE()) AND (naissance <=\'2002-02-01\') AND (naissance >=\'1913-02-01\') AND (id_user in (2233,987398)) AND (login LIKE "%francis%" OR nom LIKE "%francis%" OR prenom LIKE "%francis%" OR pseudo LIKE "%francis%" OR mail LIKE "%francis%" OR idabon LIKE "%francis%") AND (role_level <= 7)',
                  'limitPage' => [1, 20]])
          ->answers([$francis])
 
          ->whenCalled('countBy')
          ->with(['role_level' => 2,
                  'order' => 'nom asc',
-                 'where' => '(STR_TO_DATE(date_fin, \'%Y-%m-%d\') >= CURDATE()) AND (id_user in (2233,987398)) AND (login LIKE "%francis%" OR nom LIKE "%francis%" OR prenom LIKE "%francis%" OR pseudo LIKE "%francis%" OR mail LIKE "%francis%" OR idabon LIKE "%francis%") AND (role_level <= 7)'])
+                 'where' => '(STR_TO_DATE(date_fin, \'%Y-%m-%d\') >= CURDATE()) AND (naissance <=\'2002-02-01\') AND (naissance >=\'1913-02-01\') AND (id_user in (2233,987398)) AND (login LIKE "%francis%" OR nom LIKE "%francis%" OR prenom LIKE "%francis%" OR pseudo LIKE "%francis%" OR mail LIKE "%francis%" OR idabon LIKE "%francis%") AND (role_level <= 7)'])
          ->answers(55)
          ->beStrict();
 
@@ -494,7 +494,7 @@ class UsersControllerPostMarcusDataTest extends UsersControllerWithMarcusTestCas
                           'ville' => 'Paris',
                           'civilite' => 1,
                           'mobile' => '06 12 45 09 87',
-                          'naissance' => '1976-02-17',
+                          'naissance' => '17/02/1976',
                           'user_group_ids' => '22-25-e',
                           'date_debut' => '',
                           'date_fin' => '']);
diff --git a/tests/db/UpgradeDBTest.php b/tests/db/UpgradeDBTest.php
index b53c4b98d10..2c5d6da0a76 100644
--- a/tests/db/UpgradeDBTest.php
+++ b/tests/db/UpgradeDBTest.php
@@ -3047,3 +3047,72 @@ class UpgradeDB_383_Test extends UpgradeDBTestCase {
                         $filters);
   }
 }
+
+
+
+class UpgradeDB_384_Test extends UpgradeDBTestCase {
+  protected $_ids,
+    $_count_dates;
+
+  public function prepare() {
+    $this->silentQuery("insert into bib_admin_users (NOM,PRENOM,NAISSANCE,ID_SITE,PASSWORD) values ('testupgrade384_bidulo','drole','','1','aesurthaoes')");
+    $this->silentQuery("insert into bib_admin_users (NOM,PRENOM,NAISSANCE,ID_SITE,PASSWORD) values ('testupgrade384_bidulo2','drole','Mercredi 12 Novembre','1','aesurthaoes')");
+    $this->silentQuery("insert into bib_admin_users (NOM,PRENOM,NAISSANCE,ID_SITE,PASSWORD) values ('testupgrade384_bidulo3','drole','2004-11-23','1','aesurthaoes')");
+    $this->silentQuery("insert into bib_admin_users (NOM,PRENOM,NAISSANCE,ID_SITE,PASSWORD) values ('testupgrade384_bidulo4','drole','20041123','1','aesurthaoes')");
+
+    $this
+      ->silentQuery('alter table bib_admin_users drop key naissance')
+      ->silentQuery('update bib_admin_users set naissance="0000-00-00" where naissance is null')
+      ->silentQuery('ALTER TABLE bib_admin_users set naissance varchar(10) not null');
+
+
+    $this->_ids = current($this->query("select count(id_bib) from int_bib where comm_sigb=11")->fetch());
+  }
+
+
+
+  public function tearDown() {
+    parent::tearDown();
+    $this->silentQuery("delete from bib_admin_users where nom like 'testupgrade384_bidulo%';");
+  }
+
+
+  /** @test */
+  public function tableUserShouldHaveColumnNaissanceDatetime() {
+    $this->assertFieldType('bib_admin_users', 'naissance', 'date');
+  }
+
+
+  /** @test */
+  public function tableUserShouldBeIndexedOnNaissance() {
+    $this->assertIndex('bib_admin_users', 'naissance');
+  }
+
+
+  /** @test */
+  public function withNaissanceEmptyShouldSetNaissanceToNull() {
+    $this->assertNull(current($this->query("select naissance from bib_admin_users  where nom='testupgrade384_bidulo'")->fetch()));
+  }
+
+
+  /** @test */
+  public function withNaissanceStringShouldSetNaissanceToNull() {
+    $this->assertNull(current($this->query("select naissance from bib_admin_users  where nom='testupgrade384_bidulo2'")->fetch()));
+  }
+
+
+  /** @test */
+  public function withNaissanceDateFormatDynixShouldSetNaissanceToDate() {
+    if (!$this->_ids)
+      return true;
+
+    $this->assertEquals('2004-11-23',current($this->query("select naissance from bib_admin_users  where nom='testupgrade384_bidulo4'")->fetch()));
+  }
+
+
+  /** @test */
+  public function withNaissanceDateShouldSetNaissanceToDate() {
+    $this->assertEquals('2004-11-23',current($this->query("select naissance from bib_admin_users  where nom='testupgrade384_bidulo3'")->fetch()));
+  }
+
+}
diff --git a/tests/library/Class/Cosmogramme/Integration/PhasePatronsTest.php b/tests/library/Class/Cosmogramme/Integration/PhasePatronsTest.php
index dadd9be8399..da55afde4dd 100644
--- a/tests/library/Class/Cosmogramme/Integration/PhasePatronsTest.php
+++ b/tests/library/Class/Cosmogramme/Integration/PhasePatronsTest.php
@@ -396,4 +396,298 @@ class PhasePatronsFullImportXMLTest extends PhasePatronsTestCase {
   public function pirlyShouldBeImported() {
     $this->assertNotNull($user = Class_Users::findFirstBy(['idabon' => '00003090']));
   }
-}
\ No newline at end of file
+}
+
+
+
+
+abstract class PhasePatronsIntegrationTestCase extends ModelTestCase {
+  protected $abon_config,
+    $_storm_default_to_volatile = true;
+
+  public function setup() {
+    parent::setup();
+
+    $this->fixture('Class_IntBib',
+                   ['id' => 2,
+                    'id_bib' => 2]);
+
+    $this->abon_config = new Class_Cosmogramme_Integration_Record_Patron($this->getIntegration());
+  }
+
+  public function getIntegration() {
+    $this->fixture('Class_IntProfilDonnees',
+                   ['id' => 101,
+                    'libelle' => 'Nanook Abonne',
+                    'accents' => Class_IntProfilDonnees::ENCODING_UTF8,
+                    'type_fichier' => Class_IntProfilDonnees::FT_PATRONS,
+                    'format' => Class_IntProfilDonnees::FORMAT_PIPED_ASCII,
+                    'attributs' => []
+                   ]);
+
+    $integration = new Class_Cosmogramme_Integration();
+    $integration->setBib(Class_IntBib::find(2));
+    $integration->setProfilDonnees(Class_IntProfilDonnees::find(101));
+    return $integration;
+  }
+
+  public function tearDown() {
+    Storm_Model_Loader::defaultToDb();
+    parent::tearDown();
+  }
+}
+
+
+
+class PhasePatronsNanookTest extends PhasePatronsIntegrationTestCase {
+  protected $user_routo, $data;
+  protected $routo_after_import;
+
+  public function setup(){
+    parent::setup();
+
+    $this->user_routo = $this->fixture('Class_Users',
+                                       ['id' => 5,
+                                        'nom'=>'Routo',
+                                        'prenom'=>'Pierre',
+                                        'role_level' => ZendAfi_Acl_AdminControllerRoles::ABONNE_SIGB,
+                                        'idabon' => '123456',
+                                        'login'=>'5  5',
+                                        'id_site' => 3,
+                                        'id_int_bib' => 3,
+                                        'pseudo' => 'riri',
+                                        'password'=>'old',
+                                        'naissance' => '1960-01-01']);
+
+    $this->data = ['5  5',
+                   '12',
+                   'Routo',
+                   'Paul',
+                   'la',
+                   '"rp@afi-sa.fr "',
+                   '1980/01/21'];
+
+  }
+
+
+  public function getIntegration() {
+    $bib = $this->fixture('Class_IntBib',
+                   ['id' => 3,
+                    'id_bib' => 3,
+                    'comm_sigb' => Class_IntBib::COM_NANOOK,
+                    'sigb' => Class_IntBib::SIGB_NANOOK]);
+
+    $integration = parent::getIntegration();
+    $integration->setBib($bib);
+    $champs = "IDABON;ID_SIGB;NOM;PRENOM;PASSWORD;MAIL;NAISSANCE";
+    $integration->getProfilDonnees()->setAttributs([1 => ['champs' => $champs]] );
+
+    return $integration;
+  }
+
+
+  /** @test **/
+  public function prenomShouldBePaul() {
+    (new Class_Cosmogramme_Integration_PhasePatrons(7,'',''))->importPatronRecord($this->data, $this->getIntegration());
+    $this->user = Class_Users::find(5);
+    $this->assertEquals('Paul',$this->user->getPrenom());
+  }
+
+
+  /** @test **/
+  public function birthdateShouldNotBeSet() {
+    (new Class_Cosmogramme_Integration_PhasePatrons(7,'',''))->importPatronRecord($this->data, $this->getIntegration());
+
+    $this->user = Class_Users::find(5);
+
+    $this->assertEquals('',$this->user->getNaissance());
+  }
+
+
+  /** @test **/
+  public function birthdateShouldBeSet() {
+    $this->data[6] = '1970-12-21';
+    (new Class_Cosmogramme_Integration_PhasePatrons(7,'',''))->importPatronRecord($this->data, $this->getIntegration());
+    $this->user = Class_Users::find(5);
+    $this->assertEquals('1970-12-21',$this->user->getNaissance());
+  }
+
+
+  /** @test **/
+  public function birthdateShouldNotBeSetIfWrongValue() {
+    $this->data[6] = 'Wrong';
+    (new Class_Cosmogramme_Integration_PhasePatrons(7,'',''))->importPatronRecord($this->data, $this->getIntegration());
+    $this->user = Class_Users::find(5);
+    $this->assertEquals('',$this->user->getNaissance());
+  }
+}
+
+
+class PhasePatronsDynixTest extends PhasePatronsIntegrationTestCase {
+  protected $user_routo, $data;
+  protected $routo_after_import;
+
+  public function setup(){
+    parent::setup();
+
+    $this->user_routo = $this->fixture('Class_Users',
+                                       ['id' => 5,
+                                        'nom'=>'Routo',
+                                        'prenom'=>'Pierre',
+                                        'role_level' => ZendAfi_Acl_AdminControllerRoles::ABONNE_SIGB,
+                                        'idabon' => '123456',
+                                        'login'=>'5  5',
+                                        'id_site' => 3,
+                                        'id_int_bib' => 3,
+                                        'pseudo' => 'riri',
+                                        'password'=>'old',
+                                        'naissance' => '1960-01-01']);
+
+    $this->data = ['5  5',
+                   '12',
+                   'Routo',
+                   'Paul',
+                   'la',
+                   '"rp@afi-sa.fr "',
+                   '19800121'];
+
+  }
+
+
+  public function getIntegration() {
+    $bib = $this->fixture('Class_IntBib',
+                   ['id' => 3,
+                    'id_bib' => 3,
+                    'comm_sigb' => Class_IntBib::COM_DYNIX,
+]);
+
+    $integration = parent::getIntegration();
+    $integration->setBib($bib);
+    $champs = "IDABON;ID_SIGB;NOM;PRENOM;PASSWORD;MAIL;NAISSANCE";
+    $integration->getProfilDonnees()->setAttributs([1 => ['champs' => $champs]] );
+
+    return $integration;
+  }
+
+
+  /** @test **/
+  public function prenomShouldBePaul() {
+    (new Class_Cosmogramme_Integration_PhasePatrons(7,'',''))->importPatronRecord($this->data, $this->getIntegration());
+    $this->user = Class_Users::find(5);
+    $this->assertEquals('Paul',$this->user->getPrenom());
+  }
+
+
+  /** @test **/
+  public function birthdateShouldBeSetIfIsoFormat() {
+    $this->data[6] = '1970-12-21';
+
+    (new Class_Cosmogramme_Integration_PhasePatrons(7,'',''))->importPatronRecord($this->data, $this->getIntegration());
+
+    $this->user = Class_Users::find(5);
+
+    $this->assertEquals('1970-12-21',$this->user->getNaissance());
+  }
+
+
+  /** @test **/
+  public function birthdateShouldBeSet() {
+    (new Class_Cosmogramme_Integration_PhasePatrons(7,'',''))->importPatronRecord($this->data, $this->getIntegration());
+    $this->user = Class_Users::find(5);
+    $this->assertEquals('1980-01-21',$this->user->getNaissance());
+  }
+
+
+  /** @test **/
+  public function birthdateShouldNotBeSetIfWrongValue() {
+    $this->data[6] = 'Wrong';
+    (new Class_Cosmogramme_Integration_PhasePatrons(7,'',''))->importPatronRecord($this->data, $this->getIntegration());
+    $this->user = Class_Users::find(5);
+    $this->assertEquals('',$this->user->getNaissance());
+  }
+}
+
+
+
+class PhasePatronsKohaTest extends PhasePatronsIntegrationTestCase {
+  protected $user_routo, $data;
+  protected $routo_after_import;
+
+  public function setup(){
+    parent::setup();
+
+    $this->user_routo = $this->fixture('Class_Users',
+                                       ['id' => 5,
+                                        'nom'=>'Routo',
+                                        'prenom'=>'Pierre',
+                                        'role_level' => ZendAfi_Acl_AdminControllerRoles::ABONNE_SIGB,
+                                        'idabon' => '123456',
+                                        'login'=>'5  5',
+                                        'id_site' => 3,
+                                        'id_int_bib' => 3,
+                                        'pseudo' => 'riri',
+                                        'password'=>'old',
+                                        'naissance' => '1960-01-01']);
+
+    $this->data = ['5  5',
+                   '12',
+                   'Routo',
+                   'Paul',
+                   'la',
+                   '"rp@afi-sa.fr "',
+                   '1980/01/21'];
+
+  }
+
+
+  public function getIntegration() {
+    $bib = $this->fixture('Class_IntBib',
+                   ['id' => 3,
+                    'id_bib' => 3,
+                    'comm_sigb' => Class_IntBib::COM_KOHA,
+                    'sigb' => Class_IntBib::SIGB_KOHA]);
+
+    $integration = parent::getIntegration();
+    $integration->setBib($bib);
+    $champs = "IDABON;ID_SIGB;NOM;PRENOM;PASSWORD;MAIL;NAISSANCE";
+    $integration->getProfilDonnees()->setAttributs([1 => ['champs' => $champs]] );
+
+    return $integration;
+  }
+
+
+  /** @test **/
+  public function prenomShouldBePaul() {
+    (new Class_Cosmogramme_Integration_PhasePatrons(7,'',''))->importPatronRecord($this->data, $this->getIntegration());
+    $this->user = Class_Users::find(5);
+    $this->assertEquals('Paul',$this->user->getPrenom());
+  }
+
+
+  /** @test **/
+  public function birthdateShouldNotBeSet() {
+    (new Class_Cosmogramme_Integration_PhasePatrons(7,'',''))->importPatronRecord($this->data, $this->getIntegration());
+
+    $this->user = Class_Users::find(5);
+
+    $this->assertEquals('',$this->user->getNaissance());
+  }
+
+
+  /** @test **/
+  public function birthdateShouldBeSet() {
+    $this->data[6] = '1970-12-21';
+    (new Class_Cosmogramme_Integration_PhasePatrons(7,'',''))->importPatronRecord($this->data, $this->getIntegration());
+    $this->user = Class_Users::find(5);
+    $this->assertEquals('1970-12-21',$this->user->getNaissance());
+  }
+
+
+  /** @test **/
+  public function birthdateShouldNotBeSetIfWrongValue() {
+    $this->data[6] = 'Wrong';
+    (new Class_Cosmogramme_Integration_PhasePatrons(7,'',''))->importPatronRecord($this->data, $this->getIntegration());
+    $this->user = Class_Users::find(5);
+    $this->assertEquals('',$this->user->getNaissance());
+  }
+}
-- 
GitLab