diff --git a/VERSIONS_HOTLINE/101015 b/VERSIONS_HOTLINE/101015
new file mode 100644
index 0000000000000000000000000000000000000000..77db7222ae4e5b1b88d1b2a3b2a444f3d78de693
--- /dev/null
+++ b/VERSIONS_HOTLINE/101015
@@ -0,0 +1 @@
+ - ticket #101015 : Amélioration de la détection des spams sur les formulaires construits avec des articles
\ No newline at end of file
diff --git a/VERSIONS_HOTLINE/101645 b/VERSIONS_HOTLINE/101645
new file mode 100644
index 0000000000000000000000000000000000000000..8cc0849d024a002da93f56d80bcc7f63f7ce4a4f
--- /dev/null
+++ b/VERSIONS_HOTLINE/101645
@@ -0,0 +1 @@
+ticket #101645 : Compte abonné : correction des liens des articles envoyés dans les notifications de nouveaux résultats dans une recherche enregistrée 
\ No newline at end of file
diff --git a/VERSIONS_HOTLINE/101658 b/VERSIONS_HOTLINE/101658
new file mode 100644
index 0000000000000000000000000000000000000000..628dfcabb86ccc1c6a2445f695d08b6862c7bc8e
--- /dev/null
+++ b/VERSIONS_HOTLINE/101658
@@ -0,0 +1 @@
+ - ticket #101658 : Lekiosk devient Cafeyn
\ No newline at end of file
diff --git a/VERSIONS_HOTLINE/101871 b/VERSIONS_HOTLINE/101871
new file mode 100644
index 0000000000000000000000000000000000000000..d07e228051654f4c877b166f6e44374eb631d7e4
--- /dev/null
+++ b/VERSIONS_HOTLINE/101871
@@ -0,0 +1 @@
+ - ticket #101871 : les adhérents expirés recoivent un message d'erreur explicite si la réservation est impossible dans koha
\ No newline at end of file
diff --git a/VERSIONS_HOTLINE/103756 b/VERSIONS_HOTLINE/103756
new file mode 100644
index 0000000000000000000000000000000000000000..ea0d5efd0c93d4ffe4a5602f01aecef81088a8be
--- /dev/null
+++ b/VERSIONS_HOTLINE/103756
@@ -0,0 +1 @@
+ - ticket #103756 : Intégrations : Correction d'une erreur pouvant survenir lors de l'import des paniers
\ No newline at end of file
diff --git a/VERSIONS_HOTLINE/104609 b/VERSIONS_HOTLINE/104609
new file mode 100644
index 0000000000000000000000000000000000000000..a0aa7b3f3958b3880801a172adb66e65c5c252d4
--- /dev/null
+++ b/VERSIONS_HOTLINE/104609
@@ -0,0 +1 @@
+ - ticket #104609 : Explorateur de fichiers : Ajout de la possibilité de définir d'autres répertoires à afficher dans l'explorateur 
\ No newline at end of file
diff --git a/VERSIONS_HOTLINE/92524 b/VERSIONS_HOTLINE/92524
new file mode 100644
index 0000000000000000000000000000000000000000..ee32b454ad8b34bf9ac8203800e5936e85e05c1b
--- /dev/null
+++ b/VERSIONS_HOTLINE/92524
@@ -0,0 +1 @@
+ - ticket #92524 : SIGB PMB : Correction des requêtes de disponibilité et de réservation des exemplaires de périodiques
\ No newline at end of file
diff --git a/application/modules/opac/controllers/FormulaireController.php b/application/modules/opac/controllers/FormulaireController.php
index 2411f2817fa73bf430e1a78af9d9aee6c763f72d..145902b4e223f5b07b2f64bb03a4a6ef40f40bc7 100644
--- a/application/modules/opac/controllers/FormulaireController.php
+++ b/application/modules/opac/controllers/FormulaireController.php
@@ -23,11 +23,11 @@ class FormulaireController extends ZendAfi_Controller_Action {
     $article = Class_Article::find($this->_getParam('id_article'));
 
     $post = $this->_request->getPost();
-    if ($this->_getParam('website'))
+
+    if ($this->_isPostFromBot($post, $article))
       return $this->_redirect('/');
 
     unset($post['website']);
-
     $formulaire = new Class_Formulaire();
     $formulaire->setData(serialize($post))
                ->setUser(Class_Users::getIdentity())
@@ -43,6 +43,26 @@ class FormulaireController extends ZendAfi_Controller_Action {
     }
   }
 
+
+  protected function _isPostFromBot($post, $article) {
+    if ($this->_getParam('website'))
+      return true;
+
+    $quote = '[\"\']';
+    $no_quotes = '([^\"\']+)';
+    $quoted_value = $quote.$no_quotes.$quote;
+
+    preg_match_all('/ name='.$quoted_value.'/',
+                   $article->getContenu(),
+                   $all_inputs);
+
+    if (array_diff(array_keys($post), $all_inputs[1]))
+      return true;
+
+    return false;
+  }
+
+
   protected function _sendFormEmail($address, $body) {
     $mail = new ZendAfi_Mail('utf8');
     $mail->setFrom(Class_Profil::getCurrentProfil()->getMailSiteOrPortail())
diff --git a/cosmogramme/php/fonctions/objets_saisie.php b/cosmogramme/php/fonctions/objets_saisie.php
index bf55c80338cdadd95d8deb579efc9a6610374695..ec0985d0990bfbe674185d9a5bc4a091da6afb0c 100644
--- a/cosmogramme/php/fonctions/objets_saisie.php
+++ b/cosmogramme/php/fonctions/objets_saisie.php
@@ -108,6 +108,11 @@ function getBlocsParams($id_bib, $type, $valeurs) {
     if (in_array($clef, [COM_PMB, COM_VSMART, COM_MICROBIB, COM_BIBLIXNET, COM_WATERBEAR]))
       $champs_params[0] = ['url_serveur'];
 
+    if (COM_PMB == $clef)
+      $champs_params[0][] = ['exp_bulletin_is_exp_notice' => function($id, $valeur) {
+          return getOuiNon($id, $valeur);
+        }];
+
     if (in_array($clef, [COM_CARTHAME]))
       $champs_params[0] = ['url_serveur', 'key','sigb_field_name'];
 
diff --git a/library/Class/AdminVar.php b/library/Class/AdminVar.php
index 713f58331ead577cc4e19ed85ee3a6379a1857c5..2ebf0a7a9058fd2ccd9429e88e517f8c44c4dc18 100644
--- a/library/Class/AdminVar.php
+++ b/library/Class/AdminVar.php
@@ -505,7 +505,8 @@ class Class_AdminVarLoader extends Storm_Model_Loader {
                                                                                    ['value' => '90'])->bePrivate(),
 
             'IMAGICK_SAMPLING_FACTORS' => Class_AdminVar_Meta::newMultiInput($this->_('Facteur d\'échantillonnage utilisé dans le redimensionnement et la compression des images.'),
-                                                                             ['value' => '2x2;1x1;1x1'])->bePrivate()];
+                                                                             ['value' => '2x2;1x1;1x1'])->bePrivate(),
+            'FILEMANAGER_OTHER_ROOTS' => Class_AdminVar_Meta::newMultiInput($this->_('Autres répertoires à afficher, uniquement visibles pour les rôles "Super administrateur".'))->bePrivate()];
   }
 
 
diff --git a/library/Class/Cosmogramme/Integration/PhasePanier.php b/library/Class/Cosmogramme/Integration/PhasePanier.php
index 79ced34fbc04229d5c6f429edd958ea7e6a524ca..7aaf7cf74cfaa76d9e383d6756b81e7eb5857ff0 100644
--- a/library/Class/Cosmogramme/Integration/PhasePanier.php
+++ b/library/Class/Cosmogramme/Integration/PhasePanier.php
@@ -177,9 +177,12 @@ class Class_Cosmogramme_Integration_PhasePanier
 
 
   protected function _afterFileProcessed($integration) {
-    Class_PanierNotice::deleteBy(['notices' => '',
-                                  'id_int_bib' => $integration->getIdBib(),
-                                  'id not' => Class_PanierNotice::findAllIdsWithCatalogue()
-                                  ]);
+    $params = ['notices' => '',
+               'id_int_bib' => $integration->getIdBib()];
+
+    if ($linked_to_domain = Class_PanierNotice::findAllIdsWithCatalogue())
+      $params['id not'] = $linked_to_domain;
+
+    Class_PanierNotice::deleteBy($params);
   }
 }
diff --git a/library/Class/FileManager.php b/library/Class/FileManager.php
index 8fd43d870d193ec5003353ca8f3d246b8a131fcc..1ce30d4a008171f2772837493d875b353d2c739d 100644
--- a/library/Class/FileManager.php
+++ b/library/Class/FileManager.php
@@ -79,8 +79,24 @@ class Class_FileManager extends Class_Entity {
 
 
   public static function getRoots() {
-    return array_filter([static::getUserfiles(),
-                         static::getSkins()]);
+    $roots = [static::getUserfiles(),
+              static::getSkins()];
+
+    if (!Class_Users::isCurrentUserSuperAdmin())
+      return array_filter($roots);
+
+    foreach(explode(';', Class_AdminVar::get('FILEMANAGER_OTHER_ROOTS')) as $other)
+      $roots = static::_injectRootInto($other, $roots);
+
+    return array_filter($roots);
+  }
+
+
+  protected static function _injectRootInto($other, $roots) {
+    if ($other && ($directory = static::directory($other)))
+      $roots[] = $directory->setPermissionRegExp('/' . str_replace('/', '\/', $other). '\/(.*)/');
+
+    return $roots;
   }
 
 
diff --git a/library/Class/Notice/MailRenderer.php b/library/Class/Notice/MailRenderer.php
index 75c44a1bbb01fd9105eda85151249b7148225f7b..0cef7cda15d8f52c5214bc8a315382df18f2495c 100644
--- a/library/Class/Notice/MailRenderer.php
+++ b/library/Class/Notice/MailRenderer.php
@@ -76,9 +76,7 @@ class Class_Notice_MailRenderer {
 
 
   protected function _urlWithoutRouter($record) {
-    return Class_Url::absolute(['controller' => 'recherche',
-                                'action' => 'viewnotice',
-                                'id' => $record->getId()], null, true);
+    return (new Class_Notice_Permalink())->absoluteFor($record, null, true);
   }
 
 
diff --git a/library/Class/WebService/SIGB/Koha/Service.php b/library/Class/WebService/SIGB/Koha/Service.php
index 40f45ea9a98fd85a85c5f806dfbd1adde3611cf7..de6d72b5539ac38311fdcbbb44f3216a28cf101c 100644
--- a/library/Class/WebService/SIGB/Koha/Service.php
+++ b/library/Class/WebService/SIGB/Koha/Service.php
@@ -59,6 +59,7 @@ class Class_WebService_SIGB_Koha_Service extends Class_WebService_SIGB_AbstractR
                        'debarred' => $this->_('compte bloqué'),
                        'PatronDebarred' => $this->_('compte bloqué'),
                        'expired' => $this->_('compte expiré'),
+                       'PatronExpired' => $this->_('compte expiré'),
                        'alreadyReserved' => $this->_('document déjà réservé sur votre compte'),
                        'none_available' => $this->_('aucun document n\'est disponible pour la réservation'),
                        'on_reserve' => $this->_('document réservé par un autre lecteur'),
diff --git a/library/Class/WebService/SIGB/PMB.php b/library/Class/WebService/SIGB/PMB.php
index 885a74d9b9c12c323146d944ddb7e015af421f51..b86338875c7a131242e60dd5ab31c62295f238b7 100644
--- a/library/Class/WebService/SIGB/PMB.php
+++ b/library/Class/WebService/SIGB/PMB.php
@@ -21,19 +21,25 @@
 
 
 class Class_WebService_SIGB_PMB {
-  protected static $service;
+  protected static $services = [];
 
 
   public static function getService($params) {
-    if (!isset(self::$service)) {
-      $instance = new self();
-      self::$service = Class_WebService_SIGB_PMB_Service::getService($params['url_serveur']);
-    }
-    return self::$service;
+    $key = md5(serialize($params));
+    if (isset(static::$services[$key]))
+      return static::$services[$key];
+
+    $service = Class_WebService_SIGB_PMB_Service::getService($params['url_serveur']);
+
+    if (isset($params['exp_bulletin_is_exp_notice'])
+        && '1' == $params['exp_bulletin_is_exp_notice'])
+      $service->bePeriodicIdWithoutBull();
+
+    return static::$services[$key] = $service;
   }
 
 
-  public static function setService($service) {
-    self::$service = $service;
+  public static function resetServices() {
+    static::$services = [];
   }
 }
\ No newline at end of file
diff --git a/library/Class/WebService/SIGB/PMB/Service.php b/library/Class/WebService/SIGB/PMB/Service.php
index 97633ca6f5ddff1585a6955d11f37bfc0765b784..5589c87502d19bd13a1840f9ab1792be0996c637 100644
--- a/library/Class/WebService/SIGB/PMB/Service.php
+++ b/library/Class/WebService/SIGB/PMB/Service.php
@@ -30,6 +30,7 @@ class Class_Webservice_SIGB_PMB_Service extends Class_WebService_SIGB_AbstractSe
   protected
     $_json_web_client,
     $_pmb_url,
+    $_periodic_id_without_bull = false,
     $_user;
 
 
@@ -63,6 +64,12 @@ class Class_Webservice_SIGB_PMB_Service extends Class_WebService_SIGB_AbstractSe
   }
 
 
+  public function bePeriodicIdWithoutBull() {
+    $this->_periodic_id_without_bull = true;
+    return $this;
+  }
+
+
   public function providesAuthentication() {
     return true;
   }
@@ -84,20 +91,27 @@ class Class_Webservice_SIGB_PMB_Service extends Class_WebService_SIGB_AbstractSe
   public function reserverExemplaire($user, $exemplaire, $code_annexe) {
     $session_token = $this->openSessionForUser($user);
 
-    $json = $this->httpPost('pmbesOPACEmpr_add_resa', [$session_token,
-                                                       $exemplaire->getIdOrigine()]);
+    $json = $this->httpPost('pmbesOPACEmpr_add_resa',
+                            [$session_token, $this->_sanitizeRecordId($exemplaire->getIdOrigine())]);
     $result = (array) $this->_getJsonData($json, 'result');
 
     if (!$result)
       return $this->_error($this->_('Réservation impossible : le service de réservation PMB via webservice est indisponible'));
 
     if(isset($result['error']))
-      return $this->_error($this->_('Réservation impossible : Vous avez déjà réservé cette exemplaire'));
+      return $this->_error($this->_('Réservation impossible : Vous avez déjà réservé cet exemplaire'));
 
     return $this->_success();
   }
 
 
+  protected function _sanitizeRecordId($id) {
+    return $this->_periodic_id_without_bull
+      ? preg_replace('/^(.*)-bull$/', '$1', $id)
+      : $id;
+  }
+
+
   public function supprimerReservation($user, $reservation_id) {
     $session_token = $this->openSessionForUser($user);
     $json = $this->httpPost('pmbesOPACEmpr_delete_resa', [$session_token,
@@ -164,9 +178,11 @@ class Class_Webservice_SIGB_PMB_Service extends Class_WebService_SIGB_AbstractSe
   }
 
 
-  public function getExemplaire($notice_id, $code_barre){
-    $json = $this->httpPost('pmbesItems_fetch_notice_items', [$notice_id]);
+  public function getExemplaire($notice_id, $code_barre) {
+    $json = $this->httpPost('pmbesItems_fetch_notice_items',
+                            [$this->_sanitizeRecordId($notice_id)]);
     $items = $this->_getItemsFromJson($json);
+
     return $this->_getItemByBarCode($items, $code_barre);
   }
 
diff --git a/library/digital_resources/Lekiosk/Config.php b/library/digital_resources/Lekiosk/Config.php
index 809a071d5564da43e6f0700e11a78d6031b1d01c..fb7626f077d6e77d9d3250276a02602b102fd648 100644
--- a/library/digital_resources/Lekiosk/Config.php
+++ b/library/digital_resources/Lekiosk/Config.php
@@ -23,31 +23,31 @@ class Lekiosk_Config extends Class_DigitalResource_Config {
 
   protected function _getConfig() {
     return [
-            'DocTypeLabel' => $this->_('Magazine numérique LeKiosk'),
-            'PermissionLabel' => $this->_('Bibliothèque numérique: accéder à LeKiosk'),
-            'MenuLabel' => $this->_('Lien vers LeKiosk'),
+            'DocTypeLabel' => $this->_('Magazine numérique Cafeyn'),
+            'PermissionLabel' => $this->_('Bibliothèque numérique: accéder à Cafeyn'),
+            'MenuLabel' => $this->_('Lien vers Cafeyn'),
             'Introduction' => $this->_('Retrouvez vos magazines préférés en numérique parmi plus de 1600 titres. Un univers riche et varié, des dernières parutions aux anciens numéros.'),
             'NotAllowedMessage' => $this->_('Votre abonnement ne permet pas l\'accès à cette ressource.'),
 
-            'HelpLink' => 'http://wiki.bokeh-library-portal.org/index.php/Le_Kiosk',
+            'HelpLink' => 'http://wiki.bokeh-library-portal.org/index.php/Cafeyn',
             'Url' => 'http://www.lekiosk.com/',
-            'Icon' => 'https://bokeh-library-portal.org/userfiles/media/ressources_numeriques/lekiosk.png',
+            'Icon' => 'https://cdn1.lekiosk.com/Public/Cafeyn/images/logo/cafeyn.svg',
 
             'MailUrl' => 'http://get.lekiosk.com/pro/?utm_source=LK&utm_campaign=B2B&utm_medium=footer',
 
             'AdminVars' => [
-                            'ID' => Class_AdminVar_Meta::newDefault($this->_('Identifiant fourni par LeKiosk'))->bePrivate(),
-                            'SSO_MODE' => Class_AdminVar_Meta::newCombo($this->_('Type de SSO lekiosk.com'),
+                            'ID' => Class_AdminVar_Meta::newDefault($this->_('Identifiant fourni par Cafeyn'))->bePrivate(),
+                            'SSO_MODE' => Class_AdminVar_Meta::newCombo($this->_('Type de SSO cafeyn.co'),
                                                                         ['options' => ['selectOptions' => ['label' => $this->_('Mode d\'authentification'),
                                                                                                            'multioptions' => ['' => 'Lien',
                                                                                                                               'CAS' => 'CAS']]]])->bePrivate(),
-                            'FTP_LOGIN' => Class_AdminVar_Meta::newDefault($this->_('Identifiant du compte FTP fourni par LeKiosk (déprécié)'))->bePrivate(),
-                            'FTP_PASSWORD' => Class_AdminVar_Meta::newDefault($this->_('Mot de passe du compte FTP fourni par LeKiosk (déprécié)'))->bePrivate(),
-                            'HTTP_LOGIN' => Class_AdminVar_Meta::newDefault($this->_('Identifiant du compte HTTP fourni par LeKiosk'))->bePrivate(),
-                            'HTTP_PASSWORD' => Class_AdminVar_Meta::newDefault($this->_('Mot de passe du compte HTTP fourni par LeKiosk'))->bePrivate(),
-                            'HARVEST_URL' => Class_AdminVar_Meta::newDefault($this->_('URL de moissonage de la ressource LeKiosk'))->bePrivate(),
-                            'AES_KEY' => Class_AdminVar_Meta::newDefault($this->_('Clé de cryptage/AES des mails fourni par LeKiosk'))->bePrivate(),
-                            'SHA1_KEY' => Class_AdminVar_Meta::newDefault($this->_('Clé de cryptage/SHA1 des accès fourni par LeKiosk'))->bePrivate(),
+                            'FTP_LOGIN' => Class_AdminVar_Meta::newDefault($this->_('Identifiant du compte FTP fourni par Cafeyn (déprécié)'))->bePrivate(),
+                            'FTP_PASSWORD' => Class_AdminVar_Meta::newDefault($this->_('Mot de passe du compte FTP fourni par Cafeyn (déprécié)'))->bePrivate(),
+                            'HTTP_LOGIN' => Class_AdminVar_Meta::newDefault($this->_('Identifiant du compte HTTP fourni par Cafeyn'))->bePrivate(),
+                            'HTTP_PASSWORD' => Class_AdminVar_Meta::newDefault($this->_('Mot de passe du compte HTTP fourni par Cafeyn'))->bePrivate(),
+                            'HARVEST_URL' => Class_AdminVar_Meta::newDefault($this->_('URL de moissonage de la ressource Cafeyn'))->bePrivate(),
+                            'AES_KEY' => Class_AdminVar_Meta::newDefault($this->_('Clé de cryptage/AES des mails fourni par Cafeyn'))->bePrivate(),
+                            'SHA1_KEY' => Class_AdminVar_Meta::newDefault($this->_('Clé de cryptage/SHA1 des accès fourni par Cafeyn'))->bePrivate(),
             ],
 
             'SsoAction' => true,
diff --git a/library/digital_resources/Lekiosk/View/Helper/Album.php b/library/digital_resources/Lekiosk/View/Helper/Album.php
index c551ddd3e73a8101d038ede111f701799bfec626..f538cecfef303968cb7cbf08e099b3c05a451b23 100644
--- a/library/digital_resources/Lekiosk/View/Helper/Album.php
+++ b/library/digital_resources/Lekiosk/View/Helper/Album.php
@@ -49,7 +49,7 @@ class Lekiosk_View_Helper_Album extends ZendAfi_View_Helper_TagRessourceNumeriqu
   protected function _renderAccessLink() {
     return $this->view
       ->tagAnchor($this->_config->getSsoUrl(Class_Users::getIdentity(), $this->_album),
-                  $this->_('Lire le magazine %s sur le site LeKiosk',
+                  $this->_('Lire le magazine %s sur le site Cafeyn',
                            $this->_album->getTitre()),
                   ['target' => '_blank',
                    'class' => 'bouton']);
diff --git a/library/digital_resources/Lekiosk/tests/LekioskTest.php b/library/digital_resources/Lekiosk/tests/LekioskTest.php
index e229b170e32c69524ef281a029ff3adccc7cb94b..b8faf74cad06815cc0e53dbb8ec84954848b4c02 100644
--- a/library/digital_resources/Lekiosk/tests/LekioskTest.php
+++ b/library/digital_resources/Lekiosk/tests/LekioskTest.php
@@ -351,7 +351,7 @@ class LekioskServiceHarvestTest extends LekioskServiceTestCase {
 
   /** @test */
   public function nationalSportDoctypeShouldBeNumericSerialLekiosk() {
-    $this->assertEquals('Magazine numérique LeKiosk', $this->_10_national_sport->getTypeDoc()->getLabel());
+    $this->assertEquals('Magazine numérique Cafeyn', $this->_10_national_sport->getTypeDoc()->getLabel());
   }
 
 
@@ -470,7 +470,7 @@ class LekioskAdminUserGroupControllerRessourcesNumeriquesTest extends Admin_Abst
   public function ressourceActivatedShouldBeVisible() {
     LekioskAdminVars::activate();
     $this->dispatch('admin/usergroup/edit/id/6', true);
-    $this->assertXPathContentContains('//label', 'Bibliothèque numérique: accéder à LeKiosk');
+    $this->assertXPathContentContains('//label', 'Bibliothèque numérique: accéder à Cafeyn');
   }
 
 
diff --git a/tests/application/modules/admin/controllers/FileManagerControllerTest.php b/tests/application/modules/admin/controllers/FileManagerControllerTest.php
index 2a1c8aa394261edbbf01a44a6f5b659e4c950432..242cbac5a95a309b440614c60c569d4d0711ccdb 100644
--- a/tests/application/modules/admin/controllers/FileManagerControllerTest.php
+++ b/tests/application/modules/admin/controllers/FileManagerControllerTest.php
@@ -339,6 +339,57 @@ class FileManagerControllerDispatchTest extends FileManagerControllerTestCase {
 
 
 
+class FileManagerControllerIndexWithOtherRootsTest extends FileManagerControllerTestCase {
+  public function setUp() {
+    parent::setUp();
+    Class_AdminVar::set('FILEMANAGER_OTHER_ROOTS', 'cosmogramme/fichiers;non-existing');
+    Class_FileManager::getFileSystem()
+      ->whenCalled('directoryAt')->with('cosmogramme/fichiers')
+      ->answers((new Class_FileManager)
+                ->setId('cosmogramme/fichiers')
+                ->setParentPath('cosmogramme')
+                ->setPath('cosmogramme/fichier')
+                ->setName('fichiers')
+                ->setDir(true))
+
+      ->whenCalled('directoryAt')->with('non-existing')
+      ->answers(null)
+      ;
+  }
+
+
+  /** @test */
+  public function pageShouldContainsFichiersDirectory() {
+    $this->dispatch('/admin/file-manager/index');
+    $this->assertXPathContentContains('//a[contains(@class, "directory")]', 'fichiers');
+  }
+
+
+  /** @test */
+  public function pageShouldNotContainsNonExistingDirectory() {
+    $this->dispatch('/admin/file-manager/index');
+    $this->assertNotXPathContentContains('//a[contains(@class, "directory")]', 'non-existing');
+  }
+
+
+  /** @test */
+  public function withoutSuperAdminPageShouldNotContainsFichiersDirectory() {
+    $user = $this->fixture('Class_Users',
+                           ['id' => 34,
+                            'login' => 'admin',
+                            'password' => 's3cr3t \o/',
+                            'role_level' => ZendAfi_Acl_AdminControllerRoles::ADMIN_PORTAIL,
+                           ]);
+
+    ZendAfi_Auth::getInstance()->logUser($user);
+    $this->dispatch('/admin/file-manager/index');
+    $this->assertNotXPathContentContains('//a[contains(@class, "directory")]', 'fichiers');
+  }
+}
+
+
+
+
 class FileManagerControllerBrowseTest extends FileManagerControllerTestCase {
   public function setUp() {
     parent::setUp();
diff --git a/tests/application/modules/admin/controllers/NewsletterControllerTest.php b/tests/application/modules/admin/controllers/NewsletterControllerTest.php
index c2f7b5e762cf8328e7a0f7cb3e3822864ebacd78..dcb3871bd70b2b629dfeed4b0e2ab6d87b446106 100644
--- a/tests/application/modules/admin/controllers/NewsletterControllerTest.php
+++ b/tests/application/modules/admin/controllers/NewsletterControllerTest.php
@@ -955,14 +955,15 @@ class Admin_NewsletterControllerPreviewActionWithArticlesSelectionTest
 
 
   /** @test */
-  public function noticeShouldBePresent() {
+  public function martineALaPlageShouldBePresent() {
     $this->assertQueryContentContains('p', 'Martine à la plage', $this->_response->getBody());
   }
 
 
   /**  @test */
   public function noticeMartineALaPlageUrlShouldBeRechercheViewNotice42() {
-    $this->assertXPath('//a[@href="' . Class_Url::absolute('/recherche/viewnotice/id/42') . '"]');
+    $this->assertXPath('//a[contains(@href,"/recherche/viewnotice/clef//id/42")]',
+                       $this->_response->getBody());
   }
 
 
diff --git a/tests/application/modules/opac/controllers/FormulaireContactTest.php b/tests/application/modules/opac/controllers/FormulaireContactTest.php
index d5ad4a5fd82121a468b2e75c7dd1e66104238cf7..7b6be4708e2ed78609fe47f3492a41327af91d97 100644
--- a/tests/application/modules/opac/controllers/FormulaireContactTest.php
+++ b/tests/application/modules/opac/controllers/FormulaireContactTest.php
@@ -105,7 +105,7 @@ class FormulaireContactInvalidPostTest extends AbstractControllerTestCase {
 
 
 
-class FormulaireContactInvalidEmailHoneyPotPostTest extends AbstractControllerTestCase {
+class FormulaireContactInvalidDataslHoneyPotPostTest extends AbstractControllerTestCase {
   public function setUp() {
     parent::setUp();
     Zend_Mail::setDefaultTransport(new MockMailTransport());
@@ -120,7 +120,7 @@ class FormulaireContactInvalidEmailHoneyPotPostTest extends AbstractControllerTe
 
 
   /** @test */
-  public function botShouldBeRedirect() {
+  public function withWebsiteInPostShouldBeRedirect() {
     $this->postDispatch('/opac/index/formulairecontact',
                         ['website' => 'email@email.com']);
     $this->assertRedirectTo('/');
@@ -129,6 +129,7 @@ class FormulaireContactInvalidEmailHoneyPotPostTest extends AbstractControllerTe
 
 
 
+
 abstract class FormulaireContactValidPostTestCase extends AbstractControllerTestCase {
   protected
     $_mail,
diff --git a/tests/application/modules/opac/controllers/FormulaireControllerTest.php b/tests/application/modules/opac/controllers/FormulaireControllerTest.php
index b75feb28b7f122e745cc4e67b499f45bdcaa0843..9435783773967cd285fbf9908022663e64a172bc 100644
--- a/tests/application/modules/opac/controllers/FormulaireControllerTest.php
+++ b/tests/application/modules/opac/controllers/FormulaireControllerTest.php
@@ -27,19 +27,17 @@ abstract class FormulaireControllerPostActionTestCase extends AbstractController
   public function setUp() {
     parent::setUp();
 
-
-    Class_Article::newInstanceWithId(45, ['titre' => 'Contactez nous']);
-
-    Storm_Test_ObjectWrapper::onLoaderOfModel('Class_Formulaire')
-    ->whenCalled('save')
-    ->willDo(function ($formulaire) {
-        $formulaire->setId(2)->cache();
-        return true;
-      });
-
-
-    $timesource = new TimeSourceForTest('2012-10-23 12:32:00');
-    Class_Formulaire::setTimeSource($timesource);
+    Class_Article::newInstanceWithId(45, ['titre' => 'Contactez nous',
+                                          'contenu' => '<form action="/formulaire/add/id_article/45" method="POST" name="Nous écrire">'
+                                          . '<p>Votre nom<input name="nom" type="text" /></p>'
+                                          . '<p>Votre prénom<input name="prenom" type="text" /></p>'
+                                          . '</form>']);
+    $this->fixture('Class_Formulaire',
+                   ['id' => 1,
+                    'data' => serialize(['nom' => 'existing',
+                                         'prenom' => 'form'] )]);
+
+    Class_Formulaire::setTimeSource(new TimeSourceForTest('2012-10-23 12:32:00'));
   }
 }
 
@@ -207,27 +205,36 @@ class FormulaireControllerWithoutConnectedUserPostActionTest extends FormulaireC
 
 
 class FormulaireControllerPostAsBotTest extends FormulaireControllerPostActionTestCase {
-  public function setUp() {
-    parent::setUp();
-
+  /** @test */
+  public function withWebsiteInPostAnswerShouldRedirectToRoot() {
     $this->postDispatch('/formulaire/add/id_article/45',
                         ['nom' => 'Tinguette' ,
                          'prenom' => 'Quentin',
                          'website' => 'i am a bot'],
                         true);
 
-    $this->new_formulaire = Class_Formulaire::find(2);
+    $this->assertRedirectTo('/');
+    return Class_Formulaire::find(2);
   }
 
 
-  /** @test */
-  public function formulaireShouldNotBeCreated() {
-    $this->assertNull($this->new_formulaire);
+  /**
+   * @depends withWebsiteInPostAnswerShouldRedirectToRoot
+   * @test
+   */
+  public function formulaireShouldNotBeCreated($formulaire) {
+    $this->assertNull($formulaire);
   }
 
 
   /** @test */
-  public function answerShouldRedirectToRoot() {
+  public function withDataInPostNotInFormAnswerShouldRedirectToRoot() {
+    $this->postDispatch('/formulaire/add/id_article/45',
+                        ['nom' => 'Tinguette' ,
+                         'prenom' => 'Quentin',
+                         'whoareyou' => 'i am a bot'],
+                        true);
+
     $this->assertRedirectTo('/');
   }
 }
diff --git a/tests/application/modules/opac/controllers/NoticeAjaxControllerPMBTest.php b/tests/application/modules/opac/controllers/NoticeAjaxControllerPMBTest.php
index 8c79f97eb5a45675e21ae3057f4c39c97eb211f8..3de06bd92aecf5a2873574bc966b69728d2172c6 100644
--- a/tests/application/modules/opac/controllers/NoticeAjaxControllerPMBTest.php
+++ b/tests/application/modules/opac/controllers/NoticeAjaxControllerPMBTest.php
@@ -48,10 +48,12 @@ class NoticeAjaxControllerPMBRecordTest extends AbstractControllerTestCase {
     $config['exemplaires']['grouper'] = '1';
     Class_Profil::getCurrentProfil()->setCfgNotice($config);
 
-    $this->fixture('Class_IntBib',
-                   ['id' => 31,
-                    'libelle' => 'PMB library',
-                    'comm_sigb' => Class_IntBib::COM_PMB]);
+    $comm_params = ['url_serveur' => 'http://mon-pmb.net/ws/connector_out.php?source_id=2'];
+    $int_bib = $this->fixture('Class_IntBib',
+                              ['id' => 31,
+                               'libelle' => 'PMB library',
+                               'comm_sigb' => Class_IntBib::COM_PMB,
+                               'comm_params' => $comm_params]);
 
     $this->fixture('Class_Bib',
                    ['id' => 3,
@@ -78,7 +80,7 @@ class NoticeAjaxControllerPMBRecordTest extends AbstractControllerTestCase {
       ->answers('')
       ->beStrict();
 
-    $this->_service = Class_WebService_SIGB_PMB::getService(['url_serveur' => 'http://mon-pmb.net/ws/connector_out.php?source_id=2']);
+    $this->_service = Class_WebService_SIGB_PMB::getService($int_bib->getModeComm());
     $this->_service->setJsonWebClient($this->http_client);
 
     $this->dispatch('/opac/noticeajax/exemplaires/id_notice/2', true);
@@ -86,7 +88,7 @@ class NoticeAjaxControllerPMBRecordTest extends AbstractControllerTestCase {
 
 
   public function tearDown() {
-    Class_WebService_SIGB_PMB::setService(null);
+    Class_WebService_SIGB_PMB::resetServices();
     parent::tearDown();
   }
 
diff --git a/tests/application/modules/telephone/controllers/FormulaireControllerTest.php b/tests/application/modules/telephone/controllers/FormulaireControllerTest.php
index 9d6bfb1933c041b1f65c7e7e020c39e67683cd5f..5e544e8a1c07c2e51b302c56f3c68e3f4cd8d121 100644
--- a/tests/application/modules/telephone/controllers/FormulaireControllerTest.php
+++ b/tests/application/modules/telephone/controllers/FormulaireControllerTest.php
@@ -16,34 +16,33 @@
  *
  * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
  * along with BOKEH; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA 
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
 
 require_once 'TelephoneAbstractControllerTestCase.php';
 
 
 class Telephone_FormulaireControllerPostActionTestCase extends TelephoneAbstractControllerTestCase {
+  protected $_storm_default_to_volatile = true;
+
   public function setUp() {
     parent::setUp();
 
-    Class_Article::newInstanceWithId(45, ['titre' => 'Contactez nous']);
-
-    Storm_Test_ObjectWrapper::onLoaderOfModel('Class_Formulaire')
-    ->whenCalled('save')
-    ->willDo(function ($formulaire) {
-        $formulaire->setId(2)->cache();
-        return true;
-      });
+    Class_Article::newInstanceWithId(45, ['titre' => 'Contactez nous',
+                                          'contenu' => '<form action="/formulaire/add/id_article/45" method="POST" name="Nous écrire">'
+                                          . '<p>Votre nom<input name="nom" type="text" /></p>'
+                                          . '<p>Votre prénom<input name="prenom" type="text" /></p>'
+                                          . '</form>']);
 
-    $this->postDispatch('/formulaire/add/id_article/45', 
+    $this->postDispatch('/formulaire/add/id_article/45',
                         ['nom' => 'Tinguette' ,
                          'prenom' => 'Quentin' ]
                         ,true);
 
-    $this->new_formulaire = Class_Formulaire::find(2);
+    $this->new_formulaire = Class_Formulaire::find(1);
   }
 
-  
+
   /** @test */
   public function saveFormulaireShouldHaveNomTinguette() {
     $this->assertEquals('Tinguette', $this->new_formulaire->getNom());
@@ -53,7 +52,7 @@ class Telephone_FormulaireControllerPostActionTestCase extends TelephoneAbstract
   /** @test */
   public function aLIShouldContainsTinguette() {
     $this->assertXPathContentContains('//li', 'Tinguette');
-    
+
   }
 }
 
diff --git a/tests/library/Class/Cosmogramme/Integration/PhasePanierTest.php b/tests/library/Class/Cosmogramme/Integration/PhasePanierTest.php
index 049de9b90d7030c9c69572d47d96da79fa692a1d..9ce502309cdddc424e014e8a290577edd05fa77b 100644
--- a/tests/library/Class/Cosmogramme/Integration/PhasePanierTest.php
+++ b/tests/library/Class/Cosmogramme/Integration/PhasePanierTest.php
@@ -228,6 +228,34 @@ class PhasePanierKohaSameIdOrigineTest extends PhasePanierKohaTestCase {
 
 
 
+class PhasePanierFullImportWithoutLinkedToDomainTest extends PhasePanierKohaTestCase {
+  protected $_wrapper;
+
+  public function _prepareFixtures() {
+    parent::_prepareFixtures();
+    Class_PanierNotice::find(5)->delete();
+
+    $this->onLoaderOfModel('Class_PanierNotice');
+    Class_Cosmogramme_Integration::find(999)->beTotalImport()->save();
+  }
+
+
+  /** @test */
+  public function noLinkedToDomainShouldExists() {
+    $this->assertEquals([], Class_PanierNotice::findAllIdsWithCatalogue());
+  }
+
+
+  /** @test */
+  public function panierDeleteByShouldNotContainsIdParam() {
+    $this->assertNotContains('id not',
+                             array_keys(Class_PanierNotice::getFirstAttributeForLastCallOn('deleteBy')));
+  }
+}
+
+
+
+
 class PhasePanierFullImportTest extends PhasePanierKohaTestCase {
   public function _prepareFixtures() {
     parent::_prepareFixtures();
diff --git a/tests/library/Class/NewsletterMailingTest.php b/tests/library/Class/NewsletterMailingTest.php
index 1951286af315aea02e72ca4a59045489fd262655..841fd9c562b82b8b1d3ff0806bd46e1c0256fa8a 100644
--- a/tests/library/Class/NewsletterMailingTest.php
+++ b/tests/library/Class/NewsletterMailingTest.php
@@ -22,8 +22,6 @@ require_once 'ModelTestCase.php';
 
 
 abstract class NewsletterMailingTestCase extends ModelTestCase {
-  protected $_storm_default_to_volatile = true;
-
   public function setup() {
     parent::setup();
 
@@ -363,7 +361,7 @@ class NewsletterMailingConcertsPanierTextTest extends NewsletterMailingTestCase
 
   /** @test */
   public function shouldContainsURLMillenium() {
-    $this->assertBodyTextContains(Class_Url::absolute('/recherche/viewnotice/id/345'));
+    $this->assertBodyTextContains(Class_Url::absolute('/recherche/viewnotice/clef//id/345'));
   }
 
 
@@ -381,7 +379,7 @@ class NewsletterMailingConcertsPanierTextTest extends NewsletterMailingTestCase
 
   /** @test */
   public function shouldContainsURLPotter() {
-    $this->assertBodyTextContains(Class_Url::absolute("/recherche/viewnotice/id/987"));
+    $this->assertBodyTextContains(Class_Url::absolute("/recherche/viewnotice/clef//id/987"));
   }
 }
 
@@ -397,7 +395,7 @@ class NewsletterMailingConcertsPanierHtmlTest extends NewsletterMailingTestCase
 
 	/** @test */
 	public function shouldContainsLinkMillenium() {
-		$this->assertBodyHTMLContains('<a href="' . Class_Url::absolute('/recherche/viewnotice/id/345'));
+		$this->assertBodyHTMLContains('<a href="' . Class_Url::absolute('/recherche/viewnotice/clef//id/345'));
 	}
 
 
@@ -422,7 +420,7 @@ class NewsletterMailingConcertsPanierHtmlTest extends NewsletterMailingTestCase
 
 	/** @test */
 	public function shouldContainsPotterLink() {
-		$this->assertBodyHTMLContains('<a href="' . Class_Url::absolute('/recherche/viewnotice/id/987"'));
+		$this->assertBodyHTMLContains('<a href="' . Class_Url::absolute('/recherche/viewnotice/clef//id/987"'));
 	}
 
 }
@@ -630,6 +628,6 @@ class NewsletterMailingRecordAbsoluteUrlTest extends ModelTestCase {
 
 
   protected function _recordUrlOf($id) {
-    return Class_Url::absolute('/recherche/viewnotice/id/' . $id);
+    return Class_Url::absolute('/recherche/viewnotice/clef//id/' . $id);
   }
 }
diff --git a/tests/library/Class/WebService/SIGB/KohaTest.php b/tests/library/Class/WebService/SIGB/KohaTest.php
index 244216e848c33a9ee42fd2ad7a7a0974d4489fdb..251fb60af1d46399cead49f8472c028be1886769 100644
--- a/tests/library/Class/WebService/SIGB/KohaTest.php
+++ b/tests/library/Class/WebService/SIGB/KohaTest.php
@@ -1702,7 +1702,9 @@ class KohaErrorMessagesOperationTest extends KohaTestCase {
             ['tooManyReserves' , 'nombre maximum de réservations atteint'],
             ['notReservable' , 'ce document ne peut normalement pas être réservé'],
             ['debarred' , 'compte bloqué'],
+            ['PatronDebarred' , 'compte bloqué'],
             ['expired' , 'compte expiré'],
+            ['PatronExpired' , 'compte expiré'],
             ['alreadyReserved' , 'document déjà réservé sur votre compte'],
             ['none_available' , 'aucun document n\'est disponible pour la réservation']
     ];
diff --git a/tests/library/Class/WebService/SIGB/PMBTest.php b/tests/library/Class/WebService/SIGB/PMBTest.php
index 7c7f28ff07efdcd84f01370b0173b7c1bcf14599..041c69f515ea1706e9f93e7aea62db322e8c902d 100644
--- a/tests/library/Class/WebService/SIGB/PMBTest.php
+++ b/tests/library/Class/WebService/SIGB/PMBTest.php
@@ -67,20 +67,21 @@ abstract class PMBTestCase extends ModelTestCase {
       ->beStrict();
 
 
-    $this->_service = Class_WebService_SIGB_PMB::getService(['url_serveur' => 'http://cedre.proxience.net/ws/connector_out.php?source_id=2']);
+    $this->_service = Class_WebService_SIGB_PMB::getService(['url_serveur' => 'http://cedre.proxience.net/ws/connector_out.php?source_id=2',
+                                                             'exp_bulletin_is_exp_notice' => '1']);
     $this->_service->setJsonWebClient($this->_json_web_client);
   }
 
 
   public function tearDown() {
-    Class_WebService_SIGB_PMB::setService(null);
+    Class_WebService_SIGB_PMB::resetServices();
     parent::tearDown();
   }
 }
 
 
 
-class PMBServiceTest extends PMBTestCase {
+class PMBSimpleServiceTest extends PMBTestCase {
   protected
     $_paul,
     $_le_kiosque,
@@ -233,6 +234,59 @@ class PMBServiceTest extends PMBTestCase {
 
 
 
+class PMBPeriodicServiceTest extends ModelTestCase {
+  public function tearDown() {
+    Class_WebService_SIGB_PMB::resetServices();
+    parent::tearDown();
+  }
+
+
+  /** @test */
+  public function periodicItemShouldQueryWithoutDashBull() {
+    $service = Class_WebService_SIGB_PMB::getService(['url_serveur' => 'http://cedre.proxience.net/ws/connector_out.php?source_id=2',
+                                                      'exp_bulletin_is_exp_notice' => '1']);
+    $service->setJsonWebClient($client = $this->mock()->whenCalled('httpPost')->answers(''));
+    $service->getExemplaire('18390-bull', '000159');
+    $this->assertEquals(['18390'], $client->getAttributesForLastCallOn('httpPost')[1]);
+  }
+
+
+  /** @test */
+  public function periodicItemHoldShouldQueryWithoutDashBull() {
+    $service = Class_WebService_SIGB_PMB::getService(['url_serveur' => 'http://cedre.proxience.net/ws/connector_out.php?source_id=2',
+                                                      'exp_bulletin_is_exp_notice' => '1']);
+    $service->setJsonWebClient($client = $this->mock()->whenCalled('httpPost')->answers(''));
+    $service->reserverExemplaire((new Class_Entity),
+                                 (new Class_Entity)->setIdOrigine('18390-bull'),
+                                 '');
+    $this->assertEquals([null, '18390'], $client->getAttributesForLastCallOn('httpPost')[1]);
+  }
+
+
+  /** @test */
+  public function periodicItemShouldQueryWithDashBull() {
+    $service = Class_WebService_SIGB_PMB::getService(['url_serveur' => 'http://cedre.proxience.net/ws/connector_out.php?source_id=2']);
+    $service->setJsonWebClient($client = $this->mock()->whenCalled('httpPost')->answers(''));
+    $service->getExemplaire('18390-bull', '000159');
+    $this->assertEquals(['18390-bull'], $client->getAttributesForLastCallOn('httpPost')[1]);
+  }
+
+
+  /** @test */
+  public function periodicItemHoldShouldQueryWithDashBull() {
+    $service = Class_WebService_SIGB_PMB::getService(['url_serveur' => 'http://cedre.proxience.net/ws/connector_out.php?source_id=2']);
+    $service->setJsonWebClient($client = $this->mock()->whenCalled('httpPost')->answers(''));
+    $service->reserverExemplaire((new Class_Entity),
+                                 (new Class_Entity)->setIdOrigine('18390-bull'),
+                                 '');
+    $this->assertEquals([null, '18390-bull'], $client->getAttributesForLastCallOn('httpPost')[1]);
+  }
+
+}
+
+
+
+
 /** @see http://forge.afi-sa.fr/issues/102712 */
 class PMBNotPreviouslyConnectedTest extends PMBTestCase {
   protected $_patron;
diff --git a/tests/scenarios/bookmarks/SearchTest.php b/tests/scenarios/bookmarks/SearchTest.php
index 8acd88ad415a4939235a341a08fc4fd3ebdb2bcc..74ff43cc0f3acc4922fb3f60d2641ef7ca0f4417 100644
--- a/tests/scenarios/bookmarks/SearchTest.php
+++ b/tests/scenarios/bookmarks/SearchTest.php
@@ -437,6 +437,7 @@ class Bookmarks_SearchDependentsDeleteTest
 
 
 
+
 class Bookmarks_SearchCosmogrammePhaseTest
   extends Class_Cosmogramme_Integration_PhaseTestCase {
 
@@ -538,6 +539,22 @@ class Bookmarks_SearchCosmogrammePhaseTest
   }
 
 
+  protected function _assertBodyTextContains($content) {
+    $this->assertContains($content, $this->_decodedBodyText());
+  }
+
+
+  protected function _assertNotBodyTextContains($content) {
+    $this->assertNotContains($content, $this->_decodedBodyText());
+  }
+
+
+  protected function _decodedBodyText() {
+    $mail = $this->_mail_transport->getSentMails()[0];
+    return quoted_printable_decode($mail->getBodyText(true));
+  }
+
+
   /** @test */
   public function logShouldNotContainsError() {
     $this->assertNotError();
@@ -561,7 +578,7 @@ class Bookmarks_SearchCosmogrammePhaseTest
   public function withRecordDiffShouldNotifyItToUserByMail() {
     $mail = $this->_mail_transport->getSentMails()[0];
     $this->assertContains('laurel@server.io', $mail->getRecipients());
-    $this->assertContains('Droit devant', $mail->getBodyText(true));
+    $this->_assertBodyTextContains('Droit devant');
   }
 
 
@@ -575,23 +592,169 @@ class Bookmarks_SearchCosmogrammePhaseTest
 
   /** @test */
   public function withRecordDiffShouldNotNotifyMoreThan20Records() {
-    $mail = $this->_mail_transport->getSentMails()[0];
-    $this->assertNotContains('Testing 21', $mail->getBodyText(true));
+    $this->_assertNotBodyTextContains('Testing 21');
   }
 
 
   /** @test */
   public function withRecordDiffShouldContainsLinkToFullDiff() {
-    $mail = $this->_mail_transport->getSentMails()[0];
-    $this->assertContains('/recherche/simple/expressionRecherche/Harry+Potter/bookmarked_search/8/bookmarked_version/2018-02-07_105048',
-                          quoted_printable_decode($mail->getBodyText(true)));
+    $this->_assertBodyTextContains('/recherche/simple/expressionRecherche/Harry+Potter/bookmarked_search/8/bookmarked_version/2018-02-07_105048');
   }
 
 
   /** @test */
   public function withRecordDiffShouldContainsLinkToUnnotify() {
-    $mail = $this->_mail_transport->getSentMails()[0];
-    $this->assertContains('/bookmarked-searches/unnotify/id/8', $mail->getBodyText(true));
+    $this->_assertBodyTextContains('/bookmarked-searches/unnotify/id/8');
+  }
+}
+
+
+
+
+class Bookmarks_SearchMailWithArticlePhaseTest
+  extends Class_Cosmogramme_Integration_PhaseTestCase {
+
+  protected
+    $_result,
+    $_file_system,
+    $_files_content,
+    $_mail_transport,
+    $_mail;
+
+  protected function _getPreviousPhase() {
+    return (new Class_Cosmogramme_Integration_Phase(17))
+      ->beCron();
+  }
+
+
+  protected function _prepareFixtures() {
+    $criteres_potter = (new Class_CriteresRecherche)
+      ->setParams(['expressionRecherche' => 'Harry Potter',
+                   'page' => 2]);
+
+    $laurel = $this->fixture('Class_Users',
+                             ['id' => 34,
+                              'login' => 'll',
+                              'password' => 'needs_hardy',
+                              'mail' => 'laurel@server.io']);
+
+    $this->fixture('Class_User_BookmarkedSearch',
+                   ['id' => 8,
+                    'criterias' => serialize($criteres_potter),
+                    'user' => $laurel,
+                    'notified' => 1,
+                    'label' => 'mega original',
+                    'memory_cleaner' => function(){}]);
+
+    $this->fixture('Class_Article',
+                   ['id' => 883392,
+                    'titre' => 'Mon POT POTTER',
+                    'id_user' => 1,
+                    'indexation' => 1,
+                    'contenu' => 'Articles are going to be auto indexed !'
+                   ])
+         ->index();
+
+    $this->fixture('Class_Notice',
+                   ['id' => 1888,
+                    'clef_alpha' => 'DROITDEVANT--ODAEIICHIRO-15-GLENAT-2013-1',
+                    'titre_principal' => 'Droit devant']);
+
+    $result = array_map(function($record)
+                        {
+                          return [$record->getId(), ''];
+                        },
+                        Class_Notice::findAll());
+
+    Zend_Registry::set('sql',
+                       $this->mock()
+                       ->whenCalled('fetchAll')
+                       ->with("select id_notice, facettes from notices Where (MATCH(titres, auteurs, editeur, collection, matieres, dewey) AGAINST('+(HARRY HARRYS ARI) +(POTTER POTTERS POT)' IN BOOLEAN MODE)) and type=1 order by (MATCH(titres) AGAINST(' HARRY POTTER') * 1.5) + (MATCH(auteurs) AGAINST(' HARRY POTTER')) desc", true, false)
+                       ->answers($result)
+                       ->beStrict());
+
+    $this
+      ->fixture('Class_Profil',
+                ['id' => 12,
+                 'mail_site' => 'admin@bokeh-portal.fr'])
+      ->beCurrentProfil();
+  }
+
+
+  public function setUp() {
+    parent::setUp();
+    Storm_Cache::beVolatile();
+
+    $this->_mail_transport = new MockMailTransport();
+    Zend_Mail::setDefaultTransport($this->_mail_transport);
+
+    $this->_phase = $this->_buildPhase('BookmarkedSearches')
+                         ->setMemoryCleaner(function() {})
+                         ->setPrinter($this->_printer);
+
+    $persistence = Class_Versions_BookmarkedSearch::getPersistence();
+    $persistence
+      ->atPut(8, '2017-03-23_072428', [])
+      ->setTimeSource(new TimeSourceForTest('2018-02-07 10:50:48'));
+
+    $this->_result = $this->_phase->run();
+
+    $this->_files_content = $persistence->asArray();
+    $this->_mail = $this->_mail_transport->getSentMails()[0];
+  }
+
+
+  protected function _assertBodyTextContains($content) {
+    $this->assertContains($content,
+                          quoted_printable_decode($this->_mail->getBodyText(true)));
+  }
+
+
+  public function tearDown() {
+    Storm_Cache::setDefaultZendCache(null);
+    parent::tearDown();
+  }
+
+
+  /** @test */
+  public function logShouldNotContainsError() {
+    $this->assertNotError();
+  }
+
+
+  /** @test */
+  public function mailFromShouldBeAdminAtBokehPortal() {
+    $this->assertEquals('admin@bokeh-portal.fr', $this->_mail->getFrom());
+  }
+
+
+  /** @test */
+  public function mailRecipientShouldBeLaurelAtServerDotIo() {
+    $this->assertContains('laurel@server.io', $this->_mail->getRecipients());
+  }
+
+
+  /** @test */
+  public function mailShouldContainMonPotPotterTitle() {
+    $this->_assertBodyTextContains('Mon POT POTTER');
+  }
+
+
+  /** @test */
+  public function mailShouldContainsMonPotPotterUrl() {
+    $this->_assertBodyTextContains('/cms/articleview/id/883392');
+  }
+
+
+  /** @test */
+  public function mailShoultContainsDroitDevantTitle() {
+    $this->_assertBodyTextContains('Droit devant');
+  }
+
+
+  /** @test */
+  public function mailShouldContainsDroitDevantUrl() {
+    $this->_assertBodyTextContains('/recherche/viewnotice/clef/DROITDEVANT--ODAEIICHIRO-15-GLENAT-2013-1/id/1888');
   }
 }