diff --git a/.gitmodules b/.gitmodules
index 59c95bea89e947f98ba61bf7f8e3e12e039789cd..a17826c40d12815d562853c5a940aa40a339ab71 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -16,3 +16,9 @@
 [submodule "library/matomo-php-tracker"]
 	path = library/matomo-php-tracker
 	url = http://git.afi-sa.fr/afi/matomo-php-tracker.git
+[submodule "library/phpseclib"]
+	path = library/phpseclib
+	url = https://git.afi-sa.net/afi/phpseclib.git
+[submodule "library/activitystreams"]
+	path = library/activitystreams
+	url = https://git.afi-sa.net/afi/activitystreams.git
diff --git a/FEATURES/55588 b/FEATURES/55588
new file mode 100644
index 0000000000000000000000000000000000000000..87bd4b6a894244a320215022d3c830f062991360
--- /dev/null
+++ b/FEATURES/55588
@@ -0,0 +1,10 @@
+        '55588' =>
+            ['Label' => $this->_('Indexation en texte intégral des documents liés (pdf, html)'),
+             'Desc' => $this->_('Bokeh permet d\'indexer les fichiers décrits dans une zone des notices et présents dans sur le serveur d\'hébergement'),
+             'Image' => '',
+             'Video' => '',
+             'Category' => '',
+             'Right' => function($feature_description, $user) {return true;},
+             'Wiki' => 'http://wiki.bokeh-library-portal.org/index.php?title=Indexation_de_fichiers_li%C3%A9s_aux_notices',
+             'Test' => '',
+             'Date' => '2019-07-19'],
\ No newline at end of file
diff --git a/FEATURES/86039 b/FEATURES/86039
new file mode 100644
index 0000000000000000000000000000000000000000..5cc5f2a560f09fa6ee04d5ce18338d0590ae2e44
--- /dev/null
+++ b/FEATURES/86039
@@ -0,0 +1,10 @@
+        '86039' =>
+            ['Label' => $this->_('SIGB Koha : réservation calendaire de lots d\'exemplaires'),
+             'Desc' => $this->_('Vous pouvez configurer Bokeh pour utiliser un autre formulaire de réservation sur les exemplaires considérés comme des malles. Ce formulaire affichera les réservations déjà enregistrées, ainsi que la saisie de dates de début et de fin de prêt souhaitées'),
+             'Image' => '',
+             'Video' => 'https://youtu.be/Wtg53t7dsGs',
+             'Category' => $this->('Circulation'),
+             'Right' => function($feature_description, $user) {return true;},
+             'Wiki' => 'http://wiki.bokeh-library-portal.org/index.php?title=R%C3%A9servation_calendaire_de_lots',
+             'Test' => '',
+             'Date' => '2019-07-26'],
\ No newline at end of file
diff --git a/VERSIONS b/VERSIONS
index 5391188822862b096345ae3051ade934ab868219..1487f6724c2dcee78303934ad1aeddf9e53eb652 100644
--- a/VERSIONS
+++ b/VERSIONS
@@ -1,14 +1,31 @@
+10/09/2019 - v8.0.24
+
+ - ticket #96451 : Mail : correction de l'encodage des mails dont les sujets dépassent 78 caractères
+ 
+ - ticket #95613 : Redmine : accès aux pièces jointes de la forge via Bokeh
+
+
+
+
 20/08/2019 - v8.0.23
 
  - ticket #94332 : WebService : amélioration de l'affichage des sites de retraits des reservations.
+
  - ticket #95190 : Administration : Export CSV des médias
+
  - ticket #68507 : WebService Koha : Correction du format de la date de naissance dans le formulaire de pré-inscription.
+
  - ticket #93504 : Bibliothèque numérique : pouvoir ouvrir/télécharger des images avec l'extension JPG
+
  - ticket #94528 : Arte VOD : Maintenance du connecteur 
+
  - ticket #93542 : Affichage des vignettes : prise en compte des champs unimarc spécifiés dans la configuration du profil
+
  - ticket #95885 : Version Mobile : Affichage des horaires d'ouvertures dans la fiche bibliothèque.
 
 
+
+
 29/07/2019 - v8.0.22
 
  - ticket #80976 : Page notice : connecté en tant qu'administrateur, l'outil "Inspector Gadget" permet de supprimer un exemplaire
diff --git a/VERSIONS_WIP/55588 b/VERSIONS_WIP/55588
new file mode 100644
index 0000000000000000000000000000000000000000..9b1ab1393cbd92d7ebad4c9ff8b92baf5ed8b16f
--- /dev/null
+++ b/VERSIONS_WIP/55588
@@ -0,0 +1 @@
+ - ticket #55588 : Intégration : Indexation en texte intégral des documents liés aux notices (pdf, html)
\ No newline at end of file
diff --git a/VERSIONS_WIP/78968 b/VERSIONS_WIP/78968
new file mode 100644
index 0000000000000000000000000000000000000000..72118159f90ba095d4cf941304e4e89f04172836
--- /dev/null
+++ b/VERSIONS_WIP/78968
@@ -0,0 +1 @@
+ - ticket #78968 : Intégration : Ajout de la possibilité de différencier les minuscules et majuscules dans les paramétrage des sections, genres et emplacements
\ No newline at end of file
diff --git a/VERSIONS_WIP/86039 b/VERSIONS_WIP/86039
new file mode 100644
index 0000000000000000000000000000000000000000..c4d3786f598212ffc94b7362c4b3763650ba6951
--- /dev/null
+++ b/VERSIONS_WIP/86039
@@ -0,0 +1 @@
+ - ticket #86039 : SIGB Koha : ajout de la réservation calendaire de lots d'exemplaires (malles)
\ No newline at end of file
diff --git a/VERSIONS_WIP/92386 b/VERSIONS_WIP/92386
new file mode 100644
index 0000000000000000000000000000000000000000..150bfb95eb9159f16f10cd97f3061c9fd43ab358
--- /dev/null
+++ b/VERSIONS_WIP/92386
@@ -0,0 +1 @@
+ - ticket #92386 : Import périodiques PMB
\ No newline at end of file
diff --git a/VERSIONS_WIP/93553 b/VERSIONS_WIP/93553
new file mode 100644
index 0000000000000000000000000000000000000000..8da49c3bd45147c062b43fa549b27dfa873fabf1
--- /dev/null
+++ b/VERSIONS_WIP/93553
@@ -0,0 +1 @@
+ - ticket #93553 : Avis : Ajout d'une fédération des avis
\ No newline at end of file
diff --git a/VERSIONS_WIP/94469 b/VERSIONS_WIP/94469
new file mode 100644
index 0000000000000000000000000000000000000000..640968ccd86c7d59601d178289d0dc86e49f76c4
--- /dev/null
+++ b/VERSIONS_WIP/94469
@@ -0,0 +1 @@
+ - ticket #94469 : Notices autorité : Ajout d'un écran de recherche / parcours des autorités
\ No newline at end of file
diff --git a/VERSIONS_WIP/95747 b/VERSIONS_WIP/95747
new file mode 100644
index 0000000000000000000000000000000000000000..e3b8446c202012b2192459b8b448cc818627a697
--- /dev/null
+++ b/VERSIONS_WIP/95747
@@ -0,0 +1 @@
+ - ticket #95747 : Bokeh supporte l'extension PHP memcached
\ No newline at end of file
diff --git a/VERSIONS_WIP/96423 b/VERSIONS_WIP/96423
new file mode 100644
index 0000000000000000000000000000000000000000..b9705faa68e34b3d66ccf0c38483be2b811191eb
--- /dev/null
+++ b/VERSIONS_WIP/96423
@@ -0,0 +1 @@
+ - ticket #96423 : SIGB Orphée : ajout d'une option pour activer les réservations à l'exemplaire
\ No newline at end of file
diff --git a/application/modules/activitypub/controllers/ReviewController.php b/application/modules/activitypub/controllers/ReviewController.php
new file mode 100644
index 0000000000000000000000000000000000000000..eb25b048281f79b194e697800bf74bef89c38896
--- /dev/null
+++ b/application/modules/activitypub/controllers/ReviewController.php
@@ -0,0 +1,310 @@
+<?php
+/**
+ * Copyright (c) 2012-2017, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+require_once __DIR__ . '/../../../../library/activitystreams/autoload.php';
+
+use Patbator\ActivityStreams\Model\Join;
+use Patbator\ActivityStreams\Model\Leave;
+use Patbator\ActivityStreams\Model\Accept;
+use Patbator\ActivityStreams\Model\Reject;
+use Patbator\ActivityStreams\Model\CollectionPage;
+use Patbator\ActivityStreams\Stream;
+
+
+class Activitypub_ReviewController extends ZendAfi_Controller_Action {
+  use Trait_TimeSource;
+
+  public function preDispatch() {
+    parent::preDispatch();
+
+    $this->getHelper('ViewRenderer')->setNoRender();
+    $this->_log = null;//new Zend_Log(new Zend_Log_Writer_Stream(PATH_TEMP . 'activitypub_server.log'));
+  }
+
+
+  public function unavailableAction() {
+    $this->_response->setHttpResponseCode(503);
+  }
+
+
+  public function indexAction() {
+    if (Class_WebService_ActivityPub::MIME_TYPE != $this->_request->getHeader('Accept'))
+      return $this->_response->setHttpResponseCode(500);
+
+    $service = $this->_service()->asActor()
+
+                    ->inbox($this->_absoluteUrl(['module' => 'activitypub',
+                                                 'controller' => 'review',
+                                                 'action' => 'inbox']))
+
+                    ->outbox($this->_absoluteUrl(['module' => 'activitypub',
+                                                  'controller' => 'review',
+                                                  'action' => 'outbox']))
+
+                    ->publicKey($this->_absoluteUrl(['module' => 'activitypub',
+                                                     'controller' => 'review',
+                                                     'action' => 'pubkey']))
+      ;
+
+    $this->_activityResponseWith($service);
+  }
+
+
+  public function pubkeyAction() {
+    if (Class_WebService_ActivityPub::MIME_TYPE != $this->_request->getHeader('Accept'))
+      return $this->_response->setHttpResponseCode(500);
+
+    $key = (new Class_ActivityPub_PublicKey)
+      ->id($this->_absoluteUrl(['module' => 'activitypub',
+                                'controller' => 'review',
+                                'action' => 'pubkey']))
+
+      ->owner($this->_absoluteUrl(['module' => 'activitypub',
+                                   'controller' => 'review']))
+
+      ->publicKeyPem((new Class_Federation())->getPublicKey());
+
+    $this->_activityResponseWith($key);
+  }
+
+
+  public function inboxAction() {
+    if (!$this->_request->isPost()
+        || Class_WebService_ActivityPub::MIME_TYPE != $this->_request->getHeader('Content-Type'))
+      return $this->_response->setHttpResponseCode(500);
+
+    if (!$signature = $this->_request->getHeader('Signature'))
+      return $this->_response->setHttpResponseCode(400);
+
+    $rawBody = $this->_request->getRawBody();
+
+    if ((!$activity = Stream::fromJson($rawBody)->getRoot())
+        || (!$actor = $activity->actor()))
+      return $this->_response->setHttpResponseCode(400);
+
+    $service = (new Class_WebService_ActivityPub($actor->id()))
+      ->setLogger($this->_log);
+
+    if (!$service->validateRequest($this->_request))
+      return $this->_response->setHttpResponseCode(400);
+
+    if ($handler = Activitypub_ReviewController_GroupHandler::handlerFor($activity)) {
+      $response = $handler->handle()
+                          ->actor($this->_service());
+      return $this->_activityResponseWith($response);
+    }
+
+    $this->_response->setHttpResponseCode(400);
+  }
+
+
+  public function outboxAction() {
+    if (Class_WebService_ActivityPub::MIME_TYPE != $this->_request->getHeader('Accept'))
+      return $this->_response->setHttpResponseCode(500);
+
+    if ((!$auth = $this->_request->getHeader('Authorization'))
+        || (!$bearer = trim(str_replace('Bearer ', '', $auth))))
+      return $this->_response->setHttpResponseCode(403);
+
+    if ($key = $this->_getParam('key'))
+      return $this->_receiveRecordQueryFrom($key, $bearer);
+
+    if (Class_AdminVar::get('FEDERATION_COMMUNITY_SERVER') != $bearer)
+      return $this->_response->setHttpResponseCode(403);
+
+    $filters = ['abon_ou_bib' => Class_AvisNotice::TYPE_LIBRARIAN];
+    if (Class_AdminVar::isLibrarianReviewsModerated())
+      $filters['statut'] = Class_AvisNotice::STATUS_VALIDATED;
+
+    if ($from = $this->_getParam('from')) {
+      if (false === $from_date = DateTime::createFromFormat('Y-m-d', $from))
+        return $this->_response->setHttpResponseCode(400);
+
+      $filters['where'] = 'date_avis >= "' . $from_date->format('Y-m-d') . '"';
+    }
+
+    $this->_reviewPageFilteredBy($filters);
+  }
+
+
+  protected function _receiveRecordQueryFrom($key, $bearer) {
+    if (!Class_Federation_GroupMembership::findFirstBy(['actor_id' => $bearer,
+                                                        'group_name' => 'REVIEW_DISPLAY']))
+      return $this->_response->setHttpResponseCode(403);
+
+    $filters = ['clef_oeuvre' => $key, 'source_actor_id not' => null];
+
+    $this->_reviewPageFilteredBy($filters, ['key' => $key]);
+  }
+
+
+  protected function _reviewPageFilteredBy($filters, $url_params=[]) {
+    $items_by_page = 15;
+    $total = Class_AvisNotice::countBy($filters);
+    $page = $this->_getParam('page', 1);
+    $models = Class_AvisNotice::findAllBy(array_merge($filters,
+                                                      ['limitPage' => [$page, $items_by_page]]));
+    $items = array_map([$this, '_filterAttributes'], $models);
+
+    $activity = (new CollectionPage)
+      ->id($this->_absoluteUrl(array_merge(['module' => 'activitypub',
+                                            'controller' => 'review',
+                                            'action' => 'outbox',
+                                            'page' => $page],
+                                           $url_params)))
+      ->totalItems($total)
+      ->items($items);
+
+    $this->_activityResponseWith($activity);
+  }
+
+
+  protected function _filterAttributes($review) {
+    return ['id' => $review->getId(),
+            'clef_oeuvre' => $review->getClefOeuvre(),
+            'date_avis' => $review->getDateAvis(),
+            'date_mod' => $review->getDateMod(),
+            'note' => $review->getNote(),
+            'entete' => $review->getEntete(),
+            'avis' => $review->getAvis(),
+            'abon_ou_bib' => $review->getAbonOuBib(),
+            'source_author' => $review->getSourceAuthor()];
+  }
+
+
+  protected function _absoluteUrl($params) {
+    return Class_Url::absolute($params, null, true, false);
+  }
+
+
+  protected function _service() {
+    return (new Class_ActivityPub_Service())
+      ->id($this->_absoluteUrl(['module' => 'activitypub',
+                                'controller' => 'review']))
+      ->name((new Class_Federation())->getActorName());
+  }
+
+
+  protected function _activityResponseWith($activity) {
+    (new Class_WebService_ActivityPubServer($this->_absoluteUrl(['module' => 'activitypub',
+                                                                 'controller' => 'review'])))
+      ->setLogger($this->_log)
+      ->respondTo($this->_request, $this->_response, $activity);
+  }
+}
+
+
+
+
+abstract class Activitypub_ReviewController_GroupHandler {
+  protected $_group_name, $_actor_id, $_activity_id;
+
+  public static function handlerFor($activity) {
+    if (!$activity->object()
+        || (!$group_name = $activity->object()->name()))
+      return;
+
+    if ($activity instanceof Join)
+      return new Activitypub_ReviewController_JoinHandler($activity);
+
+    if ($activity instanceof Leave)
+      return new Activitypub_ReviewController_LeaveHandler($activity);
+  }
+
+
+  public function __construct($activity) {
+    $this->_group_name = $activity->object()->name();
+    $this->_actor_id = $activity->actor()->id();
+    $this->_activity_id = $activity->id();
+  }
+
+
+  protected function _findMembership() {
+    return Class_Federation_GroupMembership::findFirstBy($this->_membershipParams());
+  }
+
+
+  protected function _newMembership() {
+    return Class_Federation_GroupMembership::newInstance($this->_membershipParams());
+  }
+
+
+  protected function _membershipParams() {
+    return ['actor_id' => $this->_actor_id,
+            'group_name' => $this->_group_name];
+  }
+
+
+  protected function _reject($message) {
+    return (new Reject)->summary($message);
+  }
+
+
+  protected function _accept($member) {
+    return (new Accept)->id(Class_Url::absolute(['module' => 'activitypub',
+                                                 'controller' => 'review',
+                                                 'action' => 'group-membership',
+                                                 'id' => $member->getId()],
+                                                null, true, false));
+  }
+
+
+  public function handle() {
+    return Class_AdminVar::isFederationCommunityServer()
+      ? $this->_handle()
+      : $this->_reject('Service unavailable');
+  }
+
+
+  abstract protected function _handle();
+}
+
+
+
+
+class Activitypub_ReviewController_JoinHandler extends Activitypub_ReviewController_GroupHandler {
+  protected function _handle() {
+    if (!$member = $this->_findMembership())
+      $member = $this->_newMembership();
+
+    $response = ($member->isNew() && !$member->save())
+      ? $this->_reject(implode(', ', $member->getErrors()))
+      : $this->_accept($member);
+
+    return $response->object((new Join)->id($this->_activity_id));
+  }
+}
+
+
+
+
+class Activitypub_ReviewController_LeaveHandler extends Activitypub_ReviewController_GroupHandler {
+  protected function _handle() {
+    if ($member = $this->_findMembership())
+      $member->delete();
+
+    $response = (!$member)
+      ? $this->_reject('Cannot leave a group you did not join')
+      : $this->_accept($member);
+
+    return $response->object((new Leave)->id($this->_activity_id));
+  }
+}
\ No newline at end of file
diff --git a/application/modules/admin/controllers/FederationReviewsController.php b/application/modules/admin/controllers/FederationReviewsController.php
new file mode 100644
index 0000000000000000000000000000000000000000..524c2953410c79c4925b560a1a1b639f62a6b26f
--- /dev/null
+++ b/application/modules/admin/controllers/FederationReviewsController.php
@@ -0,0 +1,64 @@
+<?php
+/**
+ * Copyright (c) 2012-2017, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+class Admin_FederationReviewsController extends ZendAfi_Controller_Action {
+  public function indexAction() {
+    $this->view->titre = $this->_('Avis communautaires');
+  }
+
+
+  public function enableDisplayAction() {
+    $federation = Class_FederationReview::getInstance();
+    $message = $federation->enableDisplay()
+      ? $this->_('Affichage des avis communautaires activé')
+      : $this->_('Activation impossible : %s',
+                 $federation->getLastMessage());
+
+    $this->_helper->notify($message);
+    $this->_redirectToIndex();
+  }
+
+
+  public function disableDisplayAction() {
+    Class_FederationReview::getInstance()->disableDisplay();
+    $this->_helper->notify($this->_('Affichage des avis communautaires désactivé'));
+    $this->_redirectToIndex();
+  }
+
+
+  public function enableShareAction() {
+    $federation = Class_FederationReview::getInstance();
+    $message = $federation->enableShare()
+      ? $this->_('Partage des avis à la communauté activé')
+      : $this->_('Activation impossible : %s',
+                 $federation->getLastMessage());
+
+    $this->_helper->notify($message);
+    $this->_redirectToIndex();
+  }
+
+
+  public function disableShareAction() {
+    Class_FederationReview::getInstance()->disableShare();
+    $this->_helper->notify($this->_('Partage des avis à la communauté désactivé'));
+    $this->_redirectToIndex();
+  }
+}
\ No newline at end of file
diff --git a/application/modules/admin/controllers/JournalController.php b/application/modules/admin/controllers/JournalController.php
new file mode 100644
index 0000000000000000000000000000000000000000..3920b3ae52e864a8b159a83f2aaeb782458e9876
--- /dev/null
+++ b/application/modules/admin/controllers/JournalController.php
@@ -0,0 +1,28 @@
+<?php
+/**
+ * Copyright (c) 2012-2017, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class Admin_JournalController extends ZendAfi_Controller_Action {
+
+  public function getPlugins() {
+    return ['ZendAfi_Controller_Plugin_ResourceDefinition_Journal'];
+  }
+}
\ No newline at end of file
diff --git a/application/modules/admin/controllers/RedmineController.php b/application/modules/admin/controllers/RedmineController.php
index a6dc51e07e5406fc71a4795bb55a0717e2a38597..5f69c41b0800933f077f4c822630ce85b1247f13 100644
--- a/application/modules/admin/controllers/RedmineController.php
+++ b/application/modules/admin/controllers/RedmineController.php
@@ -175,6 +175,21 @@ class Admin_RedmineController extends ZendAfi_Controller_Action {
   }
 
 
+  public function downloadFileAction() {
+    $library = Class_Bib::find($this->_getParam('id_lib', 0));
+    $service = new Class_WebService_Redmine($library);
+    $fileid = $this->_getParam('fileid');
+
+    if (!$content = $service->downloadFile($fileid)) {
+      $this->_helper->notify($this->_('Impossible de télécharger cette pièce jointe'));
+      $this->_redirectToReferer();
+      return;
+    }
+
+    $this->_helper->binaryDownload($content, $this->_getParam('filename'));
+  }
+
+
   public function uploadFileAction() {
     if (!$this->_request->isPost())
       return $this->_jsonError($this->_('La requête doit être de type POST'));
diff --git a/application/modules/admin/views/scripts/federation-reviews/index.phtml b/application/modules/admin/views/scripts/federation-reviews/index.phtml
new file mode 100644
index 0000000000000000000000000000000000000000..40af77a22a6762bd503072187d4e3be3a0f5f208
--- /dev/null
+++ b/application/modules/admin/views/scripts/federation-reviews/index.phtml
@@ -0,0 +1,23 @@
+<?php
+echo $this->tag('p',
+                $this->_('Bokeh peut se connecter à un serveur communautaire de partage d\'avis.'));
+
+$this->disable_onchange = true;
+
+$federation_review = Class_FederationReview::getInstance();
+
+echo $federation_review->isDisplayEnabled()
+  ? $this->Button_Cancel((new Class_Entity())
+                         ->setText($this->_('Désactiver l\'affichage des avis communautaires'))
+                         ->setUrl($this->url(['action' => 'disable-display'])))
+  : $this->Button_New((new Class_Entity())
+                      ->setText($this->_('Activer l\'affichage des avis communautaires'))
+                      ->setUrl($this->url(['action' => 'enable-display'])));
+
+echo $federation_review->isShareEnabled()
+  ? $this->Button_Cancel((new Class_Entity())
+                         ->setText($this->_('Désactiver l\'envoi des avis de ce portail à la communauté'))
+                         ->setUrl($this->url(['action' => 'disable-share'])))
+  : $this->Button_New((new Class_Entity())
+                      ->setText($this->_('Activer l\'envoi des avis de ce portail à la communauté'))
+                      ->setUrl($this->url(['action' => 'enable-share'])));
diff --git a/application/modules/opac/controllers/AbonneController.php b/application/modules/opac/controllers/AbonneController.php
index e2cfe91a7c40d9d1fab994277c4427e2c0ca4b70..f068b135d0cc0f8ad958cd5619eb2d1228f53c90 100644
--- a/application/modules/opac/controllers/AbonneController.php
+++ b/application/modules/opac/controllers/AbonneController.php
@@ -181,20 +181,22 @@ class AbonneController extends ZendAfi_Controller_Action {
         $this->_user
           ->setPseudo($this->_request->getParam('avisSignature'))
           ->save();
+
         $this->_helper->notify($this->_('Votre avis à bien été enregistré'));
+
         return $this->_redirectClose($this->_getReferer());
       }
 
       $this->view->message = implode('.', $avis->getErrors());
     }
 
-
     if ($avis != null) {
       $this->view->id = $avis->getId();
       $this->view->avisEntete = $avis->getEntete();
       $this->view->avisTexte = $avis->getAvis();
       $this->view->avisNote = $avis->getNote();
     }
+
     $this->view->avisSignature = $this->_user->getNomAff();
     $this->view->id_notice = $id_notice;
   }
diff --git a/application/modules/opac/controllers/AuthorController.php b/application/modules/opac/controllers/AuthorController.php
index d27bd0b72b56c3d27a433c8663df532cbead80a4..eeec805d4eb440bdc874edf0c1644cb11ef2bb04 100644
--- a/application/modules/opac/controllers/AuthorController.php
+++ b/application/modules/opac/controllers/AuthorController.php
@@ -58,15 +58,9 @@ class AuthorController extends ZendAfi_Controller_Action {
 
 
   protected function _addInspectorGadget($author) {
-    if (!$ig = Zend_Controller_Front::getInstance()
-         ->getPlugin('ZendAfi_Controller_Plugin_InspectorGadget'))
-      return;
-
-    if(!$ig->isEnabled())
-      return;
-
-    $ig->addButton(new Class_Entity(['Label' => $this->_('Auteur Bokeh'),
-                                     'Content' => $this->view->renderAuthorMetadata($author)]));
+    if ($this->_isInspecting())
+      $this->_inspectorButton(new Class_Entity(['Label' => $this->_('Auteur Bokeh'),
+                                                'Content' => $this->view->renderAuthorMetadata($author)]));
   }
 
 
diff --git a/application/modules/opac/controllers/AuthoritySearchController.php b/application/modules/opac/controllers/AuthoritySearchController.php
new file mode 100644
index 0000000000000000000000000000000000000000..166e9c2c829ba37158609070088b85d9dfd2c767
--- /dev/null
+++ b/application/modules/opac/controllers/AuthoritySearchController.php
@@ -0,0 +1,70 @@
+<?php
+/**
+ * Copyright (c) 2012-2019, 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 AuthoritySearchController extends ZendAfi_Controller_Action {
+  public function indexAction() {
+    $criteres = (new Class_CriteresRecherche_Authority)->setParams($this->_request->getParams());
+
+    if ($this->_request->isPost()) {
+      $redirect_params = $criteres->getUrlCriteres();
+      return $this->_redirect($this->view->url($redirect_params, null, true),
+                              ['prependBase' => false]);
+    }
+
+    $prefs = $this->_getActionPreferences();
+
+    $this->view->titre = $prefs['titre'];
+    $this->view->tree_roots = $criteres->getParam('tree_roots');
+    $action_params = $criteres->getUrlWithoutExpression();
+    $this->view->form = (new ZendAfi_Form_AuthoritySearch())
+      ->setAction($this->view->url($action_params, null, true));
+
+    $this->view->form->populate($this->_request->getParams());
+
+    if (($record_id = (int)$this->_getParam('record_id'))
+        && ($record = Class_Notice::find($record_id)))
+      return $this->_handleRecord($record);
+
+    if ($criteres->isEmpty())
+      return; // do not search without criteria
+
+    $search_start_time = microtime(true);
+    $this->view->search_result = $search_result = Class_MoteurRecherche::getInstance()->lancerRecherche($criteres);
+
+    $search_result
+      ->setDuration(microtime(true) - $search_start_time)
+      ->setSettings(['liste_format' => Class_Systeme_ModulesAppli::LISTE_FORMAT_LINKS]);
+
+    if ($this->_isInspecting())
+      $this->_inspectorButton(new Class_Entity(['Label' => $this->_('Configuration de la recherche'),
+                                                'Content' => $this->view->searchInspector($search_result)]));
+  }
+
+
+  protected function _handleRecord($record) {
+    if ($this->_isInspecting())
+      $this->_inspectorButton(new Class_Entity(['Label' => $this->_('Notice Bokeh'),
+                                                'Content' => $this->view->notice_Unimarc($record)]));
+
+    return $this->view->record = $record;
+  }
+}
diff --git a/application/modules/opac/controllers/ErrorController.php b/application/modules/opac/controllers/ErrorController.php
index 8f01967fe020bd3fccc3ba1ac8c498c96427816f..0deba4e4495c8c6d5e3360d017735b2608255f3d 100644
--- a/application/modules/opac/controllers/ErrorController.php
+++ b/application/modules/opac/controllers/ErrorController.php
@@ -34,11 +34,8 @@ class ErrorController extends ZendAfi_Controller_Action {
     $this->_response->clearBody();
 
     if (($this->_request->getServer('HTTP_HOST') == 'localhost')
-        || (($ig = Zend_Controller_Front::getInstance()
-             ->getPlugin('ZendAfi_Controller_Plugin_InspectorGadget'))
-            && $ig->isEnabled())
-        || ((null != ($user = Class_Users::getLoader()->getIdentity()))
-            && $user->isAdmin())) {
+        || $this->_isInspecting()
+        || Class_Users::isCurrentUserAdmin()) {
       $this->view->errors = $errors;
       return;
     }
diff --git a/application/modules/opac/controllers/NoticeajaxController.php b/application/modules/opac/controllers/NoticeajaxController.php
index 7b2e4b706d1cec3f774392e8d540051671479bfd..ccdff7df6cd41bc94fb2a8668363b631a59b42b6 100644
--- a/application/modules/opac/controllers/NoticeajaxController.php
+++ b/application/modules/opac/controllers/NoticeajaxController.php
@@ -121,10 +121,15 @@ class NoticeAjaxController extends ZendAfi_Controller_Action {
 
 
   public function exemplairesAction() {
+    if ($this->notice->isFirstItemTypeSerialArticle())
+      $this->notice = $this->notice->getFirstExemplaire()->getPMBSerialRecord();
+
     $nb_notices_oeuvre = Class_Notice::countBy(['clef_oeuvre' => $this->notice->getClefOeuvre(),
-                                                'id_notice not' =>  $this->id_notice]);
+                                                'id_notice not' =>  $this->notice->getId()]);
 
-    $this->renderExemplaires($this->id_notice, $nb_notices_oeuvre, 'normal');
+    $this->renderExemplaires($this->notice->getId(),
+                             $nb_notices_oeuvre,
+                             'normal');
   }
 
 
diff --git a/application/modules/opac/controllers/RechercheController.php b/application/modules/opac/controllers/RechercheController.php
index bded529f364f4075dfb80925e723bad72bb14d39..1b5d0343d82d045c7420f775ed41ecc361c1995e 100644
--- a/application/modules/opac/controllers/RechercheController.php
+++ b/application/modules/opac/controllers/RechercheController.php
@@ -166,11 +166,9 @@ class RechercheController extends ZendAfi_Controller_Action {
 
 
   protected function _logSearch($search_result) {
-    if (($ig = Zend_Controller_Front::getInstance()
-         ->getPlugin('ZendAfi_Controller_Plugin_InspectorGadget'))
-        && $ig->isEnabled())
-      $ig->addButton(new Class_Entity(['Label' => $this->_('Configuration de la recherche'),
-                                       'Content' => $this->view->searchInspector($search_result)]));
+    if ($this->_isInspecting())
+      $this->_inspectorButton(new Class_Entity(['Label' => $this->_('Configuration de la recherche'),
+                                                'Content' => $this->view->searchInspector($search_result)]));
   }
 
 
@@ -671,7 +669,7 @@ class RechercheController extends ZendAfi_Controller_Action {
     $viewRenderer = $this->getHelper('ViewRenderer');
     $viewRenderer->setNoRender();
 
-    if (!$user=$this->userConnected())
+    if (!$user = $this->userConnected())
       return;
 
     $id_bib = (int)$this->_getParam('id_bib');
@@ -681,15 +679,130 @@ class RechercheController extends ZendAfi_Controller_Action {
     if (Class_CosmoVar::isSiteRetraitResaPatronLibrary())
       $code_annexe = $user->getUserIdSite();
 
-    $comm = new Class_CommSigb();
-
-    $ret = $comm->reserverExemplaire($id_bib, $id_exemplaire, $code_annexe);
+    try {
+      $ret = Class_CommSigb::getInstance()->reserverExemplaire($id_bib, $id_exemplaire, $code_annexe);
+    } catch(Class_WebService_SIGB_RequiresCalendarHoldException $e) {
+      return $this->_redirect(sprintf('/recherche/reservation-calendar-ajax/id_bib/%s/copy_id/%s/code_annexe/%s',
+                                      $id_bib,
+                                      $id_exemplaire,
+                                      $code_annexe));
+    }
 
     $this->renderPopupResult($this->view->_('Réservation'),
                              $this->_getHoldMessage($user, $id_exemplaire, $ret));
   }
 
 
+  public function reservationCalendarAjaxAction() {
+    $id_bib = (int)$this->_getParam('id_bib');
+    $id_exemplaire = $this->_getParam('copy_id');
+    $code_annexe = $this->_getParam('code_annexe');
+
+    $form = $this->view
+      ->newForm(['id'  => 'reservation-calendar'])
+      ->setAction($this->view->url())
+      ->addElement('dateRangePicker',
+                   'reservation-date',
+                   ['label' => $this->_('Période de réservation'),
+                    'start' => ['name' => 'reservedate',
+                                'allowEmpty' => false,
+                                'DateFormat' => 'DD/MM/YYYY',
+                                'validators' => [new Zend_Validate_Date('DD/MM/YYYY')]],
+                    'end' => ['name' => 'expirationdate',
+                              'allowEmpty' => false,
+                              'DateFormat' => 'DD/MM/YYYY',
+                              'validators' => [new Zend_Validate_Date('DD/MM/YYYY')]]
+                   ])
+      ->addElement('Submit', $this->_('Valider'))
+      ->addElement('Button', $this->_('Annuler'),
+                   ['onclick' => 'opacDialogClose();return false;'])
+      ->addUniqDisplayGroup('hold');
+
+    if ($this->_request->isPost() && $form->isValid($this->_request->getPost())) {
+      $reservedate = Class_Date::frToIso($this->_getParam('reservedate'));
+      $expirationdate = Class_Date::frToIso($this->_getParam('expirationdate'));
+
+      $ret = Class_CommSigb::getInstance()->reserverExemplaire($id_bib,
+                                                               $id_exemplaire,
+                                                               $code_annexe,
+                                                               $reservedate,
+                                                               $expirationdate);
+
+      if ($ret['statut'])
+        return $this->renderPopupResult($this->view->_('Réservation'),
+                                        $this->_getHoldMessage($this->userConnected(),
+                                                               $id_exemplaire,
+                                                               $ret));
+
+      $form
+        ->addError($ret['erreur'])
+        ->addDecorator('Errors');
+    }
+
+    $holds = $this->_renderHolds($id_exemplaire);
+    $form = $this->view->renderForm($form);
+    return $this->renderPopupResult($this->view->_('Réservation'),
+                                    Class_ScriptLoader::getInstance()->html()
+                                    . $holds
+                                    . $form );
+  }
+
+
+  protected function _renderHolds($id_exemplaire) {
+    $item = Class_Exemplaire::find($id_exemplaire);
+    $holds = Class_CommSigb::getInstance()->holdsForItem($item)['holds'];
+    usort($holds,
+          function($a, $b)
+          {
+            return strcmp($a->getReserveDate(), $b->getReserveDate());
+          });
+
+    $html = implode('',
+                    array_map(function($hold)
+                              {
+                                return $this->view->tag('li',
+                                                        $hold->renderRange());
+                              },
+                              $holds));
+
+    $js_holds = json_encode(array_map(
+                                      function($hold)
+                                      {
+                                        return [$hold->getReserveDate(), $hold->getExpirationDate()];
+                                      },
+                                      $holds));
+
+    Class_ScriptLoader::getInstance()
+      ->addAdminScript('jquery_ui_datepicker_i18n/datepicker-fr.js')
+      ->addJQueryReady('
+                    Date.prototype.withoutTime = function () {
+                        var d = new Date(this);
+                        d.setHours(0, 0, 0, 0);
+                        return d;
+                    }
+                    $.datepicker.setDefaults($.datepicker.regional["' . ((Zend_Registry::get('locale') == 'en_US') ? '' : 'fr' ). '"]);
+                       $("input").blur();
+                       $("#holds_view").datepicker(
+                        {
+                          numberOfMonths: 3,
+                          beforeShowDay: function(date) {
+                                            var holds = '. $js_holds . ';
+                                            var day = date.withoutTime();
+                                            var hold = holds.find( (hold) => {
+                                               return (new Date(hold[0]).withoutTime() <= day) && (day <= new Date(hold[1]).withoutTime());
+                                            });
+
+                                           if (undefined == hold)
+                                              return [false, "day-without-hold"];
+
+                                            return [false, "day-with-hold", "' . $this->_("Reservation déjà présente ce jour") . '"];
+                                          }}
+                      );');
+
+    return '<div id="holds_view"></div>' . $this->view->tag('ul',$html);
+  }
+
+
   protected function _getHoldMessage($user, $item_id, $response) {
     if ($response['erreur'])
       return $response['erreur'];
@@ -895,19 +1008,15 @@ class RechercheController extends ZendAfi_Controller_Action {
 
 
   protected function _addInspectorGadget($record) {
-    if (!$ig = Zend_Controller_Front::getInstance()
-         ->getPlugin('ZendAfi_Controller_Plugin_InspectorGadget'))
-      return;
-
-    if(!$ig->isEnabled())
+    if (!$this->_isInspecting())
       return;
 
-    $ig->addButton(new Class_Entity(['Label' => $this->_('Notice Bokeh'),
-                                     'Content' => $this->view->notice_Unimarc($record)]));
+    $this->_inspectorButton(new Class_Entity(['Label' => $this->_('Notice Bokeh'),
+                                              'Content' => $this->view->notice_Unimarc($record)]));
 
-    if($record->isDilicom())
-      $ig->addButton(new Class_Entity(['Label' => $this->_('Dilicom'),
-                                       'Content' => $this->view->notice_Dilicom($record)]));
+    if ($record->isDilicom())
+      $this->_inspectorButton(new Class_Entity(['Label' => $this->_('Dilicom'),
+                                                'Content' => $this->view->notice_Dilicom($record)]));
   }
 }
 
diff --git a/application/modules/opac/views/scripts/authority-search/index.phtml b/application/modules/opac/views/scripts/authority-search/index.phtml
new file mode 100644
index 0000000000000000000000000000000000000000..5d1459f3ed3ed10bf096883dab58e6467d079e0e
--- /dev/null
+++ b/application/modules/opac/views/scripts/authority-search/index.phtml
@@ -0,0 +1,6 @@
+<?php
+$this->openBoite($this->titre);
+echo $this->renderForm($this->form);
+echo $this->AuthoritySearch_Header($this->search_result);
+echo $this->AuthoritySearch_Result($this->search_result, $this->record, $this->tree_roots);
+$this->closeBoite();
diff --git a/cosmogramme/VERSIONS_WIP/90319 b/cosmogramme/VERSIONS_WIP/90319
new file mode 100644
index 0000000000000000000000000000000000000000..a2836c300389e101ecd4efff83ad9b72ad611355
--- /dev/null
+++ b/cosmogramme/VERSIONS_WIP/90319
@@ -0,0 +1 @@
+ - ticket #90319 : SIGB Orphée : affichage de la date de fin de réservation dans le tableau des réservations
\ No newline at end of file
diff --git a/cosmogramme/cosmozend/application/modules/cosmo/controllers/EmplacementController.php b/cosmogramme/cosmozend/application/modules/cosmo/controllers/EmplacementController.php
index ad0a19639af81024835719412daf56ba29259566..6523c4dded3b75e495b1915f86e430b269471b1a 100644
--- a/cosmogramme/cosmozend/application/modules/cosmo/controllers/EmplacementController.php
+++ b/cosmogramme/cosmozend/application/modules/cosmo/controllers/EmplacementController.php
@@ -16,71 +16,13 @@
  *
  * 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
  */
 
 
-class Cosmo_EmplacementController extends Zend_Controller_Action {
-	public function preDispatch() {
-		$this->cosmoPath = $this->view->cosmoPath = new CosmoPaths();
-	}
-
-
-	public function indexAction() {
-		$this->view->models = Class_CodifEmplacement::findAllBy(['order' => 'libelle']);
-		$this->view->currentId = $this->_getParam('id');
-	}
-
-
-	public function addAction() {
-		$this->view->model = Class_CodifEmplacement::newInstance([
-			'libelle' => '** nouvel emplacement **',
-			'regles' => '995$k/R',
-			'ne_pas_afficher' => 0]);
-	}
-
-
-	public function validateAction() {
-		if (!$this->_request->isPost()) {
-			$this->_gotoIndex();
-			return;
-		}
-
-		if (!$model = Class_CodifEmplacement::find($this->_getParam('id')))
-			$model = Class_CodifEmplacement::newInstance();
-		
-		$model
-			->setLibelle($this->_getParam('libelle'))
-			->setRegles($this->_getParam('regles'))
-			->setNePasAfficher($this->_getParam('ne_pas_afficher'));
-
-		if (!$model->isValid()) {
-			$this->view->model = $model;
-			$this->view->errors = $model->getErrors();
-			$this->render('add');
-			return;
-		}
-
-		$model->save();
-		$this->_gotoIndex($model->getId());
-	}
-
-
-	public function deleteAction() {
-		if ($model = Class_CodifEmplacement::find($this->_getParam('id')))
-			$model->delete();
-		$this->_gotoIndex();
-	}
-
-
-	protected function _gotoIndex($id=null) {
-		$this->_redirect($this->view->url([
-			'module' => 'cosmo',
-			'controller' => 'emplacement',
-			'action' => 'index',
-			'id' => $id
-		], null, true), 
-		['prependBase' => false]);
-	}
+class Cosmo_EmplacementController extends ZendAfi_Controller_Action {
+  public function getPlugins() {
+    return ['ZendAfi_Controller_Plugin_ResourceDefinition_Emplacement',
+            'ZendAfi_Controller_Plugin_Manager_Emplacement'];
+  }
 }
-?>
\ No newline at end of file
diff --git a/cosmogramme/cosmozend/application/modules/cosmo/controllers/GenreController.php b/cosmogramme/cosmozend/application/modules/cosmo/controllers/GenreController.php
new file mode 100644
index 0000000000000000000000000000000000000000..6cd41225b4a2fd53e9d3c6add95cfe27f4092029
--- /dev/null
+++ b/cosmogramme/cosmozend/application/modules/cosmo/controllers/GenreController.php
@@ -0,0 +1,28 @@
+<?php
+/**
+ * Copyright (c) 2012-2017, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class Cosmo_GenreController extends ZendAfi_Controller_Action {
+  public function getPlugins() {
+    return ['ZendAfi_Controller_Plugin_ResourceDefinition_Genre',
+            'ZendAfi_Controller_Plugin_Manager_CosmoCodification'];
+  }
+}
\ No newline at end of file
diff --git a/cosmogramme/cosmozend/application/modules/cosmo/controllers/SectionController.php b/cosmogramme/cosmozend/application/modules/cosmo/controllers/SectionController.php
index 020c64a76ac82ce77464bef993cb142c51b098d5..4e8a1f5f754ad4e48e4d7b8ca475e03f912c8aaf 100644
--- a/cosmogramme/cosmozend/application/modules/cosmo/controllers/SectionController.php
+++ b/cosmogramme/cosmozend/application/modules/cosmo/controllers/SectionController.php
@@ -23,6 +23,6 @@
 class Cosmo_SectionController extends ZendAfi_Controller_Action {
   public function getPlugins() {
     return ['ZendAfi_Controller_Plugin_ResourceDefinition_Section',
-            'ZendAfi_Controller_Plugin_Manager_Section'];
+            'ZendAfi_Controller_Plugin_Manager_CosmoCodification'];
   }
 }
\ No newline at end of file
diff --git a/cosmogramme/cosmozend/application/modules/cosmo/views/scripts/emplacement/add.phtml b/cosmogramme/cosmozend/application/modules/cosmo/views/scripts/emplacement/add.phtml
index 5e0ced92b84e0d96c51bdfa4afbb77e201b5de5c..f9d4cb1251f5b7e8920f9e7887cb9b52d074bcb5 100644
--- a/cosmogramme/cosmozend/application/modules/cosmo/views/scripts/emplacement/add.phtml
+++ b/cosmogramme/cosmozend/application/modules/cosmo/views/scripts/emplacement/add.phtml
@@ -1,19 +1,3 @@
-<?php $model = $this->model; ?>
-<h1>Codification des emplacements</h1>
-<div class="liste">
-	<?php if ($this->errors) { ?>
-		<ul style="color:red;font-weight:bolder;">
-			<?php foreach ($this->errors as $error) { ?>
-			<li><?php echo $this->escape($error);?></li>
-			<?php } ?>
-		</ul>
-	<?php }  ?>
-	<?php echo $this->cosmoEmplacement($this->model, true); ?>
-</div>
-<br/><br/>
-
-<?php 
-echo $this->cosmoButton('Retour', 
-												$this->url(['module' => 'cosmo', 'controller' => 'emplacement'],
-																	 null, true));
-?>
+<?php
+echo $this->tag('h1', $this->titre);
+echo $this->renderForm($this->form);
diff --git a/cosmogramme/cosmozend/application/modules/cosmo/views/scripts/emplacement/edit.phtml b/cosmogramme/cosmozend/application/modules/cosmo/views/scripts/emplacement/edit.phtml
new file mode 100644
index 0000000000000000000000000000000000000000..f9d4cb1251f5b7e8920f9e7887cb9b52d074bcb5
--- /dev/null
+++ b/cosmogramme/cosmozend/application/modules/cosmo/views/scripts/emplacement/edit.phtml
@@ -0,0 +1,3 @@
+<?php
+echo $this->tag('h1', $this->titre);
+echo $this->renderForm($this->form);
diff --git a/cosmogramme/cosmozend/application/modules/cosmo/views/scripts/emplacement/index.phtml b/cosmogramme/cosmozend/application/modules/cosmo/views/scripts/emplacement/index.phtml
index b1cdb1c8aedf26e5bfaf635b6eaf004eb5796e6e..4fe9152882a20c6fd6f97a65dddb6a67b51191eb 100644
--- a/cosmogramme/cosmozend/application/modules/cosmo/views/scripts/emplacement/index.phtml
+++ b/cosmogramme/cosmozend/application/modules/cosmo/views/scripts/emplacement/index.phtml
@@ -1,29 +1,19 @@
-<h1>Codification des emplacements</h1>
-<div class="liste">
-	<?php 
-	foreach($this->models as $model) {
-		$img = 'plus.gif';
-		$display = false;
-		if ($model->getId() == $this->currentId) {
-			$img = 'moins.gif';
-			$display = true;
-		} 
-	?>
-	<div class="liste_img">
-		<?php echo $this->tagImg($this->cosmoPath->getCosmoBaseUrl() . 'images/'.$img,
-														 ['id' => 'Iemplacement' . $model->getId(),
-															'style' => 'cursor:pointer',
-															'onclick' => 'contracter_bloc(\'emplacement'.$model->getId().'\')']);?>
-	</div>
-	<div class="liste_titre"><?php echo $model->getLibelle();?></div>
-	<?php echo $this->cosmoEmplacement($model, $display); ?>
-	<?php } ?>
-</div>
+<?php
+echo $this->tag('h1', $this->titre);
 
-<br/><br/>
-<?php 
-echo $this->cosmoButton('Ajouter un emplacement', 
-												$this->url(['module' => 'cosmo',
-																		'controller' => 'emplacement', 
-																		'action' => 'add'], null, true));
-?>
+echo $this->Button_New((new Class_Entity())
+                           ->setText($this->_('Ajouter un emplacement')));
+
+$description = (new Class_TableDescription('emplacements'))
+  ->addColumn($this->_('libellé'), 'libelle')
+  ->addColumn($this->_('Règles de reconnaissance'), 'regles')
+  ->addColumn($this->_('Affichage des exemplaires'),
+              function($model)
+              {
+                return $model->getNePasAfficher() ? $this->_('Non') : $this->_('Oui');
+              })
+  ->addRowPluginsActions()
+;
+
+echo $this->renderTable(new Class_TableDescription_CosmoEmplacement('emplacements'),
+                        $this->emplacements);
diff --git a/cosmogramme/cosmozend/application/modules/cosmo/views/scripts/genre/add.phtml b/cosmogramme/cosmozend/application/modules/cosmo/views/scripts/genre/add.phtml
new file mode 100644
index 0000000000000000000000000000000000000000..f9d4cb1251f5b7e8920f9e7887cb9b52d074bcb5
--- /dev/null
+++ b/cosmogramme/cosmozend/application/modules/cosmo/views/scripts/genre/add.phtml
@@ -0,0 +1,3 @@
+<?php
+echo $this->tag('h1', $this->titre);
+echo $this->renderForm($this->form);
diff --git a/cosmogramme/cosmozend/application/modules/cosmo/views/scripts/genre/edit.phtml b/cosmogramme/cosmozend/application/modules/cosmo/views/scripts/genre/edit.phtml
new file mode 100644
index 0000000000000000000000000000000000000000..f9d4cb1251f5b7e8920f9e7887cb9b52d074bcb5
--- /dev/null
+++ b/cosmogramme/cosmozend/application/modules/cosmo/views/scripts/genre/edit.phtml
@@ -0,0 +1,3 @@
+<?php
+echo $this->tag('h1', $this->titre);
+echo $this->renderForm($this->form);
diff --git a/cosmogramme/cosmozend/application/modules/cosmo/views/scripts/genre/index.phtml b/cosmogramme/cosmozend/application/modules/cosmo/views/scripts/genre/index.phtml
new file mode 100644
index 0000000000000000000000000000000000000000..7243564ee52f66f4650490e703ac0d9147a02e87
--- /dev/null
+++ b/cosmogramme/cosmozend/application/modules/cosmo/views/scripts/genre/index.phtml
@@ -0,0 +1,8 @@
+<?php
+echo $this->tag('h1', $this->titre);
+
+echo $this->Button_New((new Class_Entity())
+                           ->setText($this->_('Ajouter un genre')));
+
+echo $this->renderTable(new Class_TableDescription_CosmoCodification('genres'),
+                        $this->genres);
diff --git a/cosmogramme/cosmozend/application/modules/cosmo/views/scripts/section/add.phtml b/cosmogramme/cosmozend/application/modules/cosmo/views/scripts/section/add.phtml
index 4cbf41858aff0ec2e6a75bb2528a42c4a2fe5b07..f9d4cb1251f5b7e8920f9e7887cb9b52d074bcb5 100644
--- a/cosmogramme/cosmozend/application/modules/cosmo/views/scripts/section/add.phtml
+++ b/cosmogramme/cosmozend/application/modules/cosmo/views/scripts/section/add.phtml
@@ -1,3 +1,3 @@
 <?php
+echo $this->tag('h1', $this->titre);
 echo $this->renderForm($this->form);
-?>
\ No newline at end of file
diff --git a/cosmogramme/cosmozend/application/modules/cosmo/views/scripts/section/edit.phtml b/cosmogramme/cosmozend/application/modules/cosmo/views/scripts/section/edit.phtml
index 4cbf41858aff0ec2e6a75bb2528a42c4a2fe5b07..f9d4cb1251f5b7e8920f9e7887cb9b52d074bcb5 100644
--- a/cosmogramme/cosmozend/application/modules/cosmo/views/scripts/section/edit.phtml
+++ b/cosmogramme/cosmozend/application/modules/cosmo/views/scripts/section/edit.phtml
@@ -1,3 +1,3 @@
 <?php
+echo $this->tag('h1', $this->titre);
 echo $this->renderForm($this->form);
-?>
\ No newline at end of file
diff --git a/cosmogramme/cosmozend/application/modules/cosmo/views/scripts/section/index.phtml b/cosmogramme/cosmozend/application/modules/cosmo/views/scripts/section/index.phtml
index 171db88906e56271ab3a15e405c848fb9b3ea9db..a7eb9d5c41281ea88c9a978d59db3700e0be3fcc 100644
--- a/cosmogramme/cosmozend/application/modules/cosmo/views/scripts/section/index.phtml
+++ b/cosmogramme/cosmozend/application/modules/cosmo/views/scripts/section/index.phtml
@@ -1,17 +1,7 @@
 <?php
-echo $this->tagAnchor(['action' => 'add',
-                       'id' => null],
-                      $this->_('Ajouter une section'));
-echo $this->tagModelTable($this->sections,
-                          [$this->_('libellé'),
-                           $this->_('Règles de reconnaissance'),
-                           $this->_('Rejeter les exemplaires')],
-                          ['libelle',
-                           'regles',
-                           'invisible'],
-                          [function($model)
-                           {
-                             return $this->renderPluginsActions($model);
-                           }],
-                          'sections');
-?>
\ No newline at end of file
+echo $this->tag('h1', $this->titre);
+
+echo $this->Button_New((new Class_Entity())
+                           ->setText($this->_('Ajouter une section')));
+
+echo $this->renderTable(new Class_TableDescription_CosmoSection('sections'), $this->sections);
diff --git a/cosmogramme/cosmozend/tests/CosmoControllerTestCase.php b/cosmogramme/cosmozend/tests/CosmoControllerTestCase.php
index c8e88a8fc2350894745d08fee232eba1cbc27650..49c0dc4c8ff04bcf05f12c2ce11d488084b09227 100644
--- a/cosmogramme/cosmozend/tests/CosmoControllerTestCase.php
+++ b/cosmogramme/cosmozend/tests/CosmoControllerTestCase.php
@@ -31,6 +31,7 @@ abstract class CosmoControllerTestCase extends Zend_Test_PHPUnit_ControllerTestC
     parent::setUp();
     Storm_Model_Abstract::unsetLoaders();
     Storm_Cache::setDefaultZendCache(null);
+    Class_AdminVar::set('NOM_DOMAINE', 'http://localhost');
     if ($this->_storm_default_to_volatile)
       Storm_Model_Loader::defaultToVolatile();
 
diff --git a/cosmogramme/cosmozend/tests/application/modules/cosmo/controllers/DataProfileControllerTest.php b/cosmogramme/cosmozend/tests/application/modules/cosmo/controllers/DataProfileControllerTest.php
index bdcfdc9d61191bd3893c37c9ed287e954b00e074..bcd6a0e21a975efed75035f4a6bf11ffb1802693 100644
--- a/cosmogramme/cosmozend/tests/application/modules/cosmo/controllers/DataProfileControllerTest.php
+++ b/cosmogramme/cosmozend/tests/application/modules/cosmo/controllers/DataProfileControllerTest.php
@@ -30,6 +30,7 @@ abstract class Cosmo_DataProfileControllerTestCase extends CosmoControllerTestCa
       ->setId(56)
       ->setItemField(Class_IntProfilDonnees::FIELD_ITEM_ID_ORIGINE, ['zone' => '999',
                                                                      'champ' => 'c'])
+      ->setItemField(Class_IntProfilDonnees::FIELD_ITEM_BUNDLE_ID, 8)
       ->save();
 
     Class_IntProfilDonnees::forNanook()
@@ -310,6 +311,12 @@ class Cosmo_DataProfileControllerEditUnimarcKohaTest extends Cosmo_DataProfileCo
   }
 
 
+  /** @test */
+  public function itemSectionShouldContainsUpperCaseLetterChoice() {
+    $this->assertXPath('//form//select[@id="champ_section"]/option[@value="Q"]');
+  }
+
+
   /** @test */
   public function itemGenreShouldBePresent() {
     $this->assertXPath('//form//select[@id="champ_genre"]/option[@value=""]');
@@ -334,6 +341,12 @@ class Cosmo_DataProfileControllerEditUnimarcKohaTest extends Cosmo_DataProfileCo
   }
 
 
+  /** @test */
+  public function itemBundleIdShouldBePresent() {
+    $this->assertXPath('//form//select[@id="champ_bundle_id"]/option[@value="8"][@selected]');
+  }
+
+
   /** @test */
   public function multiInputItemUrlShouldBePresent() {
     $this->assertXPath('//form//div[@id="multi_inputs_item_url"]');
@@ -358,6 +371,18 @@ class Cosmo_DataProfileControllerEditUnimarcKohaTest extends Cosmo_DataProfileCo
   }
 
 
+  /** @test */
+  public function multiInputIndexFilesShouldBePresent() {
+    $this->assertXPath('//form//div[@id="multi_inputs_index_files"]');
+  }
+
+
+  /** @test */
+  public function inputIndexFileUriRegexDefaultValueShouldBeUserfilesFilesStarPDF() {
+    $this->assertXPathContentContains('//script', '"index_file_uri_regex":["\/userfiles\/files\/[a-zA-Z0-9_\\\-]+\\\.pdf"]');
+  }
+
+
   /** @test */
   public function selectRejetPeriodiquesShoudHaveNoSelected() {
     $this->assertXPathContentContains('//form//select[@id="rejet_periodiques"]/option[@value="0"][@selected]', 'Non');
@@ -366,7 +391,7 @@ class Cosmo_DataProfileControllerEditUnimarcKohaTest extends Cosmo_DataProfileCo
 
   /** @test */
   public function selectIdArticlePeriodiqueShouldBePresent() {
-    $this->assertXPath('//form//select[@id="id_article_periodique"]/option[@value="' . Class_IntProfilDonnees::SERIAL_FORMAT_KOHA . '"][@selected]');
+    $this->assertXPathContentContains('//form//select[@id="id_article_periodique"]/option[@value="' . Class_IntProfilDonnees::SERIAL_FORMAT_KOHA . '"][@selected]', 'Koha');
   }
 
 
@@ -416,6 +441,7 @@ class Cosmo_DataProfileControllerPostEditUnimarcKohaFileFormatTest extends Cosmo
              'champ_genre' => 'z',
              'champ_emplacement' => 'u',
              'champ_annexe' => 'a',
+             'champ_bundle_id' => '8',
              'champ_availability' => 't',
              'url_zone' => [0 => '932'],
              'url_champ' => [0 => 'n'],
@@ -431,6 +457,9 @@ class Cosmo_DataProfileControllerPostEditUnimarcKohaFileFormatTest extends Cosmo
                                      'new'],
              'interest_zone' => [0 => '932'],
              'interest_champ' =>  [0 => '7'],
+             'index_file_zone' => ['856'],
+             'index_file_field' => ['u'],
+             'index_file_uri_regex' => ['/userfiles/files/public/.+.pdf'],
              'holds' => 'SUPPORT; SUPPORT',
              'carts' => 'LIBELLE; ROLE',
              'csv_item_fields' => 'ean;ean',
@@ -549,10 +578,35 @@ class Cosmo_DataProfileControllerPostEditUnimarcKohaFileFormatTest extends Cosmo
   public function interestFieldShouldBe7() {
     $this->assertEquals('7', $this->_koha->getInterestField());
   }
+
+
+  /** @test */
+  public function bundleIdFieldShouldBe8() {
+    $this->assertEquals('8', $this->_koha->getBundleIdField());
+  }
+
+
+  /** @test */
+  public function indexFileZoneShouldBe856() {
+    $this->assertEquals('856', $this->_koha->getIndexFileZone());
+  }
+
+
+  /** @test */
+  public function indexFileFieldShouldBeU() {
+    $this->assertEquals('u', $this->_koha->getIndexFileField());
+  }
+
+
+  /** @test */
+  public function indexFildUriRegexShouldBeUserfilesPublic() {
+    $this->assertEquals('/userfiles/files/public/.+.pdf', $this->_koha->getIndexFileUriRegex());
+  }
 }
 
 
 
+
 class Cosmo_DataProfileControllerPostEditFieldsTest extends Cosmo_DataProfileControllerTestCase {
   protected
     $_nanook_profile;
@@ -1110,7 +1164,10 @@ class Cosmo_DataProfileControllerMultiValuesTest extends Cosmo_DataProfileContro
                                     4 => [],
                                     5 => [],
                                     6 => ['zone' => '995',
-                                          'champ' => 'z']
+                                          'champ' => 'z'],
+                                    7 => ['index_file_zone' => ['934'],
+                                          'index_file_field' => ['u'],
+                                          'index_file_uri_regex' => ['/.*']]
                     ]]);
 
     $this->dispatch('cosmo/data-profile/edit/id/321', true);
@@ -1146,6 +1203,15 @@ class Cosmo_DataProfileControllerMultiValuesTest extends Cosmo_DataProfileContro
     $this->assertXPathContentContains('//script', 'values:{"1_label":["am","bm","","em","mm","","","","","","",""],"1_zone":["BDA","BDJ","LFA","LFJ","LDA","LDJ","LCDA","LCDJ","PATIMP","PATMS","PERIP","PATINC"]}',
                                       $this->_response->getBody());
   }
+
+
+  /** @test */
+  public function indexFileShouldContainsZone934FieldURegexStar() {
+    $this->assertXPathContentContains('//script', 'values:{"index_file_zone":["934"],"index_file_field":["u"],"index_file_uri_regex":["\/.*"]}',
+                                      $this->_response->getBody());
+  }
+
+
 }
 
 
diff --git a/cosmogramme/cosmozend/tests/application/modules/cosmo/controllers/EmplacementControllerTest.php b/cosmogramme/cosmozend/tests/application/modules/cosmo/controllers/EmplacementControllerTest.php
index 0f9a17c470494c71dfb319e60bde4ccdefa01f67..7ea7d3164d2cd53088e55226961ba981923d409c 100644
--- a/cosmogramme/cosmozend/tests/application/modules/cosmo/controllers/EmplacementControllerTest.php
+++ b/cosmogramme/cosmozend/tests/application/modules/cosmo/controllers/EmplacementControllerTest.php
@@ -16,239 +16,193 @@
  *
  * 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
  */
 
 
 abstract class Cosmo_EmplacementControllerTestCase extends CosmoControllerTestCase {
-	public function setUp() {
-		parent::setUp();
-
-		$this->fixture('Class_CodifEmplacement', [
-			'id' => 18,
-			'libelle' => 'Albums',
-			'regles' => '995$x=55',
-			'ne_pas_afficher' => 0,
-		]);
-	}
+  public function setUp() {
+    parent::setUp();
+
+    $this->fixture('Class_CodifEmplacement', ['id' => 18,
+                                              'libelle' => 'Albums',
+                                              'regles' => '995$x=55',
+                                              'ne_pas_afficher' => 0]);
+  }
 }
 
 
 
 class Cosmo_EmplacementControllerIndexTest extends Cosmo_EmplacementControllerTestCase {
-	public function setUp() {
-		parent::setUp();
-		$this->dispatch('/cosmo/emplacement/index', true);
-	}
+  public function setUp() {
+    parent::setUp();
+    $this->dispatch('/cosmo/emplacement/index', true);
+  }
 
 
-	/** @test */
-	public function titleShouldBePresent() {
-		$this->assertXpathContentContains('//h1', 'Codification des emplacements');
-	}
+  /** @test */
+  public function addLinkShouldBePresent() {
+    $this->assertXpath('//button[contains(@data-url, "/cosmo/emplacement/add")]');
+  }
 
 
-	/** @test */
-	public function addButtonShouldBePresent() {
-		$this->assertXpath('//input[contains(@onclick, "/cosmo/emplacement/add")]');
-	}
+  /** @test */
+  public function albumLibelleShouldBePresent() {
+    $this->assertXPathContentContains('//td', 'Albums');
+  }
 
 
-	/** @test */
-	public function albumsFormShouldBePresent() {
-		$this->assertXpath('//form[contains(@action, "/cosmo/emplacement/validate/id/18")]');
-	}
+  /** @test */
+  public function albumRuleShouldBePresent() {
+    $this->assertXPathContentContains('//td', '995$x=55');
+  }
 
 
-	/** @test */
-	public function albumsLabelShouldBePresent() {
-		$this->assertXpath('//input[@name="libelle"][@value="Albums"]');
-	}
+  /** @test */
+  public function albumItemDisplayShouldBePresent() {
+    $this->assertXPathContentContains('//td', 'Oui');
+  }
 
 
-	/** @test */
-	public function albumsRulesShouldBePresent() {
-		$this->assertXpathContentContains('//textarea[@name="regles"]', '995$x=55');
-	}
+  /** @test */
+  public function albumsDeleteButtonShouldBePresent() {
+    $this->assertXpath('//a[contains(@href, "/cosmo/emplacement/delete/id/18")]');
+  }
 
 
-	/** @test */
-	public function albumsDisplayShouldBePresent() {
-		$this->assertXpathContentContains(
-			'//select[@name="ne_pas_afficher"]/option[@selected="selected"]', 
-			'Afficher les exemplaires');
-	}
-
-
-	/** @test */
-	public function albumsDeleteButtonShouldBePresent() {
-		$this->assertXpath('//input[contains(@onclick, "/cosmo/emplacement/delete/id/18")]');
-	}
+  /** @test */
+  public function albumsEditButtonShouldBePresent() {
+    $this->assertXpath('//a[contains(@href, "/cosmo/emplacement/edit/id/18")]');
+  }
 }
 
 
 
-class Cosmo_EmplacementControllerAlbumsValidatePostTest extends Cosmo_EmplacementControllerTestCase {
-	public function setUp() {
-		parent::setUp();
-		$this->postDispatch(
-			'/cosmo/emplacement/validate/id/18',
-			[
-				'libelle' => 'Alboums',
-				'regles' => '995$x=07',
-				'ne_pas_afficher' => 1]);
+class Cosmo_EmplacementControllerEditTest extends Cosmo_EmplacementControllerTestCase {
+  public function setUp() {
+    parent::setUp();
+    $this->dispatch('/cosmo/emplacement/edit/id/18', true);
+  }
 
-		$this->model = Class_CodifEmplacement::find(18);
-	}
 
+  /** @test */
+  public function albumsLabelShouldBePresent() {
+    $this->assertXpath('//input[@name="libelle"][@value="Albums"]');
+  }
 
-	/** @test */
-	public function labelShouldBeAlboums() {
-		$this->assertEquals('Alboums', $this->model->getLibelle());
-	}
 
+  /** @test */
+  public function albumsMultiInputRulesShouldBePresent() {
+    $this->assertXpathContentContains('//script', '"rule_zone":["995"],"rule_field":["x"],"rule_sign":["="],"rule_values":["55"]', $this->_response->getBody());
+  }
 
-	/** @test */
-	public function rulesShouldBeDollarXEqualsZeroSeven() {
-		$this->assertEquals('995$x=07', $this->model->getRegles());
-	}
 
-
-	/** @test */
-	public function displayHideShouldBeHide() {
-		$this->assertEquals(1, $this->model->getNePasAfficher());
-	}
+  /** @test */
+  public function albumsDisplayShouldBePresent() {
+    $this->assertXpathContentContains('//select[@name="ne_pas_afficher"]/option[@selected="selected"]',
+                                      'Oui');
+  }
 }
 
 
 
-class Cosmo_EmplacementControllerAddTest extends Cosmo_EmplacementControllerTestCase {
-	public function setUp() {
-		parent::setUp();
-		$this->dispatch('/cosmo/emplacement/add', true);
-	}
-
-
-	/** @test */
-	public function titleShouldBePresent() {
-		$this->assertXpathContentContains('//h1', 'Codification des emplacements');
-	}
+class Cosmo_EmplacementControllerEditPostTest extends Cosmo_EmplacementControllerTestCase {
+  public function setUp() {
+    parent::setUp();
+    $this->postDispatch('/cosmo/emplacement/edit/id/18',
+                        [
+                         'libelle' => 'Alboums',
+                         'rule_zone' => ['995'],
+                         'rule_field' => ['x'],
+                         'rule_sign' => ['='],
+                         'rule_values' => ['07'],
+                         'ne_pas_afficher' => 1]);
 
+    $this->model = Class_CodifEmplacement::find(18);
+  }
 
-	/** @test */
-	public function formActionShouldGoToValidate() {
-		$this->assertXpath('//form[contains(@action, "/cosmo/emplacement/validate")]');
-	}
 
+  /** @test */
+  public function shouldRedirect() {
+    $this->assertRedirectTo('/cosmo/emplacement/edit/id/18');
+  }
 
-	/** @test */
-	public function labelShouldDefaultToNouvelEmplacement() {
-		$this->assertXpath('//input[@name="libelle"][@value="** nouvel emplacement **"]');
-	}
 
+  /** @test */
+  public function labelShouldBeAlboums() {
+    $this->assertEquals('Alboums', $this->model->getLibelle());
+  }
 
-	/** @test */
-	public function rulesShouldDefault995DollarKSlashR() {
-		$this->assertXpathContentContains('//textarea[@name="regles"]', '995$k/R');
-	}
 
+  /** @test */
+  public function rulesShouldBeDollarXEqualsZeroSeven() {
+    $this->assertEquals('995$x=07', $this->model->getRegles());
+  }
 
-	/** @test */
-	public function albumsDisplayShouldBePresent() {
-		$this->assertXpathContentContains(
-			'//select[@name="ne_pas_afficher"]/option[@selected="selected"]', 
-			'Afficher les exemplaires');
-	}
 
-
-	/** @test */
-	public function backButtonShouldBePresent() {
-		$this->assertXpath('//input[@value="Retour"][contains(@onclick, "/cosmo/emplacement")]');
-	}
+  /** @test */
+  public function displayHideShouldBeHide() {
+    $this->assertEquals(1, $this->model->getNePasAfficher());
+  }
 }
 
 
 
-class Cosmo_EmplacementControllerAddPostTest extends Cosmo_EmplacementControllerTestCase {
-	public function setUp() {
-		parent::setUp();
-		$this->postDispatch(
-			'/cosmo/emplacement/validate', 
-			[
-				'libelle' => 'Atlantis',
-				'regles' => '999$y/Arcadia',
-				'ne_pas_afficher' => 0]);
-
-		$this->model = Class_CodifEmplacement::find(19);
-	}
-
-
-	/** @test */
-	public function atlantisShouldBeSaved() {
-		$this->assertNotNull($this->model);
-	}
-
-
-	/** @test */
-	public function labelShouldBeAtlantis() {
-		$this->assertEquals('Atlantis', $this->model->getLibelle());
-	}
+class Cosmo_EmplacementControllerAddTest extends Cosmo_EmplacementControllerTestCase {
+  public function setUp() {
+    parent::setUp();
+    $this->dispatch('/cosmo/emplacement/add', true);
+  }
 
 
-	/** @test */
-	public function rulesShouldBe999DollarYSlashArcadia() {
-		$this->assertEquals('999$y/Arcadia', $this->model->getRegles());
-	}
+  /** @test */
+  public function defaultRuleShouldBePresent() {
+    $this->assertXpathContentContains('//script', 'values:{"rule_zone":["995"],"rule_field":["k"],"rule_sign":["\/"],"rule_values":["R"]}');
+  }
 
 
-	/** @test */
-	public function displayHideShouldBeShow() {
-		$this->assertEquals(0, $this->model->getNePasAfficher());
-	}
+  /** @test */
+  public function defaultItemDisplayyShouldBePresent() {
+    $this->assertXpathContentContains(
+                                      '//select[@name="ne_pas_afficher"]/option[@selected="selected"]',
+                                      'Oui');
+  }
 }
 
 
 
-class Cosmo_EmplacementControllerAlbumsValidatePostWithErrorsTest extends Cosmo_EmplacementControllerTestCase {
-	public function setUp() {
-		parent::setUp();
-		$this->postDispatch(
-			'/cosmo/emplacement/validate/id/18',
-			[
-				'libelle' => '',
-				'regles' => '9#95$x=07',
-				'ne_pas_afficher' => 1]);
-
-		$this->model = Class_CodifEmplacement::find(18);
-	}
+class Cosmo_EmplacementControllerAlbumsValidatePostWithErrorsTest
+  extends Cosmo_EmplacementControllerTestCase {
 
+  public function setUp() {
+    parent::setUp();
+    $this->postDispatch('/cosmo/emplacement/edit/id/18',
+                        ['libelle' => '',
+                         'rule_zone' => ['9#95'],
+                         'rule_field' => ['#'],
+                         'rule_sign' => ['/'],
+                         'rule_values' => [''],
+                         'ne_pas_afficher' => 1]);
 
-	public function errorsProvider() {
-		return [
-			['Vous devez définir le libellé'],
-			['Le $ est absent ou mal positionné pour la règle 9#95$x=07'],
-			['Le champ n\'est pas compris entre a et z ni entre 0 et 9'],
-			['Signe de comparaison incorrect']];
-	}
+    $this->model = Class_CodifEmplacement::find(18);
+  }
 
 
-	/** 
-	 * @test 
-	 * @dataProvider errorsProvider
-	 */
-	public function errorShouldBePresent($message) {
-		$this->assertXpathContentContains('//li', $message, $this->_response->getBody());
-	}
+  /** @test */
+  public function errorsShouldBePresent() {
+    $this->assertXpathContentContains('//script',
+                                      'line_errors:[["Un champ de cette ligne ne peut \u00eatre vide lorsqu\'un des autres champs est renseign\u00e9"]]');
+  }
 
 
-	/** @test */
-	public function labelShouldNotBeSaved() {
-		$this->assertTrue($this->model->hasChangedAttribute('libelle'));
-	}
+  /** @test */
+  public function labelShouldNotBeSaved() {
+    $this->assertTrue($this->model->hasChangedAttribute('libelle'));
+  }
 
 
-	/** @test */
-	public function rulesShouldNotBeSaved() {
-		$this->assertTrue($this->model->hasChangedAttribute('regles'));
-	}
-}
\ No newline at end of file
+  /** @test */
+  public function rulesShouldNotBeSaved() {
+    $this->assertTrue($this->model->hasChangedAttribute('regles'));
+  }
+}
diff --git a/cosmogramme/cosmozend/tests/application/modules/cosmo/controllers/GenreControllerTest.php b/cosmogramme/cosmozend/tests/application/modules/cosmo/controllers/GenreControllerTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..df6ab476bfcf73fa6320c9d1469473b08abed9a2
--- /dev/null
+++ b/cosmogramme/cosmozend/tests/application/modules/cosmo/controllers/GenreControllerTest.php
@@ -0,0 +1,193 @@
+<?php
+/**
+ * Copyright (c) 2012-2014, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+abstract class Cosmo_GenreControllerTestCase extends CosmoControllerTestCase {
+  public function setUp() {
+    parent::setUp();
+    $this->fixture('Class_CodifGenre',
+                   ['id' => 5,
+                    'libelle' => '01 Energie Enjeux',
+                    'regles' => '995$q=98']);
+
+    $this->fixture('Class_CodifGenre',
+                   ['id' => 7,
+                    'libelle' => '01 Energie Politique',
+                    'regles' => '995$q=99']);
+  }
+}
+
+
+
+
+class Cosmo_GenreControllerIndexTest extends Cosmo_GenreControllerTestCase {
+  public function setUp() {
+    parent::setUp();
+    $this->dispatch('/cosmo/genre', true);
+  }
+
+
+  /** @test */
+  public function addNewGenreShouldBePresent() {
+    $this->assertXPathContentContains('//button[contains(@data-url, "/cosmo/genre/add")]',
+                                      'Ajouter un genre');
+  }
+
+
+  /** @test */
+  public function energieEnjeuxShouldBeInTable() {
+    $this->assertXPathContentContains('//table//tr//td', '01 Energie Enjeux');
+  }
+
+
+  /** @test */
+  public function energiePolitiqueShouldBeInTable() {
+    $this->assertXPathContentContains('//table//tr//td', '01 Energie Politique');
+  }
+
+
+  /** @test */
+  public function energiePolitiqueReglesShouldBeInTable() {
+    $this->assertXPathContentContains('//table//tr//td', '995$q=99');
+  }
+
+
+  /** @test */
+  public function editEnergiePolitiqueShouldBePresent() {
+    $this->assertXPath('//table//tr//td//a[contains(@href, "/cosmo/genre/edit/id/7")]');
+  }
+
+
+  /** @test */
+  public function deleteEnergiePolitiqueShouldBePresent() {
+    $this->assertXPath('//table//tr//td//a[contains(@href, "/cosmo/genre/delete/id/7")]');
+  }
+}
+
+
+
+
+class Cosmo_GenreControllerEditTest extends Cosmo_GenreControllerTestCase {
+  public function setUp() {
+    parent::setUp();
+    $this->dispatch('/cosmo/genre/edit/id/7', true);
+  }
+
+
+  /** @test */
+  public function libelleValueShouldBe01EnergiePolitique() {
+    $this->assertXPath('//form//input[@name="libelle"][@value="01 Energie Politique"]');
+  }
+
+
+  /** @test */
+  public function reglesValueShouldBe995DollarQEquals99() {
+    $this->assertXpathContentContains('//script', '"rule_zone":["995"],"rule_field":["q"],"rule_sign":["="],"rule_values":["99"]', $this->_response->getBody());
+  }
+}
+
+
+
+
+class Cosmo_GenreControllerEditPostTest extends Cosmo_GenreControllerTestCase {
+  protected $_model;
+
+  public function setUp() {
+    parent::setUp();
+    $this->postDispatch('/cosmo/genre/edit/id/7',
+                        ['libelle' => '02 Energie Politique',
+                         'rule_zone' => ['995'],
+                         'rule_field' => ['Q'],
+                         'rule_sign' => ['*'],
+                         'rule_values' => ['99']]);
+
+    $this->_model = Class_CodifGenre::find(7);
+  }
+
+
+  /** @test */
+  public function shouldRedirectToCosmoEdit() {
+    $this->assertRedirectTo('/cosmo/genre/edit/id/7');
+  }
+
+
+  /** @test */
+  public function libelleShouldBe02EnergiePolitique() {
+    $this->assertEquals('02 Energie Politique', $this->_model->getLibelle());
+  }
+
+
+  /** @test */
+  public function reglesShouldBe995DollarQStar99() {
+    $this->assertEquals('995$Q*99', $this->_model->getRegles());
+  }
+}
+
+
+
+
+class Cosmo_GenreControllerEditInvalidPostTest extends Cosmo_GenreControllerTestCase {
+  public function setUp() {
+    parent::setUp();
+    $this->postDispatch('/cosmo/genre/edit/id/7',
+                        ['libelle' => '02 Energie Politique',
+                         'rule_zone' => ['99588'],
+                         'rule_field' => ['Q'],
+                         'rule_sign' => ['='],
+                         'rule_values' => ['99']
+                         ]);
+  }
+
+
+  /** @test */
+  public function shouldNotRedirect() {
+    $this->assertNotRedirect();
+  }
+
+
+  /** @test */
+  public function pageShouldDisplayMarcZoneError() {
+    $this->assertXPathContentContains('//script',
+                                      'line_errors:[["\"99588\" n\'est pas une zone marc valide (de 001 \u00e0 999)"]]',$this->_response->getBody());
+  }
+}
+
+
+
+
+class Cosmo_GenreControllerDeleteTest extends Cosmo_GenreControllerTestCase {
+  public function setUp() {
+    parent::setUp();
+    $this->dispatch('/cosmo/genre/delete/id/7', true);
+  }
+
+
+  /** @test */
+  public function shouldRemoveItFromDB() {
+    $this->assertNull(Class_CodifGenre::find(7));
+  }
+
+
+  /** @test */
+  public function shouldRedirectToGenreIndex() {
+    $this->assertRedirectTo('/cosmo/genre/index');
+  }
+}
\ No newline at end of file
diff --git a/cosmogramme/cosmozend/tests/application/modules/cosmo/controllers/SectionControllerTest.php b/cosmogramme/cosmozend/tests/application/modules/cosmo/controllers/SectionControllerTest.php
index fef7aaf33b9fca6fe452cc7593beeb13690724a3..ddeb4454c4fdd96c003979d9080332ff4f0e5060 100644
--- a/cosmogramme/cosmozend/tests/application/modules/cosmo/controllers/SectionControllerTest.php
+++ b/cosmogramme/cosmozend/tests/application/modules/cosmo/controllers/SectionControllerTest.php
@@ -21,8 +21,6 @@
 
 
 abstract class Cosmo_SectionControllerTestCase extends CosmoControllerTestCase {
-  protected $_storm_default_to_volatile = true;
-
   public function setUp() {
     parent::setUp();
     $this->fixture('Class_CodifSection',
@@ -50,7 +48,8 @@ class Cosmo_SectionControllerIndexTest extends Cosmo_SectionControllerTestCase {
 
   /** @test */
   public function addNewSectionShouldBePresent() {
-    $this->assertXPath('//a', 'Ajouter une section');
+    $this->assertXPathContentContains('//button[contains(@data-url, "/cosmo/section/add")]',
+                                      'Ajouter une section');
   }
 
 
@@ -74,26 +73,50 @@ class Cosmo_SectionControllerIndexTest extends Cosmo_SectionControllerTestCase {
 
 
 
-
-class Cosmo_SectionControllerActionsTest extends Cosmo_SectionControllerTestCase {
+class Cosmo_SectionControllerEditPostTest extends Cosmo_SectionControllerTestCase {
+  protected $_model;
 
   public function setUp() {
     parent::setUp();
+    $this->postDispatch('/cosmo/section/edit/id/7',
+                        [
+                         'libelle' => '02 Dérivation',
+                         'rule_zone' => ['995'],
+                         'rule_field' => ['x'],
+                         'rule_sign' => ['='],
+                         'rule_values' => ['07'],
+                        ]);
+
+    $this->_model = Class_CodifSection::find(7);
   }
 
 
   /** @test */
-  public function inputLibelleShouldBePresent() {
-    $this->dispatch('/cosmo/section/edit/id/7', true);
-    $this->assertXPath('//form//input[@value="01 Energie Politique"]');
+  public function shouldRedirectToSectionEditIdSeven() {
+    $this->assertRedirectTo('/cosmo/section/edit/id/7');
   }
 
 
   /** @test */
-  public function postLibelle02ShouldSaveSection() {
-    $this->postDispatch('/cosmo/section/edit/id/7',
-                        ['libelle' => '02 Energie Politique']);
-    $this->assertEquals('02 Energie Politique', Class_CodifSection::find(7)->getLibelle());
+  public function labelShouldBeDerivation() {
+    $this->assertEquals('02 Dérivation', $this->_model->getLibelle());
+  }
+
+
+  /** @test */
+  public function rulesShouldBeDollarXEqualsZeroSeven() {
+    $this->assertEquals('995$x=07', $this->_model->getRegles());
+  }
+}
+
+
+
+
+class Cosmo_SectionControllerActionsTest extends Cosmo_SectionControllerTestCase {
+  /** @test */
+  public function inputLibelleShouldBePresent() {
+    $this->dispatch('/cosmo/section/edit/id/7', true);
+    $this->assertXPath('//form//input[@value="01 Energie Politique"]');
   }
 
 
diff --git a/cosmogramme/php/_menu.php b/cosmogramme/php/_menu.php
index 68695196cd1426c8d89467023faba3a70cbe0807..c204b75b00ce01aa5a9152567fe502caee332b0d 100644
--- a/cosmogramme/php/_menu.php
+++ b/cosmogramme/php/_menu.php
@@ -101,7 +101,7 @@ else
 	<div class="menu_section">Autorités et codifications</div>
 	<?php
 	ligneMenu("Sections","../cosmozend/cosmo/section");
-	ligneMenu("Genres","codif_genre.php");
+	ligneMenu("Genres","../cosmozend/cosmo/genre");
 	ligneMenu("Emplacements", '../cosmozend/cosmo/emplacement');
 	ligneMenu("Auteurs","codif_auteur.php");
 	ligneMenu("Matières","codif_matiere.php");
diff --git a/cosmogramme/php/classes/classe_notice_integration.php b/cosmogramme/php/classes/classe_notice_integration.php
index 180443e837fa3cdda08c18c7618badf4129c8153..0d15b3b7c42919a66eda2c959bf99384a14a1f01 100644
--- a/cosmogramme/php/classes/classe_notice_integration.php
+++ b/cosmogramme/php/classes/classe_notice_integration.php
@@ -191,7 +191,7 @@ class notice_integration {
       return;
     }
 
-    if($this->notice["type_doc"] == Class_TypeDoc::PERIODIQUE) {
+    if ($this->notice["type_doc"] == Class_TypeDoc::PERIODIQUE) {
       // Periodiques Koha et orphee (1 exemplaire pour chaque numéro)
       if(($this->id_article_periodique == Class_IntProfilDonnees::SERIAL_FORMAT_KOHA
           or $this->id_article_periodique == Class_IntProfilDonnees::SERIAL_FORMAT_ORPHEE)
@@ -212,6 +212,10 @@ class notice_integration {
                                                'date_maj not' => $date]);
       }
 
+      if($this->id_article_periodique == Class_IntProfilDonnees::SERIAL_FORMAT_PMB)
+        $this->_traitePeriodiquesPMB();
+
+
       // opsys indexpresse : on cree les articles manquants
       if($this->id_article_periodique == Class_IntProfilDonnees::SERIAL_FORMAT_ALOES_INDEXPRESS
          and $this->notice['articles_periodiques']) {
@@ -263,6 +267,37 @@ class notice_integration {
   }
 
 
+  protected function _traitePeriodiquesPMB() {
+    if ($this->notice['exemplaires']) {
+      $titre_chapeau = $this->analyseur->get_subfield('200', 'h')[0];
+      $this->notice["clef_chapeau"] = $this->indexation->codeAlphaTitre($titre_chapeau);
+      return;
+    }
+
+
+    $head_link = $this->analyseur->get_subfield('463', '9');
+    if (!$id_bull = end(explode(':', $head_link[0])))
+      return;
+
+    $id_head_record = $id_bull . '-bull';
+    $titre_chapeau = $this->analyseur->get_subfield('461', 't')[0] . ' ' . $id_head_record;
+    $this->notice["clef_chapeau"] = $this->indexation->codeAlphaTitre($titre_chapeau);
+
+    $this->notice['exemplaires'] =
+      [
+       ['code_barres' => sprintf('%s-%s',
+                                 $this->id_int_bib,
+                                 $this->notice["id_origine"]),
+        'type' => Class_Notice::TYPE_SERIAL_ARTICLE,
+        'zone995' => serialize([ ['code' => '0',
+                                  'valeur' => $id_head_record] ])
+       ]
+      ];
+    $this->notice["statut_exemplaires"]["nb_ex"] = 1;
+  }
+
+
+
   public function traiteHomogene($id_notice, $isbn, $ean, $id_commerciale, $no_request) {
     global $sql;
 
@@ -512,6 +547,7 @@ class notice_integration {
             'collection' => $this->indexation->getfullText($this->notice['collection']),
             'matieres' => $this->indexation->getfullText($this->_getFulltextSubjects()),
             'dewey' => $this->indexation->getfullText($this->notice['full_dewey']),
+            'file_content' => $this->notice['file_content'],
             'facettes' => $this->notice['facettes'],
             'isbn' => $this->notice['isbn'],
             'ean' => $this->notice['ean'],
@@ -721,7 +757,8 @@ class notice_integration {
         ->setIdNotice($id_notice)
         ->setIdOrigine($this->notice['id_origine'])
         ->setIdBib($this->id_bib)
-        ->setIdIntBib($this->id_int_bib);
+        ->setIdIntBib($this->id_int_bib)
+        ->setIdDataProfile($this->id_profil);
 
       /**
        * @see http://forge.afi-sa.fr/issues/14279
diff --git a/cosmogramme/php/classes/classe_profil_donnees.php b/cosmogramme/php/classes/classe_profil_donnees.php
index e5beeeb933cb2812b3c19bf9300fc98421272880..c6f51ca655322388bdeae9a4ac94a437da73ea2e 100644
--- a/cosmogramme/php/classes/classe_profil_donnees.php
+++ b/cosmogramme/php/classes/classe_profil_donnees.php
@@ -20,283 +20,274 @@
  */
 
 class profil_donnees {
-	const
-		HOMOGENIZATION_ISBN = -1,
-		HOMOGENIZATION_EAN = -2;
-
-	protected static
-		$_profil_cache = [],
-		$_type_docs_cache = null;
-
-	private $id_profil;									// Id sgbd
-	private $libelle;										// Libellé du profil
-	private $rejet_periodiques;					// Rejet des periodiques dans les imports
-	private $id_article_periodique;			// Mode de reconnaissance pour les articles de periodiques
-	private $accents=0;									// Types de caractères accentués
-	private $type_fichier=0;						// Type de fichier à parser (variable type_fichier)
-	private $format;										// Format du fichier à parser (variable import_format)
-	private $attributs;									// Bloc de donnees associe au format
-
-	protected $_to_array=[]; //legacy refactoring @see getProfil
-
-
-	public static function clearCache() {
-		static::$_profil_cache = [];
-		static::$_type_docs_cache = null;
-	}
-
-	public static function find($id_profil) {
-		if (isset(static::$_profil_cache[$id_profil]))
-			return static::$_profil_cache[$id_profil];
-
-		$profil = new static();
-		$profil->lire($id_profil);
-
-		return static::$_profil_cache[$profil->getId()] = $profil;
-	}
-
-
-	public function getId() {
-		return $this->id_profil;
-	}
-
-
-	public function lire($id_profil) {
-		if (!static::$_type_docs_cache)
-			static::$_type_docs_cache = Class_TypeDoc::findAll();
-
-		$all_type_docs = static::$_type_docs_cache;
-
-		// Profils d'homogeneisation : -1= isbn -2=ean
-		if ($id_profil < 0) {
-			$this->getProfilStandard($id_profil);
-			return $id_profil;
-		}
-
-		if (!$data = Class_IntProfilDonnees::find($id_profil)) {
-			if ($data = Class_IntProfilDonnees::find(1)) {
-				$this->lire(1);
-				$this->id_profil=0;
-				$this->libelle="** nouveau profil **";
-				return 0;
-			}
-
-			// Sinon on initialise a vide
-			$this->id_profil=0;
-			$this->libelle="** nouveau profil **";
-			$this->accents=0;
-			$this->rejet_periodiques=0;
-			$this->id_article_periodique=0;
-			$this->type_fichier=0;
-			$this->format=0;
-			$this->attributs=array();
-
-			// Init structure unimarc
-			foreach($all_type_docs as $type_doc)	{
-				$this->attributs[0]["type_doc"][] = ['label' => [],
-																						 'zone_995' => []];
-			}
-			return 0;
-		}
-
-
-		$this->id_profil = $id_profil;
-		$this->libelle = $data->getLibelle();
-		$this->accents = $data->getAccents();
-		$this->rejet_periodiques = $data->getRejetPeriodiques();
-		$this->id_article_periodique = $data->getIdArticlePeriodique();
-		$this->type_fichier = $data->getTypeFichier();
-		$this->format = $data->getFormat();
-	  $this->attributs=unserialize($data->getAttributs());
-
-		// Decompacter et consolider les types de docs
-		$td = $this->attributs[0]["type_doc"];
-		foreach($all_type_docs as $i => $type_doc)	{
+  const
+    HOMOGENIZATION_ISBN = -1,
+    HOMOGENIZATION_EAN = -2;
+
+  protected static
+    $_profil_cache = [],
+    $_type_docs_cache = null;
+
+  private $id_profil;                   // Id sgbd
+  private $libelle;                     // Libellé du profil
+  private $rejet_periodiques;           // Rejet des periodiques dans les imports
+  private $id_article_periodique;       // Mode de reconnaissance pour les articles de periodiques
+  private $accents=0;                   // Types de caractères accentués
+  private $type_fichier=0;            // Type de fichier à parser (variable type_fichier)
+  private $format;                    // Format du fichier à parser (variable import_format)
+  private $attributs;                   // Bloc de donnees associe au format
+
+  protected $_to_array=[]; //legacy refactoring @see getProfil
+
+
+  public static function clearCache() {
+    static::$_profil_cache = [];
+    static::$_type_docs_cache = null;
+  }
+
+  public static function find($id_profil) {
+    if (isset(static::$_profil_cache[$id_profil]))
+      return static::$_profil_cache[$id_profil];
+
+    $profil = new static();
+    $profil->lire($id_profil);
+
+    return static::$_profil_cache[$profil->getId()] = $profil;
+  }
+
+
+  public function __call($method, $args) {
+    if (method_exists($this->_data_profile, $method))
+      return call_user_func_array(array($this->_data_profile, $method), $args);
+
+    throw new RuntimeException('Call to undefined method profil_donnees::' . $method);
+  }
+
+
+  public function getId() {
+    return $this->id_profil;
+  }
+
+
+  protected static function _getTypeDocs() {
+    return static::$_type_docs_cache
+      ? static::$_type_docs_cache
+      : static::$_type_docs_cache = Class_TypeDoc::findAll();
+  }
+
+
+  public function lire($id_profil) {
+    // Profils d'homogeneisation : -1= isbn -2=ean
+    if ($id_profil < 0)
+      return $this->_init($id_profil,
+                          $id_profil == self::HOMOGENIZATION_ISBN
+                          ? Class_IntProfilDonnees::forIsbnHomogenization()
+                          : Class_IntProfilDonnees::forEanHomogenization());
+
+    if ($data_profile = Class_IntProfilDonnees::find($id_profil))
+      return $this->_init($id_profil, $data_profile);
+
+    if ($data_profile = Class_IntProfilDonnees::find(1)) {
+      $this->_init(0, $data_profile);
+      $this->libelle = '** nouveau profil **';
+      return 0;
+    }
+
+    $attribs = [];
+    foreach($this->_getTypeDocs() as $type_doc)  {
+      $this->attributs[0]['type_doc'][] = ['label' => '',
+                                           'zone_995' => ''];
+    }
+
+    $this->_init(0,
+                 Class_IntProfilDonnees::newInstance(['libelle' => '** nouveau profil **',
+                                                      'attributs' => serialize($attribs)]));
+    return 0;
+  }
+
+
+  protected function _init($id_profil, $data_profile) {
+    $this->id_profil = $id_profil;
+    $this->_data_profile = $data_profile;
+
+    $this->libelle = $this->_data_profile->getLibelle();
+    $this->accents = $this->_data_profile->getAccents();
+    $this->rejet_periodiques = $this->_data_profile->getRejetPeriodiques();
+    $this->id_article_periodique = $this->_data_profile->getIdArticlePeriodique();
+    $this->type_fichier = $this->_data_profile->getTypeFichier();
+    $this->format = $this->_data_profile->getFormat();
+    $this->attributs = unserialize($this->_data_profile->getAttributs());
+
+    // Decompacter et consolider les types de docs
+    $td = $this->attributs[0]["type_doc"];
+    foreach($this->_getTypeDocs() as $i => $type_doc)  {
       $this->attributs[0]["type_doc"][$i]["code"] = $type_doc->getId();
-			$this->attributs[0]["type_doc"][$i]["libelle"] = $type_doc->getLibelle();
-			$this->attributs[0]["type_doc"][$i]["label"] = array();
-			$this->attributs[0]["type_doc"][$i]["zone_995"] = array();
+      $this->attributs[0]["type_doc"][$i]["libelle"] = $type_doc->getLibelle();
+      $this->attributs[0]["type_doc"][$i]["label"] = [];
+      $this->attributs[0]["type_doc"][$i]["zone_995"] = [];
 
       for($j=0; $j < count($td); $j++) {
         if(!isset($td[$j]["label"]))
           continue;
 
-				if($td[$j]["code"] == $type_doc->getId()) {
-					$this->attributs[0]["type_doc"][$i]["label"]=explode(";",$td[$j]["label"]);
-					$this->attributs[0]["type_doc"][$i]["zone_995"]=explode(";",$td[$j]["zone_995"]);
-					break;
-				}
-			}
-		}
-
-		// decompacter et consolider champs xml
-		$champs=getCodifsVariable("champs_abonne");
-		foreach($champs as $champ) {
-			$code=$champ["code"];
-			if($code !="NULL" and !isset($this->attributs[5]["xml_champs_abonne"][$code]))
+        if($td[$j]["code"] == $type_doc->getId()) {
+          $this->attributs[0]["type_doc"][$i]["label"]=explode(";",$td[$j]["label"]);
+          $this->attributs[0]["type_doc"][$i]["zone_995"]=explode(";",$td[$j]["zone_995"]);
+          break;
+        }
+      }
+    }
+
+    // decompacter et consolider champs xml
+    $champs = getCodifsVariable("champs_abonne");
+    foreach($champs as $champ) {
+      $code = $champ["code"];
+      if ($code != "NULL" and !isset($this->attributs[5]["xml_champs_abonne"][$code]))
         $this->attributs[5]["xml_champs_abonne"][$code]="";
-		}
-		return $this->id_profil;
-	}
-
-
-	public function toArray() {
-		$profil["id_profil"]=$this->id_profil;
-		$profil["libelle"]=$this->libelle;
-		$profil["accents"]=$this->accents;
-		$profil["rejet_periodiques"]=$this->rejet_periodiques;
-		$profil["id_article_periodique"]=$this->id_article_periodique;
-		$profil["type_fichier"]=$this->type_fichier;
-		$profil["format"]=$this->format;
-		$profil["attributs"]=$this->attributs;
-
-		if(!isset($profil["attributs"][0]["champ_cote"])
+    }
+
+    return $this->id_profil;
+  }
+
+
+  public function toArray() {
+    $profil["id_profil"]=$this->id_profil;
+    $profil["libelle"]=$this->libelle;
+    $profil["accents"]=$this->accents;
+    $profil["rejet_periodiques"]=$this->rejet_periodiques;
+    $profil["id_article_periodique"]=$this->id_article_periodique;
+    $profil["type_fichier"]=$this->type_fichier;
+    $profil["format"]=$this->format;
+    $profil["attributs"]=$this->attributs;
+
+    if(!isset($profil["attributs"][0]["champ_cote"])
        || !$profil["attributs"][0]["champ_cote"])
       $profil["attributs"][0]["champ_cote"] = "k";
 
-		if(!isset($profil["attributs"][0]["champ_url"]))
-			$profil["attributs"][0]["champ_url"] = ['zone' => '', 'champ' => ''];
-
-		return $profil;
-	}
-
-	public function getProfil($id_profil) {
-		return static::find($id_profil)->toArray();
-	}
-
-
-	/**
-	 * Profils standard réhomogénéisations : -1: panier d'isbn -2:panier d'ean
-	 */
-	private function getProfilStandard($id_profil) {
-		$this->id_profil=$id_profil;
-		$this->accents=1;
-		$this->rejet_periodiques=0;
-		$this->id_article_periodique=0;
-		$this->type_fichier=0;
-		$this->format=1;
-		if($id_profil == self::HOMOGENIZATION_ISBN) {
-			$this->libelle="Homogénéisation d'isbn";
-			$this->attributs[1]["champs"]="isbn";
-		} else {
-			$this->libelle="Homogénéisation d'ean";
-			$this->attributs[1]["champs"]="ean";
-		}
-	}
-
-
-	public function getCombo($valeur) {
-		global $sql;
-		$data=$sql->fetchAll("Select id_profil,libelle from profil_donnees");
-		$combo='<select name="profil">';
-		for($i=0; $i<count($data); $i++)
-		{
-			$lig=$data[$i];
-			if($valeur==$lig["id_profil"]) $selected=" selected"; else $selected="";
-			$combo.='<option value="'.$lig["id_profil"].'"'.$selected.'>'.$lig["libelle"].'</option>';
-		}
-		$combo.='</select>';
-		return $combo;
-	}
-
-
-	public function ecrire($id_profil,$libelle,$accents,$rejet_periodiques,$type_fichier,$format,$attributs,$id_article_periodique) {
-		//tracedebug(1,$attributs,true);
-		if(!trim($libelle)) return false;
-		global $sql;
-		$attributs=serialize($attributs);
-		$data=compact("id_profil","libelle","accents","rejet_periodiques","type_fichier","format","attributs","id_article_periodique");
-		if( $id_profil == 0 ) $sql->insert("profil_donnees", $data);
-		else $sql->update("Update profil_donnees set @SET@ Where id_profil ='$id_profil'",$data);
-	}
-
-
-	public function getTypeDoc($label, $z995r, $z995p) {
-		if (isset($z995p[0]) && (strToUpper(substr(trim($z995p[0]), 0, 1)) == 'P'))
-			return ['code' => 2, 'libelle' => 'Périodiques'];
-
-		if ($this->isArticlePeriodique($label))
-			return ['code' => Class_TypeDoc::SERIAL_ARTICLE, 'libelle' => 'Article de périodique'];
-
-		// First we check 995$r subfield.
-		foreach($this->attributs[0]["type_doc"] as $td)	{
-			if ($this->isTypeDocMatchOneBiblioItem($z995r, $td))
-				return ['code' => $td['code'], 'libelle' => $td['libelle']];
-		}
-
-		// Then, we check label.
-		foreach($this->attributs[0]["type_doc"] as $td)	{
-			if ($this->isTypeDocMatchLabel($label, $td))
-				return ['code' => $td['code'], 'libelle' => $td['libelle']];
-		}
-
-		return ['code' => 0, 'libelle' => 'non identifié'];
-	}
-
-
-	public function isTypeDocMatchOneBiblioItem($z995, $td) {
-		$z995 = array_map('strtolower', $z995);
+    if(!isset($profil["attributs"][0]["champ_url"]))
+      $profil["attributs"][0]["champ_url"] = ['zone' => '', 'champ' => ''];
+
+    return $profil;
+  }
+
+
+  public function getProfil($id_profil) {
+    return static::find($id_profil)->toArray();
+  }
+
+
+  public function getCombo($valeur) {
+    global $sql;
+    $data=$sql->fetchAll("Select id_profil,libelle from profil_donnees");
+    $combo='<select name="profil">';
+    for($i=0; $i<count($data); $i++)
+      {
+        $lig=$data[$i];
+        if($valeur==$lig["id_profil"]) $selected=" selected"; else $selected="";
+        $combo.='<option value="'.$lig["id_profil"].'"'.$selected.'>'.$lig["libelle"].'</option>';
+      }
+    $combo.='</select>';
+    return $combo;
+  }
+
+
+  public function ecrire($id_profil,$libelle,$accents,$rejet_periodiques,$type_fichier,$format,$attributs,$id_article_periodique) {
+    //tracedebug(1,$attributs,true);
+    if(!trim($libelle)) return false;
+    global $sql;
+    $attributs=serialize($attributs);
+    $data=compact("id_profil","libelle","accents","rejet_periodiques","type_fichier","format","attributs","id_article_periodique");
+    if( $id_profil == 0 ) $sql->insert("profil_donnees", $data);
+    else $sql->update("Update profil_donnees set @SET@ Where id_profil ='$id_profil'",$data);
+  }
+
+
+  public function getTypeDoc($label, $z995r, $z995p) {
+    if (isset($z995p[0]) && (strToUpper(substr(trim($z995p[0]), 0, 1)) == 'P'))
+      return ['code' => 2, 'libelle' => 'Périodiques'];
+
+    if ($this->isArticlePeriodique($label))
+      return ['code' => Class_TypeDoc::SERIAL_ARTICLE, 'libelle' => 'Article de périodique'];
+
+    // First we check 995$r subfield.
+    foreach($this->attributs[0]["type_doc"] as $td)   {
+      if ($this->isTypeDocMatchOneBiblioItem($z995r, $td))
+        return ['code' => $td['code'], 'libelle' => $td['libelle']];
+    }
+
+    // Then, we check label.
+    foreach($this->attributs[0]["type_doc"] as $td)   {
+      if ($this->isTypeDocMatchLabel($label, $td))
+        return ['code' => $td['code'], 'libelle' => $td['libelle']];
+    }
+
+    return ['code' => 0, 'libelle' => 'non identifié'];
+  }
+
+
+  public function isTypeDocMatchOneBiblioItem($z995, $td) {
+    $z995 = array_map('strtolower', $z995);
 
     $identification_codes = is_array($td["zone_995"])
-			? array_map('strtolower', array_filter($td["zone_995"]))
-			: [];
+      ? array_map('strtolower', array_filter($td["zone_995"]))
+      : [];
 
-		foreach($identification_codes as $identification_code)	{
-			if ($z995[0] == $identification_code)
-				return true;
-		}
-		return false;
-	}
+    foreach($identification_codes as $identification_code)  {
+      if ($z995[0] == $identification_code)
+        return true;
+    }
+    return false;
+  }
 
 
-	public function isTypeDocMatchLabel($label, $td) {
-		if (!trim($label) || !is_array($td["label"]))
-			return false;
+  public function isTypeDocMatchLabel($label, $td) {
+    if (!trim($label) || !is_array($td["label"]))
+      return false;
 
-		foreach(array_filter($td["label"]) as $item) {
-			if (0 === strpos($label, $item)) {
-				return true;
-			}
-		}
-		return false;
-	}
+    foreach(array_filter($td["label"]) as $item) {
+      if (0 === strpos($label, $item)) {
+        return true;
+      }
+    }
+    return false;
+  }
 
 //---------------------------------------------------------------------------------
 // rend les infos d'un fichier a itegrer
 //---------------------------------------------------------------------------------
-	public function getInfosFichierIntegration($id_integration)
-	{
-		// enregistrement integration
-		if(!$id_integration) return false;
-		$data=fetchEnreg("select * from integrations where id=$id_integration");
-		if(!$data) return false;
-
-		// infos integration
-		$path=getVariable("integration_path");
-		$ret["fichier"]=$data["fichier"];
-		if(file_exists($path.$ret["fichier"]))
-		{
-			$ret["taille"]=(int)filesize($path.$ret["fichier"])/1024;
-			$ret["taille"]=number_format($ret["taille"],0,","," ")." ko";
-			$ret["taille"]=str_replace(" ","&nbsp;",$ret["taille"]);
-		}
-		else $ret["taille"]="?";
-
-		// type du fichier
-		$type_fic=fetchOne("select type_fichier from profil_donnees where id_profil=".$data["profil"]);
-		$ret["type_fichier"]=getLibCodifVariable("type_fichier",$type_fic);
-
-		// retour
-		return $ret;
-	}
-
-
-	public function isArticlePeriodique($label) {
-		switch($this->id_article_periodique) {
-			case 1: if($label=="aa") return true; // pergame
-			case 2: if($label=="aa") return true; // opsys indexpresse
-			default: return false;
-		}
-	}
+  public function getInfosFichierIntegration($id_integration)
+  {
+    // enregistrement integration
+    if(!$id_integration) return false;
+    $data=fetchEnreg("select * from integrations where id=$id_integration");
+    if(!$data) return false;
+
+    // infos integration
+    $path=getVariable("integration_path");
+    $ret["fichier"]=$data["fichier"];
+    if(file_exists($path.$ret["fichier"]))
+      {
+        $ret["taille"]=(int)filesize($path.$ret["fichier"])/1024;
+        $ret["taille"]=number_format($ret["taille"],0,","," ")." ko";
+        $ret["taille"]=str_replace(" ","&nbsp;",$ret["taille"]);
+      }
+    else $ret["taille"]="?";
+
+    // type du fichier
+    $type_fic=fetchOne("select type_fichier from profil_donnees where id_profil=".$data["profil"]);
+    $ret["type_fichier"]=getLibCodifVariable("type_fichier",$type_fic);
+
+    // retour
+    return $ret;
+  }
+
+
+  public function isArticlePeriodique($label) {
+    switch($this->id_article_periodique) {
+      case 1: if($label=="aa") return true; // pergame
+      case 2: if($label=="aa") return true; // opsys indexpresse
+      default: return false;
+    }
+  }
 }
 ?>
\ No newline at end of file
diff --git a/cosmogramme/php/classes/classe_unimarc.php b/cosmogramme/php/classes/classe_unimarc.php
index aee82556b88fdc97aab999103cb7a543326083b0..9d1ccacbc8d32a64645140bdf7180f35fc36e413 100644
--- a/cosmogramme/php/classes/classe_unimarc.php
+++ b/cosmogramme/php/classes/classe_unimarc.php
@@ -121,7 +121,7 @@ class notice_unimarc extends iso2709_record {
     if ($type_doc['code'] == Class_TypeDoc::SERIAL_ARTICLE)
       return $this->getNoticeIntegrationArticlePeriodique();
 
-    if ($type_doc['code'] == 2)
+    if ($type_doc['code'] == Class_TypeDoc::PERIODIQUE)
       $notice['articles_periodiques'] = $this->getIdArticlesPeriodiques();
 
     // exemplaires
@@ -195,6 +195,7 @@ class notice_unimarc extends iso2709_record {
     $notice["langues"] = $this->getLangues();
     $notice["champs_forces"] = $this->getChampsForces();
     $notice["interet"] = $this->getCentreInteret();
+    $notice["file_content"] = $this->getFileContent();
     $notice["statut_exemplaires"] = $ex["statut_exemplaires"];
     $notice["exemplaires"] = isset($ex["exemplaires"]) ?  $ex["exemplaires"] : [];
 
@@ -254,8 +255,7 @@ class notice_unimarc extends iso2709_record {
     // identifiants
     switch($this->profil["id_article_periodique"])
     {
-      // pergame
-      case 1:
+      case Class_IntProfilDonnees::SERIAL_FORMAT_PERGAME:
         $titre=$this->get_subfield("461","t");
         $numero=$this->get_subfield("461","v");
         $notice["clef_chapeau"]=$this->indexation->codeAlphaTitre($titre[0]);
@@ -263,8 +263,7 @@ class notice_unimarc extends iso2709_record {
         $notice["titre_numero"]=$titre[0]." n° ".$numero[0];
         break;
 
-      // opsys indexpresse
-      case 2:
+      case Class_IntProfilDonnees::SERIAL_FORMAT_ALOES_INDEXPRESS:
         $id=$this->get_subfield("001");
         $notice["clef_unimarc"]=$id[0];
         $notice["info_id"]=$id[0];
@@ -286,8 +285,7 @@ class notice_unimarc extends iso2709_record {
 // Identifiants des articles de periodiques a partir de la notice du numero
 // ----------------------------------------------------------------------------
   private function getIdArticlesPeriodiques()  {
-    // opsys indexpresse
-    if ($this->profil["id_article_periodique"] != 2)
+    if ($this->profil["id_article_periodique"] != Class_IntProfilDonnees::SERIAL_FORMAT_ALOES_INDEXPRESS)
       return [];
 
     $ret = [];
@@ -342,6 +340,7 @@ class notice_unimarc extends iso2709_record {
     return $champs_nouveaute;
   }
 
+
   public function getExemplaires() {
     $champ_code_barres = $this->getBarcode();
 
@@ -1004,6 +1003,7 @@ class notice_unimarc extends iso2709_record {
   public function getClefChapeau() {
     if (!$titre = $this->get_subfield('461', 't'))
       $titre = $this->get_subfield('410', 't');
+
     return $titre
       ? $this->indexation->codeAlphaTitre(trim($titre[0]))
       : '';
@@ -1351,6 +1351,12 @@ class notice_unimarc extends iso2709_record {
   }
 
 
+  public function getFileContent() {
+    return (new Class_Cosmogramme_Integration_Record_FileContent())
+      ->getContent($this, $this->profil_unimarc);
+  }
+
+
   public function getCentreInteret() {
     $zone_interet = (!$this->profil['attributs'][6]['zone'])
       ? '932'
diff --git a/cosmogramme/php/codif_emplacement.php b/cosmogramme/php/codif_emplacement.php
deleted file mode 100644
index 93414f6be7d0df016d70e135088aee2a94adabdb..0000000000000000000000000000000000000000
--- a/cosmogramme/php/codif_emplacement.php
+++ /dev/null
@@ -1,145 +0,0 @@
-<?PHP
-/**
- * Copyright (c) 2012, Agence Française Informatique (AFI). All rights reserved.
- *
- * BOKEH is free software; you can redistribute it and/or modify
- * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
- * the Free Software Foundation.
- *
- * There are special exceptions to the terms and conditions of the AGPL as it
- * is applied to this software (see README file).
- *
- * BOKEH is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
- *
- * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
- * along with BOKEH; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA 
- */
-///////////////////////////////////////////////////////////////////
-//
-//        CODIFS : EMPLACEMENTS
-//
-////////////////////////////////////////////////////////////////////
-include("_init_frame.php");
-
-require_once( "fonctions/objets_saisie.php");
-
-?>
-
-<h1>Codification des emplacements</h1>
- 
-<?PHP
-
-//---------------------------------------------------------------------------------
-// CREER
-//---------------------------------------------------------------------------------
-if($_REQUEST["action"]=="CREER")
-{
-	$action=array("libelle" => "** nouvel emplacement **","regles" => "995\$k/R");
-	print(BR.BR.BR);
-	afficherEmplacement($action,"block");
-	print('</form></body></html>');
-	exit;
-}
-//---------------------------------------------------------------------------------
-// VALIDER
-//---------------------------------------------------------------------------------
-if($_REQUEST["action"]=="VALIDER")
-{
-	// Vérif de la syntaxe des règles
-	$regles=trim($_POST["regles"]);
-	if(! $regles) erreurFormat("Vous devez définir au moins une règle");		$regles=str_replace(" ","",$regles);
-	$test = explode("\n",$regles);
-	foreach($test as $regle)
-	{
-		$nb++;
-		$zone=substr($regle,0,3); if(intval($zone) != $zone) erreurFormat("La zone est mal définie pour la règle n° ".$nb);
-		if(substr($regle,3,1) != "\$") erreurFormat("Le \$ est absent ou mal positionné pour la règle n° ".$nb);
-		$champ=substr($regle,4,1); if(($champ < "0" or $champ >"9") and ($champ < "a" or $champ > "z")) erreurFormat("Le champ est mal défini pour la règle n° ".$nb);
-		$valeurs=substr($regle,6); if(!trim($valeurs)) erreurFormat("Indiquez des valeurs pour la règle n° ".$nb);
-		$signe=substr($regle,5,1); if( strpos("=/*",$signe) === false) erreurFormat("Signe de comparaison incorrect pour la règle n° ".$nb);
-		$elem=explode(" ",$regle);
-	}
-	
-	// ecriture
-	$id_emplacement=$_POST["id_emplacement"];
-	$_POST["regles"]=$regles;
-	if(!$id_emplacement) $sql->insert("codif_emplacement",$_POST);
-	else 
-	{
-		unset($_POST["id_emplacement"]);
-		$sql->update("update codif_emplacement set @SET@ where id_emplacement=$id_emplacement",$_POST);
-	}
-
-}
-//---------------------------------------------------------------------------------
-// SUPPRIMER
-//---------------------------------------------------------------------------------
-
-if($_REQUEST["action"]=="SUPPRIMER")
-{
-	$id_emplacement=$_REQUEST["id_emplacement"];
-	if($id_emplacement)	$sql->execute("delete from codif_emplacement where id_emplacement =$id_emplacement");
-}
-
-//---------------------------------------------------------------------------------
-// LISTE 
-//---------------------------------------------------------------------------------
-print('<div class="liste">');
-
-$liste=$sql->fetchAll("Select * from codif_emplacement order by libelle");
-for($p=0; $p< count($liste); $p++)
-{
-	$emplacement=$liste[$p];
-	$img="plus.gif";
-	$display="none";
-	if($emplacement["id_emplacement"] == $_REQUEST["id_emplacement"])
-		{
-			$img="moins.gif";
-			$display="block";
-		}
-		print('<div class="liste_img"><img id="Iemplacement'.$emplacement["id_emplacement"].'" src="'.URL_IMG.$img.'" onclick="contracter_bloc(\'emplacement'.$emplacement["id_emplacement"].'\')" style="cursor:pointer"></div>');
-		print('<div class="liste_titre">'. $emplacement["libelle"].'</div>');
-		afficherEmplacement($emplacement,$display);
-}
-print('</div>');
-// Bouton ajouter
-$bouton_ajout=rendBouton("Ajouter un emplacement","codif_emplacement","action=CREER");
-print(BR.BR.$bouton_ajout);
-
-print('</body></html>');
-exit;
-
-function afficherEmplacement($emplacement,$display)
-{
-	print('<div class="form" id="emplacement'.$emplacement["id_emplacement"].'" style="width:600px;margin-left:20px;display:'.$display.'">');
-	print('<form method="post" action="'.URL_BASE.'php/codif_emplacement.php?action=VALIDER">');
-	print('<input type="hidden" name="id_emplacement" value="'.$emplacement["id_emplacement"].'">');
-	print('<table class="form" cellspacing="0" cellpadding="5">');
-	print('<tr><th class="form" colspan="2" align="left">Création d\'emplacement</th></tr>');
-	print('<tr><td class="form_first" align="right" width="35%">Libellé</td><td class="form_first">'.getChamp("libelle",$emplacement["libelle"],43).'</td></tr>');
-	print('<tr><td class="form_first" align="center" colspan="2"><div class="commentaire">Syntaxe : [zone$champ][signe][valeur1;valeur2;etc...] - Ex : 995$a = a<br>Signes : "=" égal - "/" commence par - "*" contient</div></td></tr>');
-	print('<tr><td class="form" align="right" valign="top">Règles de reconnaissance</td><td class="form">'.getTextarea("regles",$emplacement["regles"],40,5).'</td></tr>');
-	print('<tr><td class="form" align="right" valign="top">Affichage des exemplaires</td><td class="form">'.getComboSimple("ne_pas_afficher",$emplacement["ne_pas_afficher"],array('0'=>'afficher les exemplaires','1'=>"Ne pas afficher les exemplaires")).'</td></tr>');
-	
-	// Boutons maj
-	print('<tr><th class="form" colspan="2" align="center">');
-	$bouton_valider='<input type="submit" class="bouton" value="Valider">';
-	$bouton_supprimer=rendBouton("Supprimer","codif_emplacement","action=SUPPRIMER&id_emplacement=".$emplacement["id_emplacement"]);
-	print($bouton_valider.str_repeat("&nbsp;",5).$bouton_supprimer);
-	print('</th></tr></table></form></div>');
-}
-
-function erreurFormat($erreur)
-{
-	print('<span class = "rouge">'.$erreur.'</span>');
-	$action=$_POST;
-	print(BR.BR.BR);
-	afficherEmplacement($action,"block");
-	print('</form></body></html>');
-	exit;
-}
-?>
\ No newline at end of file
diff --git a/cosmogramme/php/codif_genre.php b/cosmogramme/php/codif_genre.php
deleted file mode 100644
index 15d83bdac6676c0e347d8ff845bc5d951e31a819..0000000000000000000000000000000000000000
--- a/cosmogramme/php/codif_genre.php
+++ /dev/null
@@ -1,149 +0,0 @@
-<?PHP
-/**
- * Copyright (c) 2012, Agence Française Informatique (AFI). All rights reserved.
- *
- * BOKEH is free software; you can redistribute it and/or modify
- * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
- * the Free Software Foundation.
- *
- * There are special exceptions to the terms and conditions of the AGPL as it
- * is applied to this software (see README file).
- *
- * BOKEH is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
- *
- * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
- * along with BOKEH; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA 
- */
-///////////////////////////////////////////////////////////////////
-//
-//        CODIFS : GENRES
-//
-////////////////////////////////////////////////////////////////////
-include("_init_frame.php");
-
-require_once( "fonctions/objets_saisie.php");
-
-?>
-
-<h1>Codification des genres</h1>
-
-<?PHP
-
-//---------------------------------------------------------------------------------
-// CREER
-//---------------------------------------------------------------------------------
-if($_REQUEST["action"]=="CREER")
-{
-	$action=array("libelle" => "** nouveau genre **","regles" => "995\$k/R");
-	print(BR.BR.BR);
-	afficherGenre($action,"block");
-	print('</form></body></html>');
-	exit;
-}
-//---------------------------------------------------------------------------------
-// VALIDER
-//---------------------------------------------------------------------------------
-if($_REQUEST["action"]=="VALIDER")
-{
-	// Vérif de la syntaxe des règles
-	$regles=trim($_POST["regles"]);
-	if(! $regles) erreurFormat("Vous devez définir au moins une règle");
-	$regles=str_replace(" ","",$regles);
-	$test = explode("\n",$regles);
-	foreach($test as $regle)
-	{
-		$nb++;
-		$zone=substr($regle,0,3);
-		if(intval($zone) != $zone) erreurFormat("La zone est mal définie pour la règle n° ".$nb);
-		if(substr($regle,3,1) != "\$") erreurFormat("Le \$ est absent ou mal positionné pour la règle n° ".$nb);
-		$champ=substr($regle,4,1);
-		if(($champ < "0" or $champ >"9") and ($champ < "a" or $champ > "z")) erreurFormat("Le champ est mal défini pour la règle n° ".$nb);
-		$valeurs=substr($regle,6);
-		if(!trim($valeurs)) erreurFormat("Indiquez des valeurs pour la règle n° ".$nb);
-		$signe=substr($regle,5,1);
-		if( strpos("=/*",$signe) === false) erreurFormat("Signe de comparaison incorrect pour la règle n° ".$nb);
-		$elem=explode(" ",$regle);
-	}
-
-	// ecriture
-	$id_genre=$_POST["id_genre"];
-	$_POST["regles"]=$regles;
-	if(!$id_genre) $sql->insert("codif_genre",$_POST);
-	else
-	{
-		unset($_POST["id_genre"]);
-		$sql->update("update codif_genre set @SET@ where id_genre=$id_genre",$_POST);
-	}
-
-}
-//---------------------------------------------------------------------------------
-// SUPPRIMER
-//---------------------------------------------------------------------------------
-
-if($_REQUEST["action"]=="SUPPRIMER")
-{
-	$id_genre=$_REQUEST["id_genre"];
-	if($id_genre)	$sql->execute("delete from codif_genre where id_genre =$id_genre");
-}
-
-//---------------------------------------------------------------------------------
-// LISTE 
-//---------------------------------------------------------------------------------
-print('<div class="liste">');
-
-$liste=$sql->fetchAll("Select * from codif_genre order by libelle");
-for($p=0; $p< count($liste); $p++)
-{
-	$genre=$liste[$p];
-	$img="plus.gif";
-	$display="none";
-	if($genre["id_genre"] == $_REQUEST["id_genre"])
-	{
-		$img="moins.gif";
-		$display="block";
-	}
-	print('<div class="liste_img"><img id="Igenre'.$genre["id_genre"].'" src="'.URL_IMG.$img.'" onclick="contracter_bloc(\'genre'.$genre["id_genre"].'\')" style="cursor:pointer"></div>');
-	print('<div class="liste_titre">'. $genre["libelle"].'</div>');
-	affichergenre($genre,$display);
-}
-print('</div>');
-// Bouton ajouter
-$bouton_ajout=rendBouton("Ajouter un genre","codif_genre","action=CREER");
-print(BR.BR.$bouton_ajout);
-
-print('</body></html>');
-exit;
-
-function afficherGenre($genre,$display)
-{
-	print('<div class="form" id="genre'.$genre["id_genre"].'" style="width:600px;margin-left:20px;display:'.$display.'">');
-	print('<form method="post" action="'.URL_BASE.'php/codif_genre.php?action=VALIDER">');
-	print('<input type="hidden" name="id_genre" value="'.$genre["id_genre"].'">');
-	print('<table class="form" cellspacing="0" cellpadding="5">');
-	print('<tr><th class="form" colspan="2" align="left">Création de genre</th></tr>');
-	print('<tr><td class="form_first" align="right" width="35%">Libellé</td><td class="form_first">'.getChamp("libelle",$genre["libelle"],43).'</td></tr>');
-	print('<tr><td class="form_first" align="center" colspan="2"><div class="commentaire">Syntaxe : [zone$champ][signe][valeur1;valeur2;etc...] - Ex : 995$a = a<br>Signes : "=" égal - "/" commence par - "*" contient</div></td></tr>');
-	print('<tr><td class="form" align="right" valign="top">Règles de reconnaissance</td><td class="form">'.getTextarea("regles",$genre["regles"],40,5).'</td></tr>');
-
-	// Boutons maj
-	print('<tr><th class="form" colspan="2" align="center">');
-	$bouton_valider='<input type="submit" class="bouton" value="Valider">';
-	$bouton_supprimer=rendBouton("Supprimer","codif_genre","action=SUPPRIMER&id_genre=".$genre["id_genre"]);
-	print($bouton_valider.str_repeat("&nbsp;",5).$bouton_supprimer);
-	print('</th></tr></table></form></div>');
-}
-
-function erreurFormat($erreur)
-{
-	print('<span class = "rouge">'.$erreur.'</span>');
-	$action=$_POST;
-	print(BR.BR.BR);
-	affichergenre($action,"block");
-	print('</form></body></html>');
-	exit;
-}
-?>
\ No newline at end of file
diff --git a/cosmogramme/php/fonctions/objets_saisie.php b/cosmogramme/php/fonctions/objets_saisie.php
index c9b8dd6ae9145f2b822af6c95b9f0db29cefaa45..75ef019d26eeba3f5721a93f04c238cfcd876885 100644
--- a/cosmogramme/php/fonctions/objets_saisie.php
+++ b/cosmogramme/php/fonctions/objets_saisie.php
@@ -20,58 +20,58 @@
  */
 
 function getComboSimple($name, $valeur, $liste, $tous=false, $events='') {
-	if ('' != $events)
-		$events = ' ' . $events;
-	$combo = '<select id="' . $name . '" name="' . $name . '" ' . $events . '>';
-	if ($tous)
-		$combo .= '<option value="">tous</option>';
-	foreach ($liste as $clef => $libelle) {
-		$selected = ($valeur == $clef) ? ' selected' : '';
-		$combo .= '<option value="' . $clef . '"' . $selected . '>' . $libelle . '</option>';
-	}
-	return $combo . '</select>';
+  if ('' != $events)
+    $events = ' ' . $events;
+  $combo = '<select id="' . $name . '" name="' . $name . '" ' . $events . '>';
+  if ($tous)
+    $combo .= '<option value="">tous</option>';
+  foreach ($liste as $clef => $libelle) {
+    $selected = ($valeur == $clef) ? ' selected' : '';
+    $combo .= '<option value="' . $clef . '"' . $selected . '>' . $libelle . '</option>';
+  }
+  return $combo . '</select>';
 }
 
 
 function getOuiNon($name, $value) {
-	return getComboSimple($name, $value, ['' => 'Non', '1' => 'Oui']);
+  return getComboSimple($name, $value, ['' => 'Non', '1' => 'Oui']);
 }
 
 
 function getComboCodif($name, $clef, $valeur, $events='', $tous=false) {
-	$data = fetchOne('select liste from variables where clef=\''. $clef . '\'');
-	$v = array_filter(explode(chr(13) . chr(10), $data));
-	$values = [];
-	for ($i=0; $i < count($v); $i++) {
-		$elem = explode(':', $v[$i]);
-		$values[$elem[0]] = $elem[1];
-	}
-	return getComboSimple($name, $valeur, $values, $tous, $events);
+  $data = fetchOne('select liste from variables where clef=\''. $clef . '\'');
+  $v = array_filter(explode(chr(13) . chr(10), $data));
+  $values = [];
+  for ($i=0; $i < count($v); $i++) {
+    $elem = explode(':', $v[$i]);
+    $values[$elem[0]] = $elem[1];
+  }
+  return getComboSimple($name, $valeur, $values, $tous, $events);
 }
 
 
 function getComboTable($name, $table, $clef, $valeur, $condition='', $tous=false) {
-	global $sql;
-	$req = 'select ' . $clef . ',libelle from ' .$table;
-	$data = $sql->fetchAll($req . ' ' . $condition);
-	$values = [];
-	for($i=0; $i < count($data); $i++) {
-		$elem = $data[$i];
-		$values[$elem[$clef]] = $elem['libelle'];
-	}
-	return getComboSimple($name, $valeur, $values, $tous);
+  global $sql;
+  $req = 'select ' . $clef . ',libelle from ' .$table;
+  $data = $sql->fetchAll($req . ' ' . $condition);
+  $values = [];
+  for($i=0; $i < count($data); $i++) {
+    $elem = $data[$i];
+    $values[$elem[$clef]] = $elem['libelle'];
+  }
+  return getComboSimple($name, $valeur, $values, $tous);
 }
 
 
 function getChamp($id, $valeur, $taille) {
-	return '<input type="text" name="'.$id.'" value="'.$valeur .'"'
-		. (($taille) ? ' size="' . $taille . '"': '') . '>';
+  return '<input type="text" name="'.$id.'" value="'.$valeur .'"'
+    . (($taille) ? ' size="' . $taille . '"': '') . '>';
 }
 
 
 function getTextarea($id, $valeur, $largeur, $hauteur) {
-	return '<textarea name="'.$id.'" cols="'.$largeur .'" rows="'.$hauteur.'">'
-		. $valeur . '</textarea>';
+  return '<textarea name="'.$id.'" cols="'.$largeur .'" rows="'.$hauteur.'">'
+    . $valeur . '</textarea>';
 }
 
 
@@ -79,108 +79,124 @@ function getTextarea($id, $valeur, $largeur, $hauteur) {
  * Blocs des parametres de communication avec le sigb
  */
 function getBlocsParams($id_bib, $type, $valeurs) {
-	$valeurs = unserialize($valeurs);
-	$types = getCodifsVariable("comm_sigb");
+  $valeurs = unserialize($valeurs);
+  $types = getCodifsVariable("comm_sigb");
 
-	foreach($types as $item) {
-		$clef = $item['code'];
-		$bloc .= sprintf('<div id="comm_%s_%s" style="display:%s">',
-										 $id_bib, $clef, ($clef == $type) ? 'block' : 'none');
+  foreach($types as $item) {
+    $clef = $item['code'];
+    $bloc .= sprintf('<div id="comm_%s_%s" style="display:%s">',
+                     $id_bib, $clef, ($clef == $type) ? 'block' : 'none');
 
-		$params = [];
-		$champs_params = [];
-		$titres[0] = 'Paramètres';
+    $params = [];
+    $champs_params = [];
+    $titres[0] = 'Paramètres';
 
     if(in_array($clef, [COM_NANOOK]))
       $champs_params[0] = ['url_serveur',
                            ['provide_suggest' => function($id, $valeur) {
-															 return getOuiNon($id, $valeur);
-														 }],
+                               return getOuiNon($id, $valeur);
+                             }],
                            ['pre-registration' => function($id, $valeur) {
-															 return getOuiNon($id, $valeur);
-														 }]];
+                               return getOuiNon($id, $valeur);
+                             }]];
 
     if(in_array($clef, [COM_CDSCRIPT]))
       $champs_params[0] = ['server_url',
                            'remote_library_id'];
 
 
-		if (in_array($clef, [COM_PMB, COM_VSMART, COM_MICROBIB, COM_BIBLIXNET, COM_WATERBEAR]))
-			$champs_params[0] = ['url_serveur'];
-
-		if (in_array($clef, [COM_CARTHAME]))
-			$champs_params[0] = ['url_serveur', 'key','sigb_field_name'];
+    if (in_array($clef, [COM_PMB, COM_VSMART, COM_MICROBIB, COM_BIBLIXNET, COM_WATERBEAR]))
+      $champs_params[0] = ['url_serveur'];
 
-		if (in_array($clef, [COM_ORPHEE]))
-			$champs_params[0] = ['url_serveur', 'key', 'allow_hold_available_items'];
+    if (in_array($clef, [COM_CARTHAME]))
+      $champs_params[0] = ['url_serveur', 'key','sigb_field_name'];
 
-		if ($clef == COM_KOHA)
-			$champs_params[0] = ['url_serveur',
-													 ['restful' => function($id, $valeur) {
-															 return getOuiNon($id, $valeur);
-														 }],
+    if (in_array($clef, [COM_ORPHEE]))
+      $champs_params[0] = ['url_serveur',
+                           'key',
+                           'allow_hold_available_items',
+                           ['hold_mode' => function($id, $valeur) {
+                               return getComboSimple($id,
+                                                     $valeur,
+                                                     ['' => 'Au titre',
+                                                      '1' => 'A l\'exemplaire']);
+                             }]];
+
+    if ($clef == COM_KOHA)
+      $champs_params[0] = ['url_serveur',
+                           ['restful' => function($id, $valeur) {
+                               return getOuiNon($id, $valeur);
+                             }],
                            ['pre-registration' => function($id, $valeur) {
-															 return getOuiNon($id, $valeur);
-														 }],
-													 'Interdire_reservation_doc_dispo',
+                               return getOuiNon($id, $valeur);
+                             }],
+                           'Interdire_reservation_doc_dispo',
                            'use_card_number',
-													 ['Codification_disponibilites' => function($id, $valeur){
-															 return getTextArea($id, $valeur, 30, 20);
+                           ['Codification_disponibilites' => function($id, $valeur){
+                               return getTextArea($id, $valeur, 30, 20);
+                             }],
+                           ['withdrawn_mapping' => function($id, $valeur){
+                               return getTextArea($id, $valeur, 30, 5);
+                             }],
+                           'bundled_holds_minimal_duration',
+                           'bundled_holds_maximal_duration',
+                           ['grouped_holds_itypes' => function($id, $valeur){
+															 return getTextArea($id, $valeur, 30, 5);
 														 }]];
 
-		if ($clef == COM_OPSYS)
-			$champs_params[0] = ['url_serveur', 'catalogue_web', 'reserver_retrait_bib_abonne'];
-
-		if ($clef == COM_DYNIX)
-			$champs_params[0] = ['url_serveur', 'client_id'];
-
-		if ($clef == COM_Z3950)
-			$champs_params[0] = ['url_serveur', 'login', 'password', 'nom_base'];
-
-		if ($clef == COM_PERGAME) {
-			$titres = ['Règles de réservations', 'Règles de prolongations'];
-			$champs_params[0] = ['Autoriser_docs_disponibles', 'Max_par_carte' , 'Max_par_document'];
-			$champs_params[1] = ['Autoriser_prolongations', 'Interdire_si_reservation',
-													 'Nombre_max_par_document', 'Duree_en_jours', 'Anteriorite_max_en_jours'];
-		}
-
-		$defaultInputRenderer = function($id, $valeur) {
-			return getChamp($id, $valeur,	30);
-		};
-
-		$cnt = count($champs_params);
-		for ($i=0; $i < $cnt; $i++) {
-			if (!$champs_params[$i])
-				continue;
-
-			$num_aide = 0;
-			$bloc .= '<div style="margin-top:5px"><b>' . $titres[$i] . '</b></div>';
-			$bloc .= '<table>';
-
-			foreach ($champs_params[$i] as $param)	{
-				$param_name = is_array($param) ? array_keys($param)[0] : $param;
-				$renderer = is_array($param) ? array_values($param)[0] : $defaultInputRenderer;
-				$valeur = $clef == $type ? $valeurs[$param_name] : '';
-
-				$bloc.= '<tr>'
-					.'<td class="form">' . $param_name.$aide[$num_aide] . '</td>'
-					.'<td class="form">' . $renderer('comp_' . $clef . '_' . $param_name, $valeur) .'</td>'
-					.'</tr>';
-
-				$num_aide++;
-			}
-			$bloc .= '</table>';
-
-			if ($clef == COM_NANOOK) {
-				$bloc .= "Format(0.8.7): ip:port/chemin_tomcat/ilsdi/nom_base <br/>"
-					. "Ex: 62.193.55.152:8080/afi_NanookWs/ilsdi/NANOOK";
-			}
-		}
-
-		if (in_array($clef, [COM_NANOOK, COM_KOHA, COM_PMB]))
-			$bloc .= " <br/><a target='_blank' class='test' href='#'>Tester la communication</a>";
-		$bloc .= '</div>';
-	}
-	return $bloc;
+    if ($clef == COM_OPSYS)
+      $champs_params[0] = ['url_serveur', 'catalogue_web', 'reserver_retrait_bib_abonne'];
+
+    if ($clef == COM_DYNIX)
+      $champs_params[0] = ['url_serveur', 'client_id'];
+
+    if ($clef == COM_Z3950)
+      $champs_params[0] = ['url_serveur', 'login', 'password', 'nom_base'];
+
+    if ($clef == COM_PERGAME) {
+      $titres = ['Règles de réservations', 'Règles de prolongations'];
+      $champs_params[0] = ['Autoriser_docs_disponibles', 'Max_par_carte' , 'Max_par_document'];
+      $champs_params[1] = ['Autoriser_prolongations', 'Interdire_si_reservation',
+                           'Nombre_max_par_document', 'Duree_en_jours', 'Anteriorite_max_en_jours'];
+    }
+
+    $defaultInputRenderer = function($id, $valeur) {
+      return getChamp($id, $valeur, 30);
+    };
+
+    $cnt = count($champs_params);
+    for ($i=0; $i < $cnt; $i++) {
+      if (!$champs_params[$i])
+        continue;
+
+      $num_aide = 0;
+      $bloc .= '<div style="margin-top:5px"><b>' . $titres[$i] . '</b></div>';
+      $bloc .= '<table>';
+
+      foreach ($champs_params[$i] as $param)  {
+        $param_name = is_array($param) ? array_keys($param)[0] : $param;
+        $renderer = is_array($param) ? array_values($param)[0] : $defaultInputRenderer;
+        $valeur = $clef == $type ? $valeurs[$param_name] : '';
+
+        $bloc.= '<tr>'
+          .'<td class="form">' . $param_name.$aide[$num_aide] . '</td>'
+          .'<td class="form">' . $renderer('comp_' . $clef . '_' . $param_name, $valeur) .'</td>'
+          .'</tr>';
+
+        $num_aide++;
+      }
+      $bloc .= '</table>';
+
+      if ($clef == COM_NANOOK) {
+        $bloc .= "Format(0.8.7): ip:port/chemin_tomcat/ilsdi/nom_base <br/>"
+          . "Ex: 62.193.55.152:8080/afi_NanookWs/ilsdi/NANOOK";
+      }
+    }
+
+    if (in_array($clef, [COM_NANOOK, COM_KOHA, COM_PMB]))
+      $bloc .= " <br/><a target='_blank' class='test' href='#'>Tester la communication</a>";
+    $bloc .= '</div>';
+  }
+  return $bloc;
 }
 ?>
\ No newline at end of file
diff --git a/cosmogramme/sql/patch/patch_377.php b/cosmogramme/sql/patch/patch_377.php
new file mode 100644
index 0000000000000000000000000000000000000000..b3e0f41496964afc5da6c7c5061c7db2a5dea664
--- /dev/null
+++ b/cosmogramme/sql/patch/patch_377.php
@@ -0,0 +1,14 @@
+<?php
+$adapter = Zend_Db_Table_Abstract::getDefaultAdapter();
+try {
+  $adapter->query("alter table notices
+                       add column `file_content` longtext not null default '',
+                       add fulltext key `file_content` (`file_content`)" );
+} catch (Exception $e) {}
+
+
+try {
+  $adapter->query("alter table notices
+                       add column `file_content` longtext character set latin1 not null default '',
+                       add fulltext key `file_content` (`file_content`)");
+} catch (Exception $e) {}
diff --git a/cosmogramme/sql/patch/patch_378.php b/cosmogramme/sql/patch/patch_378.php
new file mode 100644
index 0000000000000000000000000000000000000000..89d0dd9d33fc2ea239c3d695d2362a503a591b1c
--- /dev/null
+++ b/cosmogramme/sql/patch/patch_378.php
@@ -0,0 +1,5 @@
+<?php
+$adapter = Zend_Db_Table_Abstract::getDefaultAdapter();
+try {
+  $adapter->query('alter table exemplaires add column id_data_profile int(11) unsigned');
+} catch(Exception $e) {}
\ No newline at end of file
diff --git a/cosmogramme/sql/patch/patch_379.php b/cosmogramme/sql/patch/patch_379.php
new file mode 100644
index 0000000000000000000000000000000000000000..d3218fe18584f6b5a0ea813ee323145618e5866a
--- /dev/null
+++ b/cosmogramme/sql/patch/patch_379.php
@@ -0,0 +1,56 @@
+<?php
+$adapter = Zend_Db_Table_Abstract::getDefaultAdapter();
+
+try {
+  $adapter->query(
+                  'CREATE TABLE `journal` ('
+                  . '`id` int(11) unsigned not null auto_increment,'
+                  . '`type` varchar(255) not null,'
+                  . '`created_at` datetime not null,'
+                  . 'primary key (id),'
+                  . 'key `type` (`type`),'
+                  . 'key `created_at` (`created_at`)'
+                  . ') engine=MyISAM default charset=utf8'
+  );
+} catch(Exception $e) {}
+
+try {
+  $adapter->query(
+                  'CREATE TABLE `journal_detail` ('
+                  . '`id` int(11) unsigned not null auto_increment,'
+                  . '`journal_id` int(11) unsigned not null,'
+                  . '`type` varchar(255) not null,'
+                  . '`value` text not null,'
+                  . 'primary key (id),'
+                  . 'key `journal_id` (`journal_id`),'
+                  . 'key `type` (`type`)'
+                  . ') engine=MyISAM default charset=utf8'
+  );
+} catch(Exception $e) {}
+
+try {
+  $adapter->query(
+                  'CREATE TABLE `federation_group_membership` ('
+                  . '`id` int(11) unsigned not null auto_increment,'
+                  . '`group_name` varchar(255) not null,'
+                  . '`actor_id` varchar(255) not null,'
+                  . '`accepted_at` datetime not null,'
+                  . 'primary key (id),'
+                  . 'key `accepted_at` (`accepted_at`),'
+                  . 'key `group_name` (`group_name`),'
+                  . 'key `actor_id` (`actor_id`)'
+                  . ') engine=MyISAM default charset=utf8'
+  );
+} catch(Exception $e) {}
+
+try {
+  $adapter->query(
+                  'ALTER TABLE `notices_avis` '
+                  . 'ADD COLUMN `source_actor_id` varchar(255) null default null,'
+                  . 'ADD COLUMN `source_author` varchar(255) null default null,'
+                  . 'ADD COLUMN `source_primary` int(11) null default null,'
+                  . 'ADD KEY `source_actor_id` (`source_actor_id`),'
+                  . 'ADD KEY `source_author` (`source_author`),'
+                  . 'ADD KEY `source_primary` (`source_primary`)'
+  );
+} catch(Exception $e) {}
diff --git a/cosmogramme/tests/php/classes/CarthameIntegrationTest.php b/cosmogramme/tests/php/classes/CarthameIntegrationTest.php
index cdf69fc11d9d1b81a65ec6aaabf0c63d634085c4..38240eff0c6940c3043df297fbc1836ab0819eab 100644
--- a/cosmogramme/tests/php/classes/CarthameIntegrationTest.php
+++ b/cosmogramme/tests/php/classes/CarthameIntegrationTest.php
@@ -132,6 +132,4 @@ class TangoMangoCarthameIntegrationTest extends CarthameIntegrationTestCase {
 		$notice = Class_Notice::find(7939934);
 		$this->assertEquals('Tangomango n° 2<br /> La gazette du pirate', $notice->getTitrePrincipal());
 	}
-
-
 }
diff --git a/cosmogramme/tests/php/classes/KohaRecordIntegrationTest.php b/cosmogramme/tests/php/classes/KohaRecordIntegrationTest.php
index 9af40c220138a19314f03e2d9e181c5c82f9a1f8..34f0c629639d6688d7495dbcbd63175b947c44f4 100644
--- a/cosmogramme/tests/php/classes/KohaRecordIntegrationTest.php
+++ b/cosmogramme/tests/php/classes/KohaRecordIntegrationTest.php
@@ -27,7 +27,9 @@ abstract class KohaRecordIntegrationTestCase extends NoticeIntegrationTestCase {
 
 
   public function getProfilDonnees() {
-    return Class_IntProfilDonnees::forKoha()->getRawAttributes();
+    return Class_IntProfilDonnees::forKoha()
+      ->setIdProfil(45)
+      ->getRawAttributes();
   }
 }
 
@@ -73,11 +75,6 @@ class KohaRecordIntegrationRecordWithoutMainTitleTest extends KohaRecordIntegrat
 
 
 class KohaRecordIntegrationBdMilleniumTest extends KohaRecordIntegrationTestCase {
-  public function getProfilDonnees() {
-    return Class_IntProfilDonnees::forKoha()->getRawAttributes();
-  }
-
-
   public function setUp() {
     parent::setUp();
     $this->loadNotice('unimarc_bd_millenium');
@@ -99,6 +96,18 @@ class KohaRecordIntegrationBdMilleniumTest extends KohaRecordIntegrationTestCase
   public function collectionMilleniumShouldBeIndexed() {
     $this->assertEquals('MILLENIUM MILENIUM', $this->millenium->getRawAttributes()['collection']);
   }
+
+
+  /** @test */
+  public function firstItemIdDataProfileShouldBe45() {
+    $this->assertEquals(45, Class_Exemplaire::find(1)->getIdDataProfile());
+  }
+
+
+  /** @test */
+  public function itemDataProfileLabelShouldBeUnimarcKoha() {
+    $this->assertEquals('Unimarc Koha', Class_Exemplaire::find(1)->getDataProfile()->getLibelle());
+  }
 }
 
 
@@ -469,6 +478,12 @@ class KohaRecordIntegrationVagabondWithTooMany610aTest extends KohaRecordIntegra
   public function shouldNotCreateThesaurusFor610_a() {
     $this->assertNull(Class_CodifThesaurus::findFirstBy(['libelle' => 'Manga']));
   }
+
+
+  /** @test */
+  public function fileContentShouldBeEmpty() {
+    $this->assertEmpty($this->_notice->getFileContent());
+  }
 }
 
 
@@ -500,6 +515,7 @@ class KohaRecordIntegrationEscapableAnnexeCodesTest extends KohaRecordIntegratio
 
 
 
+
 class KohaRecordIntegrationBdMilleniumWithAuthorityTest extends KohaRecordIntegrationTestCase {
   public function setUp() {
     parent::setUp();
@@ -610,4 +626,123 @@ class KohaRecordIntegrationDeduplicateTest extends KohaRecordIntegrationTestCase
     $this->assertEquals(2, Class_Exemplaire::countBy(['id_int_bib' => 2,
                                                       'id_origine' => 397126]));
   }
-}
\ No newline at end of file
+}
+
+
+
+
+abstract class KohaRecordIntegrationPommeWithAttachedFileTestCase
+  extends KohaRecordIntegrationTestCase {
+
+  public function setUp() {
+    parent::setUp();
+
+    Class_Indexation_File_Html::setFileInfoFactory($this->_getHtmlFileInfoFactory());
+    Class_Indexation_File_PDF::setCommand($this->_getPdfCommand());
+
+    $this->loadNotice('unimarc_pomme');
+    $this->_notice = Class_Notice::find(1);
+  }
+
+
+  protected function _getPdfCommand() {
+    return $this->mock()
+                ->whenCalled('exec')
+                ->with('pdftotext -nopgbrk -raw \'' . USERFILESPATH . '/files/public/pomme.pdf\' -')
+                ->answers(0)
+
+                ->whenCalled('getOutput')
+                ->willDo(function() { return ['chlorprophame'];  })
+                ->beStrict();
+  }
+
+
+  protected function _getHtmlFileInfoFactory() {
+    return new KohaRecordIntegrationFileInfoFactory();
+  }
+
+
+  public function tearDown() {
+    Class_Indexation_File_Html::setFileInfoFactory(null);
+    Class_Indexation_File_PDF::setCommand(null);
+    parent::tearDown();
+  }
+
+
+  /** @test */
+  public function titleShouldBeLaPommeDeTerre() {
+    $this->assertEquals('La pomme de terre', $this->_notice->getTitrePrincipal());
+  }
+}
+
+
+
+
+class KohaRecordIntegrationPommeWithAttachedPdfFileTest
+  extends KohaRecordIntegrationPommeWithAttachedFileTestCase {
+
+  public function getProfilDonnees() {
+    return Class_IntProfilDonnees::forKoha()
+      ->setIdProfil(111)
+      ->setIndexFile('856', 'u', '/userfiles/files/public/[a-zA-Z0-9_\-]+\.pdf')
+      ->getRawAttributes();
+  }
+
+
+  /** @test */
+  public function fileContentShouldContainsChlorprophame() {
+    $this->assertContains('chlorprophame', $this->_notice->getFileContent());
+  }
+}
+
+
+
+
+class KohaRecordIntegrationPommeWithAttachedPdfAndHtmlFileTest
+  extends KohaRecordIntegrationPommeWithAttachedFileTestCase {
+
+  public function getProfilDonnees() {
+    return Class_IntProfilDonnees::forKoha()
+      ->setIdProfil(111)
+      ->setIndexFile('856', 'u', '/userfiles/files/public/[a-zA-Z0-9_\-]+\.(pdf|htm)')
+      ->getRawAttributes();
+  }
+
+
+  protected function _getPdfCommand() {
+    return $this->mock()
+                ->whenCalled('exec')
+                ->with('pdftotext -nopgbrk -raw \'' . USERFILESPATH . '/files/public/pomme.pdf\' -')
+                ->answers(0)
+
+                ->whenCalled('getOutput')
+                ->willDo(function() { return ['chlorprophame']; })
+                ->beStrict();
+  }
+
+
+  protected function _getHtmlFileInfoFactory() {
+    return new KohaRecordIntegrationFileInfoFactory();
+  }
+
+
+  /** @test */
+  public function fileContentShouldContainsHtmlContentWithoutTags() {
+    $this->assertEquals('super', $this->_notice->getFileContent());
+  }
+}
+
+
+
+class KohaRecordIntegrationFileInfoFactory {
+  public function __invoke($path) {
+    $wrapper = Storm_Test_ObjectWrapper::on(new SplFileInfo($path))
+      ->whenCalled('isFile')->answers(true)
+      ->whenCalled('isReadable')->answers(true);
+
+    if ('html' == substr($path, -4))
+      $wrapper->whenCalled('getContents')->answers('<p>super</p>');
+
+    return $wrapper;
+  }
+}
diff --git a/cosmogramme/tests/php/classes/NoticeIntegrationCaseSensitivityTest.php b/cosmogramme/tests/php/classes/NoticeIntegrationCaseSensitivityTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..0ce2262e0847630cd2d49cb56f6cfc20b1a1865f
--- /dev/null
+++ b/cosmogramme/tests/php/classes/NoticeIntegrationCaseSensitivityTest.php
@@ -0,0 +1,80 @@
+<?php
+/**
+ * Copyright (c) 2012-2014, 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
+ */
+
+require_once 'NoticeIntegrationTest.php';
+
+class NoticeIntegrationCaseSensitivityTest extends NoticeIntegrationTestCase {
+  public function getProfilDonnees() {
+    return Class_IntProfilDonnees::forALOES()
+      ->setIdProfil(113)
+      ->setItemField(Class_IntProfilDonnees::FIELD_ITEM_SECTION, 'Q')
+      ->setItemField(Class_IntProfilDonnees::FIELD_ITEM_GENRE, '')
+      ->getRawAttributes();
+  }
+
+
+  public function setUp() {
+    parent::setUp();
+    $this->fixture('Class_CodifSection',
+                   ['id' => 34,
+                    'libelle' => 'Musique du monde',
+                    'regles' => '995$Q=12']);
+
+    $this->fixture('Class_CodifSection',
+                   ['id' => 36,
+                    'libelle' => 'Musique du coin',
+                    'regles' => '995$q=12']);
+
+
+    $this->fixture('Class_CodifGenre',
+                   ['id' => 12,
+                    'libelle' => 'Musique traditionnelle',
+                    'regles' => '995$N=M'
+                   ]);
+
+    $this->fixture('Class_CodifGenre',
+                   ['id' => 13,
+                    'libelle' => 'Policier',
+                    'regles' => '995$O=p'
+                   ]);
+
+    $this->fixture('Class_CodifGenre',
+                   ['id' => 14,
+                    'libelle' => 'Roman',
+                    'regles' => '995$P=r'
+                   ]);
+
+    $this->loadNotice('unimarc_zanzibara');
+  }
+
+
+  /** @test */
+  public function recordShouldBeInMusiqueDuMonde() {
+    $this->assertEquals('34', Class_Notice::find(1)->getFirstExemplaire()->getSection());
+  }
+
+
+  /** @test */
+  public function recordShouldHaveThreeGenres() {
+    foreach(['G12', 'G13', 'G14'] as $facet)
+      $this->assertContains($facet, Class_Notice::find(1)->getFacettes());
+  }
+}
\ No newline at end of file
diff --git a/cosmogramme/tests/php/classes/NoticeIntegrationTest.php b/cosmogramme/tests/php/classes/NoticeIntegrationTest.php
index 236d240ea668e84f9fc0e01eb29e06fcad1b4c1b..b6068ed7c5e3f6c5ec291085ea109a596349748d 100644
--- a/cosmogramme/tests/php/classes/NoticeIntegrationTest.php
+++ b/cosmogramme/tests/php/classes/NoticeIntegrationTest.php
@@ -25,69 +25,69 @@ require_once 'ModelTestCase.php';
 
 
 abstract class NoticeIntegrationTestCase extends ModelTestCase {
-	protected
-		$notice_sgbd,
-		$_mock_sql,
-		$_insert_increment,
+  protected
+    $notice_sgbd,
+    $_mock_sql,
+    $_insert_increment,
     $_sigb = Class_IntBib::COM_PERGAME;
 
 
-	public function getProfilDonnees() {
-		return $this->_profil_donnees;
-	}
+  public function getProfilDonnees() {
+    return $this->_profil_donnees;
+  }
 
 
-	public function setupProfilDonnees() {
-		$this->_insert_increment = 12;
+  public function setupProfilDonnees() {
+    $this->_insert_increment = 12;
 
-		$this->_profil_donnees = $this->getProfilDonnees();
+    $this->_profil_donnees = $this->getProfilDonnees();
 
-		if (!isset($this->_profil_donnees))
-			return;
+    if (!isset($this->_profil_donnees))
+      return;
 
-		$req_profils = 'select * from profil_donnees where id_profil='.$this->_profil_donnees['id_profil'];
+    $req_profils = 'select * from profil_donnees where id_profil='.$this->_profil_donnees['id_profil'];
 
     $this->_profil_donnees['id'] = $this->_profil_donnees['id_profil'];
-		$this->fixture('Class_IntProfilDonnees', $this->_profil_donnees);
-
-		$this->_mock_sql
-			->whenCalled('fetchEnreg')
-			->with($req_profils)
-			->answers($this->_profil_donnees)
-
-			->whenCalled('fetchEnreg')
-			->with($req_profils, false)
-			->answers($this->_profil_donnees);
-	}
-
-
-	public function setUp() {
-		parent::setUp();
-
-		global $sql;
-		$sql = $this->_mock_sql = $this->mock();
-		profil_donnees::clearCache();
-
-		$this->_mock_sql
-			->whenCalled('execute')->answers(true)
-			->whenCalled('fetchAll')->answers(null)
-			->whenCalled('insert')->willDo(
-				function() {
-					$args = func_get_args();
-					if ($args[0] == 'notices') {
-						$inc = $this->_insert_increment;
-						$this->_insert_increment++;
-						return $inc;
-					}
-					return 12;
-				})
-			->whenCalled('update')->answers(null)
-			->whenCalled('fetchEnreg')->answers(null)
-			->whenCalled('fetchOne')->answers(null);
-
-
-		VariableCache::getInstance()
-		  ->setValeurCache(['filtrer_fulltext' => 1,
+    $this->fixture('Class_IntProfilDonnees', $this->_profil_donnees);
+
+    $this->_mock_sql
+      ->whenCalled('fetchEnreg')
+      ->with($req_profils)
+      ->answers($this->_profil_donnees)
+
+      ->whenCalled('fetchEnreg')
+      ->with($req_profils, false)
+      ->answers($this->_profil_donnees);
+  }
+
+
+  public function setUp() {
+    parent::setUp();
+
+    global $sql;
+    $sql = $this->_mock_sql = $this->mock();
+    profil_donnees::clearCache();
+
+    $this->_mock_sql
+      ->whenCalled('execute')->answers(true)
+      ->whenCalled('fetchAll')->answers(null)
+      ->whenCalled('insert')->willDo(
+                                     function() {
+                                       $args = func_get_args();
+                                       if ($args[0] == 'notices') {
+                                         $inc = $this->_insert_increment;
+                                         $this->_insert_increment++;
+                                         return $inc;
+                                       }
+                                       return 12;
+                                     })
+      ->whenCalled('update')->answers(null)
+      ->whenCalled('fetchEnreg')->answers(null)
+      ->whenCalled('fetchOne')->answers(null);
+
+
+    VariableCache::getInstance()
+      ->setValeurCache(['filtrer_fulltext' => 1,
                         'mode_doublon'=> 1,
                         'tracer_accents_iso'=>1,
                         'non_exportable'=> 'electre;decitre;gam;zebris',
@@ -96,42 +96,42 @@ abstract class NoticeIntegrationTestCase extends ModelTestCase {
                         'unicite_code_barres' => Class_CosmoVar::UNIQ_BARCODE_ONLY,
                         'champs_sup' => '',
                         'ean_345' => ''])
-			->setListeCache(['nature_docs'=> "1:Collection\r\n2:Dataset\r\n3:Event\r\n4:Image"]);
+      ->setListeCache(['nature_docs'=> "1:Collection\r\n2:Dataset\r\n3:Event\r\n4:Image"]);
 
     Codif_Langue::getInstance()
-			->setCodif(['fre' => ['id_langue' => 'fre',
-														'libelle' => 'français']]);
+      ->setCodif(['fre' => ['id_langue' => 'fre',
+                            'libelle' => 'français']]);
     $this->fixture('Class_CodifLangue',
                    ['id' => 'fre', 'libelle' => 'Français']);
 
 
-		$this->fixture('Class_IntBib',
-									 ['id' => 1,
-										'nom' => 'My wonderful library',
-										'nom_court' => 'MWL',
-										'mail' => '',
-										'qualite' => 10,
-										'dernier_ajout' => '2015-01-01',
-										'ecart_ajouts' => '0',
-										'date_mail' => '',
-										'sigb' => $this->_sigb,
-										'planif_mode' => 'r',
-										'comm_sigb' => 0,
-										'comm_params' => 'N']);
-
-		$this->notice_sgbd = new notice_unimarc();
-		$this->setupProfilDonnees();
-	}
+    $this->fixture('Class_IntBib',
+                   ['id' => 1,
+                    'nom' => 'My wonderful library',
+                    'nom_court' => 'MWL',
+                    'mail' => '',
+                    'qualite' => 10,
+                    'dernier_ajout' => '2015-01-01',
+                    'ecart_ajouts' => '0',
+                    'date_mail' => '',
+                    'sigb' => $this->_sigb,
+                    'planif_mode' => 'r',
+                    'comm_sigb' => 0,
+                    'comm_params' => 'N']);
+
+    $this->notice_sgbd = new notice_unimarc();
+    $this->setupProfilDonnees();
+  }
 
 
-	public function loadNotice($filename) {
-		$this->loadNoticeFromString(file_get_contents(__DIR__ . '/' . $filename . '.txt'));
-	}
+  public function loadNotice($filename) {
+    $this->loadNoticeFromString(file_get_contents(__DIR__ . '/' . $filename . '.txt'));
+  }
 
 
-	public function loadNoticeFromString($unimarc, $id_bib = 1) {
-		$this->notice_integration = new notice_integration();
-		$this->notice_integration->setParamsIntegration($id_bib,
+  public function loadNoticeFromString($unimarc, $id_bib = 1) {
+    $this->notice_integration = new notice_integration();
+    $this->notice_integration->setParamsIntegration($id_bib,
                                                     0,
                                                     isset($this->_profil_donnees['id_profil'])
                                                     ? $this->_profil_donnees['id_profil']
@@ -141,20 +141,20 @@ abstract class NoticeIntegrationTestCase extends ModelTestCase {
                                   ->whenCalled('run')
                                   ->answers(['statut' => 'KO']);
     $this->notice_integration->setServiceRunner($this->_service_runner);
-		$this->notice_integration->traiteNotice($unimarc);
-		$this->notice_integration->traiteFacettes();
-		$this->notice_data = $this->notice_integration->getNotice();
-	}
+    $this->notice_integration->traiteNotice($unimarc);
+    $this->notice_integration->traiteFacettes();
+    $this->notice_data = $this->notice_integration->getNotice();
+  }
 
 
   public function loadRecordsFromFile($filename, $id_bib = 1) {
     $contents = file_get_contents(dirname(__FILE__) . "/" . $filename . '.txt');
 
-		array_map(function($content) use ($id_bib)
+    array_map(function($content) use ($id_bib)
               {
                 return $this->loadNoticeFromString($content, $id_bib);
               },
-							preg_split('/'.chr(30).chr(29).'/', $contents));
+              preg_split('/'.chr(30).chr(29).'/', $contents));
   }
 }
 
@@ -167,88 +167,88 @@ class NoticeIntegrationLollipopGeneratedNoticeRecordTest extends NoticeIntegrati
   }
 
 
-	public function setUp() {
-		parent::setUp();
+  public function setUp() {
+    parent::setUp();
 
-		$this->loadNotice('unimarc_lollipop');
-	}
+    $this->loadNotice('unimarc_lollipop');
+  }
 
 
-	/** @test */
-	public function facetteShouldContainsLangueFre() {
-		$this->assertContains(' Lfre', $this->notice_data['facettes']);
-	}
+  /** @test */
+  public function facetteShouldContainsLangueFre() {
+    $this->assertContains(' Lfre', $this->notice_data['facettes']);
+  }
 
 
-	/** @test */
-	public function codeAlphaShouldBeLollipop() {
-		$this->assertEquals('LOLLIPOP--NOSTLINGERC--ECOLEDESLOISIRS-1987-1',
+  /** @test */
+  public function codeAlphaShouldBeLollipop() {
+    $this->assertEquals('LOLLIPOP--NOSTLINGERC--ECOLEDESLOISIRS-1987-1',
                         $this->notice_data['clef_alpha']);
-	}
+  }
 
 
-	/** @test */
-	public function typeDocShouldBeBook() {
-		$this->assertEquals(Class_TypeDoc::LIVRE, $this->notice_data['type_doc']);
-	}
+  /** @test */
+  public function typeDocShouldBeBook() {
+    $this->assertEquals(Class_TypeDoc::LIVRE, $this->notice_data['type_doc']);
+  }
 
 
-	/** @test */
-	public function clefOeuvreShouldBeLollipop() {
-		$this->assertEquals('LOLLIPOP--NOSTLINGERC-', $this->notice_data['clef_oeuvre']);
-	}
+  /** @test */
+  public function clefOeuvreShouldBeLollipop() {
+    $this->assertEquals('LOLLIPOP--NOSTLINGERC-', $this->notice_data['clef_oeuvre']);
+  }
 
 
-	/** @test */
-	public function noticeDbEnregTitresShouldBeLollipopAndLolipop() {
-		$this->assertEquals('LOLLIPOP LOLIPOP NEUF NEF',
-												$this->notice_integration->noticeToDBEnreg($this->notice_data)['titres']);
-	}
+  /** @test */
+  public function noticeDbEnregTitresShouldBeLollipopAndLolipop() {
+    $this->assertEquals('LOLLIPOP LOLIPOP NEUF NEF',
+                        $this->notice_integration->noticeToDBEnreg($this->notice_data)['titres']);
+  }
 
 
-	/** @test */
-	public function noticeDbEnregEditeurShouldBeEcoleEkolLoisirsLoisir() {
-		$this->assertEquals('ECOLE EKOL LOISIRS LOISIR',
-												$this->notice_integration->noticeToDBEnreg($this->notice_data)['editeur']);
-	}
+  /** @test */
+  public function noticeDbEnregEditeurShouldBeEcoleEkolLoisirsLoisir() {
+    $this->assertEquals('ECOLE EKOL LOISIRS LOISIR',
+                        $this->notice_integration->noticeToDBEnreg($this->notice_data)['editeur']);
+  }
 }
 
 
 
 
 abstract class NoticeIntegrationMarc21ToUnimarcTest extends NoticeIntegrationTestCase {
-	public function setUp() {
-		parent::setUp();
+  public function setUp() {
+    parent::setUp();
 
-		$this->notice_marc21 = new notice_marc21();
-		$this->notice_marc21->ouvrirNotice(file_get_contents(dirname(__FILE__)."/marc21_etalon.txt"), 0);
-		$this->notice_sgbd->ouvrirNotice($this->notice_marc21->getFullRecord());
-	}
+    $this->notice_marc21 = new notice_marc21();
+    $this->notice_marc21->ouvrirNotice(file_get_contents(dirname(__FILE__)."/marc21_etalon.txt"), 0);
+    $this->notice_sgbd->ouvrirNotice($this->notice_marc21->getFullRecord());
+  }
 
 
-	/** @test */
-	public function zone461TInUnimarcShouldContainsTitres() {
-		$this->assertEquals(['Titre général ;', 'titre general ;'], $this->notice_sgbd->get_subfield('461', 't'));
-	}
+  /** @test */
+  public function zone461TInUnimarcShouldContainsTitres() {
+    $this->assertEquals(['Titre général ;', 'titre general ;'], $this->notice_sgbd->get_subfield('461', 't'));
+  }
 
 
-	/** @test */
-	public function zone461TInMarc21ShouldContainsTitres() {
-		$this->assertEquals(['Titre général ;', 'titre general ;'], $this->notice_marc21->get_subfield('461', 't'));
-	}
+  /** @test */
+  public function zone461TInMarc21ShouldContainsTitres() {
+    $this->assertEquals(['Titre général ;', 'titre general ;'], $this->notice_marc21->get_subfield('461', 't'));
+  }
 }
 
 
 abstract class NoticeIntegrationMarc21DynixTestCase extends NoticeIntegrationTestCase {
-	protected $_profil_donnees = ['id' => 150,
+  protected $_profil_donnees = ['id' => 150,
                                 'id_profil' => 150,
-																'libelle' => 'MARC21 Dynix',
-																'accents' => '4',
-																'rejet_periodiques' =>  '1',
-																'id_article_periodique' => '0',
-																'type_fichier' => '0',
-																'format' => '6',
-																'attributs' => 'a:7:{i:0;a:8:{s:8:"type_doc";a:12:{i:0;a:3:{s:4:"code";s:1:"0";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:1;a:3:{s:4:"code";s:1:"1";s:5:"label";s:5:"am;na";s:8:"zone_995";s:22:"LIV;MS;LDV;LVI;LV;LIVC";}i:2;a:3:{s:4:"code";s:1:"2";s:5:"label";s:2:"as";s:8:"zone_995";s:12:"PER;REVC;REV";}i:3;a:3:{s:4:"code";s:1:"3";s:5:"label";s:3:"i;j";s:8:"zone_995";s:17:"CD;LIVCD;LIVK7;K7";}i:4;a:3:{s:4:"code";s:1:"4";s:5:"label";s:1:"g";s:8:"zone_995";s:25:"DIAPO;DVD;VHS;VHD;VD;DVDJ";}i:5;a:3:{s:4:"code";s:1:"5";s:5:"label";s:3:"l;m";s:8:"zone_995";s:3:"CDR";}i:6;a:3:{s:4:"code";s:1:"7";s:5:"label";s:0:"";s:8:"zone_995";s:7:"LCA;LCD";}i:7;a:3:{s:4:"code";s:1:"8";s:5:"label";s:0:"";s:8:"zone_995";s:3:"DOS";}i:8;a:3:{s:4:"code";s:1:"9";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:9;a:3:{s:4:"code";s:2:"10";s:5:"label";s:0:"";s:8:"zone_995";s:6:"WEB;MF";}i:10;a:3:{s:4:"code";s:2:"11";s:5:"label";s:0:"";s:8:"zone_995";s:2:"JV";}i:11;a:3:{s:4:"code";s:3:"100";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}}s:17:"champ_code_barres";s:3:"999";s:10:"champ_cote";s:1:"k";s:14:"champ_type_doc";s:1:"r";s:11:"champ_genre";s:0:"";s:13:"champ_section";s:1:"z";s:17:"champ_emplacement";s:1:"u";s:12:"champ_annexe";s:1:"b";}i:1;a:1:{s:6:"champs";s:0:"";}i:2;a:1:{s:6:"champs";s:0:"";}i:3;a:1:{s:6:"champs";s:0:"";}i:5;a:3:{s:6:"champs";s:0:"";s:17:"xml_balise_abonne";s:0:"";s:17:"xml_champs_abonne";a:11:{s:6:"IDABON";s:0:"";s:9:"ORDREABON";s:0:"";s:3:"NOM";s:0:"";s:6:"PRENOM";s:0:"";s:9:"NAISSANCE";s:0:"";s:8:"PASSWORD";s:0:"";s:4:"MAIL";s:0:"";s:10:"DATE_DEBUT";s:0:"";s:8:"DATE_FIN";s:0:"";s:7:"ID_SIGB";s:0:"";s:9:"NUM_CARTE";s:0:"";}}i:4;a:5:{s:4:"zone";s:3:"995";s:5:"champ";s:1:"v";s:6:"format";s:1:"3";s:5:"jours";s:0:"";s:7:"valeurs";s:1:"n";}i:6;a:2:{s:4:"zone";s:3:"901";s:5:"champ";s:1:"a";}}'];
+                                'libelle' => 'MARC21 Dynix',
+                                'accents' => '4',
+                                'rejet_periodiques' =>  '1',
+                                'id_article_periodique' => '0',
+                                'type_fichier' => '0',
+                                'format' => '6',
+                                'attributs' => 'a:7:{i:0;a:8:{s:8:"type_doc";a:12:{i:0;a:3:{s:4:"code";s:1:"0";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:1;a:3:{s:4:"code";s:1:"1";s:5:"label";s:5:"am;na";s:8:"zone_995";s:22:"LIV;MS;LDV;LVI;LV;LIVC";}i:2;a:3:{s:4:"code";s:1:"2";s:5:"label";s:2:"as";s:8:"zone_995";s:12:"PER;REVC;REV";}i:3;a:3:{s:4:"code";s:1:"3";s:5:"label";s:3:"i;j";s:8:"zone_995";s:17:"CD;LIVCD;LIVK7;K7";}i:4;a:3:{s:4:"code";s:1:"4";s:5:"label";s:1:"g";s:8:"zone_995";s:25:"DIAPO;DVD;VHS;VHD;VD;DVDJ";}i:5;a:3:{s:4:"code";s:1:"5";s:5:"label";s:3:"l;m";s:8:"zone_995";s:3:"CDR";}i:6;a:3:{s:4:"code";s:1:"7";s:5:"label";s:0:"";s:8:"zone_995";s:7:"LCA;LCD";}i:7;a:3:{s:4:"code";s:1:"8";s:5:"label";s:0:"";s:8:"zone_995";s:3:"DOS";}i:8;a:3:{s:4:"code";s:1:"9";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:9;a:3:{s:4:"code";s:2:"10";s:5:"label";s:0:"";s:8:"zone_995";s:6:"WEB;MF";}i:10;a:3:{s:4:"code";s:2:"11";s:5:"label";s:0:"";s:8:"zone_995";s:2:"JV";}i:11;a:3:{s:4:"code";s:3:"100";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}}s:17:"champ_code_barres";s:3:"999";s:10:"champ_cote";s:1:"k";s:14:"champ_type_doc";s:1:"r";s:11:"champ_genre";s:0:"";s:13:"champ_section";s:1:"z";s:17:"champ_emplacement";s:1:"u";s:12:"champ_annexe";s:1:"b";}i:1;a:1:{s:6:"champs";s:0:"";}i:2;a:1:{s:6:"champs";s:0:"";}i:3;a:1:{s:6:"champs";s:0:"";}i:5;a:3:{s:6:"champs";s:0:"";s:17:"xml_balise_abonne";s:0:"";s:17:"xml_champs_abonne";a:11:{s:6:"IDABON";s:0:"";s:9:"ORDREABON";s:0:"";s:3:"NOM";s:0:"";s:6:"PRENOM";s:0:"";s:9:"NAISSANCE";s:0:"";s:8:"PASSWORD";s:0:"";s:4:"MAIL";s:0:"";s:10:"DATE_DEBUT";s:0:"";s:8:"DATE_FIN";s:0:"";s:7:"ID_SIGB";s:0:"";s:9:"NUM_CARTE";s:0:"";}}i:4;a:5:{s:4:"zone";s:3:"995";s:5:"champ";s:1:"v";s:6:"format";s:1:"3";s:5:"jours";s:0:"";s:7:"valeurs";s:1:"n";}i:6;a:2:{s:4:"zone";s:3:"901";s:5:"champ";s:1:"a";}}'];
 }
 
 
@@ -257,76 +257,78 @@ abstract class NoticeIntegrationMarc21DynixTestCase extends NoticeIntegrationTes
 class NoticeIntegrationMarc21CoupCavalierToUnimarcTest
   extends NoticeIntegrationMarc21DynixTestCase {
 
-	public function setUp() {
-		parent::setUp();
+  public function setUp() {
+    parent::setUp();
 
-		$this->fixture('Class_CodifSection',
-									 ['id' => 2, 'regles' => '996$z=ADU']);
+    $this->fixture('Class_CodifSection',
+                   ['id' => 2,
+                    'libelle' => 'Adulte',
+                    'regles' => '996$z=ADU']);
 
-		$this->notice_marc21 = new notice_marc21();
-		$this->notice_marc21->ouvrirNotice(file_get_contents(dirname(__FILE__)."/marc21_coup_cavalier.txt"),
+    $this->notice_marc21 = new notice_marc21();
+    $this->notice_marc21->ouvrirNotice(file_get_contents(dirname(__FILE__)."/marc21_coup_cavalier.txt"),
                                        $this->_profil_donnees['id_profil']);
-		$this->notice_data = $this->notice_marc21->getNoticeIntegration();
-	}
+    $this->notice_data = $this->notice_marc21->getNoticeIntegration();
+  }
 
 
-	/** @test */
-	public function zone200AShouldBeLeCoupDuCavalier() {
-		$this->assertEquals('Le coup du cavalier',
+  /** @test */
+  public function zone200AShouldBeLeCoupDuCavalier() {
+    $this->assertEquals('Le coup du cavalier',
                         $this->notice_marc21->get_subfield('200', 'a')[0]);
-	}
-
-
-	/** @test */
-	public function zone210CShouldBeEditeurMetailie() {
-		$this->assertEquals('Métailié,', $this->notice_marc21->get_subfield('210', 'c')[0]);
-	}
+  }
 
 
-	/** @test */
-	public function zone996iShouldBe00715585() {
-		$this->assertEquals('00715585', $this->notice_marc21->get_subfield('996', 'i')[0]);
-	}
+  /** @test */
+  public function zone210CShouldBeEditeurMetailie() {
+    $this->assertEquals('Métailié,', $this->notice_marc21->get_subfield('210', 'c')[0]);
+  }
 
 
-	/** @test */
-	public function sectionShouldHaveId2() {
-		$this->assertEquals(2, $this->notice_data['exemplaires'][0]['section']);
-	}
+  /** @test */
+  public function zone996iShouldBe00715585() {
+    $this->assertEquals('00715585', $this->notice_marc21->get_subfield('996', 'i')[0]);
+  }
 
 
-	/** @test */
-	public function getAllShouldReturnAllFields() {
-		$all = $this->notice_marc21->getAll();
+  /** @test */
+  public function sectionShouldHaveId2() {
+    $this->assertEquals(2, $this->notice_data['exemplaires'][0]['section']);
+  }
 
-		$this->assertEquals('Le coup du cavalier', $all['titre_princ']);
-		$this->assertEquals([	['Longueur de la notice', 920],
-		['Statut de la notice', 'n'],
-		['Type de document', 'am'],
-		['Niveau hiérarchique', 0],
-		['Adresse des données', 193],
-		['Niveau de catalogage' , '1']
-		],
 
-		$all['label']);
-		$this->assertEquals('Quadruppani, Serge', $all['zones'][11]['champs'][0]['valeur']);
-		$this->assertEquals('ADU', trim($all['zones'][12]['champs'][14]['valeur']));
-	}
+  /** @test */
+  public function getAllShouldReturnAllFields() {
+    $all = $this->notice_marc21->getAll();
+
+    $this->assertEquals('Le coup du cavalier', $all['titre_princ']);
+    $this->assertEquals([   ['Longueur de la notice', 920],
+                         ['Statut de la notice', 'n'],
+                         ['Type de document', 'am'],
+                         ['Niveau hiérarchique', 0],
+                         ['Adresse des données', 193],
+                         ['Niveau de catalogage' , '1']
+                         ],
+
+                        $all['label']);
+    $this->assertEquals('Quadruppani, Serge', $all['zones'][11]['champs'][0]['valeur']);
+    $this->assertEquals('ADU', trim($all['zones'][12]['champs'][14]['valeur']));
+  }
 }
 
 
 
 
 class NoticeIntegrationMarc21BorisToUnimarcTest extends NoticeIntegrationMarc21DynixTestCase {
-	public function setUp() {
-		parent::setUp();
-		$this->loadNotice('marc21_boris');
-	}
-
-	/** @test */
-	public function sectionShouldHaveId2() {
-		$this->assertEquals('BORIS-JAI1AN1AN1AN1AN---TMAGNIER-2014-0', $this->notice_data['clef_alpha']);
-	}
+  public function setUp() {
+    parent::setUp();
+    $this->loadNotice('marc21_boris');
+  }
+
+  /** @test */
+  public function sectionShouldHaveId2() {
+    $this->assertEquals('BORIS-JAI1AN1AN1AN1AN---TMAGNIER-2014-0', $this->notice_data['clef_alpha']);
+  }
 }
 
 
@@ -360,7 +362,7 @@ class NoticeIntegrationMarc21WithItemsIn952Test extends NoticeIntegrationMarc21D
                                       'valeurs' => '']]])
       ->setIdProfil(23)
       ->getRawAttributes();
-	}
+  }
 
 
   public function setUp() {
@@ -400,104 +402,105 @@ class NoticeIntegrationMarc21WithItemsIn952Test extends NoticeIntegrationMarc21D
 
 
 class NoticeIntegrationBourdieuWithElectreGeneratedNoticeRecordTest extends NoticeIntegrationTestCase {
-	public function setUp() {
-		parent::setUp();
-
-		Codif_langue::getInstance()
-			->setCodif(['fre' => ['id_langue' => 'fre',
-														'libelle' => 'français']]);
-
-		$this->fixture('Class_CodifThesaurus',
-									 ['id' => 2222,
-										'id_origine' => 'T380500',
-										'id_thesaurus' => 'AAAA0001222',
-										'code' => 'thèmeelectre',
-										'libelle' => 'Modes de vie et comportements selon les pays',
-										'rules' => null]);
+  public function setUp() {
+    parent::setUp();
 
-		$this->fixture('Class_CodifThesaurus',
-									 ['id' => 88,
-										'id_origine' => 'PS0100',
-										'id_thesaurus' => 'AAAA88',
-										'code' => 'publicelectre',
-										'libelle' => 'Public motivé',
-										'rules' => null]);
+    Codif_langue::getInstance()
+      ->setCodif(['fre' => ['id_langue' => 'fre',
+                            'libelle' => 'français']]);
+
+    $this->fixture('Class_CodifThesaurus',
+                   ['id' => 2222,
+                    'id_origine' => 'T380500',
+                    'id_thesaurus' => 'AAAA0001222',
+                    'code' => 'thèmeelectre',
+                    'libelle' => 'Modes de vie et comportements selon les pays',
+                    'rules' => null]);
+
+    $this->fixture('Class_CodifThesaurus',
+                   ['id' => 88,
+                    'id_origine' => 'PS0100',
+                    'id_thesaurus' => 'AAAA88',
+                    'code' => 'publicelectre',
+                    'libelle' => 'Public motivé',
+                    'rules' => null]);
 
-		$this->notice_integration = new notice_integration();
-		$this->notice_integration->setParamsIntegration(1, 0, 1);
-		$this->notice_integration->traiteNotice(file_get_contents(dirname(__FILE__)."/unimarc_bourdieu.txt"));
-		$this->notice_integration->traiteFacettes();
-		$this->notice_data = $this->notice_integration->getNotice();
+    $this->notice_integration = new notice_integration();
+    $this->notice_integration->setParamsIntegration(1, 0, 1);
+    $this->notice_integration->traiteNotice(file_get_contents(dirname(__FILE__)."/unimarc_bourdieu.txt"));
+    $this->notice_integration->traiteFacettes();
+    $this->notice_data = $this->notice_integration->getNotice();
 
-	}
+  }
 
 
-	/** @test */
-	public function clefOeuvreShouldBeLaMisereDuMonde() {
-		$this->assertEquals('MISEREDUMONDELA--BOURDIEUP-', $this->notice_data['clef_oeuvre']);
-	}
+  /** @test */
+  public function clefOeuvreShouldBeLaMisereDuMonde() {
+    $this->assertEquals('MISEREDUMONDELA--BOURDIEUP-', $this->notice_data['clef_oeuvre']);
+  }
 
 
-	/** @test */
-	public function themeElectreShouldBeTAAAA0001222() {
-		$this->assertEquals('AAAA0001222', $this->notice_data['thesauri'][0]->getIdThesaurus());
-	}
+  /** @test */
+  public function themeElectreShouldBeTAAAA0001222() {
+    $this->assertEquals('AAAA0001222', $this->notice_data['thesauri'][0]->getIdThesaurus());
+  }
 
 
-	/** @test */
-	public function codeShouldBePublicElectre() {
-		$this->assertEquals('publicelectre', $this->notice_data['thesauri'][2]->getCode());
-	}
+  /** @test */
+  public function codeShouldBePublicElectre() {
+    $this->assertEquals('publicelectre', $this->notice_data['thesauri'][2]->getCode());
+  }
 
 
-	/** @test */
-	public function libelleShouldBePublicMotive() {
-		$this->assertEquals('Public motivé', $this->notice_data['thesauri'][2]->getLibelle());
-	}
+  /** @test */
+  public function libelleShouldBePublicMotive() {
+    $this->assertEquals('Public motivé', $this->notice_data['thesauri'][2]->getLibelle());
+  }
 
 
-	/** @test */
-	public function facetteShouldContainsThesaurusIds() {
-		$this->assertContains('HAAAA0001222 HAAAA88 ', $this->notice_data['facettes']);
-	}
+  /** @test */
+  public function facetteShouldContainsThesaurusIds() {
+    $this->assertContains('HAAAA0001222 HAAAA88 ', $this->notice_data['facettes']);
+  }
 
 
-	/** @test */
-	public function fullTextShouldContainsThesaurusLibelles() {
-		$this->assertContains('Modes de vie et comportements selon les pays Public motivé', $this->notice_data['full_dewey']);
-	}
+  /** @test */
+  public function fullTextShouldContainsThesaurusLibelles() {
+    $this->assertContains('Modes de vie et comportements selon les pays Public motivé', $this->notice_data['full_dewey']);
+  }
 }
 
 
 
 
 class NoticeIntegrationSupertrampWithElectreAndPcmd4GeneratedNoticeRecordTest extends NoticeIntegrationTestCase {
-	public function setUp() {
-		parent::setUp();
+  public function setUp() {
+    parent::setUp();
 
-		Codif_langue::getInstance()->setCodif(['fre' => ['id_langue' => 'fre',
-			'libelle' => 'français']]);
+    Codif_langue::getInstance()->setCodif(['fre' => ['id_langue' => 'fre',
+                                                     'libelle' => 'français']]);
 
 
-		$this->notice_integration = new notice_integration();
-		$this->notice_integration->setParamsIntegration(1, 0, 1);
-		$this->notice_integration->traiteNotice(file_get_contents(dirname(__FILE__)."/unimarc_supertramp.txt"));
-		$this->notice_integration->traiteFacettes();
-		$this->notice_data = $this->notice_integration->getNotice();
+    $this->notice_integration = new notice_integration();
+    $this->notice_integration->setParamsIntegration(1, 0, 1);
+    $this->notice_integration->traiteNotice(file_get_contents(dirname(__FILE__)."/unimarc_supertramp.txt"));
+    $this->notice_integration->traiteFacettes();
+    $this->notice_data = $this->notice_integration->getNotice();
 
-	}
+  }
 
 
-	/** @test */
-	public function facetteShouldBePcdm4() {
-		$this->assertContains('P215', $this->notice_data['facettes']);
-	}
+  /** @test */
+  public function facetteShouldBePcdm4() {
+    $this->assertContains('P215', $this->notice_data['facettes']);
+  }
 }
 
 
 
 class NoticeIntegrationKohaNeonWithPcmd4GeneratedNoticeRecordTest extends NoticeIntegrationTestCase {
   protected $_storm_default_to_volatile = true;
+
   public function setUp() {
     parent::setUp();
 
@@ -506,9 +509,17 @@ class NoticeIntegrationKohaNeonWithPcmd4GeneratedNoticeRecordTest extends Notice
 
 
     $this->notice_integration = new notice_integration();
-    $this->notice_integration->setParamsIntegration(1, 0, 1);
+    $this->notice_integration->setParamsIntegration(1, 0, 110);
+  }
+
+
+  public function getProfilDonnees() {
+    return Class_IntProfilDonnees::forKoha()
+      ->setIdProfil(110)
+      ->getRawAttributes();
   }
 
+
   public function processUnimarc($file){
     $this->notice_integration->traiteNotice(file_get_contents(dirname(__FILE__)."/".$file));
     $this->notice_integration->traiteFacettes();
@@ -552,190 +563,190 @@ class NoticeIntegrationKohaNeonWithPcmd4GeneratedNoticeRecordTest extends Notice
 
 
 class NoticeIntegrationMussoWithoutRenvoisTest extends NoticeIntegrationTestCase {
-	public function setUp() {
-		parent::setUp();
-		$this->loadNotice("unimarc_musso");
-	}
+  public function setUp() {
+    parent::setUp();
+    $this->loadNotice("unimarc_musso");
+  }
 
 
-	/** @test */
-	public function auteursShouldContainsMUSSO() {
-		$this->assertContains('MUSSO MUSO GUILLAUME',
-		$this->notice_integration->noticeToDBEnreg($this->notice_data)['auteurs']);
-	}
+  /** @test */
+  public function auteursShouldContainsMUSSO() {
+    $this->assertContains('MUSSO MUSO GUILLAUME',
+                          $this->notice_integration->noticeToDBEnreg($this->notice_data)['auteurs']);
+  }
 }
 
 
 
 class NoticeIntegrationCekovTest extends NoticeIntegrationTestCase {
-	protected
-		$_profil_donnees =
-		['id_profil' => 409,
-		'libelle' => 'UNIMARC',
-		'accents' => '1',
-		'rejet_periodiques' =>  '1',
-		'id_article_periodique' => '0',
-		'type_fichier' => '0',
-		'format' => '0',
-		'attributs' => 'a:6:{i:0;a:7:{s:8:"type_doc";a:11:{i:0;a:3:{s:4:"code";s:1:"0";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:1;a:3:{s:4:"code";s:1:"1";s:5:"label";s:5:"am;na";s:8:"zone_995";s:0:"";}i:2;a:3:{s:4:"code";s:1:"2";s:5:"label";s:2:"as";s:8:"zone_995";s:0:"";}i:3;a:3:{s:4:"code";s:1:"3";s:5:"label";s:3:"i;j";s:8:"zone_995";s:0:"";}i:4;a:3:{s:4:"code";s:1:"4";s:5:"label";s:1:"g";s:8:"zone_995";s:0:"";}i:5;a:3:{s:4:"code";s:1:"5";s:5:"label";s:3:"l;m";s:8:"zone_995";s:0:"";}i:6;a:3:{s:4:"code";s:1:"8";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:7;a:3:{s:4:"code";s:1:"9";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:8;a:3:{s:4:"code";s:2:"10";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:9;a:3:{s:4:"code";s:3:"100";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:10;a:3:{s:4:"code";s:2:"10";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}}s:17:"champ_code_barres";s:1:"f";s:10:"champ_cote";s:1:"k";s:11:"champ_genre";s:0:"";s:13:"champ_section";s:1:"q";s:17:"champ_emplacement";s:1:"u";s:12:"champ_annexe";s:1:"a";}i:1;a:1:{s:6:"champs";s:0:"";}i:2;a:1:{s:6:"champs";s:0:"";}i:3;a:1:{s:6:"champs";s:0:"";}i:4;a:5:{s:4:"zone";s:0:"";s:5:"champ";s:0:"";s:6:"format";s:0:"";s:5:"jours";s:0:"";s:7:"valeurs";s:0:"";}i:5;a:2:{s:17:"xml_balise_abonne";s:0:"";s:17:"xml_champs_abonne";a:10:{s:6:"IDABON";s:0:"";s:9:"ORDREABON";s:0:"";s:3:"NOM";s:0:"";s:6:"PRENOM";s:0:"";s:9:"NAISSANCE";s:0:"";s:8:"PASSWORD";s:0:"";s:4:"MAIL";s:0:"";s:10:"DATE_DEBUT";s:0:"";s:8:"DATE_FIN";s:0:"";s:7:"ID_SIGB";s:0:"";}}}'
-		];
-
-	public function setUp() {
-		parent::setUp();
-		$this->loadNotice("unimarc_cekov");
-		$this->notice_integration->analyseurUpdate();
-	}
-
-
-	/** @test */
-	public function auteurPrincipalShouldBeCechov() {
-		$this->assertEquals('ÄŒehov',
-		$this->notice_integration->get_subfield('700', 'a')[0]);
-	}
-
-
-	/** @test */
-	public function zone200bShouldBeTexteImprime() {
-		$this->assertEquals('Texte imprimé',
-		$this->notice_integration->get_subfield('200', 'b')[0]);
-	}
-
-
-	/** @test */
-	public function noticeShouldHaveOneItem() {
-		$this->assertEquals(1, count($this->notice_data['exemplaires']));
-	}
-
-
-	/** @test */
-	public function noticeFirstItemShouldHaveZone995AsSerializedArray() {
-		$this->assertEquals(
-			serialize([['code' => 'a', 'valeur' => 'Béalières'],
-								 ['code' => 'f', 'valeur' => '0002'],
-								 ['code' => 'k', 'valeur' => '915.770 ÄŒEH'],
-								 ['code' => 'm', 'valeur' => '20131106'],
-								 ['code' => 'q', 'valeur' => 'a'],
-								 ['code' => 'r', 'valeur' => 'aa'],
-								 ['code' => 'o', 'valeur' => 'c'],
-								 ['code' => '2', 'valeur' => '[DISP][Disponible][0][1][En rayon][0][0][0][0]'],
-								 ['code' => '4', 'valeur' => '2014-02-04'],
-								 ['code' => '8', 'valeur' => '2'],
-								 ['code' => '9', 'valeur' => '2']]),
-
-			$this->notice_data['exemplaires'][0]['zone995']);
-	}
-
-
-	/** @test */
-	public function firstItemCoteShouldBe915Etc() {
-		$this->assertEquals('915.770 ÄŒEH', $this->notice_data['exemplaires'][0]['cote']);
-	}
-
-
-	/** @test */
-	public function firstItemActivityShouldBeEnRayon() {
-		$this->assertEquals('En rayon',
-												$this->notice_data['exemplaires'][0]['activite']);
-	}
+  protected
+    $_profil_donnees =
+    ['id_profil' => 409,
+     'libelle' => 'UNIMARC',
+     'accents' => '1',
+     'rejet_periodiques' =>  '1',
+     'id_article_periodique' => '0',
+     'type_fichier' => '0',
+     'format' => '0',
+     'attributs' => 'a:6:{i:0;a:7:{s:8:"type_doc";a:11:{i:0;a:3:{s:4:"code";s:1:"0";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:1;a:3:{s:4:"code";s:1:"1";s:5:"label";s:5:"am;na";s:8:"zone_995";s:0:"";}i:2;a:3:{s:4:"code";s:1:"2";s:5:"label";s:2:"as";s:8:"zone_995";s:0:"";}i:3;a:3:{s:4:"code";s:1:"3";s:5:"label";s:3:"i;j";s:8:"zone_995";s:0:"";}i:4;a:3:{s:4:"code";s:1:"4";s:5:"label";s:1:"g";s:8:"zone_995";s:0:"";}i:5;a:3:{s:4:"code";s:1:"5";s:5:"label";s:3:"l;m";s:8:"zone_995";s:0:"";}i:6;a:3:{s:4:"code";s:1:"8";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:7;a:3:{s:4:"code";s:1:"9";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:8;a:3:{s:4:"code";s:2:"10";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:9;a:3:{s:4:"code";s:3:"100";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:10;a:3:{s:4:"code";s:2:"10";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}}s:17:"champ_code_barres";s:1:"f";s:10:"champ_cote";s:1:"k";s:11:"champ_genre";s:0:"";s:13:"champ_section";s:1:"q";s:17:"champ_emplacement";s:1:"u";s:12:"champ_annexe";s:1:"a";}i:1;a:1:{s:6:"champs";s:0:"";}i:2;a:1:{s:6:"champs";s:0:"";}i:3;a:1:{s:6:"champs";s:0:"";}i:4;a:5:{s:4:"zone";s:0:"";s:5:"champ";s:0:"";s:6:"format";s:0:"";s:5:"jours";s:0:"";s:7:"valeurs";s:0:"";}i:5;a:2:{s:17:"xml_balise_abonne";s:0:"";s:17:"xml_champs_abonne";a:10:{s:6:"IDABON";s:0:"";s:9:"ORDREABON";s:0:"";s:3:"NOM";s:0:"";s:6:"PRENOM";s:0:"";s:9:"NAISSANCE";s:0:"";s:8:"PASSWORD";s:0:"";s:4:"MAIL";s:0:"";s:10:"DATE_DEBUT";s:0:"";s:8:"DATE_FIN";s:0:"";s:7:"ID_SIGB";s:0:"";}}}'
+    ];
+
+  public function setUp() {
+    parent::setUp();
+    $this->loadNotice("unimarc_cekov");
+    $this->notice_integration->analyseurUpdate();
+  }
+
+
+  /** @test */
+  public function auteurPrincipalShouldBeCechov() {
+    $this->assertEquals('ÄŒehov',
+                        $this->notice_integration->get_subfield('700', 'a')[0]);
+  }
+
+
+  /** @test */
+  public function zone200bShouldBeTexteImprime() {
+    $this->assertEquals('Texte imprimé',
+                        $this->notice_integration->get_subfield('200', 'b')[0]);
+  }
+
+
+  /** @test */
+  public function noticeShouldHaveOneItem() {
+    $this->assertEquals(1, count($this->notice_data['exemplaires']));
+  }
+
+
+  /** @test */
+  public function noticeFirstItemShouldHaveZone995AsSerializedArray() {
+    $this->assertEquals(
+                        serialize([['code' => 'a', 'valeur' => 'Béalières'],
+                                   ['code' => 'f', 'valeur' => '0002'],
+                                   ['code' => 'k', 'valeur' => '915.770 ÄŒEH'],
+                                   ['code' => 'm', 'valeur' => '20131106'],
+                                   ['code' => 'q', 'valeur' => 'a'],
+                                   ['code' => 'r', 'valeur' => 'aa'],
+                                   ['code' => 'o', 'valeur' => 'c'],
+                                   ['code' => '2', 'valeur' => '[DISP][Disponible][0][1][En rayon][0][0][0][0]'],
+                                   ['code' => '4', 'valeur' => '2014-02-04'],
+                                   ['code' => '8', 'valeur' => '2'],
+                                   ['code' => '9', 'valeur' => '2']]),
+
+                        $this->notice_data['exemplaires'][0]['zone995']);
+  }
+
+
+  /** @test */
+  public function firstItemCoteShouldBe915Etc() {
+    $this->assertEquals('915.770 ÄŒEH', $this->notice_data['exemplaires'][0]['cote']);
+  }
+
+
+  /** @test */
+  public function firstItemActivityShouldBeEnRayon() {
+    $this->assertEquals('En rayon',
+                        $this->notice_data['exemplaires'][0]['activite']);
+  }
 }
 
 
 
 
 class NoticeIntegrationLearningWithRenvoisTest extends NoticeIntegrationTestCase {
-	public function setUp() {
-		parent::setUp();
-		$this->loadNotice("unimarc_learning_from_vernacular");
-	}
+  public function setUp() {
+    parent::setUp();
+    $this->loadNotice("unimarc_learning_from_vernacular");
+  }
 
 
-	/** @test */
-	public function auteursShouldContainsFrey() {
-		$this->assertContains('FREY FRAI PIERRE PIER',
-		$this->notice_integration->noticeToDBEnreg($this->notice_data)['auteurs']);
-	}
+  /** @test */
+  public function auteursShouldContainsFrey() {
+    $this->assertContains('FREY FRAI PIERRE PIER',
+                          $this->notice_integration->noticeToDBEnreg($this->notice_data)['auteurs']);
+  }
 }
 
 
 
 
 class NoticeIntegrationBearsBeerMicrobibTest extends NoticeIntegrationTestCase {
-	public function getProfilDonnees() {
-		return
-			['id_profil' => 106,
-			 'libelle' => 'Microbib',
-			 'accents' => '1',
-			 'rejet_periodiques' =>  '1',
-			 'id_article_periodique' => '2',
-			 'type_fichier' => '0',
-			 'format' => '0',
-			 'attributs' => serialize(
-				 [ [
-						 'type_doc' =>  [
-							 [ 'code' => '0', 'label' => '', 'zone_995' => '' ],
-							 [ 'code' => '1', 'label' => 'am;as', 'zone_995' => 'az' ],
-							 [ 'code' => '2', 'label' => '', 'zone_995' => ''],
-							 [ 'code' => '3', 'label' => 'i;j;k', 'zone_995' => ''],
-							 [ 'code' => '4', 'label' => 'g','zone_995' => ''],
-							 [ 'code' => '5', 'label' => 'l;m', 'zone_995' => ''],
-							 [ 'code' => '8', 'label' => '', 'zone_995' => ''],
-							 [ 'code' => '9', 'label' => '', 'zone_995' => '' ],
-							 [ 'code' => '10', 'label' => '', 'zone_995' => ''],
-							 [ 'code' => '9', 'label' => '', 'zone_995' => ''],
-							 [ 'code' => '10', 'label' => '', 'zone_995' => '']
-							 ],
-						 'champ_code_barres' => 'f',
-						 'champ_cote' => 'k',
-						 'champ_type_doc' => '',
-						 'champ_genre' => 'e',
-						 'champ_section' => 'q',
-						 'champ_emplacement' => 'u',
-						 'champ_annexe' => ''
-						 ],
-
-					 [ 'champs' => ''],
-					 [ 'champs' => ''],
-					 [ 'champs' => ''],
-					 [ 'champs' => '', 'xml_balise_abonne' => '', 'xml_champs_abonne' =>  [ 'IDABON' => '',
-																																									'ORDREABON' => '',
-																																									'NOM' => '',
-																																									'PRENOM' => '',
-																																									'NAISSANCE' => '',
-																																									'PASSWORD' => '',
-																																									'MAIL' => '',
-																																									'DATE_DEBUT' => '',
-																																									'DATE_FIN' => '',
-																																									'ID_SIGB' => '' ] ],
-					 [ 'zone' => '995', 'champ' => 's', 'format' => '3', 'jours' => '', 'valeurs' => 'nouveaute']
-					 ])];
-	}
-
-
-	public function setUp() {
-		parent::setUp();
-		$this->loadNotice('unimarc_bears_beer');
-	}
-
-
-	/** @test */
-	public function auteursShouldContainsBEAULIEUJIMMY() {
-		$this->assertEquals('BEAULIEU BOLI JIMMY JIMI APOSTOLIDES APOSTOLID JEAN JAN MARIE MARI BOSSE BOS SIMON BOUCHARD BOUCHAR GREGOIRE GREGOIR PIERRE PIER BROERSMA MATTHEW MATEW DELPORTE DELPORT JULIE JULI DOYON DOION RIVEST RIVES EKEBOM EKEBON TERHI TERI FORSYTHE FORSIT GENEST CATHERINE KATERIN GIARD JIAR LUC  GIRARD JIRAR PASCAL PASKAL GOLDBERG GOLDBER ELEONORE ELEONOR HUBER UB MARKUS MARKU IRIS IRI JOLY JOLI BENOIT BENOI LEMAY LEMAI SYLVAIN SILVIN MUSTURI TOMMI TOMI NYLSO NILSO OBOM OBON PISHIER PICHI RICCI RIKSI STEFANO SAMSON JACQUES JAK DIECK DIEK MARTIN TOM TRAHAN TRAN SEBASTIEN SEBASTIN TURGEON TURJON DAVID DAVI VAYRYNEN VAIRINAN MIKKO MIKO VIAU VIO MICHEL WARD OIR BARNABY BARNABI WIGGERT OUIJER GREGOR ZVIANE ZVIAN',
-		$this->notice_integration->noticeToDBEnreg($this->notice_data)['auteurs']);
-	}
-
-
-	/** @test */
-	public function titlesContainsBEAR() {
-		$this->assertEquals('BEARS BEAR BEER BE FORMULE FORMUL 1',
-		$this->notice_integration->noticeToDBEnreg($this->notice_data)['titres']);
-	}
-
-
-	/** @test */
-	public function titleShouldBeBears() {
-		$this->assertEquals('Bears + beer : formule n°1',
-												$this->notice_integration->get_subfield('200', 'a')[0]);
-	}
+  public function getProfilDonnees() {
+    return
+      ['id_profil' => 106,
+       'libelle' => 'Microbib',
+       'accents' => '1',
+       'rejet_periodiques' =>  '1',
+       'id_article_periodique' => '2',
+       'type_fichier' => '0',
+       'format' => '0',
+       'attributs' => serialize(
+                                [ [
+                                   'type_doc' =>  [
+                                                   [ 'code' => '0', 'label' => '', 'zone_995' => '' ],
+                                                   [ 'code' => '1', 'label' => 'am;as', 'zone_995' => 'az' ],
+                                                   [ 'code' => '2', 'label' => '', 'zone_995' => ''],
+                                                   [ 'code' => '3', 'label' => 'i;j;k', 'zone_995' => ''],
+                                                   [ 'code' => '4', 'label' => 'g','zone_995' => ''],
+                                                   [ 'code' => '5', 'label' => 'l;m', 'zone_995' => ''],
+                                                   [ 'code' => '8', 'label' => '', 'zone_995' => ''],
+                                                   [ 'code' => '9', 'label' => '', 'zone_995' => '' ],
+                                                   [ 'code' => '10', 'label' => '', 'zone_995' => ''],
+                                                   [ 'code' => '9', 'label' => '', 'zone_995' => ''],
+                                                   [ 'code' => '10', 'label' => '', 'zone_995' => '']
+                                   ],
+                                   'champ_code_barres' => 'f',
+                                   'champ_cote' => 'k',
+                                   'champ_type_doc' => '',
+                                   'champ_genre' => 'e',
+                                   'champ_section' => 'q',
+                                   'champ_emplacement' => 'u',
+                                   'champ_annexe' => ''
+                                  ],
+
+                                 [ 'champs' => ''],
+                                 [ 'champs' => ''],
+                                 [ 'champs' => ''],
+                                 [ 'champs' => '', 'xml_balise_abonne' => '', 'xml_champs_abonne' =>  [ 'IDABON' => '',
+                                                                                                       'ORDREABON' => '',
+                                                                                                       'NOM' => '',
+                                                                                                       'PRENOM' => '',
+                                                                                                       'NAISSANCE' => '',
+                                                                                                       'PASSWORD' => '',
+                                                                                                       'MAIL' => '',
+                                                                                                       'DATE_DEBUT' => '',
+                                                                                                       'DATE_FIN' => '',
+                                                                                                       'ID_SIGB' => '' ] ],
+                                 [ 'zone' => '995', 'champ' => 's', 'format' => '3', 'jours' => '', 'valeurs' => 'nouveaute']
+                                ])];
+  }
+
+
+  public function setUp() {
+    parent::setUp();
+    $this->loadNotice('unimarc_bears_beer');
+  }
+
+
+  /** @test */
+  public function auteursShouldContainsBEAULIEUJIMMY() {
+    $this->assertEquals('BEAULIEU BOLI JIMMY JIMI APOSTOLIDES APOSTOLID JEAN JAN MARIE MARI BOSSE BOS SIMON BOUCHARD BOUCHAR GREGOIRE GREGOIR PIERRE PIER BROERSMA MATTHEW MATEW DELPORTE DELPORT JULIE JULI DOYON DOION RIVEST RIVES EKEBOM EKEBON TERHI TERI FORSYTHE FORSIT GENEST CATHERINE KATERIN GIARD JIAR LUC  GIRARD JIRAR PASCAL PASKAL GOLDBERG GOLDBER ELEONORE ELEONOR HUBER UB MARKUS MARKU IRIS IRI JOLY JOLI BENOIT BENOI LEMAY LEMAI SYLVAIN SILVIN MUSTURI TOMMI TOMI NYLSO NILSO OBOM OBON PISHIER PICHI RICCI RIKSI STEFANO SAMSON JACQUES JAK DIECK DIEK MARTIN TOM TRAHAN TRAN SEBASTIEN SEBASTIN TURGEON TURJON DAVID DAVI VAYRYNEN VAIRINAN MIKKO MIKO VIAU VIO MICHEL WARD OIR BARNABY BARNABI WIGGERT OUIJER GREGOR ZVIANE ZVIAN',
+                        $this->notice_integration->noticeToDBEnreg($this->notice_data)['auteurs']);
+  }
+
+
+  /** @test */
+  public function titlesContainsBEAR() {
+    $this->assertEquals('BEARS BEAR BEER BE FORMULE FORMUL 1',
+                        $this->notice_integration->noticeToDBEnreg($this->notice_data)['titres']);
+  }
+
+
+  /** @test */
+  public function titleShouldBeBears() {
+    $this->assertEquals('Bears + beer : formule n°1',
+                        $this->notice_integration->get_subfield('200', 'a')[0]);
+  }
 }
 
 
@@ -743,124 +754,126 @@ class NoticeIntegrationBearsBeerMicrobibTest extends NoticeIntegrationTestCase {
 
 
 class NoticeIntegrationItemsIn999Test extends NoticeIntegrationTestCase {
-	protected $_profil_donnees =
-		['id_profil' => 111,
-		'libelle' => 'Unimarc Dynix',
-		'accents' => '1',
-		'rejet_periodiques' =>  '1',
-		'id_article_periodique' => '2',
-		'type_fichier' => '0',
-		'format' => '6',
-		'attributs' => 'a:7:{i:0;a:8:{s:8:"type_doc";a:22:{i:0;a:3:{s:4:"code";s:1:"0";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:1;a:3:{s:4:"code";s:1:"1";s:5:"label";s:11:"am;na;ac;ad";s:8:"zone_995";s:0:"";}i:2;a:3:{s:4:"code";s:1:"2";s:5:"label";s:8:"as;aa;ab";s:8:"zone_995";s:0:"";}i:3;a:3:{s:4:"code";s:1:"3";s:5:"label";s:1:"j";s:8:"zone_995";s:2:"CD";}i:4;a:3:{s:4:"code";s:1:"4";s:5:"label";s:1:"g";s:8:"zone_995";s:3:"DVD";}i:5;a:3:{s:4:"code";s:1:"5";s:5:"label";s:3:"l;m";s:8:"zone_995";s:0:"";}i:6;a:3:{s:4:"code";s:1:"6";s:5:"label";s:1:"c";s:8:"zone_995";s:3:"PAR";}i:7;a:3:{s:4:"code";s:1:"7";s:5:"label";s:2:"em";s:8:"zone_995";s:0:"";}i:8;a:3:{s:4:"code";s:1:"8";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:9;a:3:{s:4:"code";s:1:"9";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:10;a:3:{s:4:"code";s:2:"10";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:11;a:3:{s:4:"code";s:2:"15";s:5:"label";s:1:"i";s:8:"zone_995";s:0:"";}i:12;a:3:{s:4:"code";s:3:"100";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:13;a:3:{s:4:"code";s:3:"101";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:14;a:3:{s:4:"code";s:3:"102";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:15;a:3:{s:4:"code";s:3:"103";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:16;a:3:{s:4:"code";s:3:"104";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:17;a:3:{s:4:"code";s:3:"105";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:18;a:3:{s:4:"code";s:3:"106";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:19;a:3:{s:4:"code";s:3:"107";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:20;a:3:{s:4:"code";s:3:"108";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:21;a:3:{s:4:"code";s:3:"109";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}}s:17:"champ_code_barres";s:3:"999";s:10:"champ_cote";s:1:"k";s:14:"champ_type_doc";s:1:"x";s:11:"champ_genre";s:0:"";s:13:"champ_section";s:1:"z";s:17:"champ_emplacement";s:1:"l";s:12:"champ_annexe";s:1:"m";}i:1;a:1:{s:6:"champs";s:0:"";}i:2;a:1:{s:6:"champs";s:0:"";}i:3;a:1:{s:6:"champs";s:0:"";}i:5;a:3:{s:6:"champs";s:0:"";s:17:"xml_balise_abonne";s:0:"";s:17:"xml_champs_abonne";a:11:{s:6:"IDABON";s:0:"";s:9:"ORDREABON";s:0:"";s:3:"NOM";s:0:"";s:6:"PRENOM";s:0:"";s:9:"NAISSANCE";s:0:"";s:8:"PASSWORD";s:0:"";s:4:"MAIL";s:0:"";s:10:"DATE_DEBUT";s:0:"";s:8:"DATE_FIN";s:0:"";s:7:"ID_SIGB";s:0:"";s:9:"NUM_CARTE";s:0:"";}}i:4;a:5:{s:4:"zone";s:3:"996";s:5:"champ";s:1:"u";s:6:"format";s:1:"4";s:5:"jours";s:2:"90";s:7:"valeurs";s:0:"";}i:6;a:2:{s:4:"zone";s:0:"";s:5:"champ";s:0:"";}}'];
-
-	public function setUp() {
-		parent::setUp();
-
-		$this->fixture('Class_CodifSection',
-									 ['id' => 2,
-										'libelle' => 'enfants',
-										'regles' => '996$z=ENF']);
-
-
-		$this->fixture('Class_CodifEmplacement',
-									 ['id' => 54,
-										'libelle' => 'enfants',
-										'regles' => '996$l=07MJENF']);
-
-
-		$this->loadNotice('unimarc_items_in_996');
-	}
-
-
-	/**
-	 * @return format [expected, item index, item field]
-	 */
-	public function itemsProvider() {
-		return [
-			['00519824', 0, 'code_barres'],
-			['EM A MUS J', 0, 'cote'],
-			[2, 0, 'section'],
-			['CRETBUS', 0, 'annexe'],
-			[54, 0, 'emplacement'],
-			['2010-02-23', 0, 'date_nouveaute'],
-			[false, 0, 'ignore_exemplaire'],
-			[true, 3, 'ignore_exemplaire'],
-		];
-	}
-
-
-	/** @test */
-	public function noticeShouldHave5Items() {
-		$this->assertEquals(5, count($this->notice_data['exemplaires']));
-	}
-
-
-	/**
-	 * @test
-	 * @dataProvider itemsProvider
-	 */
-	public function itemsPropertyShouldBeAsExpected($expected, $index, $property) {
-		$this->assertEquals($expected, $this->notice_data['exemplaires'][$index][$property]);
-	}
+  protected $_profil_donnees =
+    ['id_profil' => 111,
+     'libelle' => 'Unimarc Dynix',
+     'accents' => '1',
+     'rejet_periodiques' =>  '1',
+     'id_article_periodique' => '2',
+     'type_fichier' => '0',
+     'format' => '6',
+     'attributs' => 'a:7:{i:0;a:8:{s:8:"type_doc";a:22:{i:0;a:3:{s:4:"code";s:1:"0";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:1;a:3:{s:4:"code";s:1:"1";s:5:"label";s:11:"am;na;ac;ad";s:8:"zone_995";s:0:"";}i:2;a:3:{s:4:"code";s:1:"2";s:5:"label";s:8:"as;aa;ab";s:8:"zone_995";s:0:"";}i:3;a:3:{s:4:"code";s:1:"3";s:5:"label";s:1:"j";s:8:"zone_995";s:2:"CD";}i:4;a:3:{s:4:"code";s:1:"4";s:5:"label";s:1:"g";s:8:"zone_995";s:3:"DVD";}i:5;a:3:{s:4:"code";s:1:"5";s:5:"label";s:3:"l;m";s:8:"zone_995";s:0:"";}i:6;a:3:{s:4:"code";s:1:"6";s:5:"label";s:1:"c";s:8:"zone_995";s:3:"PAR";}i:7;a:3:{s:4:"code";s:1:"7";s:5:"label";s:2:"em";s:8:"zone_995";s:0:"";}i:8;a:3:{s:4:"code";s:1:"8";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:9;a:3:{s:4:"code";s:1:"9";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:10;a:3:{s:4:"code";s:2:"10";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:11;a:3:{s:4:"code";s:2:"15";s:5:"label";s:1:"i";s:8:"zone_995";s:0:"";}i:12;a:3:{s:4:"code";s:3:"100";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:13;a:3:{s:4:"code";s:3:"101";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:14;a:3:{s:4:"code";s:3:"102";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:15;a:3:{s:4:"code";s:3:"103";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:16;a:3:{s:4:"code";s:3:"104";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:17;a:3:{s:4:"code";s:3:"105";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:18;a:3:{s:4:"code";s:3:"106";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:19;a:3:{s:4:"code";s:3:"107";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:20;a:3:{s:4:"code";s:3:"108";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:21;a:3:{s:4:"code";s:3:"109";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}}s:17:"champ_code_barres";s:3:"999";s:10:"champ_cote";s:1:"k";s:14:"champ_type_doc";s:1:"x";s:11:"champ_genre";s:0:"";s:13:"champ_section";s:1:"z";s:17:"champ_emplacement";s:1:"l";s:12:"champ_annexe";s:1:"m";}i:1;a:1:{s:6:"champs";s:0:"";}i:2;a:1:{s:6:"champs";s:0:"";}i:3;a:1:{s:6:"champs";s:0:"";}i:5;a:3:{s:6:"champs";s:0:"";s:17:"xml_balise_abonne";s:0:"";s:17:"xml_champs_abonne";a:11:{s:6:"IDABON";s:0:"";s:9:"ORDREABON";s:0:"";s:3:"NOM";s:0:"";s:6:"PRENOM";s:0:"";s:9:"NAISSANCE";s:0:"";s:8:"PASSWORD";s:0:"";s:4:"MAIL";s:0:"";s:10:"DATE_DEBUT";s:0:"";s:8:"DATE_FIN";s:0:"";s:7:"ID_SIGB";s:0:"";s:9:"NUM_CARTE";s:0:"";}}i:4;a:5:{s:4:"zone";s:3:"996";s:5:"champ";s:1:"u";s:6:"format";s:1:"4";s:5:"jours";s:2:"90";s:7:"valeurs";s:0:"";}i:6;a:2:{s:4:"zone";s:0:"";s:5:"champ";s:0:"";}}'];
+
+  public function setUp() {
+    parent::setUp();
+
+    $this->fixture('Class_CodifSection',
+                   ['id' => 2,
+                    'libelle' => 'enfants',
+                    'regles' => '996$z=ENF']);
+
+
+    $this->fixture('Class_CodifEmplacement',
+                   ['id' => 54,
+                    'libelle' => 'enfants',
+                    'regles' => '996$l=07MJENF']);
+
+
+    $this->loadNotice('unimarc_items_in_996');
+  }
+
+
+  /**
+   * @return format [expected, item index, item field]
+   */
+  public function itemsProvider() {
+    return [
+            ['00519824', 0, 'code_barres'],
+            ['EM A MUS J', 0, 'cote'],
+            [2, 0, 'section'],
+            ['CRETBUS', 0, 'annexe'],
+            [54, 0, 'emplacement'],
+            ['2010-02-23', 0, 'date_nouveaute'],
+            [false, 0, 'ignore_exemplaire'],
+            [true, 3, 'ignore_exemplaire'],
+    ];
+  }
+
+
+  /** @test */
+  public function noticeShouldHave5Items() {
+    $this->assertEquals(5, count($this->notice_data['exemplaires']));
+  }
+
+
+  /**
+   * @test
+   * @dataProvider itemsProvider
+   */
+  public function itemsPropertyShouldBeAsExpected($expected, $index, $property) {
+    $this->assertEquals($expected, $this->notice_data['exemplaires'][$index][$property]);
+  }
 }
 
 
 
 class NoticeIntegrationItemsIn852Test extends NoticeIntegrationTestCase {
-	protected $_profil_donnees =
-		['id_profil' => 111,
-		'libelle' => 'Unimarc Moulins',
-		'accents' => '1',
-		'rejet_periodiques' =>  '1',
-		'id_article_periodique' => '2',
-		'type_fichier' => '0',
-		'format' => '0',
-		'attributs' => 'a:7:{i:0;a:8:{s:8:"type_doc";a:19:{i:0;a:3:{s:4:"code";s:1:"0";s:5:"label";s:0:"";s:8:"zone_995";s:6:"PATEST";}i:1;a:3:{s:4:"code";s:1:"1";s:5:"label";s:16:"am;bm;cm;em;gm;m";s:8:"zone_995";s:59:"BDA;BDJ;LFA;LFJ;LDA;LDJ;LCDA;LCDJ;PATIMP;PATMS;PERIP;PATINC";}i:2;a:3:{s:4:"code";s:1:"2";s:5:"label";s:2:"as";s:8:"zone_995";s:14:"PEA;PEJ;PATPER";}i:3;a:3:{s:4:"code";s:1:"3";s:5:"label";s:5:"jm;im";s:8:"zone_995";s:21:"CDMA;CDMJ;CDTLA;CDTLJ";}i:4;a:3:{s:4:"code";s:1:"4";s:5:"label";s:2:"mm";s:8:"zone_995";s:43:"DVF00;DVF12;DVF16;DVF18;DVDOCA;DVDOCJ;DVDOC";}i:5;a:3:{s:4:"code";s:1:"5";s:5:"label";s:2:"lm";s:8:"zone_995";s:3:"CDR";}i:6;a:3:{s:4:"code";s:1:"6";s:5:"label";s:5:"cm;dm";s:8:"zone_995";s:10:"PAR;PATPAR";}i:7;a:3:{s:4:"code";s:1:"8";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:8;a:3:{s:4:"code";s:1:"9";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:9;a:3:{s:4:"code";s:2:"10";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:10;a:3:{s:4:"code";s:3:"100";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:11;a:3:{s:4:"code";s:3:"101";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:12;a:3:{s:4:"code";s:3:"102";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:13;a:3:{s:4:"code";s:3:"103";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:14;a:3:{s:4:"code";s:3:"104";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:15;a:3:{s:4:"code";s:3:"105";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:16;a:3:{s:4:"code";s:3:"107";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:17;a:3:{s:4:"code";s:3:"108";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:18;a:3:{s:4:"code";s:3:"109";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}}s:17:"champ_code_barres";s:3:"852";s:10:"champ_cote";s:1:"k";s:14:"champ_type_doc";s:0:"";s:11:"champ_genre";s:0:"";s:13:"champ_section";s:0:"";s:17:"champ_emplacement";s:0:"";s:12:"champ_annexe";s:0:"";}i:1;a:1:{s:6:"champs";s:0:"";}i:2;a:1:{s:6:"champs";s:0:"";}i:3;a:1:{s:6:"champs";s:0:"";}i:5;a:3:{s:6:"champs";s:0:"";s:17:"xml_balise_abonne";s:0:"";s:17:"xml_champs_abonne";a:11:{s:6:"IDABON";s:0:"";s:9:"ORDREABON";s:0:"";s:3:"NOM";s:0:"";s:6:"PRENOM";s:0:"";s:9:"NAISSANCE";s:0:"";s:8:"PASSWORD";s:0:"";s:4:"MAIL";s:0:"";s:10:"DATE_DEBUT";s:0:"";s:8:"DATE_FIN";s:0:"";s:7:"ID_SIGB";s:0:"";s:9:"NUM_CARTE";s:0:"";}}i:4;a:5:{s:4:"zone";s:3:"852";s:5:"champ";s:1:"d";s:6:"format";s:1:"5";s:5:"jours";s:2:"90";s:7:"valeurs";s:0:"";}i:6;a:2:{s:4:"zone";s:0:"";s:5:"champ";s:0:"";}}'];
-
-	public function setUp() {
-		parent::setUp();
-
-		$this->fixture('Class_CodifSection',
-									 ['id' => 2,
-										'invisible' => 0,
-										'regles' => '852$q=AVJE']);
-
-
-		$this->fixture('Class_CodifSection',
-									 ['id' => 3,
-										'invisible' => 1,
-										'regles' => '852$q=MOJE']);
-
-		$this->loadNotice('unimarc_items_in_852');
-	}
-
-
-	/**
-	 * @return format [expected, item index, item field]
-	 */
-	public function itemsProvider() {
-		return [['0024100013', 0, 'code_barres'],
-						['JBD HER 16', 0, 'cote'],
-						[2, 0, 'section'],
-						['AVER', 0, 'annexe'],
-						[false, 0, 'ignore_exemplaire'],
-						['2005-08-15', 0, 'date_nouveaute']];
-	}
-
-
-	/** @test */
-	public function noticeShouldHave2Items() {
-		$this->assertEquals(2, count($this->notice_data['exemplaires']));
-	}
-
-
-	/**
-	 * @test
-	 * @dataProvider itemsProvider
-	 */
-	public function itemsPropertyShouldBeAsExpected($expected, $index, $property) {
-		$this->assertEquals($expected, $this->notice_data['exemplaires'][$index][$property]);
-	}
+  protected $_profil_donnees =
+    ['id_profil' => 111,
+     'libelle' => 'Unimarc Moulins',
+     'accents' => '1',
+     'rejet_periodiques' =>  '1',
+     'id_article_periodique' => '2',
+     'type_fichier' => '0',
+     'format' => '0',
+     'attributs' => 'a:7:{i:0;a:8:{s:8:"type_doc";a:19:{i:0;a:3:{s:4:"code";s:1:"0";s:5:"label";s:0:"";s:8:"zone_995";s:6:"PATEST";}i:1;a:3:{s:4:"code";s:1:"1";s:5:"label";s:16:"am;bm;cm;em;gm;m";s:8:"zone_995";s:59:"BDA;BDJ;LFA;LFJ;LDA;LDJ;LCDA;LCDJ;PATIMP;PATMS;PERIP;PATINC";}i:2;a:3:{s:4:"code";s:1:"2";s:5:"label";s:2:"as";s:8:"zone_995";s:14:"PEA;PEJ;PATPER";}i:3;a:3:{s:4:"code";s:1:"3";s:5:"label";s:5:"jm;im";s:8:"zone_995";s:21:"CDMA;CDMJ;CDTLA;CDTLJ";}i:4;a:3:{s:4:"code";s:1:"4";s:5:"label";s:2:"mm";s:8:"zone_995";s:43:"DVF00;DVF12;DVF16;DVF18;DVDOCA;DVDOCJ;DVDOC";}i:5;a:3:{s:4:"code";s:1:"5";s:5:"label";s:2:"lm";s:8:"zone_995";s:3:"CDR";}i:6;a:3:{s:4:"code";s:1:"6";s:5:"label";s:5:"cm;dm";s:8:"zone_995";s:10:"PAR;PATPAR";}i:7;a:3:{s:4:"code";s:1:"8";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:8;a:3:{s:4:"code";s:1:"9";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:9;a:3:{s:4:"code";s:2:"10";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:10;a:3:{s:4:"code";s:3:"100";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:11;a:3:{s:4:"code";s:3:"101";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:12;a:3:{s:4:"code";s:3:"102";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:13;a:3:{s:4:"code";s:3:"103";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:14;a:3:{s:4:"code";s:3:"104";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:15;a:3:{s:4:"code";s:3:"105";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:16;a:3:{s:4:"code";s:3:"107";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:17;a:3:{s:4:"code";s:3:"108";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:18;a:3:{s:4:"code";s:3:"109";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}}s:17:"champ_code_barres";s:3:"852";s:10:"champ_cote";s:1:"k";s:14:"champ_type_doc";s:0:"";s:11:"champ_genre";s:0:"";s:13:"champ_section";s:0:"";s:17:"champ_emplacement";s:0:"";s:12:"champ_annexe";s:0:"";}i:1;a:1:{s:6:"champs";s:0:"";}i:2;a:1:{s:6:"champs";s:0:"";}i:3;a:1:{s:6:"champs";s:0:"";}i:5;a:3:{s:6:"champs";s:0:"";s:17:"xml_balise_abonne";s:0:"";s:17:"xml_champs_abonne";a:11:{s:6:"IDABON";s:0:"";s:9:"ORDREABON";s:0:"";s:3:"NOM";s:0:"";s:6:"PRENOM";s:0:"";s:9:"NAISSANCE";s:0:"";s:8:"PASSWORD";s:0:"";s:4:"MAIL";s:0:"";s:10:"DATE_DEBUT";s:0:"";s:8:"DATE_FIN";s:0:"";s:7:"ID_SIGB";s:0:"";s:9:"NUM_CARTE";s:0:"";}}i:4;a:5:{s:4:"zone";s:3:"852";s:5:"champ";s:1:"d";s:6:"format";s:1:"5";s:5:"jours";s:2:"90";s:7:"valeurs";s:0:"";}i:6;a:2:{s:4:"zone";s:0:"";s:5:"champ";s:0:"";}}'];
+
+  public function setUp() {
+    parent::setUp();
+
+    $this->fixture('Class_CodifSection',
+                   ['id' => 2,
+                    'libelle' => 'AVJE',
+                    'invisible' => 0,
+                    'regles' => '852$q=AVJE']);
+
+
+    $this->fixture('Class_CodifSection',
+                   ['id' => 3,
+                    'libelle' => 'MOJE',
+                    'invisible' => 1,
+                    'regles' => '852$q=MOJE']);
+
+    $this->loadNotice('unimarc_items_in_852');
+  }
+
+
+  /**
+   * @return format [expected, item index, item field]
+   */
+  public function itemsProvider() {
+    return [['0024100013', 0, 'code_barres'],
+            ['JBD HER 16', 0, 'cote'],
+            [2, 0, 'section'],
+            ['AVER', 0, 'annexe'],
+            [false, 0, 'ignore_exemplaire'],
+            ['2005-08-15', 0, 'date_nouveaute']];
+  }
+
+
+  /** @test */
+  public function noticeShouldHave2Items() {
+    $this->assertEquals(2, count($this->notice_data['exemplaires']));
+  }
+
+
+  /**
+   * @test
+   * @dataProvider itemsProvider
+   */
+  public function itemsPropertyShouldBeAsExpected($expected, $index, $property) {
+    $this->assertEquals($expected, $this->notice_data['exemplaires'][$index][$property]);
+  }
 }
 
 
@@ -870,294 +883,294 @@ class NoticeIntegrationItemsIn852Test extends NoticeIntegrationTestCase {
  * @see http://forge.afi-sa.fr/issues/14869
  */
 class NoticeIntegrationPergameEmplacementZeroTest extends NoticeIntegrationTestCase {
-	protected $_profil_donnees = [
-		'id_profil' => 100,
-		'libelle' => 'Unimarc Pergame',
-		'accents' => 2,
-		'rejet_periodiques' => 0,
-		'id_article_periodique' => 1,
-		'type_fichier' => 0,
-		'format' => 0,
-		'attributs' => 'a:6:{i:0;a:6:{s:8:"type_doc";a:11:{i:0;a:3:{s:4:"code";s:1:"0";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:1;a:3:{s:4:"code";s:1:"1";s:5:"label";s:2:"am";s:8:"zone_995";s:0:"";}i:2;a:3:{s:4:"code";s:1:"2";s:5:"label";s:2:"as";s:8:"zone_995";s:0:"";}i:3;a:3:{s:4:"code";s:1:"3";s:5:"label";s:2:"je";s:8:"zone_995";s:0:"";}i:4;a:3:{s:4:"code";s:1:"4";s:5:"label";s:2:"gd";s:8:"zone_995";s:0:"";}i:5;a:3:{s:4:"code";s:1:"5";s:5:"label";s:2:"le";s:8:"zone_995";s:0:"";}i:6;a:3:{s:4:"code";s:1:"8";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:7;a:3:{s:4:"code";s:1:"9";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:8;a:3:{s:4:"code";s:2:"10";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:9;a:3:{s:4:"code";s:1:"9";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:10;a:3:{s:4:"code";s:2:"10";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}}s:17:"champ_code_barres";s:1:"f";s:11:"champ_genre";s:0:"";s:13:"champ_section";s:1:"q";s:17:"champ_emplacement";s:1:"6";s:12:"champ_annexe";s:1:"8";}i:1;a:1:{s:6:"champs";s:0:"";}i:2;a:1:{s:6:"champs";s:0:"";}i:3;a:1:{s:6:"champs";s:0:"";}i:4;a:5:{s:4:"zone";s:3:"995";s:5:"champ";s:1:"4";s:6:"format";s:1:"1";s:5:"jours";s:2:"90";s:7:"valeurs";s:0:"";}i:5;a:2:{s:17:"xml_balise_abonne";s:0:"";s:17:"xml_champs_abonne";a:10:{s:6:"IDABON";s:0:"";s:9:"ORDREABON";s:0:"";s:3:"NOM";s:0:"";s:6:"PRENOM";s:0:"";s:9:"NAISSANCE";s:0:"";s:8:"PASSWORD";s:0:"";s:4:"MAIL";s:0:"";s:10:"DATE_DEBUT";s:0:"";s:8:"DATE_FIN";s:0:"";s:7:"ID_SIGB";s:0:"";}}}'
-	];
+  protected $_profil_donnees = [
+                                'id_profil' => 100,
+                                'libelle' => 'Unimarc Pergame',
+                                'accents' => 2,
+                                'rejet_periodiques' => 0,
+                                'id_article_periodique' => 1,
+                                'type_fichier' => 0,
+                                'format' => 0,
+                                'attributs' => 'a:6:{i:0;a:6:{s:8:"type_doc";a:11:{i:0;a:3:{s:4:"code";s:1:"0";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:1;a:3:{s:4:"code";s:1:"1";s:5:"label";s:2:"am";s:8:"zone_995";s:0:"";}i:2;a:3:{s:4:"code";s:1:"2";s:5:"label";s:2:"as";s:8:"zone_995";s:0:"";}i:3;a:3:{s:4:"code";s:1:"3";s:5:"label";s:2:"je";s:8:"zone_995";s:0:"";}i:4;a:3:{s:4:"code";s:1:"4";s:5:"label";s:2:"gd";s:8:"zone_995";s:0:"";}i:5;a:3:{s:4:"code";s:1:"5";s:5:"label";s:2:"le";s:8:"zone_995";s:0:"";}i:6;a:3:{s:4:"code";s:1:"8";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:7;a:3:{s:4:"code";s:1:"9";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:8;a:3:{s:4:"code";s:2:"10";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:9;a:3:{s:4:"code";s:1:"9";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:10;a:3:{s:4:"code";s:2:"10";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}}s:17:"champ_code_barres";s:1:"f";s:11:"champ_genre";s:0:"";s:13:"champ_section";s:1:"q";s:17:"champ_emplacement";s:1:"6";s:12:"champ_annexe";s:1:"8";}i:1;a:1:{s:6:"champs";s:0:"";}i:2;a:1:{s:6:"champs";s:0:"";}i:3;a:1:{s:6:"champs";s:0:"";}i:4;a:5:{s:4:"zone";s:3:"995";s:5:"champ";s:1:"4";s:6:"format";s:1:"1";s:5:"jours";s:2:"90";s:7:"valeurs";s:0:"";}i:5;a:2:{s:17:"xml_balise_abonne";s:0:"";s:17:"xml_champs_abonne";a:10:{s:6:"IDABON";s:0:"";s:9:"ORDREABON";s:0:"";s:3:"NOM";s:0:"";s:6:"PRENOM";s:0:"";s:9:"NAISSANCE";s:0:"";s:8:"PASSWORD";s:0:"";s:4:"MAIL";s:0:"";s:10:"DATE_DEBUT";s:0:"";s:8:"DATE_FIN";s:0:"";s:7:"ID_SIGB";s:0:"";}}}'
+  ];
 
 
-	public function setUp() {
-		parent::setUp();
+  public function setUp() {
+    parent::setUp();
 
-		$this->fixture('Class_CodifSection',
-									 ['id' => 1,
-										'libelle' => 'adulte',
-										'regles' => '995$q=A']);
+    $this->fixture('Class_CodifSection',
+                   ['id' => 1,
+                    'libelle' => 'adulte',
+                    'regles' => '995$q=A']);
 
 
-		$this->fixture('Class_CodifEmplacement',
-									 ['id' => 3,
-										'libelle' => 'enfants',
-										'regles' => '995$6=0']);
+    $this->fixture('Class_CodifEmplacement',
+                   ['id' => 3,
+                    'libelle' => 'enfants',
+                    'regles' => '995$6=0']);
 
-		$this->loadNotice('unimarc_emplacement_codif_zero');
-		$this->exemplaire = Class_Exemplaire::findFirstBy([]);
-	}
+    $this->loadNotice('unimarc_emplacement_codif_zero');
+    $this->exemplaire = Class_Exemplaire::findFirstBy([]);
+  }
 
 
-	/** @test */
-	public function shouldHaveOneItem() {
-		$this->assertEquals(1, count(Class_Exemplaire::findAll()));
-	}
+  /** @test */
+  public function shouldHaveOneItem() {
+    $this->assertEquals(1, count(Class_Exemplaire::findAll()));
+  }
 
 
-	/** @test */
-	public function exemplaireEmplacementShouldBeThree() {
-		$this->assertEquals(3, $this->exemplaire->getEmplacement());
-	}
+  /** @test */
+  public function exemplaireEmplacementShouldBeThree() {
+    $this->assertEquals(3, $this->exemplaire->getEmplacement());
+  }
 
 
-	/** @test */
-	public function exemplaireBarcodeShouldBeL01528() {
-		$this->assertEquals('L-01528', $this->exemplaire->getCodeBarres());
-	}
+  /** @test */
+  public function exemplaireBarcodeShouldBeL01528() {
+    $this->assertEquals('L-01528', $this->exemplaire->getCodeBarres());
+  }
 
 
-	/** @test */
-	public function exemplaireCodeShouldBeOk() {
-		$this->assertEquals('R BOI B', $this->exemplaire->getCote());
-	}
+  /** @test */
+  public function exemplaireCodeShouldBeOk() {
+    $this->assertEquals('R BOI B', $this->exemplaire->getCote());
+  }
 
 
-	/** @test */
-	public function exemplaireSectionShouldBeOne() {
-		$this->assertEquals(1, $this->exemplaire->getSection());
-	}
+  /** @test */
+  public function exemplaireSectionShouldBeOne() {
+    $this->assertEquals(1, $this->exemplaire->getSection());
+  }
 
 
-	/** @test */
-	public function exemplaireZone995ShouldContaineDollarSixEqualsZero() {
-		foreach(unserialize($this->exemplaire->getZone995()) as $field) {
-			if ('6' == $field['code']) {
-				$this->assertEquals('0', $field['valeur']);
-				return;
-			}
-		}
-		$this->fail('No 995$6 found');
-	}
+  /** @test */
+  public function exemplaireZone995ShouldContaineDollarSixEqualsZero() {
+    foreach(unserialize($this->exemplaire->getZone995()) as $field) {
+      if ('6' == $field['code']) {
+        $this->assertEquals('0', $field['valeur']);
+        return;
+      }
+    }
+    $this->fail('No 995$6 found');
+  }
 }
 
 
 
 
 class NoticeIntegrationDossier64Test extends NoticeIntegrationTestCase {
-	protected $_profil_donnees = [
-		'id_profil' => 100,
-		'libelle' => 'Unimarc Pergame',
-		'accents' => 1,
-		'rejet_periodiques' => 1,
-		'id_article_periodique' => 0,
-		'type_fichier' => 0,
-		'format' => 0,
-		'attributs' => 'a:4:{i:0;a:6:{s:8:"type_doc";a:11:{i:0;a:3:{s:4:"code";s:1:"0";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:1;a:3:{s:4:"code";s:1:"1";s:5:"label";s:2:"am";s:8:"zone_995";s:0:"";}i:2;a:3:{s:4:"code";s:1:"2";s:5:"label";s:2:"as";s:8:"zone_995";s:0:"";}i:3;a:3:{s:4:"code";s:1:"3";s:5:"label";s:2:"je";s:8:"zone_995";s:0:"";}i:4;a:3:{s:4:"code";s:1:"4";s:5:"label";s:2:"gd";s:8:"zone_995";s:0:"";}i:5;a:3:{s:4:"code";s:1:"5";s:5:"label";s:2:"le";s:8:"zone_995";s:0:"";}i:6;a:3:{s:4:"code";s:1:"8";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:7;a:3:{s:4:"code";s:1:"9";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:8;a:3:{s:4:"code";s:2:"10";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:9;a:3:{s:4:"code";s:1:"9";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:10;a:3:{s:4:"code";s:2:"10";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}}s:17:"champ_code_barres";s:1:"f";s:11:"champ_genre";s:0:"";s:13:"champ_section";s:1:"q";s:17:"champ_emplacement";s:1:"6";s:12:"champ_annexe";s:1:"8";}i:1;a:1:{s:6:"champs";s:0:"";}i:2;a:1:{s:6:"champs";s:0:"";}i:3;a:1:{s:6:"champs";s:0:"";}}'
-	];
-
-
-	public function setUp() {
-		parent::setUp();
-		$this->loadNotice('unimarc_dossier64');
-		$this->datas = $this->notice_integration->noticeToDBEnreg($this->notice_data);
-		$this->notice_sgbd->ouvrirNotice($this->datas['unimarc'], 0);
-	}
-
-	/** @test */
-	public function priceShouldBeTwentyTwoEuros() {
-		$this->assertEquals('22.90 €', $this->notice_sgbd->get_subfield('010', 'd')[0]);
-	}
-
-	/** @test */
-	public function titleShouldBeDossier64() {
-		$this->assertContains('DOSSIER', $this->datas['titres']);
-	}
-
-	/** @test */
-	public function zone200aShouldBeDossier64() {
-		$this->assertEquals('Dossier 64', $this->notice_sgbd->get_subfield('200', 'a')[0]);
-	}
+  protected $_profil_donnees = [
+                                'id_profil' => 100,
+                                'libelle' => 'Unimarc Pergame',
+                                'accents' => 1,
+                                'rejet_periodiques' => 1,
+                                'id_article_periodique' => 0,
+                                'type_fichier' => 0,
+                                'format' => 0,
+                                'attributs' => 'a:4:{i:0;a:6:{s:8:"type_doc";a:11:{i:0;a:3:{s:4:"code";s:1:"0";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:1;a:3:{s:4:"code";s:1:"1";s:5:"label";s:2:"am";s:8:"zone_995";s:0:"";}i:2;a:3:{s:4:"code";s:1:"2";s:5:"label";s:2:"as";s:8:"zone_995";s:0:"";}i:3;a:3:{s:4:"code";s:1:"3";s:5:"label";s:2:"je";s:8:"zone_995";s:0:"";}i:4;a:3:{s:4:"code";s:1:"4";s:5:"label";s:2:"gd";s:8:"zone_995";s:0:"";}i:5;a:3:{s:4:"code";s:1:"5";s:5:"label";s:2:"le";s:8:"zone_995";s:0:"";}i:6;a:3:{s:4:"code";s:1:"8";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:7;a:3:{s:4:"code";s:1:"9";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:8;a:3:{s:4:"code";s:2:"10";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:9;a:3:{s:4:"code";s:1:"9";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:10;a:3:{s:4:"code";s:2:"10";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}}s:17:"champ_code_barres";s:1:"f";s:11:"champ_genre";s:0:"";s:13:"champ_section";s:1:"q";s:17:"champ_emplacement";s:1:"6";s:12:"champ_annexe";s:1:"8";}i:1;a:1:{s:6:"champs";s:0:"";}i:2;a:1:{s:6:"champs";s:0:"";}i:3;a:1:{s:6:"champs";s:0:"";}}'
+  ];
+
+
+  public function setUp() {
+    parent::setUp();
+    $this->loadNotice('unimarc_dossier64');
+    $this->datas = $this->notice_integration->noticeToDBEnreg($this->notice_data);
+    $this->notice_sgbd->ouvrirNotice($this->datas['unimarc'], 0);
+  }
+
+  /** @test */
+  public function priceShouldBeTwentyTwoEuros() {
+    $this->assertEquals('22.90 €', $this->notice_sgbd->get_subfield('010', 'd')[0]);
+  }
+
+  /** @test */
+  public function titleShouldBeDossier64() {
+    $this->assertContains('DOSSIER', $this->datas['titres']);
+  }
+
+  /** @test */
+  public function zone200aShouldBeDossier64() {
+    $this->assertEquals('Dossier 64', $this->notice_sgbd->get_subfield('200', 'a')[0]);
+  }
 }
 
 
 
 
 class NoticeIntegrationGenreMultiple902Test extends NoticeIntegrationTestCase {
-	public function setUp() {
-		parent::setUp();
-
-		$this->fixture('Class_CodifGenre',
-									 ['id' => 81,
-										'libelle' => 'Genre1',
-										'regles' => '902$a=Genre1']);
-
-		$this->fixture('Class_CodifGenre',
-									 ['id' => 82,
-										'libelle' => 'Genre2',
-										'regles' => '902$a=Genre2']);
-
-		$this->fixture('Class_CodifGenre',
-									 ['id' => 83,
-										'libelle' => 'Genre3',
-										'regles' => '902$a=Genre3']);
-
-		$this->loadNotice('test-genre-multiple-902');
-	}
-
-
-	/** @test */
-	public function noticeShouldHaveThreeGenres() {
-		$this->assertInternalType('array', $this->notice_data['genres']);
-		$this->assertCount(3, $this->notice_data['genres']);
-		$this->assertContains(81, $this->notice_data['genres']);
-		$this->assertContains(82, $this->notice_data['genres']);
-		$this->assertContains(83, $this->notice_data['genres']);
-	}
-
-	/** @test */
-	public function facettesShouldContainThreeGenres() {
-		$this->assertContains(' G81', $this->notice_data['facettes']);
-		$this->assertContains(' G82', $this->notice_data['facettes']);
-		$this->assertContains(' G83', $this->notice_data['facettes']);
-	}
+  public function setUp() {
+    parent::setUp();
+
+    $this->fixture('Class_CodifGenre',
+                   ['id' => 81,
+                    'libelle' => 'Genre1',
+                    'regles' => '902$a=Genre1']);
+
+    $this->fixture('Class_CodifGenre',
+                   ['id' => 82,
+                    'libelle' => 'Genre2',
+                    'regles' => '902$a=Genre2']);
+
+    $this->fixture('Class_CodifGenre',
+                   ['id' => 83,
+                    'libelle' => 'Genre3',
+                    'regles' => '902$a=Genre3']);
+
+    $this->loadNotice('test-genre-multiple-902');
+  }
+
+
+  /** @test */
+  public function noticeShouldHaveThreeGenres() {
+    $this->assertInternalType('array', $this->notice_data['genres']);
+    $this->assertCount(3, $this->notice_data['genres']);
+    $this->assertContains(81, $this->notice_data['genres']);
+    $this->assertContains(82, $this->notice_data['genres']);
+    $this->assertContains(83, $this->notice_data['genres']);
+  }
+
+  /** @test */
+  public function facettesShouldContainThreeGenres() {
+    $this->assertContains(' G81', $this->notice_data['facettes']);
+    $this->assertContains(' G82', $this->notice_data['facettes']);
+    $this->assertContains(' G83', $this->notice_data['facettes']);
+  }
 }
 
 
 
 
 class NoticeIntegrationUpdateExistingNoticeTest  extends NoticeIntegrationTestCase {
-	protected $_profil_donnees = [
-		'id_profil' => 100,
-		'libelle' => 'Unimarc Orphee',
-		'accents' => 1,
-		'rejet_periodiques' => 0,
-		'id_article_periodique' => 4,
-		'type_fichier' => 0,
-		'format' => 0,
-		'attributs' => 'a:7:{i:0;a:8:{s:8:"type_doc";a:18:{i:0;a:3:{s:4:"code";s:1:"0";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:1;a:3:{s:4:"code";s:1:"1";s:5:"label";s:8:"am;na;mn";s:8:"zone_995";s:9:"LIV;MS;uu";}i:2;a:3:{s:4:"code";s:1:"2";s:5:"label";s:2:"as";s:8:"zone_995";s:3:"PER";}i:3;a:3:{s:4:"code";s:1:"3";s:5:"label";s:3:"i;j";s:8:"zone_995";s:17:"CD;LIVCD;LIVK7;K7";}i:4;a:3:{s:4:"code";s:1:"4";s:5:"label";s:1:"g";s:8:"zone_995";s:20:"DIAPO;DVD;VHS;VHD;VD";}i:5;a:3:{s:4:"code";s:1:"5";s:5:"label";s:6:"l;m;mm";s:8:"zone_995";s:3:"CDR";}i:6;a:3:{s:4:"code";s:1:"8";s:5:"label";s:0:"";s:8:"zone_995";s:3:"DOS";}i:7;a:3:{s:4:"code";s:1:"9";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:8;a:3:{s:4:"code";s:2:"10";s:5:"label";s:0:"";s:8:"zone_995";s:6:"WEB;MF";}i:9;a:3:{s:4:"code";s:3:"100";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:10;a:3:{s:4:"code";s:3:"101";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:11;a:3:{s:4:"code";s:3:"102";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:12;a:3:{s:4:"code";s:3:"103";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:13;a:3:{s:4:"code";s:3:"104";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:14;a:3:{s:4:"code";s:3:"105";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:15;a:3:{s:4:"code";s:3:"106";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:16;a:3:{s:4:"code";s:3:"107";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:17;a:3:{s:4:"code";s:3:"108";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}}s:17:"champ_code_barres";s:1:"a";s:10:"champ_cote";s:1:"f";s:14:"champ_type_doc";s:0:"";s:11:"champ_genre";s:1:"#";s:13:"champ_section";s:1:"w";s:17:"champ_emplacement";s:1:"x";s:12:"champ_annexe";s:1:"h";}i:1;a:1:{s:6:"champs";s:0:"";}i:2;a:1:{s:6:"champs";s:0:"";}i:3;a:1:{s:6:"champs";s:0:"";}i:5;a:3:{s:6:"champs";s:0:"";s:17:"xml_balise_abonne";s:0:"";s:17:"xml_champs_abonne";a:11:{s:6:"IDABON";s:0:"";s:9:"ORDREABON";s:0:"";s:3:"NOM";s:0:"";s:6:"PRENOM";s:0:"";s:9:"NAISSANCE";s:0:"";s:8:"PASSWORD";s:0:"";s:4:"MAIL";s:0:"";s:10:"DATE_DEBUT";s:0:"";s:8:"DATE_FIN";s:0:"";s:7:"ID_SIGB";s:0:"";s:9:"NUM_CARTE";s:0:"";}}i:4;a:5:{s:4:"zone";s:3:"995";s:5:"champ";s:1:"5";s:6:"format";s:1:"3";s:5:"jours";s:0:"";s:7:"valeurs";s:1:"1";}i:6;a:2:{s:4:"zone";s:0:"";s:5:"champ";s:0:"";}}'
-	];
-
-
-	public function setUp() {
-		parent::setUp();
-		$this->loadNotice('unimarc_chaperon_rouge');
-		$this->datas = $this->notice_integration->noticeToDBEnreg($this->notice_data);
-		$this->notice_sgbd->ouvrirNotice($this->datas['unimarc'], 0);
-	}
-
-
-
-	/** @test */
-	public function auteurShouldBeChristianGuibbaud() {
-		$this->assertContains('Guibbaud|Christian', $this->notice_data['auteurs'][0]);
-	}
-
-	/** @test */
-	public function zone700aShouldBeGuibbaud() {
-		$this->assertEquals('Guibbaud', $this->notice_sgbd->get_subfield('700', 'a')[0]);
-	}
+  protected $_profil_donnees = [
+                                'id_profil' => 100,
+                                'libelle' => 'Unimarc Orphee',
+                                'accents' => 1,
+                                'rejet_periodiques' => 0,
+                                'id_article_periodique' => 4,
+                                'type_fichier' => 0,
+                                'format' => 0,
+                                'attributs' => 'a:7:{i:0;a:8:{s:8:"type_doc";a:18:{i:0;a:3:{s:4:"code";s:1:"0";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:1;a:3:{s:4:"code";s:1:"1";s:5:"label";s:8:"am;na;mn";s:8:"zone_995";s:9:"LIV;MS;uu";}i:2;a:3:{s:4:"code";s:1:"2";s:5:"label";s:2:"as";s:8:"zone_995";s:3:"PER";}i:3;a:3:{s:4:"code";s:1:"3";s:5:"label";s:3:"i;j";s:8:"zone_995";s:17:"CD;LIVCD;LIVK7;K7";}i:4;a:3:{s:4:"code";s:1:"4";s:5:"label";s:1:"g";s:8:"zone_995";s:20:"DIAPO;DVD;VHS;VHD;VD";}i:5;a:3:{s:4:"code";s:1:"5";s:5:"label";s:6:"l;m;mm";s:8:"zone_995";s:3:"CDR";}i:6;a:3:{s:4:"code";s:1:"8";s:5:"label";s:0:"";s:8:"zone_995";s:3:"DOS";}i:7;a:3:{s:4:"code";s:1:"9";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:8;a:3:{s:4:"code";s:2:"10";s:5:"label";s:0:"";s:8:"zone_995";s:6:"WEB;MF";}i:9;a:3:{s:4:"code";s:3:"100";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:10;a:3:{s:4:"code";s:3:"101";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:11;a:3:{s:4:"code";s:3:"102";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:12;a:3:{s:4:"code";s:3:"103";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:13;a:3:{s:4:"code";s:3:"104";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:14;a:3:{s:4:"code";s:3:"105";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:15;a:3:{s:4:"code";s:3:"106";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:16;a:3:{s:4:"code";s:3:"107";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:17;a:3:{s:4:"code";s:3:"108";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}}s:17:"champ_code_barres";s:1:"a";s:10:"champ_cote";s:1:"f";s:14:"champ_type_doc";s:0:"";s:11:"champ_genre";s:1:"#";s:13:"champ_section";s:1:"w";s:17:"champ_emplacement";s:1:"x";s:12:"champ_annexe";s:1:"h";}i:1;a:1:{s:6:"champs";s:0:"";}i:2;a:1:{s:6:"champs";s:0:"";}i:3;a:1:{s:6:"champs";s:0:"";}i:5;a:3:{s:6:"champs";s:0:"";s:17:"xml_balise_abonne";s:0:"";s:17:"xml_champs_abonne";a:11:{s:6:"IDABON";s:0:"";s:9:"ORDREABON";s:0:"";s:3:"NOM";s:0:"";s:6:"PRENOM";s:0:"";s:9:"NAISSANCE";s:0:"";s:8:"PASSWORD";s:0:"";s:4:"MAIL";s:0:"";s:10:"DATE_DEBUT";s:0:"";s:8:"DATE_FIN";s:0:"";s:7:"ID_SIGB";s:0:"";s:9:"NUM_CARTE";s:0:"";}}i:4;a:5:{s:4:"zone";s:3:"995";s:5:"champ";s:1:"5";s:6:"format";s:1:"3";s:5:"jours";s:0:"";s:7:"valeurs";s:1:"1";}i:6;a:2:{s:4:"zone";s:0:"";s:5:"champ";s:0:"";}}'
+  ];
+
+
+  public function setUp() {
+    parent::setUp();
+    $this->loadNotice('unimarc_chaperon_rouge');
+    $this->datas = $this->notice_integration->noticeToDBEnreg($this->notice_data);
+    $this->notice_sgbd->ouvrirNotice($this->datas['unimarc'], 0);
+  }
+
+
+
+  /** @test */
+  public function auteurShouldBeChristianGuibbaud() {
+    $this->assertContains('Guibbaud|Christian', $this->notice_data['auteurs'][0]);
+  }
+
+  /** @test */
+  public function zone700aShouldBeGuibbaud() {
+    $this->assertEquals('Guibbaud', $this->notice_sgbd->get_subfield('700', 'a')[0]);
+  }
 }
 
 
 
 class NoticeIntegrationMachecoulTest extends NoticeIntegrationTestCase {
-	public function setUp() {
-		parent::setUp();
-		$this->loadNotice('unimarc_ledu_stephanie');
-		$this->datas = $this->notice_integration->noticeToDBEnreg($this->notice_data);
-		$this->notice_sgbd->ouvrirNotice($this->datas['unimarc'], 0);
-	}
+  public function setUp() {
+    parent::setUp();
+    $this->loadNotice('unimarc_ledu_stephanie');
+    $this->datas = $this->notice_integration->noticeToDBEnreg($this->notice_data);
+    $this->notice_sgbd->ouvrirNotice($this->datas['unimarc'], 0);
+  }
 
 
-	/** @test */
-	public function auteurShoulBeLeduStephanie() {
-		$this->assertContains('Ledu|Stéphanie', $this->notice_data['auteurs'][0]);
-	}
+  /** @test */
+  public function auteurShoulBeLeduStephanie() {
+    $this->assertContains('Ledu|Stéphanie', $this->notice_data['auteurs'][0]);
+  }
 
 
-	/** @test */
-	public function facetteShouldContainsA12() {
-		$this->assertContains('A1', $this->notice_data['facettes']);
-	}
+  /** @test */
+  public function facetteShouldContainsA12() {
+    $this->assertContains('A1', $this->notice_data['facettes']);
+  }
 
 
-	/** @test */
-	public function authorStephanieLeduShouldHaveBeenCreated() {
-		$this->assertEquals('Stéphanie Ledu',
-												Class_CodifAuteur::findFirstBy(['formes' => 'LEDUxSTEPHANIE'])
-												->getLibelle());
-	}
+  /** @test */
+  public function authorStephanieLeduShouldHaveBeenCreated() {
+    $this->assertEquals('Stéphanie Ledu',
+                        Class_CodifAuteur::findFirstBy(['formes' => 'LEDUxSTEPHANIE'])
+                        ->getLibelle());
+  }
 }
 
 
 
 /** @see http://forge.afi-sa.fr/issues/15762 */
 class NoticeIntegrationConcertoSaintSaensTest extends NoticeIntegrationTestCase {
-	public function getProfilDonnees() {
-		$profil = Class_IntProfilDonnees::forDynix()->setIdProfil(108);
-		return $profil->getRawAttributes();
-	}
-
-
-	public function setUp() {
-		parent::setUp();
-		$this->fixture('Class_Notice',
-									 ['id' => 239003,
-										'type_doc' => 3,
-										'alpha_titre' => 'CONCERTO POUR VIOLONCELLE N 1  OP  33  LA MINEUR',
-										'alpha_auteur' => 'SAINT SAENS  CAMILLE',
-										'titres' => 'CONCERTO KONSERTO POUR VIOLONCELLE VIOLONSEL 1  33 MINEUR MINER SONATE SONAT PIANO 32 UT ROMANCE ROMANS 36 SERENADE SERENAD SUITE SUIT 16 CARNAVAL KARNAVAL ANIMAUX ANIMO GRANDE GRAND FANTASIE FANTASI ZOOLOGIQUE ZOULOJIK',
-										'auteurs' => 'SAINT SIN SAENS SAN CAMILLE KAMIL DEMARQUETTE DEMARKET HENRI ANRI BEREZOVSKY BEREZOVSKI BORIS BORI ENGERER ANJER BRIGITTE BRIJIT SWENSEN SWENSAN JOSEPH JOS ENSEMBLE ANSANBL ORCHESTRAL ORKESTRAL PARIS PARI',
-										'editeur' => 'Mirare,',
-										'collection' => '',
-										'matieres' => 'MUSIQUE MUSIK INSTRUMENTALE INSTRUMANTAL FRANCE FRANS 19EME EM SIECLE SIEKL CONTES KONT MUSICAUX MUSIKO CONCERTOS KONSERTO VIOLONCELLE VIOLONSEL',
-										'dewey' => '',
-										'facettes' => 'A16708 A27427 A29866 A70213 A45981 A57131 M44307 M26694 M1319 T3 B2 YLB 2000-01',
-										'code' => '3 SAI 19.43',
-										'isbn' => '',
-										'ean' => '',
-										'id_commerciale' => 'MIRAREMIR108CONCERTOPOURVIOLONCE',
-										'id_bnf' => '',
-										'clef_alpha' => 'CONCERTOPOURVIOLONCELLEN1OP33LAMINEUR--SAINTSAENSCAMILLE--MIRARE-2010-3',
-										'clef_oeuvre' => 'CONCERTOPOURVIOLONCELLEN1OP33LAMINEUR--SAINTSAENSCAMILLE-',
-										'clef_chapeau' => '',
-										'tome_alpha' => '',
-										'annee' => 2010,
-										'qualite' => 5,
-										'exportable' => 1,
-										'date_creation' => '2000-01-01 00:00:00',
-										'date_maj' => '2014-09-16 12:25:58',
-										'unimarc' => '02580njm0 22003131i 450 001000700000005004100007071002100048200008900069210002600158215002100184330071300205464027100918464007501189464011601264464012301380464010901503464012401612464011601736606005001852606002001902606002801922700003901950702005001989702004302039702004302082702002002125711003302145996008802178419536140812s2010       |             | |||||d01aMIR108 ;bMirare01aConcerto pour violoncelle N°1, op. 33, la mineurb[enr. CD] /fCamille Saint-Saëns01aS.l. :cMirare,d201001a1 disque compact01aPour ce disque-concept autour des oeuvres pour violoncelle de Camille Saint-Saëns, Henri Demarquette s\'est entouré de ses amis et partenaires de prédilection Brigitte Engerer et Boris Berezovsky (piano), ainsi que l\'Ensemble Orchestral de Paris et ses solistes, sous la houlette de Joseph Swensen, pour présenter un disque original. Au côté de certaines oeuvres très connues, comme le facétieux Carnaval des animaux, et le Concerto pour violoncelle et orchestre n',
-										'z3950_retry' => 0,
-										'nb_visu' => 0,
-										'nb_resa' => 0,
-										'url_vignette' => '',
-										'url_image' => '']);
-
-
-		$this->fixture('Class_IntBib',
-									 ['id' => 1,
-										'qualite' => 5]);
-
-		$this->loadNotice('unimarc_concerto_saintsaens');
-	}
-
-
-	/** @test */
-	public function notesShouldNotContainsRawAsciiDegree() {
-		$this->assertNotContains('n' . chr(0xb0),
-														 Class_Notice::find(239003)->get_subfield(330, 'a')[0]);
-	}
-
-
-	/** @test */
-	public function notesShouldContainsValidUtf8Degree() {
-		$this->assertContains('n' . chr(0xc2).chr(0xb0),
-													Class_Notice::find(239003)->get_subfield(330, 'a')[0]);
-	}
+  public function getProfilDonnees() {
+    $profil = Class_IntProfilDonnees::forDynix()->setIdProfil(108);
+    return $profil->getRawAttributes();
+  }
+
+
+  public function setUp() {
+    parent::setUp();
+    $this->fixture('Class_Notice',
+                   ['id' => 239003,
+                    'type_doc' => 3,
+                    'alpha_titre' => 'CONCERTO POUR VIOLONCELLE N 1  OP  33  LA MINEUR',
+                    'alpha_auteur' => 'SAINT SAENS  CAMILLE',
+                    'titres' => 'CONCERTO KONSERTO POUR VIOLONCELLE VIOLONSEL 1  33 MINEUR MINER SONATE SONAT PIANO 32 UT ROMANCE ROMANS 36 SERENADE SERENAD SUITE SUIT 16 CARNAVAL KARNAVAL ANIMAUX ANIMO GRANDE GRAND FANTASIE FANTASI ZOOLOGIQUE ZOULOJIK',
+                    'auteurs' => 'SAINT SIN SAENS SAN CAMILLE KAMIL DEMARQUETTE DEMARKET HENRI ANRI BEREZOVSKY BEREZOVSKI BORIS BORI ENGERER ANJER BRIGITTE BRIJIT SWENSEN SWENSAN JOSEPH JOS ENSEMBLE ANSANBL ORCHESTRAL ORKESTRAL PARIS PARI',
+                    'editeur' => 'Mirare,',
+                    'collection' => '',
+                    'matieres' => 'MUSIQUE MUSIK INSTRUMENTALE INSTRUMANTAL FRANCE FRANS 19EME EM SIECLE SIEKL CONTES KONT MUSICAUX MUSIKO CONCERTOS KONSERTO VIOLONCELLE VIOLONSEL',
+                    'dewey' => '',
+                    'facettes' => 'A16708 A27427 A29866 A70213 A45981 A57131 M44307 M26694 M1319 T3 B2 YLB 2000-01',
+                    'code' => '3 SAI 19.43',
+                    'isbn' => '',
+                    'ean' => '',
+                    'id_commerciale' => 'MIRAREMIR108CONCERTOPOURVIOLONCE',
+                    'id_bnf' => '',
+                    'clef_alpha' => 'CONCERTOPOURVIOLONCELLEN1OP33LAMINEUR--SAINTSAENSCAMILLE--MIRARE-2010-3',
+                    'clef_oeuvre' => 'CONCERTOPOURVIOLONCELLEN1OP33LAMINEUR--SAINTSAENSCAMILLE-',
+                    'clef_chapeau' => '',
+                    'tome_alpha' => '',
+                    'annee' => 2010,
+                    'qualite' => 5,
+                    'exportable' => 1,
+                    'date_creation' => '2000-01-01 00:00:00',
+                    'date_maj' => '2014-09-16 12:25:58',
+                    'unimarc' => '02580njm0 22003131i 450 001000700000005004100007071002100048200008900069210002600158215002100184330071300205464027100918464007501189464011601264464012301380464010901503464012401612464011601736606005001852606002001902606002801922700003901950702005001989702004302039702004302082702002002125711003302145996008802178419536140812s2010       |             | |||||d01aMIR108 ;bMirare01aConcerto pour violoncelle N°1, op. 33, la mineurb[enr. CD] /fCamille Saint-Saëns01aS.l. :cMirare,d201001a1 disque compact01aPour ce disque-concept autour des oeuvres pour violoncelle de Camille Saint-Saëns, Henri Demarquette s\'est entouré de ses amis et partenaires de prédilection Brigitte Engerer et Boris Berezovsky (piano), ainsi que l\'Ensemble Orchestral de Paris et ses solistes, sous la houlette de Joseph Swensen, pour présenter un disque original. Au côté de certaines oeuvres très connues, comme le facétieux Carnaval des animaux, et le Concerto pour violoncelle et orchestre n',
+                    'z3950_retry' => 0,
+                    'nb_visu' => 0,
+                    'nb_resa' => 0,
+                    'url_vignette' => '',
+                    'url_image' => '']);
+
+
+    $this->fixture('Class_IntBib',
+                   ['id' => 1,
+                    'qualite' => 5]);
+
+    $this->loadNotice('unimarc_concerto_saintsaens');
+  }
+
+
+  /** @test */
+  public function notesShouldNotContainsRawAsciiDegree() {
+    $this->assertNotContains('n' . chr(0xb0),
+                             Class_Notice::find(239003)->get_subfield(330, 'a')[0]);
+  }
+
+
+  /** @test */
+  public function notesShouldContainsValidUtf8Degree() {
+    $this->assertContains('n' . chr(0xc2).chr(0xb0),
+                          Class_Notice::find(239003)->get_subfield(330, 'a')[0]);
+  }
 }
 
 
@@ -1168,144 +1181,144 @@ class NoticeIntegrationArchivesAlsaceTest extends NoticeIntegrationTestCase {
 
 
   public function getProfilDonnees() {
-		return Class_IntProfilDonnees::forKarvi()
-			->setIdProfil(110)
-			->getRawAttributes();
-	}
+    return Class_IntProfilDonnees::forKarvi()
+      ->setIdProfil(110)
+      ->getRawAttributes();
+  }
 
 
-	public function setUp() {
-		parent::setUp();
+  public function setUp() {
+    parent::setUp();
 
     $this->fixture('Class_CodifLangue',
                    ['id' => 'fre',
                     'libelle' => 'Français']);
 
-		$this->fixture('Class_CodifMatiere',
+    $this->fixture('Class_CodifMatiere',
                    ['id' => 1,
                     'libelle' => 'Alsace (France) : Descriptions Et Voyages : Vues : 1870-1914',
                     'code_alpha' => 'ALSACE  FRANCE    DESCRIPTIONS ET VOYAGES   VUES   1870 1914',
                    ]);
 
-		$this->fixture('Class_CodifDewey',
-									 ['id' => 94438,
-										'libelle' => 'Lorraine et Alsace']);
+    $this->fixture('Class_CodifDewey',
+                   ['id' => 94438,
+                    'libelle' => 'Lorraine et Alsace']);
 
     Class_CodifAuteur::beVolatile();
 
-		Class_CosmoVar::newInstanceWithId('unimarc_zone_matiere',
-																			['valeur' => '600abcjxyz;601abcx;602ajxyz;605a;606ajxyz;607ajxyz;608ajxyz;610aejxyz;615amnx;616acfjxy;620abcdefghi']);
+    Class_CosmoVar::newInstanceWithId('unimarc_zone_matiere',
+                                      ['valeur' => '600abcjxyz;601abcx;602ajxyz;605a;606ajxyz;607ajxyz;608ajxyz;610aejxyz;615amnx;616acfjxy;620abcdefghi']);
 
 
-		$this->loadNotice('unimarc_archives_alsace');
-	}
+    $this->loadNotice('unimarc_archives_alsace');
+  }
 
 
-	/** @test */
-	public function matieresShouldAlsaceDescriptions() {
-		$this->assertEquals('ALSACE ALSAS FRANCE FRANS DESCRIPTIONS DESKRIPSION VOYAGES VOIAJ VUES VU 1870  1914 OUVRAGES OUVRAJ ILLUSTRES ILUSTR 1871 1918 PERIODE PERIOD ALLEMANDE ALEMAND',
-												Class_Notice::find(1)->getMatieres());
-	}
+  /** @test */
+  public function matieresShouldAlsaceDescriptions() {
+    $this->assertEquals('ALSACE ALSAS FRANCE FRANS DESCRIPTIONS DESKRIPSION VOYAGES VOIAJ VUES VU 1870  1914 OUVRAGES OUVRAJ ILLUSTRES ILUSTR 1871 1918 PERIODE PERIOD ALLEMANDE ALEMAND',
+                        Class_Notice::find(1)->getMatieres());
+  }
 
 
-	/** @test */
-	public function deweyShouldBeLorraineAlsace() {
-		$this->assertEquals('LORRAINE LORAIN ALSACE ALSAS',
-												Class_Notice::find(1)->getDewey());
-	}
+  /** @test */
+  public function deweyShouldBeLorraineAlsace() {
+    $this->assertEquals('LORRAINE LORAIN ALSACE ALSAS',
+                        Class_Notice::find(1)->getDewey());
+  }
 
-	/** @test */
-	public function codifMatiereOneShouldContainsAlsace() {
-		$this->assertEquals('Alsace (France) : Descriptions et voyages : Vues : 1870-1914',
-												Class_CodifMatiere::find(1)->getLibelle());
-	}
+  /** @test */
+  public function codifMatiereOneShouldContainsAlsace() {
+    $this->assertEquals('Alsace (France) : Descriptions et voyages : Vues : 1870-1914',
+                        Class_CodifMatiere::find(1)->getLibelle());
+  }
 
 
-	/** @test */
-	public function codifMatiereTwoShouldContainsOuvragesIllustres() {
-		$this->assertEquals('Alsace (France) : Ouvrages illustrés : 1871-1918 (Période allemande)',
-												Class_CodifMatiere::find(2)->getLibelle());
-	}
+  /** @test */
+  public function codifMatiereTwoShouldContainsOuvragesIllustres() {
+    $this->assertEquals('Alsace (France) : Ouvrages illustrés : 1871-1918 (Période allemande)',
+                        Class_CodifMatiere::find(2)->getLibelle());
+  }
 
 
-	/** @test */
-	public function facettesShouldContainsM1AndM2AndD1() {
-		$this->assertEquals('T1 D94438 A1 M1 M2 Lfre',
-												Class_Notice::find(1)->getFacettes());
-	}
+  /** @test */
+  public function facettesShouldContainsM1AndM2AndD1() {
+    $this->assertEquals('T1 D94438 A1 M1 M2 Lfre',
+                        Class_Notice::find(1)->getFacettes());
+  }
 
 
-	/** @test */
-	public function facettesWithExemplairesShouldContainsM1AndM2AndD1() {
-		$this->assertEquals('D94438 A1 M1 M2 Lfre T1 B1 YBibliothèque des Dominicains',
-												Class_Notice::find(1)->updateFacetsFromExemplaires()->getFacettes());
-	}
+  /** @test */
+  public function facettesWithExemplairesShouldContainsM1AndM2AndD1() {
+    $this->assertEquals('D94438 A1 M1 M2 Lfre T1 B1 YBibliothèque des Dominicains',
+                        Class_Notice::find(1)->updateFacetsFromExemplaires()->getFacettes());
+  }
 }
 
 
 
 /** @see http://forge.afi-sa.fr/issues/15989 */
 class NoticeIntegrationMarioCartWiiTest extends NoticeIntegrationTestCase {
-	public function getProfilDonnees() {
-		$profil = Class_IntProfilDonnees::forAstrolab()->setIdProfil(110);
-		return $profil->getRawAttributes();
-	}
+  public function getProfilDonnees() {
+    $profil = Class_IntProfilDonnees::forAstrolab()->setIdProfil(110);
+    return $profil->getRawAttributes();
+  }
 
 
-	public function setUp() {
-		parent::setUp();
-		$this->fixture('Class_CodifEmplacement',
-									 ['id' => 3, 'libelle' => 'Art', 'regles' => '995$u=ART']);
-		$this->fixture('Class_CodifEmplacement',
-									 ['id' => 8, 'libelle' => 'Enfance', 'regles' => '995$u=ENF']);
-		$this->fixture('Class_CodifEmplacement',
-									 ['id' => 9, 'libelle' => 'Espace bébés', 'regles' => '995$u=43;Espace bébés']);
+  public function setUp() {
+    parent::setUp();
+    $this->fixture('Class_CodifEmplacement',
+                   ['id' => 3, 'libelle' => 'Art', 'regles' => '995$u=ART']);
+    $this->fixture('Class_CodifEmplacement',
+                   ['id' => 8, 'libelle' => 'Enfance', 'regles' => '995$u=ENF']);
+    $this->fixture('Class_CodifEmplacement',
+                   ['id' => 9, 'libelle' => 'Espace bébés', 'regles' => '995$u=43;Espace bébés']);
 
 
-		$writer = new Class_NoticeUnimarc_Writer();
+    $writer = new Class_NoticeUnimarc_Writer();
     $writer->setNotice(file_get_contents(dirname(__FILE__)."/unimarc_mario_kart.txt"));
     $writer->add_field('995',
                        '  ',
                        [
                         ['f', '12345'],
-												['r', 'JV'],
+                        ['r', 'JV'],
                         ['u', 'Espace bébés'],
-												['a', 'MED'],
-												['q', 'BEB'],
-												['k', 'JV WII MAR B']
+                        ['a', 'MED'],
+                        ['q', 'BEB'],
+                        ['k', 'JV WII MAR B']
                        ]);
-		$writer->update();
+    $writer->update();
 
     Class_CosmoVar::setValueOf('other_index_fields', '300$a;330$a');
 
 
-		$this->loadNoticeFromString($writer->getFullRecord());
-		$this->items = Class_Notice::findFirstBy([])->getExemplaires();
-	}
+    $this->loadNoticeFromString($writer->getFullRecord());
+    $this->items = Class_Notice::findFirstBy([])->getExemplaires();
+  }
 
 
-	/** @test */
-	public function firstItemLocationShouldBeSet() {
-		$this->assertItemLocation($this->items[0], 3);
-	}
+  /** @test */
+  public function firstItemLocationShouldBeSet() {
+    $this->assertItemLocation($this->items[0], 3);
+  }
 
 
-	/** @test */
-	public function secondItemLocationShouldBeSet() {
-		$this->assertItemLocation($this->items[1], 8);
-	}
+  /** @test */
+  public function secondItemLocationShouldBeSet() {
+    $this->assertItemLocation($this->items[1], 8);
+  }
 
 
-	/** @test */
-	public function thirdItemLocationShouldBeSet() {
-		$this->assertItemLocation($this->items[2] , 3);
-	}
+  /** @test */
+  public function thirdItemLocationShouldBeSet() {
+    $this->assertItemLocation($this->items[2] , 3);
+  }
 
 
-		/** @test */
-	public function fourthItemLocationShouldBeSet() {
-		$this->assertItemLocation($this->items[3], 9);
-	}
+  /** @test */
+  public function fourthItemLocationShouldBeSet() {
+    $this->assertItemLocation($this->items[3], 9);
+  }
 
 
   /** @test */
@@ -1322,36 +1335,36 @@ class NoticeIntegrationMarioCartWiiTest extends NoticeIntegrationTestCase {
   }
 
 
-	protected function assertItemLocation($item, $location) {
-		$this->assertEquals($location, $item->getEmplacement(),
-												json_encode($item->getRawAttributes()));
-	}
+  protected function assertItemLocation($item, $location) {
+    $this->assertEquals($location, $item->getEmplacement(),
+                        json_encode($item->getRawAttributes()));
+  }
 }
 
 
 
 class NoticeIntegrationNoNoticeTest extends NoticeIntegrationTestCase {
-	public function tearDown() {
-		Storm_Model_Loader::defaultToDb();
-		parent::tearDown();
-	}
+  public function tearDown() {
+    Storm_Model_Loader::defaultToDb();
+    parent::tearDown();
+  }
 
 
-	public function setUp() {
-		parent::setUp();
-		Storm_Model_Loader::defaultToVolatile();
-		$this->_notice_integration = new notice_integration();
-	}
+  public function setUp() {
+    parent::setUp();
+    Storm_Model_Loader::defaultToVolatile();
+    $this->_notice_integration = new notice_integration();
+  }
 
 
-	/** @test */
-	public function updateNoticeShouldNotCrash() {
-		$this->assertEquals(1, $this->_notice_integration->updateNotice(1,5));
-	}
+  /** @test */
+  public function updateNoticeShouldNotCrash() {
+    $this->assertEquals(1, $this->_notice_integration->updateNotice(1,5));
+  }
 
 
-	/** @test */
-	public function writeItemShouldNotCrash() {
-		$this->assertNull($this->_notice_integration->ecrireExemplaires(1));
-	}
+  /** @test */
+  public function writeItemShouldNotCrash() {
+    $this->assertNull($this->_notice_integration->ecrireExemplaires(1));
+  }
 }
diff --git a/cosmogramme/tests/php/classes/PMBIntegrationTest.php b/cosmogramme/tests/php/classes/PMBIntegrationTest.php
index 5e7320d78d2b1be2e6b1a9478cd748e5ddc57809..0fc462125e3d422d3754695db5f82ca846ea26e8 100644
--- a/cosmogramme/tests/php/classes/PMBIntegrationTest.php
+++ b/cosmogramme/tests/php/classes/PMBIntegrationTest.php
@@ -33,14 +33,17 @@ abstract class PMBIntegrationRecordsTestCase extends NoticeIntegrationTestCase {
 class PMBIntegrationRecordsTest extends PMBIntegrationRecordsTestCase {
   public function setUp() {
     parent::setUp();
-		$this->fixture('Class_CodifSection',
-									 ['id' => 1,
+    $this->fixture('Class_CodifSection',
+                   ['id' => 1,
+                    'libelle' => 'Romans jeunesse',
                     'regles' => '996$x=Romans jeunesse']);
-		$this->fixture('Class_CodifSection',
-									 ['id' => 2,
+    $this->fixture('Class_CodifSection',
+                   ['id' => 2,
+                    'libelle' => 'BD adultes',
                     'regles' => '996$x=BD adultes']);
-		$this->fixture('Class_CodifSection',
-									 ['id' => 3,
+    $this->fixture('Class_CodifSection',
+                   ['id' => 3,
+                    'libelle' => 'Documentaires',
                     'regles' => '996$x=Documentaires']);
 
     $this->loadRecordsFromFile('unimarc_pmb');
@@ -79,6 +82,7 @@ class PMBIntegrationRecordsTest extends PMBIntegrationRecordsTestCase {
 
 
 
+
 class PMBIntegrationRecordsSixSequencesTest extends PMBIntegrationRecordsTestCase {
   public function setUp() {
     parent::setUp();
@@ -103,4 +107,70 @@ class PMBIntegrationRecordsSixSequencesTest extends PMBIntegrationRecordsTestCas
     $this->assertEquals('Angers',
                         $this->_record->getFirstExemplaire()->getLibelleSite());
   }
+}
+
+
+
+
+class PMBIntegrationSerialsTest extends PMBIntegrationRecordsTestCase {
+  public function setUp() {
+    parent::setUp();
+
+    $this->loadRecordsFromFile('unimarc_pmb_periodiques');
+  }
+
+
+  /** @test */
+  public function numberOfRecordsShouldBeFourtySix() {
+    $this->assertCount(46, Class_Notice::findAll());
+  }
+
+
+  /** @test */
+  public function firstRecordItemBarCodeShouldBeIdIntBibOneDashIdRecord21119() {
+    $this->assertEquals('1-21119',
+                        Class_Notice::find(1)->getFirstExemplaire()->getCodeBarres());
+  }
+
+
+  /** @test */
+  public function firstRecordItemIdIntBibShouldBeOne() {
+    $this->assertEquals(1,
+                        Class_Notice::find(1)->getFirstExemplaire()->getIdIntBib());
+  }
+
+
+  /** @test */
+  public function itemWithBarcode0012036ShouldBeTypeBibliographic() {
+    $this->assertEquals(Class_Notice::TYPE_BIBLIOGRAPHIC,
+                        Class_Exemplaire::findFirstBy(['code_barres' => '0012036'])->getType());
+  }
+
+
+  /** @test */
+  public function item0012036ClefChapeauShouldBeREVUE_FR() {
+    $this->assertEquals('REVUE FRANCAISE DE PEDAGOGIE',
+                        Class_Exemplaire::findFirstBy(['code_barres' => '0012036'])->getNotice()->getClefChapeau());
+  }
+
+
+  /** @test */
+  public function itemWithBarcode1Dash21331ShouldBeTypeSerialArticle() {
+    $this->assertEquals(Class_Notice::TYPE_SERIAL_ARTICLE,
+                        Class_Exemplaire::findFirstBy(['code_barres' => '1-21331'])->getType());
+  }
+
+
+  /** @test */
+  public function itemWithBarcode1Dash21331ShouldHaveZone995Dollar0ToEquals60DashBull() {
+    $this->assertEquals('60-bull',
+                        Class_Exemplaire::findFirstBy(['code_barres' => '1-21331'])->getSubfield(0));
+  }
+
+
+  /** @test */
+  public function record21331ClefChapeauShouldBeREVUE_FR_60_BULL() {
+    $this->assertEquals('REVUE FRANCAISE DE PEDAGOGIE 60 BULL',
+                        Class_Exemplaire::findFirstBy(['code_barres' => '1-21331'])->getNotice()->getClefChapeau());
+  }
 }
\ No newline at end of file
diff --git a/cosmogramme/tests/php/classes/pomme.pdf b/cosmogramme/tests/php/classes/pomme.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..03540046580d04e896b9c62b8a29b37251e0661c
Binary files /dev/null and b/cosmogramme/tests/php/classes/pomme.pdf differ
diff --git a/cosmogramme/tests/php/classes/unimarc_pmb_periodiques.txt b/cosmogramme/tests/php/classes/unimarc_pmb_periodiques.txt
new file mode 100644
index 0000000000000000000000000000000000000000..abc5e0881fb5ed7005ce9938c3c6cfab30129b5c
--- /dev/null
+++ b/cosmogramme/tests/php/classes/unimarc_pmb_periodiques.txt
@@ -0,0 +1 @@
+10795nas1 22006371i 450 00100060000010000350000620000350004131900280007689600220010446201230012646200900024946201200033946401760045946401990063546402640083446401950109846401980129346402000149146402010169146402110189246403250210346402540242846403230268246402010300546402780320646402130348446403150369746401410401246401620415346401600431546401860447546402090466146402280487046402240509846402910532246402100561346401970582346402450602046401800626546402580644546402320670346401710693546402060710646402050731246402350751746401860775246402380793846401930817646402890836946401960865846402650885446402820911946402410940146402530964246402620989517165  a20181208u        u  u0frey50  1 aRevue française de pédagogie  aAucun droit spécifique  a./images/vide.png  d2018-08-01eJanvier-Février-Mars 2017vn° 198tRegards croisés sur le baccalauréat professionnel 9id:359lnk:bull  d2018-11-01eAvril-Mai-Juin 2017vn° 199tL'histoire des disciplines9id:369lnk:bull  d2019-03-01eJuillet-Août-Septembre 2017vn° 200tRecherche, politique et pratiques en éducation9id:609lnk:bull  021119tFaire et agir sur l'élèvetRegards croisés sur le baccalauréat professionnel d2018-08-01eJanvier-Février-Mars 2017vn° 1989id:211199page:61-74 p.9lnk:art  021120tRegards croisés sur le baccalauréat professionneltRegards croisés sur le baccalauréat professionnel d2018-08-01eJanvier-Février-Mars 2017vn° 1989id:211209page:5-10 p.9lnk:art  021121tLa gouvernance par les résultats est-elle un mode de régulation de l'école légitime aux yeux des enseignants ?tRegards croisés sur le baccalauréat professionnel d2018-08-01eJanvier-Février-Mars 2017vn° 1989id:211219page:93-108 p.9lnk:art  021126tCHOPIN Marie-Pierre. Pédagogues de la dansetRegards croisés sur le baccalauréat professionnel d2018-08-01eJanvier-Février-Mars 2017vn° 1989id:211269page:109-111 p.9lnk:art  021142tLes mathématiques au baccalauréat professionneltRegards croisés sur le baccalauréat professionnel d2018-08-01eJanvier-Février-Mars 2017vn° 1989id:211429page:p. 23-349lnk:art  021344tLe baccalauréat professionnel de 1985 à nos jourstRegards croisés sur le baccalauréat professionnel d2018-08-01eJanvier-Février-Mars 2017vn° 1989id:213449page:p. 11-229lnk:art  021345tElèves et enseignant·e·s de lycée professionnel.tRegards croisés sur le baccalauréat professionnel d2018-08-01eJanvier-Février-Mars 2017vn° 1989id:213459page:p. 35-489lnk:art  021346tLes bacheliers professionnels face à Admission Post-Bac (APB)tRegards croisés sur le baccalauréat professionnel d2018-08-01eJanvier-Février-Mars 2017vn° 1989id:213469page:p. 49-609lnk:art  021347tL’influence d’une formation au tutorat sur les performances en résolution de problèmes et sur la motivation autodéterminée d’élèves de fin d’enseignement primairetRegards croisés sur le baccalauréat professionnel d2018-08-01eJanvier-Février-Mars 2017vn° 1989id:213479page:p. 75-929lnk:art  021348tDUTERCQ Yves & MAROY Christian (dir.). Professionnalisme enseignant et politiques de responsabilisationtRegards croisés sur le baccalauréat professionnel d2018-08-01eJanvier-Février-Mars 2017vn° 1989id:213489page:p. 111-1159lnk:art  021349tGARNIER Pascale, BROUGÈRE Gilles, RAYNA Sylvie & RUPIN Pablo. À 2 ans, vivre dans un collectif d’enfants. Crèche, école maternelle, classe passerelle, jardin materneltRegards croisés sur le baccalauréat professionnel d2018-08-01eJanvier-Février-Mars 2017vn° 1989id:213499page:p. 115-1199lnk:art  021350tLHOSTE Yann. Épistémologie et didactique des SVTtRegards croisés sur le baccalauréat professionnel d2018-08-01eJanvier-Février-Mars 2017vn° 1989id:213509page:p. 119-1219lnk:art  021351tTHÉMINES Jean-François & DOUSSOT Sylvain. Acteurs et action. Perspectives en didactiques de l’histoire et de la géographietRegards croisés sur le baccalauréat professionnel d2018-08-01eJanvier-Février-Mars 2017vn° 1989id:213519page:p. 121-1229lnk:art  021352tVEYRUNES Philippe. La Classe : hier, aujourd’hui et demain ?tRegards croisés sur le baccalauréat professionnel d2018-08-01eJanvier-Février-Mars 2017vn° 1989id:213529page:p. 123-1249lnk:art  021353tZAID Abdelkarim. Élaborer, transmettre et construire des contenus. Perspective didactique des dispositifs d’éducation et de formation en sciences et technologietRegards croisés sur le baccalauréat professionnel d2018-08-01eJanvier-Février-Mars 2017vn° 1989id:213539page:p. 124-1269lnk:art  021123tL'histoire des disciplinestL'histoire des disciplinesd2018-11-01eAvril-Mai-Juin 2017vn° 1999id:211239page:5-22 p.9lnk:art  021124tEcrire des manuels pour « une » discipline ?tL'histoire des disciplinesd2018-11-01eAvril-Mai-Juin 2017vn° 1999id:211249page:59-80 p.9lnk:art  021125tUne ségrégation peut en cacher une autretL'histoire des disciplinesd2018-11-01eAvril-Mai-Juin 2017vn° 1999id:211259page:117-138 p.9lnk:art  021143tL'enseignement des langues étrangères sous la Troisième RépubliquetL'histoire des disciplinesd2018-11-01eAvril-Mai-Juin 2017vn° 1999id:211439page:p. 23-379lnk:art  021333tLa Société des professeurs d’histoire et de géographie (SPHG) et ses membres (1910-1939)tL'histoire des disciplinesd2018-11-01eAvril-Mai-Juin 2017vn° 1999id:213339page:p. 39-579lnk:art  021334tLes professeurs d’ENNA et leur rôle dans la structuration de la discipline « français » entre 1945 et 1960tL'histoire des disciplinesd2018-11-01eAvril-Mai-Juin 2017vn° 1999id:213349page:p. 81-949lnk:art  021335tDes disciplines en recomposition ? Heurs et malheurs d’une réforme du curriculum au collège (1988-1989)tL'histoire des disciplinesd2018-11-01eAvril-Mai-Juin 2017vn° 1999id:213359page:p. 95-1169lnk:art  021337tALBERO Brigitte, YURÉN Teresa & GUÉRIN Jérôme (dir.). Modèles de formation et architecture dans l’enseignement supérieur. Culture numérique et développement humaintL'histoire des disciplinesd2018-11-01eAvril-Mai-Juin 2017vn° 1999id:213379page:p. 139-1419lnk:art  021338tDEVIGNE Matthieu. L’École des années noires. Une histoire du primaire en temps de guerretL'histoire des disciplinesd2018-11-01eAvril-Mai-Juin 2017vn° 1999id:213389page:p. 141-1439lnk:art  021339tLESSARD Claude & CARPENTIER Anylène. Politiques éducatives. La mise en œuvretL'histoire des disciplinesd2018-11-01eAvril-Mai-Juin 2017vn° 1999id:213399page:p. 143-1459lnk:art  021340tMONNIER Anne. Le temps des dissertations. Chronique de l’accès des jeunes filles aux études supérieures (Genève xixe-xxe)tL'histoire des disciplinesd2018-11-01eAvril-Mai-Juin 2017vn° 1999id:213409page:p. 145-1479lnk:art  021341tPAYET Jean-Paul. École et familles. Une approche sociologiquetL'histoire des disciplinesd2018-11-01eAvril-Mai-Juin 2017vn° 1999id:213419page:p. 147-1489lnk:art  021342tPIERROT Alain, CARVALHO Isabel & MEDAETS Chantal (dir.). Domination et apprentissage. Anthropologie des formes de la transmission culturelletL'histoire des disciplinesd2018-11-01eAvril-Mai-Juin 2017vn° 1999id:213429page:p. 148-1539lnk:art  021343tVEILLARD Laurent. La formation professionnelle initiale. Apprendre dans l’alternance entre différents contextestL'histoire des disciplinesd2018-11-01eAvril-Mai-Juin 2017vn° 1999id:213439page:p. 153-1549lnk:art  021319tTransmettre et légitimertRecherche, politique et pratiques en éducationd2019-03-01eJuillet-Août-Septembre 2017vn° 2009id:213199page:p. 11-219lnk:art  021320tLa refondation de l’éducation prioritaire et la recherchetRecherche, politique et pratiques en éducationd2019-03-01eJuillet-Août-Septembre 2017vn° 2009id:213209page:p. 23-329lnk:art  021321tDes possibilités de sociologie en territoire d’expertisetRecherche, politique et pratiques en éducationd2019-03-01eJuillet-Août-Septembre 2017vn° 2009id:213219page:p. 33-419lnk:art  021322tRéconcilier l’action publique, la recherche et l’engagement professionnel et citoyentRecherche, politique et pratiques en éducationd2019-03-01eJuillet-Août-Septembre 2017vn° 2009id:213229page:p. 43-509lnk:art  021323tPour des collectifs militants apprenantstRecherche, politique et pratiques en éducationd2019-03-01eJuillet-Août-Septembre 2017vn° 2009id:213239page:p. 51-599lnk:art  021324tDe l’usage des outils issus de la recherche pour accompagner les établissements scolairestRecherche, politique et pratiques en éducationd2019-03-01eJuillet-Août-Septembre 2017vn° 2009id:213249page:p. 61-679lnk:art  021325tObjets et pratiques de l’évaluation scolairetRecherche, politique et pratiques en éducationd2019-03-01eJuillet-Août-Septembre 2017vn° 2009id:213259page:p. 69-799lnk:art  021326tUne recherche sur la scolarité des enfants du voyage ou quelques conditions de transformations de pratiques institutionnelles par la recherchetRecherche, politique et pratiques en éducationd2019-03-01eJuillet-Août-Septembre 2017vn° 2009id:213269page:p. 81-879lnk:art  021327tArticuler recherche et développement pédagogiquetRecherche, politique et pratiques en éducationd2019-03-01eJuillet-Août-Septembre 2017vn° 2009id:213279page:p. 89-979lnk:art  021328tLes effets de l’intégration sociale étudiante sur la réussite universitaire en 1er cycle sont-ils significatifs ?tRecherche, politique et pratiques en éducationd2019-03-01eJuillet-Août-Septembre 2017vn° 2009id:213289page:p. 99-1179lnk:art  021329tL’impact conjoint du genre de l’étudiant et du diplôme de ses parents pour comprendre le processus de persévérance académiquetRecherche, politique et pratiques en éducationd2019-03-01eJuillet-Août-Septembre 2017vn° 2009id:213299page:p. 119-1339lnk:art  021330tBOURDON Étienne. La forge gauloise de la nation. Ernest Lavisse et la fabrique des ancêtrestRecherche, politique et pratiques en éducationd2019-03-01eJuillet-Août-Septembre 2017vn° 2009id:213309page:p. 135-1369lnk:art  021331tLIGNIER Wilfried & PAGIS Julie. L’enfance de l’ordre. Comment les enfants perçoivent le monde socialtRecherche, politique et pratiques en éducationd2019-03-01eJuillet-Août-Septembre 2017vn° 2009id:213319page:p. 136-1399lnk:art  021332tMILLET Mathias & CROIZET Jean-Claude. L’école des incapables ? La maternelle, un apprentissage de la dominationtRecherche, politique et pratiques en éducationd2019-03-01eJuillet-Août-Septembre 2017vn° 2009id:213329page:p. 139-1419lnk:art02914naa2 22002531i 450 00100060000010000350000600900150004120001390005685600460019510100080024121500130024932706870026233012320094931900280218170200310220970100350224070000350227570200330231021000090234389600220235261001000237446100630247446301230253721119  a20190318u        u  u0frey50    a2018-08-011 aFaire et agir sur l'élèveeconceptions ambivalentes et usages pluriels de l'élève sur la scène de l'entretien enseignante-parents  uhttps://journals.openedition.org/rfp/52820 afre  a61-74 p.  aIntroduction  Variations et justifications de la présence de l'élève Des variations entre établissements Des sollicitations différentes selon l'âge Ménager l'enfant, aménager les conditions des annonces délicates  L'expression de l'élève, entre utilitarisme et reconnaissance d'une expertise L'élève, une preuve vivante Favoriser l'expression de l'élève  Agir sur l'élève et sur ses parents durant l'entretien S'assurer du bon usage des prescriptions La mise à l'épreuve de la relation entre le parent et l'élève L'élève, cible du travail éducatif enseignant et levier de l'éducation parentale Faire promettre l'élève pour modifier sa conduite  Conclusion  aPromues comme un outil pour la mise en œuvre du paradigme collaboratif et comme un remède à l'échec scolaire par l'institution scolaire, les rencontres individuelles entre enseignants et parents ont été récemment systématisées. Constatant que l'élève y assiste dans la majorité des cas, cet article explore la place qui lui est faite et détermine la conception de l'enfant qui prévaut dans l'école. Basé sur une enquête ethnographique menée dans trois établissements scolaires défavorisés du canton de Genève (Suisse), il montre que le rôle de l'élève est ambivalent au cours des échanges. Dans l'entretien mis en scène par les enseignants pour asseoir leur expertise et leur professionnalité, l'élève est aussi sollicité pour relater son vécu scolaire et pour permettre aux enseignants d'ajuster leurs pratiques. Lorsque l'élève rencontre des difficultés, l'entretien vise à agir sur lui afin qu'il acquière des pratiques scolaires adéquates et définies par les professionnels. En somme, l'article souligne la conception et l'usage pluriels de l'élève à l'école, entre un acteur à écouter et à entendre et un autre qu'il s'agit de transformer et d'acculturer aux normes scolaires.  aAucun droit spécifique 1aRufinbDiane40709id:4437 1aPayetbJean-Paul40709id:4436 1aDeshayesbFabien40709id:4435 1aPelhatebJulie40709id:4438  d2018  a./images/vide.png0 arelation parents-école;participation des élèves;relation adulte-enfant;communication verbale  017165tRevue française de pédagogie9id:171659lnk:perio  d2018-08-01eJanvier-Février-Mars 2017vn° 198tRegards croisés sur le baccalauréat professionnel 9id:359lnk:bull00650naa2 22001811i 450 00100060000010000350000600900150004120000560005685600540011210100080016621500120017431900280018670000370021421000090025189600220026046100630028246301230034521120  a20190318u        u  u0frey50    a2018-08-011 aRegards croisés sur le baccalauréat professionnel  uhttps://journals.openedition.org/rfp/5222#authors0 afre  a5-10 p.  aAucun droit spécifique 1aMaillardbFabienne40709id:4439  d2018  a./images/vide.png  017165tRevue française de pédagogie9id:171659lnk:perio  d2018-08-01eJanvier-Février-Mars 2017vn° 198tRegards croisés sur le baccalauréat professionnel 9id:359lnk:bull02892naa2 22002171i 450 00100060000010000350000600900150004120001720005685600460022810100080027421500140028232712840029633007800158031900280236070000340238870100350242221000090245789600220246646100630248846301230255121121  a20190318u        u  u0frey50    a2018-08-011 aLa gouvernance par les résultats est-elle un mode de régulation de l'école légitime aux yeux des enseignants ?eune enquête qualitative dans 4 systèmes scolaires  uhttps://journals.openedition.org/rfp/53260 afre  a93-108 p.  aIntroduction  Problématique et cadre théorique Les enseignants face aux politiques de gouvernance par les résultats Trois dimensions de la légitimité des politiques : cognitive, morale, pragmatique  Présentation des 4 contextes de l'étude (1) Responsabilisation douce : des enseignants d'une zone d'inspection en Belgique et d'un canton en Suisse (2) Reddition de comptes et responsabilisation réflexive : des écoles d'un conseil scolaire en Ontario (3) Reddition de compte néo-bureaucratique : des écoles d'une commission scolaire au Québec Méthodologie  Résultats  Ambivalences cognitives Une politique utile pour le système surtout, mais lourde de pression pour les élèves Une évaluation externe standardisée plus « objective », mais aussi productrice de biais et d'effets pervers Les enseignants acceptent une « obligation de moyens », mais pas une « obligation de résultats » Ambivalences morales Des politiques ambiguës du point de vue de la justice scolaire Les enjeux de la responsabilité professionnelle de l'enseignant Ambivalences pragmatiques Un rapport au métier intégrant le principe de performance… au prix d'une accentuation de la pression Des bénéfices pour les pratiques des enseignants… mais limités  Discussion  Conclusion  aL'objectif de cette contribution est d'interroger la manière dont les enseignants donnent sens aux politiques de gouvernance par les résultats. Quels types de légitimité (morale, pragmatique et cognitive) leur accordent-ils ou non ? Une analyse qualitative est menée sur le discours de 53 enseignants du primaire issus de 4 contextes éducatifs francophones distincts quant à leur politique de gouvernance par les résultats : en Suisse (N = 16), en Belgique (N = 12), au Québec (N = 11) et en Ontario (N = 14). Notre analyse identifie les nombreuses ambivalences des enseignants à l'égard des principes moraux qui justifient ces politiques, mais aussi des théories et présupposés cognitifs qui les sous-tendent et de leurs incidences concrètes sur leur travail.  aAucun droit spécifique 1aYerlybGonzague40709id:4440 1aMaroybChristian40709id:4441  d2018  a./images/vide.png  017165tRevue française de pédagogie9id:171659lnk:perio  d2018-08-01eJanvier-Février-Mars 2017vn° 198tRegards croisés sur le baccalauréat professionnel 9id:359lnk:bull01756naa2 22002411i 450 00100060000010000350000600900150004120001000005685600460015610100080020221500150021030000310022532701910025633006360044731900280108370100390111170000430115021000090119389600220120246100630122446301230128789701040141021126  a20190318u        u  u0frey50    a2018-08-011 aCHOPIN Marie-Pierre. Pédagogues de la danseetransmission des savoirs et champ chorégraphique  uhttps://journals.openedition.org/rfp/53710 afre  a109-111 p.  aNote critique de l'ouvrage  aLe pédagogique en miroir Ce que la danse « fait » à la pédagogie De la pédagogie à l'art, et réciproquement Conclusion : pédagogique versus artistique, une féconde singularité  aParmi les différentes disciplines artistiques, la musique et la danse sont peut-être celles qui cultivent le plus la proximité de l'art et de l'enseignement, de l'art et de la pédagogie. C'est tout particulièrement le cas de l'art chorégraphique, dont l'exigence de transmission tient non seulement à son caractère de patrimoine immatériel, mais habite littéralement le travail de création dans toutes ses phases. Le livre de Marie-Claire Chopin, sous le titre "Pédagogues de la danse. Transmission des savoirs et champ chorégraphique", interroge cette présence de la pédagogie au cœur de l'univers chorégraphique.  aAucun droit spécifique 1aChopinbMarie-Pierre40709id:4450 1aKerlanbAlain4070f1948-....9id:1892  d2018  a./images/vide.png  017165tRevue française de pédagogie9id:171659lnk:perio  d2018-08-01eJanvier-Février-Mars 2017vn° 198tRegards croisés sur le baccalauréat professionnel 9id:359lnk:bull  ahttps://journals.openedition.org/rfp/5371b[Texte intégral en ligne]sSans statut particuliertURL02220naa2 22002051i 450 00100060000010000350000600900150004120001570005685600460021310100080025921500130026732707380028033007200101831900280173870000310176621000090179789600220180646100630182846301230189121142  a20190321u        u  u0frey50    a2018-08-011 aLes mathématiques au baccalauréat professionneleElaboration d'un enseignement en tension entre pratiques disciplinaire et professionnelle (1985-1995)  uhttps://journals.openedition.org/rfp/52360 afre  ap. 23-34  aIntroduction  Une préoccupation didactique Une enquête historique  Deux points de vue contrastés sur les pratiques mathématiques professionnelles de référence  D'une culture mathématique professionnelle… … à une culture mathématique technique Des programmes pour résoudre des problèmes avérés et potentiels L'étude de situations professionnelles et la mathématisation comme principe de cohérence  L'étude de situations professionnelles au cœur de l'enseignement La mathématisation pour élaborer et utiliser les connaissances  Des mathématiques pour rechercher la solution comme un professionnel Des savoir-faire pour privilégier l'action Certifier la maîtrise du geste mathématique à l'examen  Conclusion  aCet article s'intéresse à la mise en place d'un nouvel enseignement des mathématiques pour la filière professionnelle scolarisée, dans le contexte de la création du baccalauréat professionnel. Dans la perspective de la transmission d'une culture technique, c'est en faisant de l'activité professionnelle une référence centrale pour cet enseignement que les prescripteurs tentent de dépasser ses tensions identitaires originelles entre apprentissage du métier et formation intellectuelle. Toutefois, le rôle moteur des problèmes professionnels et des pratiques mathématiques de référence dans les choix programmatiques et de certification contribue à l'inscrire dans une perspective instrumentale.   aAucun droit spécifique 1aSidobXavier40709id:4469  d2018  a./images/vide.png  017165tRevue française de pédagogie9id:171659lnk:perio  d2018-08-01eJanvier-Février-Mars 2017vn° 198tRegards croisés sur le baccalauréat professionnel 9id:359lnk:bull02789naa2 22002051i 450 00100060000010000350000600900150004120000910005685600460014710100080019321500130020132705290021433015580074331900280230170000370232921000090236689600220237546100630239746301230246021344  a20190509u        u  u0frey50    a2018-08-011 aLe baccalauréat professionnel de 1985 à nos joursed’une singularité à l’autre  uhttps://journals.openedition.org/rfp/52280 afre  ap. 11-22  a Plan  Introduction  Le bac pro de 1985 à 2007 : un diplôme professionnel et un baccalauréat spécifiques Une réponse à des « besoins » multiples Un nouveau type de baccalauréat et de diplôme professionnel Le développement du bac pro jusqu’en 2007 : vingt ans d’impulsions et de controverses  La rénovation de la voie professionnelle : vers un nouveau bac pro ? Une nouvelle position pour le bac pro… …dont la finalité de poursuite d’études est mise en avant Des ambiguïtés persistantes  Conclusion   a Diplôme professionnel particulier et baccalauréat hétérodoxe, le baccalauréat professionnel a été créé en 1985 dans le but proclamé de revaloriser et désenclaver la voie professionnelle, tout en proposant de nouveaux profils de main-d’œuvre au marché du travail. Malgré son ambivalence et les critiques qu’il a pu générer, son expansion a néanmoins été rapide, soutenue par une politique très volontariste dont l’un des objectifs était de conduire 80 % d’une classe d’âge au niveau du baccalauréat. Vingt ans après son instauration, le baccalauréat professionnel occupait une place notable au sein de la voie professionnelle et du système éducatif, mais il était devancé par le BEP et se voyait contesté sur plusieurs fronts. Au nom de la « rénovation de la voie professionnelle » et de la mise à parité de ce diplôme avec les autres baccalauréats, un important programme de réforme a été engagé en 2007, qui a modifié le cursus et les finalités du diplôme. Celui-ci se prépare désormais en trois ans et affiche sa finalité de poursuite d’études. Est-il pour autant devenu un baccalauréat « ordinaire », équivalent aux autres baccalauréats et offrant les mêmes opportunités d’études ? En revenant sur les évolutions du bac pro, cet article met en valeur son passage d’une position à l’autre et les ambiguïtés de sa redéfinition comme de sa récente montée en puissance.  Mots-clés :formation et enseignement professionnels, diplôme, politique en matière d’éducation   aAucun droit spécifique 1aMaillardbFabienne40709id:4439  d2018  a./images/vide.png  017165tRevue française de pédagogie9id:171659lnk:perio  d2018-08-01eJanvier-Février-Mars 2017vn° 198tRegards croisés sur le baccalauréat professionnel 9id:359lnk:bull02696naa2 22002171i 450 00100060000010000350000600900150004120001180005685600460017410100080022021500130022832707450024133011820098631900280216870100340219670000310223021000090226189600220227046100630229246301230235521345  a20190509u        u  u0frey50    a2018-08-011 aElèves et enseignant·e·s de lycée professionnel.edécryptage d’une relation au prisme des rapports sociaux  uhttps://journals.openedition.org/rfp/52460 afre  ap. 35-48  a Plan  L’enquête et ses enjeux méthodologiques Observer et écouter les relations : quelle posture méthodologique ? Enquêter dans des lycées professionnels ségrégués du bâtiment et du soin à la personne  La construction relationnelle entre élèves et enseignant·e·s de LP Se construire dans les relations Injonctions des enseignant·e·s et transgressions des élèves Les relations à l’épreuve de la non-mixité de genre  Le poids des rapports sociaux et des systèmes de domination pour appréhender la relation enseignant·e·s/élèves Des dispositions construites à l’intersection de différentes sphères d’activité L’adaptation et la relation aux autres : des injonctions genrées et classées  Conclusion   a Une approche par les injonctions et les transgressions permet de mettre au jour un aspect central de la relation entre enseignant·e·s et élèves de baccalauréat professionnel. Sachant que l’enjeu premier, pour les enseignant·e·s, est de créer les conditions pour faire cours, elles et ils cherchent à instaurer des relations de confiance tout en usant d’injonctions afin que les adolescent·e·s intègrent le « métier d’élève » et se muent en adultes autonomes et responsables dans l’objectif d’accéder à l’emploi. En retour, les élèves pratiquent des transgressions, fortement genrées, qui permettent d’aménager des marges de manœuvre et de maintenir à distance les injonctions qui s’insèrent dans des rapports de classe, de sexe, de race et de génération. Empreintes de domination, les relations entre les enseignant·e·s et leurs élèves sont également, pour les premier·ère·s, une potentielle source de revalorisation professionnelle et, pour les seconds, un point de départ pour penser des possibles.  Mots-clés :travail, éducation, formation et enseignement professionnels, relation enseignant-élève, situation sociale  aAucun droit spécifique 1aKergoatbPrisca40709id:4599 1aJartybJulie40709id:4598  d2018  a./images/vide.png  017165tRevue française de pédagogie9id:171659lnk:perio  d2018-08-01eJanvier-Février-Mars 2017vn° 198tRegards croisés sur le baccalauréat professionnel 9id:359lnk:bull02104naa2 22002171i 450 00100060000010000350000600900150004120001390005685600460019510100080024121500130024932701990026233011120046131900280157370100330160170000350163421000090166989600220167846100630170046301230176321346  a20190509u        u  u0frey50    a2018-08-011 aLes bacheliers professionnels face à Admission Post-Bac (APB)e« logique commune » versus « logique formelle » de l’orientation  uhttps://journals.openedition.org/rfp/52640 afre  ap. 49-60  a Plan  Introduction La « logique formelle » d’APB Un système restreint L’« expérience directe » de la poursuite d’études Mobilisation collective et conformisme scolaire  Conclusion   a L’article montre que les difficultés d’usage des applications de gestion des affectations dans l’enseignement supérieur (APB hier, aujourd’hui Parcoursup), éprouvées par les élèves de terminale professionnelle et, plus largement, par les élèves des classes populaires, tiennent à un décalage entre deux façons de voir et de dire l’avenir scolaire et professionnel, chacune reposant sur une logique propre. L’application APB, loin de constituer un support neutre et transparent des choix des élèves, impose un langage singulier de ces choix (ce que nous avons appelé la « logique formelle » de l’orientation, en nous inspirant de Basil Bernstein) qui va favoriser ceux qui parlent la même langue (les bacheliers d’origine supérieure principalement) et qui va tenir à distance voire disqualifier les pratiques et les représentations de ceux qui arborent une autre logique, que nous avons appelée la « logique commune » de l’orientation.  Mots-clés :choix des études, orientation, enseignement supérieur, formation et enseignements professionnels, situation sociale  aAucun droit spécifique 1aOrangebSophie40709id:4601 1aLemêtrebClaire40709id:4600  d2018  a./images/vide.png  017165tRevue française de pédagogie9id:171659lnk:perio  d2018-08-01eJanvier-Février-Mars 2017vn° 198tRegards croisés sur le baccalauréat professionnel 9id:359lnk:bull02604naa2 22002171i 450 00100060000010000350000600900150004120001810005685600460023710100080028321500130029132707310030433010360103531900280207170000360209970100340213521000090216989600220217846100630220046301230226321347  a20190509u        u  u0frey50    a2018-08-011 aL’influence d’une formation au tutorat sur les performances en résolution de problèmes et sur la motivation autodéterminée d’élèves de fin d’enseignement primaire  uhttps://journals.openedition.org/rfp/53080 afre  ap. 75-92  a Plan  Introduction  Cadrage théorique Les recherches axées sur l’efficacité du tutorat Motivation autodéterminée et enseignement explicite : des éléments à combiner pour des tuteurs performants ? Question de recherche et hypothèses  Méthode de recherche et d’analyse Le dispositif de recherche Les problèmes utilisés au pré et post-test et lors de l’intervention Le questionnaire motivationnel La formation au tutorat mise en place dans la classe expérimentale Le codage des interactions au sein des dyades et le type d’analyse réalisée  Résultats Types d’interactions dans les dyades Les progrès en résolution de problèmes Évolution de la motivation autodéterminée  Discussion et conclusion   a L’objet de cette recherche exploratoire est d’évaluer l’efficacité d’un programme de formation au tutorat mis en place en résolution de problèmes en fin d’enseignement primaire (grade 6, élèves de 11-12 ans). La formation au tutorat s’appuyant sur la théorie de l’autodétermination consiste en un enseignement explicite de stratégies cognitives et métacognitives. Le dispositif mis en place permet de comparer trois modalités : élèves formés au tutorat selon l’approche susmentionnée, élèves pratiquant le tutorat sans formation spécifique et élèves travaillant sur les mêmes problèmes que dans les deux autres classes mais sans pratiquer le tutorat. Les résultats montrent que la formation des tuteurs améliore les interactions au sein des dyades, les performances des élèves (tuteurs et tutorés) ainsi que certaines composantes de la motivation autodéterminée.  Mots-clés :tutorat, motivation, stratégie d'apprentissage, méthode d'enseignement, enfant, relations interpersonnelles  aAucun droit spécifique 1aSandronbLaurence40709id:4602 1aFagnantbAnnick40709id:4603  d2018  a./images/vide.png  017165tRevue française de pédagogie9id:171659lnk:perio  d2018-08-01eJanvier-Février-Mars 2017vn° 198tRegards croisés sur le baccalauréat professionnel 9id:359lnk:bull00895naa2 22002051i 450 00100060000010000350000600900150004120001080005685600460016410100080021021500150021830000290023333001460026231900280040870000360043621000090047289600220048146100630050346301230056621348  a20190509u        u  u0frey50    a2018-08-011 aDUTERCQ Yves & MAROY Christian (dir.). Professionnalisme enseignant et politiques de responsabilisation  uhttps://journals.openedition.org/rfp/53790 afre  ap. 111-115  aTexte intégral en ligne  a Notes critiques   Référence(s) :  DUTERCQ Yves & MAROY Christian (dir.). Professionnalisme enseignant et politiques de responsabilisation.  aAucun droit spécifique 1aDraelantsbHugues40709id:4604  d2018  a./images/vide.png  017165tRevue française de pédagogie9id:171659lnk:perio  d2018-08-01eJanvier-Février-Mars 2017vn° 198tRegards croisés sur le baccalauréat professionnel 9id:359lnk:bull01031naa2 22002051i 450 00100060000010000350000600900150004120001770005685600460023310100080027921500150028730000290030233002140033131900280054570000350057321000090060889600220061746100630063946301230070221349  a20190509u        u  u0frey50    a2018-08-011 aGARNIER Pascale, BROUGÈRE Gilles, RAYNA Sylvie & RUPIN Pablo. À 2 ans, vivre dans un collectif d’enfants. Crèche, école maternelle, classe passerelle, jardin maternel  uhttps://journals.openedition.org/rfp/53910 afre  ap. 115-119  aTexte intégral en ligne  a Notes critiques  Référence(s) :  GARNIER Pascale, BROUGÈRE Gilles, RAYNA Sylvie & RUPIN Pablo. À 2 ans, vivre dans un collectif d’enfants. Crèche, école maternelle, classe passerelle, jardin maternel.  aAucun droit spécifique 1aPlaisancebÉric40709id:4605  d2018  a./images/vide.png  017165tRevue française de pédagogie9id:171659lnk:perio  d2018-08-01eJanvier-Février-Mars 2017vn° 198tRegards croisés sur le baccalauréat professionnel 9id:359lnk:bull00795naa2 22002051i 450 00100060000010000350000600900150004120000550005685600460011110100080015721500150016530000290018033000920020931900280030170000430032921000090037289600220038146100630040346301230046621350  a20190509u        u  u0frey50    a2018-08-011 aLHOSTE Yann. Épistémologie et didactique des SVT  uhttps://journals.openedition.org/rfp/54000 afre  ap. 119-121  aTexte intégral en ligne  a Notes critiques  Référence(s) :  LHOSTE Yann. Épistémologie et didactique des SVT.  aAucun droit spécifique 1aMarzin-JanvierbPatricia40709id:4606  d2018  a./images/vide.png  017165tRevue française de pédagogie9id:171659lnk:perio  d2018-08-01eJanvier-Février-Mars 2017vn° 198tRegards croisés sur le baccalauréat professionnel 9id:359lnk:bull00997naa2 22002051i 450 00100060000010000350000600900150004120001320005685600460018810100080023421500150024230000290025733002210028631900280050770000390053521000090057489600220058346100630060546301230066821351  a20190509u        u  u0frey50    a2018-08-011 aTHÉMINES Jean-François & DOUSSOT Sylvain. Acteurs et action. Perspectives en didactiques de l’histoire et de la géographie  uhttps://journals.openedition.org/rfp/54150 afre  ap. 121-122  aTexte intégral en ligne  aNotes critiques  Référence(s) :  THÉMINES Jean-François & DOUSSOT Sylvain. Acteurs et action. Perspectives en didactiques de l’histoire et de la géographie. Caen : Presses universitaires de Caen, 2016, 344 p.   aAucun droit spécifique 1aÉthierbMarc-André40709id:4607  d2018  a./images/vide.png  017165tRevue française de pédagogie9id:171659lnk:perio  d2018-08-01eJanvier-Février-Mars 2017vn° 198tRegards croisés sur le baccalauréat professionnel 9id:359lnk:bull00807naa2 22001931i 450 00100060000010000350000600900150004120000670005610100080012321500150013130000290014633001600017531900280033570000330036321000090039689600220040546100630042746301230049021352  a20190509u        u  u0frey50    a2018-08-011 aVEYRUNES Philippe. La Classe : hier, aujourd’hui et demain ?0 afre  ap. 123-124  aTexte intégral en ligne  a Notes critiques  Référence(s) :  VEYRUNES Philippe. La Classe : hier, aujourd’hui et demain ? Toulouse : Presses universitaires du Midi, 2017, 248 p.   aAucun droit spécifique 1aLeblancbSerge40709id:4608  d2018  a./images/vide.png  017165tRevue française de pédagogie9id:171659lnk:perio  d2018-08-01eJanvier-Février-Mars 2017vn° 198tRegards croisés sur le baccalauréat professionnel 9id:359lnk:bull01016naa2 22001931i 450 00100060000010000350000600900150004120001690005610100080022521500150023330000290024833002630027731900280054070000370056821000090060589600220061446100630063646301230069921353  a20190509u        u  u0frey50    a2018-08-011 aZAID Abdelkarim. Élaborer, transmettre et construire des contenus. Perspective didactique des dispositifs d’éducation et de formation en sciences et technologie0 afre  ap. 124-126  aTexte intégral en ligne  a Notes critiques  Référence(s) :  ZAID Abdelkarim. Élaborer, transmettre et construire des contenus. Perspective didactique des dispositifs d’éducation et de formation en sciences et technologie. Rennes : Presses universitaires de Rennes, 2017, 254 p.   aAucun droit spécifique 1ade HossonbCécile40709id:4609  d2018  a./images/vide.png  017165tRevue française de pédagogie9id:171659lnk:perio  d2018-08-01eJanvier-Février-Mars 2017vn° 198tRegards croisés sur le baccalauréat professionnel 9id:359lnk:bull01226naa2 22002291i 450 00100060000010000350000600900150004120000660005685600460012210100080016821500120017630000290018832703840021731900280060170000420062970100350067121000090070689600220071546100630073746300900080089701060089021123  a20190318u        u  u0frey50    a2018-11-011 aL'histoire des disciplineseun champ de recherche en mutation  uhttps://journals.openedition.org/rfp/60010 afre  a5-22 p.  aTexte intégral en ligne  aL'histoire des disciplines à la française : une configuration singulière  « Curriculum history », histoire des disciplines : des cheminements parallèles ?  Nouveaux itinéraires en histoire des disciplines Les disciplines en leur diversité : approches sociologiques, institutionnelles et territoriales Au cœur de la prise de décision L'objet « discipline » en question  aAucun droit spécifique 1aCardon-QuintbClémence40709id:4442 1ad'EnfertbRenaud40709id:4443  d2018  a./images/vide.png  017165tRevue française de pédagogie9id:171659lnk:perio  d2018-11-01eAvril-Mai-Juin 2017vn° 199tL'histoire des disciplines9id:369lnk:bull  ahttps://journals.openedition.org/rfp/6001bL’histoire des disciplinessSans statut particuliertURL01697naa2 22001931i 450 00100060000010000350000600900150004120001770005685600460023310100080027921500130028733009550030031900280125570000360128321000090131989600220132846100630135046300900141321124  a20190318u        u  u0frey50    a2018-11-011 aEcrire des manuels pour « une » discipline ?eLes auteurs de manuels de sciences physiques et de sciences naturelles pour l'école moyenne dans la France des années 1950  uhttps://journals.openedition.org/rfp/60610 afre  a59-80 p.  aL'article est centré sur les auteurs de manuels scolaires de sciences physiques et de sciences naturelles dans le contexte de la France des années 1950 ; il articule une démarche prosopographique à l'étude de dynamiques institutionnelles et disciplinaires. L'identification des auteurs s'adressant à des élèves scolarisés dans les différents types de classes qui se situent alors en aval du cours moyen 2 e année, la caractérisation de leurs carrières et la mise au jour de liens entre productions éditoriales et engagements pédagogiques ou associatifs conduisent à montrer comment ces acteurs accompagnent le rapprochement des différentes filières d'enseignement post-élémentaires. La comparaison des deux disciplines met en évidence un engagement différencié des auteurs dans la construction d'une identité disciplinaire unifiée selon leur inscription dans l'enseignement des sciences physiques ou des sciences naturelles.   aAucun droit spécifique 1aRadtkabCatherine40709id:4444  d2018  a./images/vide.png  017165tRevue française de pédagogie9id:171659lnk:perio  d2018-11-01eAvril-Mai-Juin 2017vn° 199tL'histoire des disciplines9id:369lnk:bull02116naa2 22002531i 450 00100060000010000350000600900150004120001140005685600460017010100080021621500150022432702610023933009740050031900280147470200470150270100340154970000340158370200300161770200310164721000090167889600220168746100630170946300900177221125  a20190318u        u  u0frey50    a2018-11-011 aUne ségrégation peut en cacher une autreela répartition des élèves entre classes à prendre au sérieux  uhttps://journals.openedition.org/rfp/61090 afre  a117-138 p.  aIntroduction  Une organisation ségréguante Stratifications de la carrière scolaire Quasi-marché scolaire  La mesure de la ségrégation en Fédération Wallonie-Bruxelles Ségrégation entre classes Données et méthode Résultats Discussion Conclusion  aLa Fédération Wallonie-Bruxelles (Belgique) est profondément marquée par une ségrégation académique et socio-économique qui prend source dans une organisation particulière du système éducatif. La ségrégation entre écoles a été largement mise en évidence. La ségrégation entre classes au sein des écoles n'a été que peu abordée par la littérature, mais constitue pourtant un phénomène notable. Premièrement, les apports de différentes mesures et données mobilisées pour mesurer la ségrégation entre écoles sont présentés. Deuxièmement, un indice provenant de l'analyse multiniveau est proposé pour mesurer simultanément la ségrégation entre écoles et au sein des écoles. Il est appliqué à un échantillon de 10 309 élèves de deuxième année commune (enseignement secondaire ordinaire) dans 591 classes et 97 écoles de la FWB. Les analyses montrent une ségrégation importante, notamment entre classes au sein des écoles.  aAucun droit spécifique 1aAlarcon-HenriquezbAlejandra40709id:4447 1aMartinbÉmilie40709id:4446 1aDanhierbJulien40709id:4445 1aKaelenbRob40709id:4448 1aJacobsbDirk40709id:4449  d2018  a./images/vide.png  017165tRevue française de pédagogie9id:171659lnk:perio  d2018-11-01eAvril-Mai-Juin 2017vn° 199tL'histoire des disciplines9id:369lnk:bull02305naa2 22002171i 450 00100060000010000350000600900150004120001360005685600460019210100080023821500130024632708070025933006450106631900280171170000360173921000090177589600220178461001280180646100630193446300900199721143  a20190321u        u  u0frey50    a2018-11-011 aL'enseignement des langues étrangères sous la Troisième Républiqueedes disciplines en prise avec les relations internationales  uhttps://journals.openedition.org/rfp/60050 afre  ap. 23-37  aLes enjeux idéologiques et internationaux de l'enseignement des langues vivantes La position dominée de l'enseignement des langues vivantes en France Les nouveaux défenseurs des langues vivantes Le destin croisé des langues vivantes, un enjeu du rapport de la France au monde ?  Les ambivalences de la place des étrangers dans l'enseignement des langues vivantes Lecteurs et assistants de langues : entre innovation et marginalisation La volonté récurrente de contourner les locuteurs natifs au profit d'acteurs nationaux  Les professeurs de langues vivantes et les relations internationales dans les années 1920 et 1930 Le rôle de l'APLV dans la construction de politiques publiques d'échanges de professeurs et d'élèves Des initiatives régionales pour construire la paix par les langues   aL'histoire de l'enseignement des langues étrangères en France met en lumière l'ambiguïté du rapport de la France au monde durant les années 1870 à 1940. La volonté de renforcer et de diversifier les langues enseignées en France, en particulier autour des années 1900-1902, s'accompagne d'efforts récurrents pour instrumentaliser l'enseignement des langues étrangères afin de peser dans les relations internationales. Les enseignants des différentes langues, comme l'anglais, l'allemand, l'italien, l'espagnol, l'arabe ou le russe, sont à la fois des enjeux et des acteurs dans les rapports entre la France et d'autres Etats.   aAucun droit spécifique 1aDuboisbJérémie40709id:4470  d2018  a./images/vide.png0 aenseignement des langues étrangères, transfert d’enseignants, éducation interculturelle, offre en langues étrangères  017165tRevue française de pédagogie9id:171659lnk:perio  d2018-11-01eAvril-Mai-Juin 2017vn° 199tL'histoire des disciplines9id:369lnk:bull02421naa2 22002051i 450 00100060000010000350000600900150004120001320005685600460018810100080023421500130024232707400025533009760099531900280197170000320199921000090203189600220204046100630206246300900212521333  a20190509u        u  u0frey50    a2018-11-011 aLa Société des professeurs d’histoire et de géographie (SPHG) et ses membres (1910-1939)eacteurs et disciplines scolaires  uhttps://journals.openedition.org/rfp/60370 afre  ap. 39-57  a Plan  Anatomie d’un groupement professionnel Des adhérents nombreux, mais faiblement impliqués Une minorité agissante d’agrégé(e)s parisien(ne)s La Société dans le paysage de l’Instruction publique  La caisse de résonance d’une identité professionnelle Professeurs ou historiens ? À la charnière des humanités classiques et des humanités modernes Les autres hussards noirs de la République  La SPHG, le consensus au risque de l’immobilisme Face aux réformes de structure, une élite condamnée au silence ? Méthodes et contenus : le choix du plus petit dénominateur commun La défense du statut des disciplines : un combat mené en pure perte ? La « collaboration déférente » et ses limites  Conclusion   a Apparues pour la plupart au début du xx e siècle, les sociétés de spécialistes en général et plus particulièrement la Société des professeurs d’histoire et de géographie (SPHG) ont souvent été perçues comme des groupements corporatistes conservateurs et, à ce titre, rendues responsables de la difficulté à réformer les disciplines scolaires qu’elles entendaient défendre. L’objectif de cet article est de nuancer ce point de vue pour le cas de l’histoire-géographie dans l’entre-deux-guerres en analysant le mode de fonctionnement de cette association et en la replaçant dans son contexte institutionnel afin de cerner les limites de son influence effective, tant sur le destin de la discipline scolaire historico-géographique que sur ses représentants sur le terrain, à savoir les professeurs.   Mots-clés :élaboration de programmes d’études, enseignement public, corps enseignant, politique et administration de l’enseignement  aAucun droit spécifique 1aDubosbKévin40709id:4588  d2018  a./images/vide.png  017165tRevue française de pédagogie9id:171659lnk:perio  d2018-11-01eAvril-Mai-Juin 2017vn° 199tL'histoire des disciplines9id:369lnk:bull02152naa2 22002051i 450 00100060000010000350000600900150004120001170005685600460017310100080021921500130022732705650024033008970080531900280170270000320173021000090176289600220177146100630179346300900185621334  a20190509u        u  u0frey50    a2018-11-011 aLes professeurs d’ENNA et leur rôle dans la structuration de la discipline « français » entre 1945 et 1960  uhttps://journals.openedition.org/rfp/60850 afre  ap. 81-94  a Plan  Les professeurs d’ENNA et le projet éducatif de formation de l’homme, du citoyen et du travailleur Les ENNA : un cadre nouveau mais des pratiques héritées Une petite nébuleuse réformatrice à la manœuvre Penser un humanisme du travail  Former à l’enseignement du français dans les ENNA : un modèle mal stabilisé ? Deux figures marquantes de la discipline « français » et leurs divergences : Auguste Dumeix et André Rougerie Pratiques de formation en ENNA Une dynamique de secondarisation initiée par le technique long ?  Conclusion   a Mises en place en 1945, en même temps que les centres d’apprentissage chargés de préparer au CAP des jeunes majoritairement issus des classes de fin d’études primaires, les écoles normales nationales d’apprentissage (ENNA) ont pour mission de former leurs futurs enseignants. Parmi les professeurs ayant en charge l’enseignement du français, quelques personnalités proches de la direction de l’Enseignement technique et partageant son projet éducatif et culturel émergent. L’approche qui croise histoire et didactique permet de saisir le rôle joué par ces enseignants dans la genèse de la discipline « français » entre 1945 et 1959, au moment où elle se donne pour finalité la formation des futurs travailleurs.  Mots-clés :formation initiale des enseignants, formation et enseignement professionnels, formateur, programmes d’étude, matière d’enseignement  aAucun droit spécifique 1aLopezbMaryse40709id:4589  d2018  a./images/vide.png  017165tRevue française de pédagogie9id:171659lnk:perio  d2018-11-01eAvril-Mai-Juin 2017vn° 199tL'histoire des disciplines9id:369lnk:bull02797naa2 22002051i 450 00100060000010000350000600900150004120001120005685600460016810100080021421500140022232709240023633011840116031900280234470000350237221000090240789600220241646100630243846300900250121335  a20190509u        u  u0frey50    a2018-11-011 aDes disciplines en recomposition ? Heurs et malheurs d’une réforme du curriculum au collège (1988-1989)  uhttps://journals.openedition.org/rfp/60970 afre  ap. 95-116  a Plan  Inscrire le collège dans la continuité de l’école primaire : les origines syndicales du corps des professeurs de collège De l’école fondamentale… … à son aggiornamento dans le milieu des années 1980 Un projet négocié entre la FEN et le PS  Réformer le statut des enseignants pour transformer l’organisation pédagogique du collège : un projet mort-né L’échec d’un scénario ou le refus du donnant-donnant par Lionel Jospin S’affranchir des disciplines sur le plan statutaire et pédagogique L’abandon du corps des professeurs de collège : le désencastrement du statutaire et du pédagogique  Le rapport Bourdieu-Gros : recomposer les champs disciplinaires sans toucher au statut des enseignants ? Réformer le curriculum pour changer l’école La critique des disciplines : clé de voûte du rapport Bourdieu-Gros Le rapport Bourdieu-Gros : un essai non transformé  Conclusion   a Cet article étudie les enjeux de la recomposition des champs disciplinaires entre la nomination de Lionel Jospin au ministère de l’Éducation nationale en mai 1988 et le vote de la loi d’orientation du 14 juillet 1989. À partir de fonds d’archives variés et d’entretiens, il montre en quoi cette réforme du curriculum est encastrée dans des enjeux budgétaires, statutaires, syndicaux et politiques. Cet encastrement se joue notamment autour du projet de créer un corps spécifique de professeurs de collège dont la compétence pédagogique ne correspondrait ni aux bivalences des professeurs d’enseignement général de collège (PEGC), ni aux spécialités disciplinaires des certifiés. Après avoir rappelé les origines syndicales de ce projet et expliqué ce qui a poussé Lionel Jospin à y renoncer, cet article montre de quelle façon la recomposition des champs disciplinaires s’est alors imposée comme le principal levier de la réforme de cette école moyenne de masse qu’est le collège.  Mots-clés :tronc commun, réforme des programmes d’études, contenu de l’éducation, approche interdisciplinaire, syndicat, statut de l’enseignant  aAucun droit spécifique 1aClémentbPierre40709id:4590  d2018  a./images/vide.png  017165tRevue française de pédagogie9id:171659lnk:perio  d2018-11-01eAvril-Mai-Juin 2017vn° 199tL'histoire des disciplines9id:369lnk:bull01049naa2 22002051i 450 00100060000010000350000600900150004120001780005685600460023410100080028021500150028830000290030333002660033231900280059870000330062621000090065989600220066846100630069046300900075321337  a20190509u        u  u0frey50    a2018-11-011 aALBERO Brigitte, YURÉN Teresa & GUÉRIN Jérôme (dir.). Modèles de formation et architecture dans l’enseignement supérieur. Culture numérique et développement humain  uhttps://journals.openedition.org/rfp/66930 afre  ap. 139-141  aTexte intégral en ligne  aNotes critiques  Référence(s) :  ALBERO Brigitte, YURÉN Teresa & GUÉRIN Jérôme (dir.). Modèles de formation et architecture dans l’enseignement supérieur. Culture numérique et développement humain. Dijon : Éditions Raison et Passions, 2018, 360 p.   aAucun droit spécifique 1aGarnierbBruno40709id:4591  d2018  a./images/vide.png  017165tRevue française de pédagogie9id:171659lnk:perio  d2018-11-01eAvril-Mai-Juin 2017vn° 199tL'histoire des disciplines9id:369lnk:bull00822naa2 22001931i 450 00100060000010000350000600900150004120000970005685600460015310100080019921500150020733001610022231900280038370000330041121000090044489600220045346100630047546300900053821338  a20190509u        u  u0frey50    a2018-11-011 aDEVIGNE Matthieu. L’École des années noires. Une histoire du primaire en temps de guerre  uhttps://journals.openedition.org/rfp/67050 afre  ap. 141-143  a Notes critiques   Référence(s) :  DEVIGNE Matthieu.L’École des années noires. Une histoire du primaire en temps de guerre. Paris : PUF, 2018, 336 p.   aAucun droit spécifique 1aBoutanbPierre40709id:4592  d2018  a./images/vide.png  017165tRevue française de pédagogie9id:171659lnk:perio  d2018-11-01eAvril-Mai-Juin 2017vn° 199tL'histoire des disciplines9id:369lnk:bull00801naa2 22001931i 450 00100060000010000350000600900150004120000840005685600460014010100080018621500150019433001470020931900280035670000390038421000090042389600220043246100630045446300900051721339  a20190509u        u  u0frey50    a2018-11-011 aLESSARD Claude & CARPENTIER Anylène. Politiques éducatives. La mise en œuvre  uhttps://journals.openedition.org/rfp/67130 afre  ap. 143-145  aNotes critiques  Référence(s) :  LESSARD Claude & CARPENTIER Anylène. Politiques éducatives. La mise en œuvre. Paris : PUF, 2015, 224 p.   aAucun droit spécifique 1aIndarramendibCintia40709id:4571  d2018  a./images/vide.png  017165tRevue française de pédagogie9id:171659lnk:perio  d2018-11-01eAvril-Mai-Juin 2017vn° 199tL'histoire des disciplines9id:369lnk:bull00941naa2 22002051i 450 00100060000010000350000600900150004120001320005685600460018810100080023421500150024230000290025733001980028631900280048470000390051221000090055189600220056046100630058246300900064521340  a20190509u        u  u0frey50    a2018-11-011 aMONNIER Anne. Le temps des dissertations. Chronique de l’accès des jeunes filles aux études supérieures (Genève xixe-xxe)  uhttps://journals.openedition.org/rfp/67250 afre  ap. 145-147  aTexte intégral en ligne  aNotes critiques  Référence(s) :  MONNIER Anne. Le temps des dissertations. Chronique de l’accès des jeunes filles aux études supérieures (Genève xixe-xxe). Genève : Droz, 2018, 360 p.   aAucun droit spécifique 1aChartierbAnne-Marie40709id:4593  d2018  a./images/vide.png  017165tRevue française de pédagogie9id:171659lnk:perio  d2018-11-01eAvril-Mai-Juin 2017vn° 199tL'histoire des disciplines9id:369lnk:bull00814naa2 22002051i 450 00100060000010000350000600900150004120000670005685600460012310100080016921500150017730000290019233001410022131900280036270000340039021000090042489600220043346100630045546300900051821341  a20190509u        u  u0frey50    a2018-11-011 aPAYET Jean-Paul. École et familles. Une approche sociologique  uhttps://journals.openedition.org/rfp/67330 afre  ap. 147-148  aTexte intégral en ligne  a Notes critiques   Référence(s) :  PAYET Jean-Paul. École et familles. Une approche sociologique. Bruxelles : De Boeck, 2017, 133 p.   aAucun droit spécifique 1aPothetbJessica40709id:4594  d2018  a./images/vide.png  017165tRevue française de pédagogie9id:171659lnk:perio  d2018-11-01eAvril-Mai-Juin 2017vn° 199tL'histoire des disciplines9id:369lnk:bull01016naa2 22002171i 450 00100060000010000350000600900150004120001450005685600460020110100080024721500150025530000290027033002130029931900280051270000370054070100370057721000090061489600220062346100630064546300900070821342  a20190509u        u  u0frey50    a2018-11-011 aPIERROT Alain, CARVALHO Isabel & MEDAETS Chantal (dir.). Domination et apprentissage. Anthropologie des formes de la transmission culturelle  uhttps://journals.openedition.org/rfp/67450 afre  ap. 148-153  aTexte intégral en ligne  a Notes Critiques  Référence(s) :  PIERROT Alain, CARVALHO Isabel & MEDAETS Chantal (dir.). Domination et apprentissage. Anthropologie des formes de la transmission culturelle. Paris : Hermann, 2017, 382 p.   aAucun droit spécifique 1aAkkaribAbdeljalil40709id:4595 1aFuentesbMagdalena40709id:4596  d2018  a./images/vide.png  017165tRevue française de pédagogie9id:171659lnk:perio  d2018-11-01eAvril-Mai-Juin 2017vn° 199tL'histoire des disciplines9id:369lnk:bull00941naa2 22002051i 450 00100060000010000350000600900150004120001190005685600460017510100080022121500150022930000290024433002120027331900280048570000380051321000090055189600220056046100630058246300900064521343  a20190509u        u  u0frey50    a2018-11-011 aVEILLARD Laurent. La formation professionnelle initiale. Apprendre dans l’alternance entre différents contextes  uhttps://journals.openedition.org/rfp/67650 afre  ap. 153-154  aTexte intégral en ligne  aNotes critiques  Référence(s) :  VEILLARD Laurent. La formation professionnelle initiale. Apprendre dans l’alternance entre différents contextes. Rennes : Presses universitaires de Rennes, 2017, 308 p.   aAucun droit spécifique 1aFilliettazbLaurent40709id:4597  d2018  a./images/vide.png  017165tRevue française de pédagogie9id:171659lnk:perio  d2018-11-01eAvril-Mai-Juin 2017vn° 199tL'histoire des disciplines9id:369lnk:bull02838naa2 22002171i 450 00100060000010000350000600900150004120001780005685600460023410100080028021500130028830000510030132704590035233015310081131900280234270000360237021000090240689600220241546100630243746301200250021319  a20190507u        u  u0frey50    a2019-03-011 aTransmettre et légitimereun jeu de force entre chercheurs, experts et décideurs politiques. Le cas de la mise en place de l’éducation inclusive en République tchèque  uhttps://journals.openedition.org/rfp/69050 afre  ap. 11-21  aStanislav Štech Université Charles de Prague  a Plan  Les relations entre chercheurs et politiques : des typologies à l’analyse dynamique Le contexte : quand l’intervention politique extérieure recoupe l’évolution « spontanée » Les sources de (dé)légitimation de l’éducation inclusive Les médiateurs : l’émergence de nouvelles figures d’« experts » et l’irrésistible force de l’opinion publique Le responsable politique : de l’argumentaire à la plaidoirie  Conclusion   a Les rapports entre les arguments de la recherche, leur médiation par les experts et la décision politique sont analysés à partir du cas de la mise en pratique de l’éducation inclusive en République tchèque. L’auteur - successivement chercheur en sciences de l’éducation, expert-conseiller et ministre de l’Éducation durant cette période - suit deux lignes d’analyse, celle de la (dé)légitimation et celle de la médiation des connaissances-arguments par les différentes figures d’experts. Le silence des chercheurs et l’absence de leurs arguments dans la phase de mise en place de l’éducation inclusive sont frappants. Ainsi, aux questions de l’éducabilité, de ses pré-requis et de ses conditions sont substituées les discussions autour des statistiques robustes concernant les élèves intégrés (ou ségrégués), les classements des pays selon le degré d’inclusion de leur école, les enquêtes d’opinion ou les expériences de cas particuliers. L’analyse montre que l’abandon de la tâche de médiation et de légitimation par les chercheurs a libéré la place aux médiateurs dont l’expertise n’est que lacunaire ou portée par des intérêts particuliers. Ces médiateurs ne se trouvent pas nécessairement directement auprès des responsables politiques, mais ils peuvent avoir un impact sur la décision politique via l’opinion publique qui amplifie leur voix.  Mots-clés :recherche en éducation, politique, expert en éducation, éducation inclusive, médiateur  aAucun droit spécifique 1aŠtechbStanislav40709id:4568  d2019  a./images/vide.png  017165tRevue française de pédagogie9id:171659lnk:perio  d2019-03-01eJuillet-Août-Septembre 2017vn° 200tRecherche, politique et pratiques en éducation9id:609lnk:bull02181naa2 22002171i 450 00100060000010000350000600900150004120001930005685600460024910100080029521500130030330001640031632702470048033009630072731900280169070000310171821000090174989600220175846100630178046301200184321320  a20190507u        u  u0frey50    a2019-03-011 aLa refondation de l’éducation prioritaire et la rechercheequelques pistes de relations entre la recherche et la définition, l’animation et l’évaluation d’une politique publique  uhttps://journals.openedition.org/rfp/69210 afre  ap. 23-32  aMarc Bablet est Inspecteur d’académie retraité, ancien chef du bureau de l’éducation prioritaire à la Direction générale de l’enseignement scolaire  a Plan  La refondation de l’éducation prioritaire Résultats de recherche et refondation d’une politique publique Résultats de recherche, pratiques de recherche et animation de la politique publique Pratiques de recherche et évaluation   a Ce texte est un témoignage des relations entre une politique éducative et la recherche. Il rappelle d’abord ce que fut la refondation de l’éducation prioritaire entre 2013 et 2017. Il précise comment les résultats de la recherche ont été utilisés pour élaborer les évolutions attendues de cette politique publique, comment ces résultats ont contribué à éclairer les acteurs du changement dans une démarche respectueuse du temps du changement en éducation. Il précise ensuite comment la recherche contribue à l’accompagnement de cette politique, de ses acteurs et à la formation des personnels enseignants. Il évoque également la place que la recherche prend dans l’évaluation de la politique d’éducation prioritaire.  Mots-clés :formation des enseignants, politique en matière d’éducation, démocratisation de l’enseignement, recherche en éducation, zone d’éducation prioritaire, évaluation du système éducatif  aAucun droit spécifique 1aBabletbMarc40709id:4569  d2019  a./images/vide.png  017165tRevue française de pédagogie9id:171659lnk:perio  d2019-03-01eJuillet-Août-Septembre 2017vn° 200tRecherche, politique et pratiques en éducation9id:609lnk:bull02259naa2 22002171i 450 00100060000010000350000600900150004120000640005685600460012010100080016621500130017432702830018733012560047031900280172670000340175470100390178821000090182789600220183646100630185846301200192121321  a20190507u        u  u0frey50    a2019-03-011 aDes possibilités de sociologie en territoire d’expertise  uhttps://journals.openedition.org/rfp/69290 afre  ap. 33-41  a Plan  De la commande d’une collectivité en particulier De la préoccupation sociologique dans la négociation D’un premier élément de réflexion sur la « coloration » Comment les dispositifs viennent (ou pas) aux collèges ? Conclusion : du pratique et du spéculatif   a La réflexion ici engagée prend appui sur la conduite d’une étude commandée par une collectivité locale soucieuse d’« évaluer », pour l’améliorer, sa politique éducative. Celle-ci prend la forme de l’impulsion d’un important « projet » visant à regrouper l’abondante offre éducative complémentaire adressée aux établissements scolaires implantés sur son territoire. Le retour sur les conditions de réalisation, les résultats et la réception de l’enquête réalisée permet de questionner ce qui est aujourd’hui attendu (et entendu) en matière de telles études : sur les types de problèmes qu’une collectivité comme celle-ci se donne la possibilité de reconnaître, et ceux qu’elle ne parvient pas à reconnaître en les constituant comme hors de portée de son action. Ce faisant, elle permet aussi de contribuer à la réflexion sur la pertinence, les possibilités et les difficultés de voir des chercheurs, en l’occurrence sociologues, intervenir en réponse à de telles commandes publiques d’études, qui tendent à se multiplier, mais que les mondes institutionnels et académiques semblent plutôt s’entendre et se résoudre à considérer comme demandes d’un travail d’« expertise ».   aAucun droit spécifique 1aFrandjibDaniel40709id:4570 1aIndarramendibCintia40709id:4571  d2019  a./images/vide.png  017165tRevue française de pédagogie9id:171659lnk:perio  d2019-03-01eJuillet-Août-Septembre 2017vn° 200tRecherche, politique et pratiques en éducation9id:609lnk:bull01888naa2 22002051i 450 00100060000010000350000600900150004120000940005610100080015021500130015832703790017133008160055031900280136670000380139470100360143221000090146889600220147746100630149946301200156221322  a20190507u        u  u0frey50    a2019-03-011 aRéconcilier l’action publique, la recherche et l’engagement professionnel et citoyen0 afre  ap. 43-50  a Plan  Quelques convictions La mise en place de l’Observatoire régional de la mixité sociale et de la réussite scolaire Un exemple d’action publique issue des travaux de l’Observatoire : la réforme de la dotation de solidarité Un exemple d’inachevé : le 4e atelier de l’Observatoire sur les questions et les enjeux de mixité sociale et scolaire  Conclusion   a L’Observatoire régional francilien de la mixité sociale et de la réussite scolaire a été créé en 2011 et a fonctionné jusqu’en décembre 2015. Cet article propose un récit et une analyse de cette expérience originale : faire vivre un dialogue entre responsables politiques, chercheurs et acteurs éducatifs dans l’objectif de définir et de mettre en œuvre des actions publiques. L’Observatoire a permis notamment le financement et la communication d’études inédites sur les inégalités en matière éducative à l’échelle francilienne ainsi que la définition de nouvelles politiques publiques en faveur des lycées, par exemple en termes de budget et d’actions éducatives.  Mots-clés :décideurs en matière de politique éducative, zone d’éducation prioritaire, gouvernance  aAucun droit spécifique 1aZoughebibHenriette40709id:4572 1aDelmasbGuillaume40709id:4573  d2019  a./images/vide.png  017165tRevue française de pédagogie9id:171659lnk:perio  d2019-03-01eJuillet-Août-Septembre 2017vn° 200tRecherche, politique et pratiques en éducation9id:609lnk:bull01625naa2 22002051i 450 00100060000010000350000600900150004120000450005685600460010110100080014721500130015532703490016833006260051731900280114370000340117121000090120589600220121446100630123646301200129921323  a20190507u        u  u0frey50    a2019-03-011 aPour des collectifs militants apprenants  uhttps://journals.openedition.org/rfp/69650 afre  ap. 51-59  a  Plan  Mûs par des convictions Convictions militantes, recherches académiques et politiques éducatives Libérés des injonctions paradoxales Se revendiquant « praticiens-chercheurs » S’assumant « praticiens-chercheurs » Praticiens-chercheurs, chercheurs et décideurs : quels compagnonnages ? Pour des collectifs militants apprenants   a À ChanGements pour l’Égalité, mouvement socio-pédagogique belge, nous poursuivons depuis presque 50 ans une double action pour une même finalité : infléchir les politiques éducatives en Fédération Wallonie-Bruxelles (FWB) et améliorer les pratiques enseignantes en faveur de l’émancipation sociale des enfants et jeunes issus de milieux populaires. Cette double action et cette finalité nous situent d’emblée à l’interface entre pouvoirs publics à influencer, chercheurs et praticiens à mobiliser.  Mots-clés :association, coopération, formation continue des enseignants, autorité éducative   aAucun droit spécifique 1aCornetbJacques40709id:4574  d2019  a./images/vide.png  017165tRevue française de pédagogie9id:171659lnk:perio  d2019-03-01eJuillet-Août-Septembre 2017vn° 200tRecherche, politique et pratiques en éducation9id:609lnk:bull02032naa2 22002171i 450 00100060000010000350000600900150004120000970005685600460015310100080019921500130020732704590022033008290067931900280150870000310153670100330156721000090160089600220160946100630163146301200169421324  a20190507u        u  u0frey50    a2019-03-011 aDe l’usage des outils issus de la recherche pour accompagner les établissements scolaires  uhttps://journals.openedition.org/rfp/69930 afre  ap. 61-67  a Plan  Introduction  Éléments contextuels Autonomie des établissements et pilotage local Demandes d’accompagnement liées aux injonctions à l’intégration en Suisse Quels outils pour quels usages ? Analyse des données de l’établissement Tableaux de bord participatifs Questionnaire Groupes focalisés Limites et embûches de l’accompagnement Problèmes éthiques Travestissement des outils de la recherche par les utilisateurs  Conclusion    a Les réflexions qui constituent la trame de cet article sont issues d’une pratique d’accompagnement d’établissements scolaires dans l’innovation et le changement, en Suisse romande. Dans ce contexte, le développement d’outils issus de la recherche apparaît essentiel pour répondre aux besoins des directions d’écoles et des acteurs de terrain. Notre propos vise à questionner les enjeux et le rôle des chercheuses-formatrices et chercheurs-formateurs dans l’exploration de ces nouveaux champs, ainsi qu’à identifier certaines limites et embûches liées à des problèmes éthiques et au risque de travestissement des outils développés par leurs utilisateurs.  Mots-clés :recherche en éducation, transfert des connaissances, gestion d’établissement scolaire, collecte de données, indicateur   aAucun droit spécifique 1aRamelbSerge40709id:4575 1aBoveybLaurent40709id:4576  d2019  a./images/vide.png  017165tRevue française de pédagogie9id:171659lnk:perio  d2019-03-01eJuillet-Août-Septembre 2017vn° 200tRecherche, politique et pratiques en éducation9id:609lnk:bull02438naa2 22002051i 450 00100060000010000350000600900150004120000520005685600460010810100080015421500130016232708770017533009020105231900280195470000360198221000090201889600220202746100630204946301200211221325  a20190507u        u  u0frey50    a2019-03-011 aObjets et pratiques de l’évaluation scolaire  uhttps://journals.openedition.org/rfp/70210 afre  ap. 69-79  a Plan  Introduction  Comment les décideurs institutionnels ont-ils détourné l’évaluation formative de ses objectifs initiaux ? Contexte socio-éducatif La récupération de l’évaluation formative par les politiques Conclusion La notation chiffrée des travaux écrits des élèves : un outil imparfait dont l’usage est perpétué par les politiques Une histoire ancienne Objectivité versus subjectivité de l’évaluation La notation chiffrée : un mauvais outil Le maintien de la note dans le système français : un choix politique Conclusion L’évaluation des compétences : possible ou infaisable ? Les politiques ont choisi Les compétences dans les contenus d’enseignement (en France) L’introduction des compétences dans les contenus d’enseignement ne fait pas l’unanimité parmi les chercheurs  Conclusion  Conclusion, débats, perspectives   a Dans les 40 dernières années, la massification de l’enseignement scolaire en France a eu pour conséquence l’émergence de la difficulté et de l’échec scolaires en tant que problèmes publics. Face à cette nouveauté apparente, les responsables politiques se sont emparés de thématiques ou de résultats de recherche encore largement débattus : l’évaluation formative (1978), l’approche par compétences dans les contenus d’enseignement (2006) et l’évaluation des acquis des élèves (2010). Ils ont également perpétué l’usage de la « notation chiffrée à la française », alors que la docimologie en a montré depuis longtemps les limites. À travers ces trois exemples, cet article vise à explorer les rapports entre recherche et politique, et leurs effets mutuels.  Mots-clés :évaluation formative, notation, compétence, politique en matière d’éducation  aAucun droit spécifique 1aRaulinbDominique40709id:4577  d2019  a./images/vide.png  017165tRevue française de pédagogie9id:171659lnk:perio  d2019-03-01eJuillet-Août-Septembre 2017vn° 200tRecherche, politique et pratiques en éducation9id:609lnk:bull01718naa2 22001931i 450 00100060000010000350000600900150004120001480005685600460020410100080025021500130025833009730027131900280124470000380127221000090131089600220131946100630134146301200140421326  a20190507u        u  u0frey50    a2019-03-011 aUne recherche sur la scolarité des enfants du voyage ou quelques conditions de transformations de pratiques institutionnelles par la recherche  uhttps://journals.openedition.org/rfp/70290 afre  ap. 81-87  a Afin d’éclairer les services rendus par une recherche de sociologie de l’éducation sur des pratiques institutionnelles, cette contribution présente une recherche doctorale menée par un enseignant auprès des enfants du voyage, devenu chercheur. L’article croise des cadres théoriques issus de la sociologie des inégalités scolaires et des résultats d’une enquête de terrain dans un département qui a vu se développer pendant plus de dix ans une politique coordonnée de scolarisation des enfants du voyage. Cette recherche permet de mettre en évidence quelques conditions de transformation de pratiques institutionnelles par la mise en présence des catégories et résultats de la recherche avec la réflexion d’acteurs de terrain dans des situations de formations ou d’activités professionnelles.  Mots-clés :sociologie de l’éducation, résultats de recherche, formation des enseignants, besoins éducatifs particuliers, groupe ethnique  aAucun droit spécifique 1aBettendorffbFranck40709id:4578  d2019  a./images/vide.png  017165tRevue française de pédagogie9id:171659lnk:perio  d2019-03-01eJuillet-Août-Septembre 2017vn° 200tRecherche, politique et pratiques en éducation9id:609lnk:bull01551naa2 22002051i 450 00100060000010000350000600900150004120001650005685600460022110100080026721500130027533007500028831900280103870000340106670100310110021000090113189600220114046100630116246301200122521327  a20190507u        u  u0frey50    a2019-03-011 aArticuler recherche et développement pédagogiqueeun exemple de collaboration entre un laboratoire de sciences de l’éducation et une École de la 2e chance  uhttps://journals.openedition.org/rfp/70490 afre  ap. 89-97  a Cet article témoigne d’une collaboration entre un laboratoire de sciences de l’éducation et une École de la 2e chance, autour du développement d’un dispositif d’éducation cognitive. Les différents choix méthodologiques et organisationnels effectués en vue d’articuler l’enquête scientifique, le processus décisionnel et l’ingénierie pédagogique sont présentés et commentés. À la fin de l’article, nous dressons un bilan provisoire de cette expérience, en relevant les effets engendrés (sur l’activité éducative, sur la production scientifique) et les difficultés rencontrées.  Mots-clés :recherche en éducation, partenaires en éducation, innovation pédagogique, développement, apprendre à apprendre  aAucun droit spécifique 1aVoisinbVincent40709id:4579 1aMartinbMarc40709id:4580  d2019  a./images/vide.png  017165tRevue française de pédagogie9id:171659lnk:perio  d2019-03-01eJuillet-Août-Septembre 2017vn° 200tRecherche, politique et pratiques en éducation9id:609lnk:bull01963naa2 22002051i 450 00100060000010000350000600900150004120001230005685600460017910100080022521500140023332703760024733008570062331900280148070000350150821000090154389600220155246100630157446301200163721328  a20190507u        u  u0frey50    a2019-03-011 aLes effets de l’intégration sociale étudiante sur la réussite universitaire en 1er cycle sont-ils significatifs ?  uhttps://journals.openedition.org/rfp/70770 afre  ap. 99-117  a Plan  Introduction  L’intégration sociale à l’université et ses différentes dimensions L’intégration sociale parmi les déterminants de la réussite étudiante Démarche empirique Description des données Effets de l’intégration sociale étudiante sur la réussite en licence Des effets indirects au travers de l’investissement académique ?  Conclusion   a En France, les recherches portant sur les déterminants de la réussite universitaire ont assez peu exploré les effets de l’intégration sociale, contrairement aux travaux nord-américains qui ont mis en exergue son influence positive sur la persévérance des étudiants. Cet article propose ainsi une (re)définition de ce concept appliqué à la population étudiante universitaire et en interroge les effets sur la réussite et les performances des étudiants. Les analyses montrent que l’implication dans les interactions sociales avec les pairs et la qualité perçue de l’intégration sociale à l’université favorisent l’investissement dans les études et peuvent conduire certains étudiants à réussir davantage.  Mots-clés :intégration sociale, université, études supérieures de premier cycle, résultats de l’éducation  aAucun droit spécifique 1aBerthaudbJulien40709id:4581  d2019  a./images/vide.png  017165tRevue française de pédagogie9id:171659lnk:perio  d2019-03-01eJuillet-Août-Septembre 2017vn° 200tRecherche, politique et pratiques en éducation9id:609lnk:bull02227naa2 22002291i 450 00100060000010000350000600900150004120001390005685600460019510100080024121500150024932701940026433011870045831900280164570000350167370100340170870200410174221000090178389600220179246100630181446301200187721329  a20190509u        u  u0frey50    a2019-03-011 aL’impact conjoint du genre de l’étudiant et du diplôme de ses parents pour comprendre le processus de persévérance académique  uhttps://journals.openedition.org/rfp/70810 afre  ap. 119-133  a Plan  Introduction Limites La théorie du comportement planifié pour comprendre la persévérance Notre étude Méthode Participants Procédure Mesures Analyses et résultats  Discussion   a L’influence des facteurs de background sur la persévérance est largement reconnue dans la littérature. Cependant, les résultats obtenus divergent d’une étude à l’autre et il est donc difficile de comprendre quel rôle jouent réellement ces facteurs sur la persévérance. De plus, l’impact de ces facteurs est rarement étudié conjointement, ce qui pourrait pourtant amener plus de finesse à la compréhension du phénomène de persévérance. Dans ce cadre, nous avons mené une étude auprès de 812 étudiants inscrits en première année à l’université afin d’étudier l’impact conjoint du genre et du diplôme des parents sur l’ensemble du processus de persévérance. Des analyses multigroupes nous ont permis de montrer que ces facteurs pouvaient avoir un effet modérateur sur les relations entre la persévérance et ses déterminants, mais également que la prise en compte de l’impact conjoint de ces deux facteurs de background permettait d’avoir des résultats plus riches et une meilleure compréhension du phénomène de persévérance.  Mots-clés :enseignement supérieur, motivation, psychologie, environnement socio-culturel, genre  aAucun droit spécifique 1aRolandbNathalie40709id:4582 1aFrenaybMariane40709id:4583 1aBoudrenghienbGentiane40709id:4584  d2019  a./images/vide.png  017165tRevue française de pédagogie9id:171659lnk:perio  d2019-03-01eJuillet-Août-Septembre 2017vn° 200tRecherche, politique et pratiques en éducation9id:609lnk:bull00906naa2 22002051i 450 00100060000010000350000600900150004120000980005685600460015410100080020021500150020830000290022333001710025231900280042370000350045121000090048689600220049546100630051746301200058021330  a20190509u        u  u0frey50    a2019-03-011 aBOURDON Étienne. La forge gauloise de la nation. Ernest Lavisse et la fabrique des ancêtres  uhttps://journals.openedition.org/rfp/71030 afre  ap. 135-136  aTexte intégral en ligne  a Notes critiques  Référence(s) :  BOURDON Étienne. La forge gauloise de la nation. Ernest Lavisse et la fabrique des ancêtres. Lyon : ENS Éditions, 2017, 290 p.   aAucun droit spécifique 1aLegrisbPatricia40709id:4585  d2019  a./images/vide.png  017165tRevue française de pédagogie9id:171659lnk:perio  d2019-03-01eJuillet-Août-Septembre 2017vn° 200tRecherche, politique et pratiques en éducation9id:609lnk:bull00932naa2 22002051i 450 00100060000010000350000600900150004120001100005685600460016610100080021221500150022030000290023533001820026431900280044670000380047421000090051289600220052146100630054346301200060621331  a20190509u        u  u0frey50    a2019-03-011 aLIGNIER Wilfried & PAGIS Julie. L’enfance de l’ordre. Comment les enfants perçoivent le monde social  uhttps://journals.openedition.org/rfp/71140 afre  ap. 136-139  aTexte intégral en ligne  aNotes critiques  Référence(s) : LIGNIER Wilfried & PAGIS Julie. L’enfance de l’ordre. Comment les enfants perçoivent le monde social. Paris : Éd. du Seuil, 2017, 320 p.   aAucun droit spécifique 1aMontmassonbDoriane40709id:4586  d2019  a./images/vide.png  017165tRevue française de pédagogie9id:171659lnk:perio  d2019-03-01eJuillet-Août-Septembre 2017vn° 200tRecherche, politique et pratiques en éducation9id:609lnk:bull00952naa2 22002051i 450 00100060000010000350000600900150004120001190005685600460017510100080022121500150022930000290024433001910027331900280046470000400049221000090053289600220054146100630056346301200062621332  a20190509u        u  u0frey50    a2019-03-011 aMILLET Mathias & CROIZET Jean-Claude. L’école des incapables ? La maternelle, un apprentissage de la domination  uhttps://journals.openedition.org/rfp/71270 afre  ap. 139-141  aTexte intégral en ligne  a Notes critiques   Référence(s) :  MILLET Mathias & CROIZET Jean-Claude. L’école des incapables ? La maternelle, un apprentissage de la domination. Paris : La Dispute, 2016, 232 p.   aAucun droit spécifique 1aJoigneauxbChristophe40709id:4587  d2019  a./images/vide.png  017165tRevue française de pédagogie9id:171659lnk:perio  d2019-03-01eJuillet-Août-Septembre 2017vn° 200tRecherche, politique et pratiques en éducation9id:609lnk:bull01323naa2 22001331i 450 00100080000010000350000820001490004321000090019246301670020189701290036899500610049799605560055899900750111435-bull  a20190515u        u  u0frey50  1 aRegards croisés sur le baccalauréat professionnel , Janvier-Février-Mars 2017dArticle_expl_bulletinhRevue française de pédagogiein° 198  d2018  017165d2018-08-01eJanvier-Février-Mars 2017vn° 198tRegards croisés sur le baccalauréat professionnel tRevue française de pédagogie9id:359lnk:bull_expl  ahttps://journals.openedition.org/rfp/5200bRegards croisés sur le baccalauréat professionnelsSans statut particuliertURL  aCEFEDEM AURAcCEFEDEM AURAf0012036kR 14 REV 198r18qu  f0012036kR 14 REV 198m00000000n00000000aCEFEDEM AURAb3vCEFEDEM AURAxENSEIGNEMENT ET PEDAGOGIE - RougeePERIODIQUESr181Empruntable319expl_id:165029create_date:2019-04-01 15:29:229expl_cb:00120369expl_cote:R 14 REV 1989expl_statut:19statut_libelle:Empruntable9expl_typdoc:189tdoc_libelle:PERIODIQUES9tdoc_codage_import:189expl_section:359section_libelle:ENSEIGNEMENT ET PEDAGOGIE - Rouge9expl_owner:39lender_libelle:CEFEDEM AURA9codestat_libelle:Indéterminé9statisdoc_codage_import:u9pret_flag:19location_libelle:CEFEDEM AURA  a01/04/2019lDate d'acquisitionncp_date_acquisitiontdate_boxf001203601232naa2 22001331i 450 00100080000010000350000820001160004321000090015946301340016889701040030299500610040699605560046799900750102336-bull  a20190515u        u  u0frey50  1 aL'histoire des disciplines, Avril-Mai-Juin 2017dArticle_expl_bulletinhRevue française de pédagogiein° 199  d2018  017165d2018-11-01eAvril-Mai-Juin 2017vn° 199tL'histoire des disciplinestRevue française de pédagogie9id:369lnk:bull_expl  ahttps://journals.openedition.org/rfp/5981b[Texte intégral en ligne]sSans statut particuliertURL  aCEFEDEM AURAcCEFEDEM AURAf0012037kR 14 REV 199r18qu  f0012037kR 14 REV 199m00000000n00000000aCEFEDEM AURAb3vCEFEDEM AURAxENSEIGNEMENT ET PEDAGOGIE - RougeePERIODIQUESr181Empruntable319expl_id:165039create_date:2019-04-01 15:30:339expl_cb:00120379expl_cote:R 14 REV 1999expl_statut:19statut_libelle:Empruntable9expl_typdoc:189tdoc_libelle:PERIODIQUES9tdoc_codage_import:189expl_section:359section_libelle:ENSEIGNEMENT ET PEDAGOGIE - Rouge9expl_owner:39lender_libelle:CEFEDEM AURA9codestat_libelle:Indéterminé9statisdoc_codage_import:u9pret_flag:19location_libelle:CEFEDEM AURA  a01/04/2019lDate d'acquisitionncp_date_acquisitiontdate_boxf001203701176naa2 22001211i 450 00100080000010000350000820001460004321000090018946301640019899500610036299605560042399900750097960-bull  a20190515u        u  u0frey50  1 aRecherche, politique et pratiques en éducation, Juillet-Août-Septembre 2017dArticle_expl_bulletinhRevue française de pédagogiein° 200  d2019  017165d2019-03-01eJuillet-Août-Septembre 2017vn° 200tRecherche, politique et pratiques en éducationtRevue française de pédagogie9id:609lnk:bull_expl  aCEFEDEM AURAcCEFEDEM AURAf0012059kR 14 REV 200r18qu  f0012059kR 14 REV 200m00000000n00000000aCEFEDEM AURAb3vCEFEDEM AURAxENSEIGNEMENT ET PEDAGOGIE - RougeePERIODIQUESr181Empruntable319expl_id:165289create_date:2019-05-13 17:37:449expl_cb:00120599expl_cote:R 14 REV 2009expl_statut:19statut_libelle:Empruntable9expl_typdoc:189tdoc_libelle:PERIODIQUES9tdoc_codage_import:189expl_section:359section_libelle:ENSEIGNEMENT ET PEDAGOGIE - Rouge9expl_owner:39lender_libelle:CEFEDEM AURA9codestat_libelle:Indéterminé9statisdoc_codage_import:u9pret_flag:19location_libelle:CEFEDEM AURA  a13/05/2019lDate d'acquisitionncp_date_acquisitiontdate_boxf0012059
\ No newline at end of file
diff --git a/cosmogramme/tests/php/classes/unimarc_pomme.txt b/cosmogramme/tests/php/classes/unimarc_pomme.txt
new file mode 100644
index 0000000000000000000000000000000000000000..a340f44b575f49c1686ccc5255de4296a0a78cd2
--- /dev/null
+++ b/cosmogramme/tests/php/classes/unimarc_pomme.txt
@@ -0,0 +1 @@
+00917     2200241   45000010005000000100025000050900009000301000041000392000048000802100025001282150037001532250016001901010008002063300120002146150017003346760010003516060034003617000027003954100016004228560038004388560039004769950160005154414  a2740417659d9.00 EUR  a4414  a20040402d2004    a  |0frey50  ||||ba1 a[La ]pomme de terrefTexte Anne RoyerbLIVR 1cMango-Jeunessed2004 1a32 p.cill. en coul.d25 x 20 cm1 aQui es-tu ?  afre  aPour tout savoir sur la pomme de terre : son histoire, sa plantation, sa récolte, les différentes varietés, etc.  aDocumentaire 1a635.2 196068aLégumexPomme de terre 196067aRoyerbAnne4070 1tQui es-tu ?  u/userfiles/files/public/pomme.pdf  u/userfiles/files/public/pomme.html  2095418bMAUREScMAURESeDocumentaires adultef00519000087170kE 635.2m2014-12-23o0rLIVRh8717jLibrairie jeunesse au pays bleqJeunesp9,00s17/07/2004
\ No newline at end of file
diff --git a/cosmogramme/tests/php/classes/unimarc_zanzibara.txt b/cosmogramme/tests/php/classes/unimarc_zanzibara.txt
index 3c76d435b087d55ba340855e30c6ee0e6cc08eb6..b276106425f483768743e3db1c4b25cacf5cc1b2 100644
--- a/cosmogramme/tests/php/classes/unimarc_zanzibara.txt
+++ b/cosmogramme/tests/php/classes/unimarc_zanzibara.txt
@@ -1 +1 @@
-01566njm0 2200361   450 001000800000071003800008073001800046100004100064101000800105127001100113200005000124210003600174215003100210330038800241345004800629464002500677464001100702464002400713464001900737464001200756464003800768464001700806464004400823607003400867680001100901686002100912700003300933711003700966801002201003801002601025959007501051995007801126322421800bJahazi Media/Buda Musiquea860248 0a3341348602486  a20140220d2013    u  y0frey0103    ba0 aund  a1 h 001 aZanzibara, vol. 8bCDechungufRajab Suleiman  cJahazi Media/Buda Musiqued2013  a1 disque compacte1 livret  aL’as du qanun Rajab Suleiman, la somptueuse chanteuse Saada Nassor, le prestigieux chantre Makame Faki... Kithara, c’est une dizaine de musiciens, dont une bonne partie est issue du Culture Musical Club. Violon, oud, derbouka, flÃute nay, mais aussi contrebasse occidentale, bongos latino-amÂericains ou tablas. Le taarab de Kithara mÃele langueur ocÂeane et frÂenÂesie continentale.  aGAMb3341348602486cdisque compactd17,81 E  tKombo ()(Bomu Ngoma)  tChungu  tKumbe ndivyo ulivyo  tSalaam aleikum  tRidhika  tHisiya za muungwana ()(Kidumbaki)  tPokea salaam  tKipenzi changu cha moyo ()(Kyaso ngoma)  a* TanzaniexDocuments sonores  a95 TAN  a9.1323v042PCDM 1aSuleimanbRajab95459qanÃun02aKithara95459ens. voc. & instr. 0aFRbGAMc20140220 0aFRbCTD Mirc20140327  3http://www.gamannecy.com//upload/albums/201312/3341348602486_thumb.jpg  33225712aCanalbCANALf1201525162k95 TAN m20140517n20140614q4rCDIvn
\ No newline at end of file
+01624njm0 2200373   450 001000800000071003800008073001800046100004100064101000800105127001100113200005000124210003600174215003100210330038800241345004800629464002500677464001100702464002400713464001900737464001200756464003800768464001700806464004400823607003400867680001100901686002100912700003300933711003700966801002201003801002601025950002701051959007501078995009701153322421800bJahazi Media/Buda Musiquea860248 0a3341348602486  a20140220d2013    u  y0frey0103    ba0 aund  a1 h 001 aZanzibara, vol. 8bCDechungufRajab Suleiman  cJahazi Media/Buda Musiqued2013  a1 disque compacte1 livret  aL’as du qanun Rajab Suleiman, la somptueuse chanteuse Saada Nassor, le prestigieux chantre Makame Faki... Kithara, c’est une dizaine de musiciens, dont une bonne partie est issue du Culture Musical Club. Violon, oud, derbouka, flÃute nay, mais aussi contrebasse occidentale, bongos latino-amÂericains ou tablas. Le taarab de Kithara mÃele langueur ocÂeane et frÂenÂesie continentale.  aGAMb3341348602486cdisque compactd17,81 E  tKombo ()(Bomu Ngoma)  tChungu  tKumbe ndivyo ulivyo  tSalaam aleikum  tRidhika  tHisiya za muungwana ()(Kidumbaki)  tPokea salaam  tKipenzi changu cha moyo ()(Kyaso ngoma)  a* TanzaniexDocuments sonores  a95 TAN  a9.1323v042PCDM 1aSuleimanbRajab95459qanÃun02aKithara95459ens. voc. & instr. 0aFRbGAMc20140220 0aFRbCTD Mirc20140327  GMusique traditionnelle  3http://www.gamannecy.com//upload/albums/201312/3341348602486_thumb.jpg  33225712aCanalbCANALf1201525162k95 TAN m20140517n20140614q4rCDIvnQ12NMOpProbpl
\ No newline at end of file
diff --git a/doc/extern_libs.org b/doc/extern_libs.org
index aad4fa9a7747d314b288a62166a10e0827f69741..c4fca33445cda79b2cad7c0a22be4437ef516400 100644
--- a/doc/extern_libs.org
+++ b/doc/extern_libs.org
@@ -45,4 +45,6 @@
 | icon slideshow by Javier Cabezas        | CCBY                     |               | editeur d'articles                                                   |                                                            | https://thenounproject.com/term/slideshow/6517/                                                                                |
 | PHP-Parser                              | BSD-3-Clauses            | -             | validation de fichiers php (formulaires de recherche)                |                                                            | https://github.com/nikic/PHP-Parser                                                                                            |
 | Jquery Notification                     | MIT ?                    |               | barre bleue de notification                                          | oui (barre en bas)                                         | n'existe plus                                                                                                                  |
+| activitystreams                         | MIT                      |               | Avis communautaires                                                  |                                                            | https://gitlab.com/patbator/activitystreams                                                                                    |
+| phpseclib                               | MIT                      |               | Avis communautaires                                                  | X ajout d'un autoload.php                                  | https://github.com/phpseclib/phpseclib                                                                                         |
 | leaflet.fullscreen                      | MIT                      |               | bouton plein écran sur la carte des bibliothèques                    |                                                            | https://github.com/brunob/leaflet.fullscreen                                                                                   |
diff --git a/library/Class/ActivityPub/PublicKey.php b/library/Class/ActivityPub/PublicKey.php
new file mode 100644
index 0000000000000000000000000000000000000000..c8bcd98424171f7bd6ca322a50ed815ac9207af3
--- /dev/null
+++ b/library/Class/ActivityPub/PublicKey.php
@@ -0,0 +1,32 @@
+<?php
+/**
+ * Copyright (c) 2012-2019, 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
+ */
+
+require_once __DIR__ . '/../../activitystreams/autoload.php';
+
+use \Patbator\ActivityStreams\Model\Base;
+
+
+class Class_ActivityPub_PublicKey extends Base {
+  protected $_attribs = ['id' => null,
+                         'type' => 'Key',
+                         'owner' => null,
+                         'publicKeyPem' => null];
+}
diff --git a/library/Class/ActivityPub/Service.php b/library/Class/ActivityPub/Service.php
new file mode 100644
index 0000000000000000000000000000000000000000..294d8a2611f79af6d4cfcb1624f5d1b7d56560e3
--- /dev/null
+++ b/library/Class/ActivityPub/Service.php
@@ -0,0 +1,37 @@
+<?php
+/**
+ * Copyright (c) 2012-2019, 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
+ */
+
+require_once __DIR__ . '/../../activitystreams/autoload.php';
+
+use \Patbator\ActivityStreams\Model\Service;
+
+
+class Class_ActivityPub_Service extends Service {
+  public function __construct() {
+    parent::__construct();
+    $this->_actor_attribs[] = 'publicKey';
+  }
+
+
+  public function type() {
+    return 'Service';
+  }
+}
diff --git a/library/Class/AdminVar.php b/library/Class/AdminVar.php
index f011c795fa9525f6dd2dff344e70c695145c64ef..7d3429f41812d860cacd015c1f89364a40647e38 100644
--- a/library/Class/AdminVar.php
+++ b/library/Class/AdminVar.php
@@ -136,6 +136,7 @@ class Class_AdminVarLoader extends Storm_Model_Loader {
        'static_map' => $this->_getStaticMapVars(),
        'search' => $this->_getSearchVars(),
        'file-manager' => $this->_getFileManagerVars(),
+       'federation-reviews' => $this->_getFederationVars(),
        'usergroup-agenda' => $this->_getRendezVousVars(),
        ];
   }
@@ -490,6 +491,16 @@ class Class_AdminVarLoader extends Storm_Model_Loader {
   }
 
 
+  protected function _getFederationVars() {
+    return ['FEDERATION_COMMUNITY_SERVER' => Class_AdminVar_Meta::newDefault($this->_('URL du serveur communautaire de la fédération, vide pour désactiver')),
+            'FEDERATION_ACTOR_NAME' => Class_AdminVar_Meta::newDefault($this->_('Nom d\'affichage de ce Bokeh dans la communautée. Si vide, l\'url sera utilisée')),
+            'FEDERATION_IS_COMMUNITY_SERVER' => Class_AdminVar_Meta::newOnOff($this->_('Ce Bokeh est un serveur communautaire')),
+            'FEDERATION_PUBKEY' => Class_AdminVar_Meta::newCryptKey($this->_('Clé publique permettant la vérification de signature des messages envoyés par ce Bokeh à la fédération')),
+            'FEDERATION_PRIVKEY' => Class_AdminVar_Meta::newCryptKey($this->_('Clé privée permettant de générer les signatures des messages envoyés par ce Bokeh à la fédération')),
+            ];
+  }
+
+
   protected function _getRendezVousVars() {
     return ['ENABLE_RENDEZ_VOUS' => Class_AdminVar_Meta::newOnOff($this->_('Activer la gestion des rendez-vous')),
             'NOTIFICATION_TEMPLATE_RENDEZ_VOUS' => Class_AdminVar_Meta::newEditor($this->_('Modèle utilisé pour les courriels de notifications de rendez-vous.'),
@@ -1052,6 +1063,26 @@ class Class_AdminVarLoader extends Storm_Model_Loader {
   }
 
 
+  public function isFederationCommunityServer() {
+    return Class_AdminVar::isModuleEnabled('FEDERATION_IS_COMMUNITY_SERVER');
+  }
+
+
+  public function isFederationEnabled() {
+    return ('' != trim(Class_AdminVar::get('FEDERATION_COMMUNITY_SERVER')));
+  }
+
+
+  public function isFederationServiceAvailable() {
+    return Class_AdminVar::isFederationEnabled() || Class_AdminVar::isFederationCommunityServer();
+  }
+
+
+  public function isLibrarianReviewsModerated() {
+    return Class_AdminVar::isModuleEnabled('MODO_AVIS_BIBLIO');
+  }
+
+
   public function isRendezVousEnabled() {
     return Class_AdminVar::isModuleEnabled('ENABLE_RENDEZ_VOUS');
   }
diff --git a/library/Class/AdminVar/Meta.php b/library/Class/AdminVar/Meta.php
index 8cf86285c80532fcdb85dcbfbc1d8e4b251e514f..17d0e632ea24c3b0ed4a9d7512c6f59d1f13de4b 100644
--- a/library/Class/AdminVar/Meta.php
+++ b/library/Class/AdminVar/Meta.php
@@ -21,6 +21,8 @@
 
 
 class Class_AdminVar_Meta {
+  use Trait_Translator;
+
   const
     TYPE_DEFAULT = 'default',
     TYPE_ENCODED_DATA = 'encoded-data',
@@ -28,7 +30,8 @@ class Class_AdminVar_Meta {
     TYPE_MULTI_INPUT = 'multi-input',
     TYPE_COMBO = 'combo',
     TYPE_RAW_TEXT = 'raw-text',
-    TYPE_EDITOR = 'editor';
+    TYPE_EDITOR = 'editor',
+    TYPE_CRYPT_KEY = 'crypt-key';
 
 
   protected
@@ -37,13 +40,14 @@ class Class_AdminVar_Meta {
 
 
   public static function __callStatic($name, $args) {
-    $mapping = ['Default' => self::TYPE_DEFAULT,
-                'EncodedData' => self::TYPE_ENCODED_DATA,
-                'OnOff' => self::TYPE_ON_OFF,
-                'MultiInput' => self::TYPE_MULTI_INPUT,
-                'Combo' => self::TYPE_COMBO,
-                'RawText' => self::TYPE_RAW_TEXT,
-                'Editor' => self::TYPE_EDITOR];
+    $mapping = ['Default' => static::TYPE_DEFAULT,
+                'EncodedData' => static::TYPE_ENCODED_DATA,
+                'OnOff' => static::TYPE_ON_OFF,
+                'MultiInput' => static::TYPE_MULTI_INPUT,
+                'Combo' => static::TYPE_COMBO,
+                'RawText' => static::TYPE_RAW_TEXT,
+                'Editor' => static::TYPE_EDITOR,
+                'CryptKey' => static::TYPE_CRYPT_KEY];
 
     $type_name = substr($name, 3);
 
@@ -65,8 +69,14 @@ class Class_AdminVar_Meta {
     $this->_type = $type;
     $this->_description = $description;
     $this->_attributes = $attributes;
+
     if (!isset($this->_attributes['role_level']))
       $this->_attributes['role_level'] = ZendAfi_Acl_AdminControllerRoles::ADMIN_PORTAIL;
+
+    if (!isset($this->_attributes['renderer']) && static::TYPE_CRYPT_KEY == $type)
+      $this->_attributes['renderer'] = function($value, $view) {
+        return $value ? $this->_('*** CLÉ MASQUÉE ***') : $this->_('*** VIDE ***');
+      };
   }
 
 
diff --git a/library/Class/Agenda/SQY.php b/library/Class/Agenda/SQY.php
index 92cce9d551a36c1ede4aec2d3a7c99e0daabd4fc..cc47a7807b98bda017832ab539286679b5049e77 100644
--- a/library/Class/Agenda/SQY.php
+++ b/library/Class/Agenda/SQY.php
@@ -182,18 +182,13 @@ class Class_Agenda_SQY_EventWrapper {
   }
 
 
-  public function formatDateForArticle($date) {
-    return implode('-', array_reverse(explode('/', $date)));
-  }
-
-
   public function setDateStart($date) {
-    $this->_wrapped_instance->setEventsDebut($this->formatDateForArticle($date));
+    $this->_wrapped_instance->setEventsDebut(Class_Date::frToIso($date));
   }
 
 
   public function setDateEnd($date) {
-    $this->_wrapped_instance->setEventsFin($this->formatDateForArticle($date));
+    $this->_wrapped_instance->setEventsFin(Class_Date::frToIso($date));
   }
 
 
diff --git a/library/Class/Avis.php b/library/Class/Avis.php
index d47820117cabcd59b536cfbf920adf3c29a70d9d..72033c3c30c8b721200b9bc47f0733678fdf8315 100644
--- a/library/Class/Avis.php
+++ b/library/Class/Avis.php
@@ -123,6 +123,10 @@ class Class_Avis extends Storm_Model_Abstract {
   public function getFlags() {
     return -1;
   }
-}
 
-?>
\ No newline at end of file
+
+  /** API compatibility with Class_AvisNotice */
+  public function getSourceAuthor() {
+    return null;
+  }
+}
diff --git a/library/Class/AvisNotice.php b/library/Class/AvisNotice.php
index 2f7820332623152e0b5d46cff82ee6a3ef4e5a80..0de9864d578c9bb41f09a4ecb2c980e7f89ff1f5 100644
--- a/library/Class/AvisNotice.php
+++ b/library/Class/AvisNotice.php
@@ -239,9 +239,11 @@ class AvisNoticeLoader extends Storm_Model_Loader {
 
 class Class_AvisNotice  extends Storm_Model_Abstract {
   use Trait_Avis, Trait_Translator;
-  const NO_FLAG=0;
-  const ORPHAN_FLAG=1;
-  const ARCHIVED_FLAG=2;
+  const NO_FLAG = 0;
+  const ORPHAN_FLAG = 1;
+  const ARCHIVED_FLAG = 2;
+  const STATUS_VALIDATED = 1;
+  const TYPE_LIBRARIAN = 1;
 
   protected $_loader_class = 'AvisNoticeLoader';
   protected $_table_name = 'notices_avis';
@@ -520,6 +522,14 @@ class Class_AvisNotice  extends Storm_Model_Abstract {
   }
 
 
+  public function acceptJournalVisitor($visitor) {
+    if ($author = $this->getUserName())
+      $visitor->visitDetail('AUTHOR', $author);
+
+    $visitor->visitDetail('NEEDS_VALIDATION', $this->shouldBeModerated());
+  }
+
+
   public function isMine() {
     if (!$user = Class_Users::getIdentity())
       return false;
diff --git a/library/Class/Batch.php b/library/Class/Batch.php
index 1901cd757af0ef839c403b3720360012e0a93e70..ff78f8f303a901ba68983f6825847325b349fdfc 100644
--- a/library/Class/Batch.php
+++ b/library/Class/Batch.php
@@ -41,8 +41,10 @@ class Class_BatchLoader extends Storm_Model_Loader {
                         Class_Batch_BuildSiteMap::TYPE => new Class_Batch_BuildSiteMap(),
                         Class_Batch_PremierChapitre::TYPE => new Class_Batch_PremierChapitre(),
                         Class_Batch_NoveltyFacet::TYPE => new Class_Batch_NoveltyFacet(),
+                        Class_Batch_ExternalAgenda::TYPE => new Class_Batch_ExternalAgenda(),
+                        Class_Batch_FederationReviewHarvest::TYPE => new Class_Batch_FederationReviewHarvest(),
                         Class_Batch_SendRendezVousNotification::TYPE => new Class_Batch_SendRendezVousNotification(),
-                        Class_Batch_ExternalAgenda::TYPE => new Class_Batch_ExternalAgenda]);
+                       ]);
   }
 
 
diff --git a/library/Class/Batch/FederationReviewHarvest.php b/library/Class/Batch/FederationReviewHarvest.php
new file mode 100644
index 0000000000000000000000000000000000000000..aafffe230560216317ef4562cef0326a8a034048
--- /dev/null
+++ b/library/Class/Batch/FederationReviewHarvest.php
@@ -0,0 +1,78 @@
+<?php
+/**
+ * Copyright (c) 2012-2019, 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_Batch_FederationReviewHarvest extends Class_Batch_Abstract {
+  const TYPE = 'FEDERATION_REVIEW_HARVEST';
+
+  public function getLabel() {
+    return $this->_('Moissonner les avis des portails Bokeh ayant activé le partage d\'avis');
+  }
+
+
+  public function isEnabled() {
+    return Class_AdminVar::isFederationCommunityServer();
+  }
+
+
+  public function run() {
+    foreach (Class_Federation_GroupMembership::findAllReviewShare() as $member)
+      $this->_harvestMember($member);
+  }
+
+
+  protected function _harvestMember($member) {
+    $service = new Class_WebService_ActivityPub($member->getActorId());
+    if (!$service->isValid())
+      return;
+
+    $journal_type = $member->getHarvestJournalType();
+    $from = ($last = Class_Journal::lastOf($journal_type))
+      ? $last->getCreatedAt()
+      : '';
+
+    $reviews = $service->harvestReviews($from);
+    if (!$reviews && $service->getLastMessage()) {
+      // todo log message
+      return;
+    }
+
+    $service_name = $service->name();
+    foreach($reviews as $review)
+      $this->_harvestReviewOf($review, $member, $service_name);
+
+    Class_Journal::factory($journal_type);
+  }
+
+
+  protected function _harvestReviewOf($review, $member, $name) {
+    $source_primary = $review['id'];
+    unset($review['id']);
+    $key_params = ['source_primary' => $source_primary,
+                   'source_actor_id' => $member->getActorId()];
+
+    if (!$model = Class_AvisNotice::findFirstBy($key_params))
+      $model = Class_AvisNotice::newInstance($key_params);
+
+    $review['source_author'] = $name;
+    $model->updateAttributes($review)->save();
+  }
+}
diff --git a/library/Class/CodifEmplacement.php b/library/Class/CodifEmplacement.php
index d4a7812aba9be0a98026a79a77f231b69e8db3fb..f5231f061bb57d17ea39e0f3de7ca3b8944765fe 100644
--- a/library/Class/CodifEmplacement.php
+++ b/library/Class/CodifEmplacement.php
@@ -32,7 +32,7 @@ class Class_CodifEmplacementLoader extends Storm_Model_Loader {
 
 
 class Class_CodifEmplacement extends Storm_Model_Abstract {
-  use Trait_Facetable;
+  use Trait_Facetable, Trait_Translator;
 
   const CODE_FACETTE = 'E';
 
@@ -51,34 +51,8 @@ class Class_CodifEmplacement extends Storm_Model_Abstract {
 
 
   public function validate() {
-    $this->check('' != $this->getLibelle(), 'Vous devez définir le libellé');
-    $this->check('' != $this->getRegles(), 'Vous devez définir au moins une règle');
-
-    $regles = str_replace(' ', '', $this->getRegles());
-    $test = explode("\n", $regles);
-    foreach($test as $regle) {
-      if ('' == $regle)
-        continue;
-
-      $zone = substr($regle, 0, 3);
-      $this->check(intval($zone) == $zone,
-                   'La zone doit être un nombre pour la règle ' . $regle);
-
-      $this->check(substr($regle, 3, 1) == '$',
-                   'Le $ est absent ou mal positionné pour la règle ' . $regle);
-
-      $champ = substr($regle, 4, 1);
-      $this->check(preg_match('/^[a-z0-9]$/', $champ),
-                   'Le champ n\'est pas compris entre a et z ni entre 0 et 9 pour la règle ' . $regle);
-
-      $valeurs = substr($regle, 6);
-      $this->check('' != trim($valeurs),
-                   'Indiquez des valeurs pour la règle ' . $regle);
-
-      $signe = substr($regle, 5, 1);
-      $this->check(false !== strpos("=/*",$signe),
-                   'Signe de comparaison incorrect pour la règle ' . $regle);
-    }
+    $this->checkAttribute('libelle', '' != $this->getLibelle(), $this->_('Vous devez définir le libellé'));
+    $this->checkAttribute('regles', '' != $this->getRegles(), $this->_('Vous devez définir au moins une règle'));
   }
 
 
diff --git a/library/Class/CodifGenre.php b/library/Class/CodifGenre.php
index 7c97109ff916365a6b988fe9c34f76c8d0f6f160..4e33142aa2cfd48046528065d9c0d330f5ea31fa 100644
--- a/library/Class/CodifGenre.php
+++ b/library/Class/CodifGenre.php
@@ -39,7 +39,7 @@ class Class_CodifGenreLoader extends Storm_Model_Loader {
 
 
 class Class_CodifGenre extends Storm_Model_Abstract {
-  use Trait_Facetable;
+  use Trait_Facetable, Trait_Translator;
 
   const CODE_FACETTE = 'G';
 
@@ -51,4 +51,9 @@ class Class_CodifGenre extends Storm_Model_Abstract {
                                   'libelle' => '',
                                   'regles' => '',
                                   'date_maj' => ''];
+
+
+  public function validate() {
+    $this->checkAttribute('libelle', '' != $this->getLibelle(), $this->_('Vous devez définir le libellé'));
+  }
 }
diff --git a/library/Class/CodifSection.php b/library/Class/CodifSection.php
index 0faaddec3331e9a6cbf03bd0a6ea942cb38f5848..eb3c7ac2e85674ce251fb5342f776a30066325da 100644
--- a/library/Class/CodifSection.php
+++ b/library/Class/CodifSection.php
@@ -59,7 +59,7 @@ class Class_CodifSectionLoader extends Storm_Model_Loader {
 
 
 class Class_CodifSection extends Storm_Model_Abstract {
-  use Trait_Facetable;
+  use Trait_Facetable, Trait_Translator;
 
   const CODE_FACETTE = 'S';
 
@@ -73,4 +73,9 @@ class Class_CodifSection extends Storm_Model_Abstract {
   public function getCategorie() {
     return;
   }
+
+
+  public function validate() {
+    $this->checkAttribute('libelle', '' != $this->getLibelle(), $this->_('Vous devez définir le libellé'));
+  }
 }
\ No newline at end of file
diff --git a/library/Class/Codification.php b/library/Class/Codification.php
index 9b07b110bf53bdba468785bd62a369eefc9d9837..443f8817e404d330b9fcb655664c4db7936cb723 100644
--- a/library/Class/Codification.php
+++ b/library/Class/Codification.php
@@ -262,19 +262,24 @@ class Class_Codification {
     if(!$notice)
       return '';
 
-    $me = new static();
-    $message = $me->_('Voir tous les tomes');
-
-    switch ($notice->getTypeDoc()) {
-      case Class_TypeDoc::PERIODIQUE:
-        $message = $me->_('Voir tous les numéros');
-        break;
-      case Class_TypeDoc::DVD:
-        $message = $me->_('Voir tous les épisodes');
-        break;
-    }
+    $prefix = function($notice) {
+      $me = new static();
+
+      if ($notice->isDVD())
+        return $me->_('Voir tous les épisodes');
+
+      if (!$notice->isPeriodique())
+        return $me->_('Voir tous les tomes');
+
+      if ($notice->isFirstItemTypeSerialArticle())
+        return $me->_('Voir tous les articles de ce numéro');
+
+      return $me->_('Voir tous les numéros');
+    };
+
 
-    return self::concatLibelleWithTitreChapeau($message, $notice->getTitreChapeau());
+    return self::concatLibelleWithTitreChapeau($prefix($notice),
+                                               $notice->getTitreChapeau());
   }
 
 
diff --git a/library/Class/Codification/Rule.php b/library/Class/Codification/Rule.php
index 4e3b73fd7cd7de297246e847f50691fd84c96efe..b25e3d4a023a8c542869b507ed19af7e7152a9fa 100644
--- a/library/Class/Codification/Rule.php
+++ b/library/Class/Codification/Rule.php
@@ -50,6 +50,26 @@ class Class_Codification_Rule {
   }
 
 
+  public function getZone() {
+    return substr($this->_zone, 0, 3);
+  }
+
+
+  public function getField() {
+    return substr($this->_zone, 4, 1);
+  }
+
+
+  public function getOperator() {
+    return $this->_operator;
+  }
+
+
+  public function getValuesAsString() {
+    return implode(static::VALUE_SEPARATOR, array_unique($this->_values));
+  }
+
+
   public function isSameFieldAndOperator($other) {
     return ($other
             && $this->_zone === $other->_zone
@@ -88,6 +108,6 @@ class Class_Codification_Rule {
   public function asString() {
     return $this->_zone
       . $this->_operator
-      . implode(static::VALUE_SEPARATOR, array_unique($this->_values));
+      . $this->getValuesAsString();
   }
 }
\ No newline at end of file
diff --git a/library/Class/Codification/Rules.php b/library/Class/Codification/Rules.php
index eda2aeca014ce16f3bb1a733e790d7236624deb1..8f42919172e0aa4ac7f23a7351655311c617664b 100644
--- a/library/Class/Codification/Rules.php
+++ b/library/Class/Codification/Rules.php
@@ -21,9 +21,68 @@
 
 
 class Class_Codification_Rules {
+  use Trait_Translator;
+
+  const
+    ZONE_NAME = 'rule_zone',
+    FIELD_NAME = 'rule_field',
+    SIGN_NAME = 'rule_sign',
+    VALUES_NAME = 'rule_values';
+
   protected $_rules;
 
 
+  public static function newFromPost($post) {
+    $instance = new static(null);
+
+    if (!isset($post[static::ZONE_NAME]))
+      return $instance;
+
+    foreach($post[static::ZONE_NAME] as $k => $v)
+      static::_ensureRuleAt($instance, $post, $k, $v);
+
+    return $instance;
+  }
+
+
+  protected static function _ensureRuleAt($instance, $post, $position, $zone) {
+    if (!isset($post[static::FIELD_NAME][$position])
+        || !isset($post[static::SIGN_NAME][$position])
+        || !isset($post[static::VALUES_NAME][$position]))
+      return;
+
+    $instance->ensure(implode([$zone,
+                               '$',
+                               $post[static::FIELD_NAME][$position],
+                               $post[static::SIGN_NAME][$position],
+                               $post[static::VALUES_NAME][$position]]));
+  }
+
+
+  public static function multiInputValues($rules) {
+    $values = [static::ZONE_NAME => [],
+               static::FIELD_NAME => [],
+               static::SIGN_NAME => [],
+               static::VALUES_NAME => []];
+
+    static::newFromString($rules)
+      ->eachDo(function($rule) use(&$values)
+               {
+                 $values[static::ZONE_NAME][] = $rule->getZone();
+                 $values[static::FIELD_NAME][] = $rule->getField();
+                 $values[static::SIGN_NAME][] = $rule->getOperator();
+                 $values[static::VALUES_NAME][] = $rule->getValuesAsString();
+               });
+
+    return $values;
+  }
+
+
+  public static function multiInputValidators() {
+    return [static::ZONE_NAME => [new ZendAfi_Validate_MarcZone]];
+  }
+
+
   public static function newFromString($rules) {
     $model = (new Class_Entity(['Regles' => $rules]))
       ->whenCalledDo(
@@ -53,6 +112,33 @@ class Class_Codification_Rules {
   }
 
 
+  public function multiInputFields() {
+    $possible_fields = Class_IntProfilDonnees::getAllZoneAndFields();
+
+    return [['name' => static::ZONE_NAME,
+             'label' => $this->_('Zone (001 à 999)'),
+             'attribs' => ['maxlength' => 3, 'size' => 3]],
+
+            ['name' => static::FIELD_NAME,
+             'label' => $this->_('Champ'),
+             'type' => 'select',
+             'options' => $possible_fields,
+             'attribs' => ['style' => 'width:auto'],],
+
+            ['name' => static::SIGN_NAME,
+             'label' => $this->_('Mode'),
+             'type' => 'select',
+             'options' => ['' => '',
+                           '=' => $this->_('Égal'),
+                           '/' => $this->_('Commence par'),
+                           '*' => $this->_('Contient')],
+             'attribs' => ['style' => 'width:auto'],],
+
+            ['name' => static::VALUES_NAME,
+             'label' => $this->_('Valeur(s), séparées par ;')]];
+  }
+
+
   public function ensure($rule_string) {
     if (!$rule = Class_Codification_Rule::newFrom($rule_string))
       return $this;
diff --git a/library/Class/CommSigb.php b/library/Class/CommSigb.php
index 42b331738a608d0a55191522c38dec0ed226d21c..69912de10070a919e9c1f82ad86636514d0f4b5f 100644
--- a/library/Class/CommSigb.php
+++ b/library/Class/CommSigb.php
@@ -130,7 +130,7 @@ class Class_CommSigb {
    * @param string $code_annexe
    * @return array
    */
-  public function reserverExemplaire($id_bib, $exemplaire_id, $code_annexe) {
+  public function reserverExemplaire($id_bib, $exemplaire_id, $code_annexe, $reservedate = null, $expirationdate = null) {
     if (!$user = Class_Users::getIdentity())
       return $this->_error($this->_('Vous devez vous connecter pour réserver un document.'));
 
@@ -140,8 +140,14 @@ class Class_CommSigb {
     if(!$exemplaire = Class_Exemplaire::find($exemplaire_id))
       return $this->_error($this->_('Document introuvable'));
 
-    $reserver = function ($user, $sigb) use ($exemplaire, $code_annexe) {
-      $result = $sigb->reserverExemplaire($user, $exemplaire, $code_annexe);
+    $reserver = function ($user, $sigb) use ($exemplaire, $code_annexe, $reservedate, $expirationdate) {
+
+      $params = [$user, $exemplaire, $code_annexe];
+      if ($reservedate && $expirationdate)
+        $params = array_merge($params, [$reservedate, $expirationdate]);
+
+      $result = call_user_func_array([$sigb, 'reserverExemplaire'],
+                                     $params);
 
       if (true === $result['statut'])
         $user->notifyHold($exemplaire, $code_annexe);
@@ -226,6 +232,11 @@ class Class_CommSigb {
   }
 
 
+  public function holdsForItem($item) {
+    return $item->getSIGBComm()->holdsForItem($item);
+  }
+
+
   /**
    * @param Class_Users $user
    * @param int $id_pret
diff --git a/library/Class/Cosmogramme/Integration/Record/FileContent.php b/library/Class/Cosmogramme/Integration/Record/FileContent.php
new file mode 100644
index 0000000000000000000000000000000000000000..da5cf54de168dd2136c4a82b271a94c9f1f74108
--- /dev/null
+++ b/library/Class/Cosmogramme/Integration/Record/FileContent.php
@@ -0,0 +1,72 @@
+<?php
+/**
+ * Copyright (c) 2012-2019, 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_Cosmogramme_Integration_Record_FileContent {
+  /** @var Trait_Observer */
+  protected $_observer;
+
+
+  public function __construct($observer=null) {
+    $this->_observer = $observer;
+  }
+
+
+  public function getContent($record, $profile) {
+    $zone = $profile->getIndexFileZone();
+    $field = $profile->getIndexFileField();
+    if ('' === $zone || '' === $field)
+      return '';
+
+    $uri_regex = '%' . str_replace('%', '\%', $profile->getIndexFileUriRegex()) . '%';
+
+    $paths = [];
+    foreach($record->get_subfield($zone, $field) as $path)
+      if (preg_match($uri_regex, $path))
+        $paths[] = USERFILESPATH . end(explode('/'.USERFILES, $path));
+
+    if (!$paths)
+      return '';
+
+    $this->_getObserver()->notifyMatch($record, $paths);
+    $indexation_file = new Class_Indexation_File;
+    if ('' == ($content = $indexation_file->getContent($paths))) {
+      $this->_getObserver()->notifyEmpty($record);
+      return '';
+    }
+
+    $this->_getObserver()->notifySource($record, $indexation_file->getContentSource());
+    return $content;
+  }
+
+
+  protected function _getObserver() {
+    return $this->_observer
+      ? $this->_observer
+      : new Class_Cosmogramme_Integration_Record_FileContent_NullObserver();
+  }
+}
+
+
+
+class Class_Cosmogramme_Integration_Record_FileContent_NullObserver {
+  use Trait_Observer;
+}
diff --git a/library/Class/CriteresRecherche.php b/library/Class/CriteresRecherche.php
index ae2615a9852d1aea5a408e42227aab4b0a24206e..4338012f610384c7647a169b79e4e879a7e48589 100644
--- a/library/Class/CriteresRecherche.php
+++ b/library/Class/CriteresRecherche.php
@@ -19,29 +19,7 @@
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
 
-class Class_CriteresRecherche {
-  use Trait_Translator;
-
-  const
-    SORT_RELEVANCE = '*',
-    SORT_TITLE = 'alpha_titre',
-    SORT_AUTHOR = 'alpha_auteur',
-    SORT_PUBLICATION = 'annee desc',
-    SORT_DOCTYPE = 'type_doc, alpha_titre',
-    SORT_NOVELTY = 'date_creation desc',
-    SORT_VIEWS = 'nb_visu desc',
-    SORT_RANDOM = 'RAND()',
-    MAX_PAGE_SIZE = 50;
-
-  protected static $_MAX_SEARCH_RESULTS = '';
-
-  protected
-    $_params = [],
-    $_validate_facette,
-    $_default_page_size = 20,
-    $_profil,
-    $_time,
-    $_validator;
+class Class_CriteresRecherche extends Class_CriteresRecherche_Abstract {
 
   public static $criteres = ['expressionRecherche' => 'Expression',
                              'rech_titres' => 'Titre',
@@ -78,19 +56,10 @@ class Class_CriteresRecherche {
                              'no_extension' => '',
                              'bookmarked_search' => '',
                              'bookmarked_version' => '',
+                             'in_files' => ''
   ];
 
 
-  public static function setMaxSearchResults($max) {
-    static::$_MAX_SEARCH_RESULTS = $max;
-  }
-
-
-  public function getMaxSearchResults() {
-    return (int) static::$_MAX_SEARCH_RESULTS;
-  }
-
-
   public function __construct() {
     $this->_profil = Class_Profil::getCurrentProfil();
   }
@@ -107,13 +76,6 @@ class Class_CriteresRecherche {
   }
 
 
-  public function getValidateFacette() {
-    if (!isset($this->_validate_facette))
-      $this->_validate_facette = new ZendAfi_Validate_Facette();
-    return $this->_validate_facette;
-  }
-
-
   public function getValidParameters() {
     return array_merge(array_keys(static::$criteres),
                        ['filtres',
@@ -136,6 +98,12 @@ class Class_CriteresRecherche {
   }
 
 
+  public function getSearchModeList() {
+    return [$this->_('Index seulement'),
+            $this->_('Index et contenu des fichiers')];
+  }
+
+
   public function getListeTris() {
     if (!isset($this->_liste_tris)) {
       $relevance_label = $this->getPanier()
@@ -167,48 +135,6 @@ class Class_CriteresRecherche {
   }
 
 
-  /**
-   * @param $rech array paramètres de recherche et leurs valeurs
-   * @return Class_CritereRecherche
-   */
-  public function setParams($rech) {
-    $this->_params = $this->filterParams($rech);
-    return $this;
-  }
-
-
-  public function setDefaultPageSize($page_size) {
-    $page_size = (int)$page_size;
-    if (0 < $page_size && static::MAX_PAGE_SIZE >= $page_size)
-      $this->_default_page_size = $page_size;
-    return $this;
-  }
-
-
-  public function setTime($time) {
-    $this->_time = $time;
-    return $this;
-  }
-
-
-  public function getTime() {
-    return $this->_time;
-  }
-
-
-  public function getParam($name, $default_value = null) {
-    return isset($this->_params[$name]) ? $this->_params[$name] : $default_value;
-  }
-
-
-  public function unsetParam($name) {
-    if (array_key_exists($name, $this->_params))
-      unset($this->_params[$name]);
-
-    return $this;
-  }
-
-
   public function rechercheElargie() {
     $this->_params['pertinence']=1;
     return $this;
@@ -225,18 +151,6 @@ class Class_CriteresRecherche {
   }
 
 
-  public function getFacettes() {
-    $facettes = explode('-', $this->getParam('facettes', ''));
-    $facettes[] = $this->getParam('facette', '');
-
-    return  array_unique(array_filter($facettes,
-                                      [$this->getValidateFacette(),
-                                       'isValid']
-                                      ));
-  }
-
-
-
   public function getMultiFacets() {
     return array_filter(array_merge(explode('-', $this->getParam('multifacets','')),
                                     $this->selectionToArray(Class_CodifGenre::CODE_FACETTE,
@@ -330,18 +244,6 @@ class Class_CriteresRecherche {
   }
 
 
-  public function getFacettesUrlEncoded() {
-    $facettes = $this->getFacettes();
-    return $facettes ? implode('-', $facettes) : null;
-  }
-
-
-  public function getFacettesForMoteurRecherche() {
-    $facettes = $this->getFacettes();
-    return $facettes ? '['.implode('][',$facettes).']' : '';
-  }
-
-
   public function getTypeDoc() {
     $digital_resource = Class_DigitalResource::getInstance();
     $validator = function($type) use($digital_resource){
@@ -358,6 +260,11 @@ class Class_CriteresRecherche {
   }
 
 
+  public function getInFiles() {
+    return 1 === (int)$this->getParam('in_files');
+  }
+
+
   public function getTri() {
     $tri = urldecode($this->getParam('tri', ''));
     if ('' === $tri && $this->isRechercheCatalogue())
@@ -385,7 +292,7 @@ class Class_CriteresRecherche {
     $id_panier = $this->getParam('id_panier','');
 
     if ($id_user=$this->getParam('id_user'))
-      $panier = Class_PanierNotice::getLoader()->findFirstBy(['id_user' => $id_user,
+      $panier = Class_PanierNotice::findFirstBy(['id_user' => $id_user,
                                                               'id' => $id_panier]);
     return $panier ? $panier : Class_PanierNotice::find($id_panier);
   }
@@ -547,7 +454,10 @@ class Class_CriteresRecherche {
     }
 
     if ($expression = $this->getExpressionRecherche())
-      $visitor->visitExpression($expression,$this->getPertinence(),$this->getTri());
+      $visitor->visitExpression($expression,
+                                $this->getPertinence(),
+                                $this->getTri(),
+                                $this->getInFiles());
 
     $type_recherche = $this->getTypeRecherche();
 
@@ -648,17 +558,6 @@ class Class_CriteresRecherche {
   }
 
 
-  public function getPage() {
-    return $this->getParam('page', null);
-  }
-
-
-  public function getPageSize() {
-    return $this->getParam('page_size',
-                           $this->_default_page_size);
-  }
-
-
   public function getFrom() {
     return $this->getParam('from', null);
   }
@@ -681,33 +580,8 @@ class Class_CriteresRecherche {
   }
 
 
-  public function filterParams($params) {
-    $valid_parameters = $this->getValidParameters();
-    $filtered_params = [];
-
-    $params = $this->replaceEmptyRechercheByStar($params);
-
-    foreach($valid_parameters as $key)
-      if (array_isset($key, $params)
-          && $this->_getValidator()->isValid($key, $params))
-        $filtered_params[$key] = $params[$key];
-
-    if (isset($params['page_size']) && (static::MAX_PAGE_SIZE < (int)$params['page_size']))
-      $filtered_params['page_size']  = static::MAX_PAGE_SIZE;
-
-    return $filtered_params;
-  }
-
-
-  protected function _getValidator() {
-    return $this->_validator
-      ? $this->_validator
-      : $this->_validator = new Class_CriteresRecherche_Validator();
-  }
-
-
-  public function getCriteres() {
-    return $this->_params;
+  protected function _prepareParamsForValidation($params) {
+    return $this->replaceEmptyRechercheByStar($params);
   }
 
 
@@ -767,6 +641,11 @@ class Class_CriteresRecherche {
   }
 
 
+  public function getUrlCriteres() {
+    return $this->getUrlCriteresWithFacettes();
+  }
+
+
   public function getUrlCriteresWithFacettes() {
     $intersect = $this->_params;
 
@@ -783,9 +662,9 @@ class Class_CriteresRecherche {
 
     return ['controller' => 'recherche',
             'action' => 'simple'];
-
   }
 
+
   public function getCVSUrlCriteresWithFacettes() {
     $intersect=$this->_params;
 
@@ -878,6 +757,13 @@ class Class_CriteresRecherche {
   }
 
 
+  public function getUrlPager() {
+    return array_merge($this->getUrlRetourListe(),
+                       ['controller' => 'recherche',
+                        'action' => 'simple']);
+  }
+
+
   public function getUrlRemoveFacette($facette) {
     $facettes = array_diff($this->getFacettes(), [$facette->getCle()]);
     $url = $this->getUrlRetourListe();
@@ -1026,18 +912,6 @@ class Class_CriteresRecherche {
   }
 
 
-  public function setPageSize($size) {
-    $this->_params['page_size'] = $size;
-    return $this;
-  }
-
-
-  public function setPage($page) {
-    $this->_params['page'] = $page;
-    return $this;
-  }
-
-
   protected function _getBookmark() {
     return Class_User_BookmarkedSearch::find($this->getParam('bookmarked_search'));
   }
@@ -1048,4 +922,16 @@ class Class_CriteresRecherche {
               || $this->getFacettes()
               || $this->getMultiFacets());
   }
+
+
+  public function getStateError() {
+    if ($this->hasEmptyDomain()
+        && !$this->isSearchInBasket())
+      return $this->_('Domaine non paramétré');
+
+    if ($this->isEmptySelection())
+      return $this->_('La sélection courante est vide');
+
+    return parent::getStateError();
+  }
 }
diff --git a/library/Class/CriteresRecherche/Abstract.php b/library/Class/CriteresRecherche/Abstract.php
new file mode 100644
index 0000000000000000000000000000000000000000..382255cc9dd8de0c28bb3f5d9aefbe3a7c93afa5
--- /dev/null
+++ b/library/Class/CriteresRecherche/Abstract.php
@@ -0,0 +1,228 @@
+<?php
+/**
+ * Copyright (c) 2012, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+abstract class Class_CriteresRecherche_Abstract {
+  use Trait_Translator;
+
+  const
+    SORT_RELEVANCE = '*',
+    SORT_TITLE = 'alpha_titre',
+    SORT_AUTHOR = 'alpha_auteur',
+    SORT_PUBLICATION = 'annee desc',
+    SORT_DOCTYPE = 'type_doc, alpha_titre',
+    SORT_NOVELTY = 'date_creation desc',
+    SORT_VIEWS = 'nb_visu desc',
+    SORT_RANDOM = 'RAND()',
+    MAX_PAGE_SIZE = 50;
+
+  protected static $_MAX_SEARCH_RESULTS = '';
+
+  protected
+    $_params = [],
+    $_validate_facette,
+    $_default_page_size = 20,
+    $_profil,
+    $_time,
+    $_validator;
+
+
+  public static function setMaxSearchResults($max) {
+    static::$_MAX_SEARCH_RESULTS = $max;
+  }
+
+
+  public static function getMaxSearchResults() {
+    return (int) static::$_MAX_SEARCH_RESULTS;
+  }
+
+
+  /**
+   * @param $rech array paramètres de recherche et leurs valeurs
+   * @return Class_CritereRecherche
+   */
+  public function setParams($rech) {
+    $this->_params = $this->filterParams($rech);
+    return $this;
+  }
+
+
+  public function getParam($name, $default_value = null) {
+    return isset($this->_params[$name]) ? $this->_params[$name] : $default_value;
+  }
+
+
+  public function unsetParam($name) {
+    if (array_key_exists($name, $this->_params))
+      unset($this->_params[$name]);
+
+    return $this;
+  }
+
+
+  public function getAvailablePageSize() {
+    $profil_param = (new Class_Profil_Preferences_SearchResult())->getPageSize($this->_profil);
+    $options = array_unique(['10' => 10,
+                             '20' => 20,
+                             '30' => 30,
+                             '40' => 40,
+                             '50' => 50,
+                             $profil_param => $profil_param]);
+    ksort($options);
+    return $options;
+  }
+
+
+  public function setDefaultPageSize($page_size) {
+    $page_size = (int)$page_size;
+    if (0 < $page_size && static::MAX_PAGE_SIZE >= $page_size)
+      $this->_default_page_size = $page_size;
+    return $this;
+  }
+
+
+  public function setTime($time) {
+    $this->_time = $time;
+    return $this;
+  }
+
+
+  public function getTime() {
+    return $this->_time;
+  }
+
+
+  public function getPage() {
+    return $this->getParam('page', null);
+  }
+
+
+  public function getPageSize() {
+    return $this->getParam('page_size',
+                           $this->_default_page_size);
+  }
+
+
+  public function filterParams($params) {
+    $valid_parameters = $this->getValidParameters();
+    $filtered_params = [];
+
+    $params = $this->_prepareParamsForValidation($params);
+
+    foreach($valid_parameters as $key)
+      if (array_isset($key, $params)
+          && $this->_getValidator()->isValid($key, $params))
+        $filtered_params[$key] = $params[$key];
+
+    if (isset($params['page_size']) && (static::MAX_PAGE_SIZE < (int)$params['page_size']))
+      $filtered_params['page_size']  = static::MAX_PAGE_SIZE;
+
+    return $filtered_params;
+  }
+
+
+  public function getValidParameters() {
+    return ['page', 'page_size', 'tri', 'facette', 'facettes'];
+  }
+
+
+  public function getValidateFacette() {
+    if (!isset($this->_validate_facette))
+      $this->_validate_facette = new ZendAfi_Validate_Facette();
+    return $this->_validate_facette;
+  }
+
+
+  public function getFacettes() {
+    $facettes = explode('-', $this->getParam('facettes', ''));
+    $facettes[] = $this->getParam('facette', '');
+
+    return  array_unique(array_filter($facettes,
+                                      [$this->getValidateFacette(),
+                                       'isValid']
+                                      ));
+  }
+
+
+  public function getFacettesUrlEncoded() {
+    $facettes = $this->getFacettes();
+    return $facettes ? implode('-', $facettes) : null;
+  }
+
+
+  protected function _prepareParamsForValidation($params) {
+    return $params;
+  }
+
+
+  protected function _getValidator() {
+    return $this->_validator
+      ? $this->_validator
+      : $this->_validator = new Class_CriteresRecherche_Validator();
+  }
+
+
+  public function getCriteres() {
+    return $this->_params;
+  }
+
+
+  public function setPageSize($size) {
+    $this->_params['page_size'] = $size;
+    return $this;
+  }
+
+
+  public function setPage($page) {
+    $this->_params['page'] = $page;
+    return $this;
+  }
+
+
+  public function getTri() {
+    return static::SORT_RELEVANCE;
+  }
+
+
+  public function getStateError() {
+    return false;
+  }
+
+
+  /** @return array suitable for url helpers */
+  public function getUrlPager() {
+    return $this->getUrlCriteres();
+  }
+
+
+  /**
+   * @param Trait_SearchCriteriaVisitor $visitor
+   * @return Class_CriteresRecherche_Abstract
+   */
+  abstract public function acceptVisitor($visitor);
+
+
+  /** @return bool */
+  abstract public function isEmpty();
+
+
+  /** @return array suitable for url helpers */
+  abstract public function getUrlCriteres();
+}
diff --git a/library/Class/CriteresRecherche/Authority.php b/library/Class/CriteresRecherche/Authority.php
new file mode 100644
index 0000000000000000000000000000000000000000..c24cef1e66a17a7982150fcd1393c127bd44283a
--- /dev/null
+++ b/library/Class/CriteresRecherche/Authority.php
@@ -0,0 +1,64 @@
+<?php
+/**
+ * Copyright (c) 2012-2019, 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_CriteresRecherche_Authority extends Class_CriteresRecherche_Abstract {
+  public function getValidParameters() {
+    $parameters = parent::getValidParameters();
+    $parameters[] = 'expressionRecherche';
+    $parameters[] = 'tree_roots';
+    return $parameters;
+  }
+
+
+  public function acceptVisitor($visitor) {
+    $visitor->visitNoExtension(true);
+    $visitor->visitExpression($this->getParam('expressionRecherche'), false, $this->getTri(), false);
+    $visitor->visitRecordType(Class_Notice::TYPE_AUTHORITY);
+    foreach($this->getFacettes() as $facette)
+      $visitor->visitFacette($facette);
+  }
+
+
+  public function getUrlCriteres() {
+    return array_merge($this->_params,
+                       ['controller' => 'authority-search', 'action' => 'index']);
+  }
+
+
+  public function getUrlWithoutExpression() {
+    $params = $this->getUrlCriteres();
+    unset($params['expressionRecherche']);
+    unset($params['page']);
+    return $params;
+  }
+
+
+  public function isEmpty() {
+    return in_array($this->getParam('expressionRecherche', ''),
+                    ['', '*']);
+  }
+
+
+  public function getExpressionRecherche() {
+    return $this->getParam('expressionRecherche');
+  }
+}
diff --git a/library/Class/Date.php b/library/Class/Date.php
index e06b04c9cfbad2e27dd0a80570b913aa925bb6d8..ca0f9635734484adc2237357b0bcb0da5a17c69f 100644
--- a/library/Class/Date.php
+++ b/library/Class/Date.php
@@ -35,6 +35,12 @@ class Class_Date {
     }
   }
 
+
+  public static function frToIso($date) {
+    return implode('-', array_reverse(explode('/', $date)));
+  }
+
+
   /*
    * @param string $dateFormat Format used to generate the date
    * @return string Today's date
diff --git a/library/Class/Exemplaire.php b/library/Class/Exemplaire.php
index 6ea525a123ce9ae4701af6d2cfaa307bcba4145a..76130a924210ba5aa18a301588c188503e5d3192 100644
--- a/library/Class/Exemplaire.php
+++ b/library/Class/Exemplaire.php
@@ -81,7 +81,11 @@ class Class_Exemplaire extends Storm_Model_Abstract {
                 'referenced_in' => 'id_origine'],
 
     'int_bib' => [ 'model' => 'Class_IntBib',
-                   'referenced_in' => 'id_int_bib']];
+                  'referenced_in' => 'id_int_bib'],
+
+     'data_profile' => ['model' => 'Class_IntProfilDonnees',
+                        'referenced_in' => 'id_data_profile']
+  ];
 
   protected $_default_attribute_values = [
                                           'id_origine' => null,
@@ -98,6 +102,7 @@ class Class_Exemplaire extends Storm_Model_Abstract {
                                           'date_nouveaute' => '',
                                           'to_delete' => false,
                                           'id_int_bib' => 0,
+                                          'id_data_profile' => 0,
                                           'type' => Class_Notice::TYPE_BIBLIOGRAPHIC,
                                           'cote' => ''];
 
@@ -119,6 +124,19 @@ class Class_Exemplaire extends Storm_Model_Abstract {
   }
 
 
+  public function isTypeSerialArticle() {
+    return $this->getType() == Class_Notice::TYPE_SERIAL_ARTICLE;
+  }
+
+
+  public function getPMBSerialRecord() {
+    //see Cosmogramme PMBIntegrationSerialsTest
+    $item = $this->getLoader()->findFirstBy(['id_origine' => $this->getSubfield('0'),
+                                             'id_int_bib' => $this->getIdIntBib()]);
+    return $item->getNotice();
+  }
+
+
   public function getPret() {
     return Class_Pret::findFirstBy(['id_site' => $this->getIdBib(),
                                     'id_notice_origine' => $this->getIdOrigine(),
@@ -237,7 +255,9 @@ class Class_Exemplaire extends Storm_Model_Abstract {
                                 'id_int_bib' => $this->getIdIntBib(),
                                 'id_bib' => $this->getIdBib(),
                                 'copy_id' => $this->getId(),
-                                'code_annexe' => $this->_getBranchCode()]);
+                                'code_annexe' => $this->_getBranchCode()],
+                               null,
+                               false);
   }
 
 
@@ -421,11 +441,14 @@ class Class_Exemplaire extends Storm_Model_Abstract {
   }
 
 
-  /**
-   * /!\ Temporary fix to #13903
-   */
   protected function get995Key($data) {
-    return (isset($data['clef'])) ? $data['clef'] : $data['code'];
+    if (isset($data['clef']))
+      return $data['clef'];
+
+    if (isset($data['code']))
+      return $data['code'];
+
+    return null;
   }
 
   public function toRaw() {
@@ -466,6 +489,23 @@ class Class_Exemplaire extends Storm_Model_Abstract {
   }
 
 
+  public function getBundle() {
+    if (!$data_profile = $this->getDataProfile())
+      return null;
+
+    $bundle_id_field = $data_profile->getBundleIdField();
+    $bundle_id = $this->getSubfield($bundle_id_field);
+
+    $bundle_item = Class_Exemplaire::getLoader()
+      ->findFirstBy(['id_int_bib' => $this->getIdIntBib(),
+                     'id_origine' => $bundle_id]);
+
+    return $bundle_item
+      ? $bundle_item->getNotice()
+      : null;
+  }
+
+
   public function isLoanable() {
     if(!$record = $this->getNotice())
       return false;
diff --git a/library/Class/Federation.php b/library/Class/Federation.php
new file mode 100644
index 0000000000000000000000000000000000000000..190dfade698e2a2b9d79007f246a3b288430bf96
--- /dev/null
+++ b/library/Class/Federation.php
@@ -0,0 +1,93 @@
+<?php
+/**
+ * Copyright (c) 2012-2019, 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
+ */
+
+require_once 'library/phpseclib/autoload.php';
+
+use \phpseclib\Crypt\RSA;
+
+class Class_Federation {
+  const
+    PUBLIC_KEY = 'publickey',
+    PRIVATE_KEY = 'privatekey';
+
+
+  public function getActorName() {
+    return ($name = Class_AdminVar::get('FEDERATION_ACTOR_NAME'))
+      ? $name
+      : Class_Url::absolute([], null, true);
+  }
+
+
+  public function getPrivateKey() {
+    return $this->_getOrGenerate('FEDERATION_PRIVKEY', 'privatePart');
+  }
+
+
+  public function getPublicKey() {
+    return $this->_getOrGenerate('FEDERATION_PUBKEY', 'publicPart');
+  }
+
+
+  protected function _getOrGenerate($var_name, $method_name) {
+    if ($key = Class_AdminVar::get($var_name))
+      return $key;
+
+    $pair = (new Class_Federation_KeyPair())->injectInAdminVars();
+    return call_user_func([$pair, $method_name]);
+  }
+}
+
+
+
+class Class_Federation_KeyPair {
+  const
+    PUBLIC_KEY = 'publickey',
+    PRIVATE_KEY = 'privatekey',
+    KEY_LENGTH = 2048;
+
+  protected
+    $_private_part,
+    $_public_part;
+
+
+  public function __construct() {
+    $key = (new RSA())->createKey(static::KEY_LENGTH);
+    $this->_private_part = $key[static::PRIVATE_KEY];
+    $this->_public_part = $key[static::PUBLIC_KEY];
+  }
+
+
+  public function injectInAdminVars() {
+    Class_AdminVar::set('FEDERATION_PUBKEY', $this->_public_part);
+    Class_AdminVar::set('FEDERATION_PRIVKEY', $this->_private_part);
+    return $this;
+  }
+
+
+  public function privatePart() {
+    return $this->_private_part;
+  }
+
+
+  public function publicPart() {
+    return $this->_public_part;
+  }
+}
diff --git a/library/Class/Federation/GroupMembership.php b/library/Class/Federation/GroupMembership.php
new file mode 100644
index 0000000000000000000000000000000000000000..e0bcf275a030294f87c743c02470d2235757050d
--- /dev/null
+++ b/library/Class/Federation/GroupMembership.php
@@ -0,0 +1,65 @@
+<?php
+/**
+ * Copyright (c) 2012-2019, 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_Federation_GroupMembershipLoader extends Storm_Model_Loader {
+  public function findAllReviewShare() {
+    return Class_Federation_GroupMembership::findAllBy(['group_name' => Class_Federation_GroupMembership::REVIEW_SHARE]);
+  }
+}
+
+
+
+class Class_Federation_GroupMembership extends Storm_Model_Abstract {
+  use Trait_TimeSource, Trait_Translator;
+
+  const
+    REVIEW_DISPLAY = 'REVIEW_DISPLAY',
+    REVIEW_SHARE = 'REVIEW_SHARE';
+
+  protected $_table_name = 'federation_group_membership';
+  protected $_loader_class = 'Class_Federation_GroupMembershipLoader';
+
+  public function beforeSave() {
+    if ($this->isNew())
+      $this->setAcceptedAt($this->getCurrentDateTime());
+  }
+
+
+  public function afterDelete() {
+    if (static::REVIEW_SHARE == $this->getGroupName()) {
+      Class_AvisNotice::deleteBy(['source_actor_id' => $this->getActorId()]);
+      Class_Journal::deleteBy(['type' => $this->getHarvestJournalType()]);
+    }
+  }
+
+
+  public function validate() {
+    $this->checkAttribute('group_name',
+                          in_array($this->getGroupName(), [static::REVIEW_DISPLAY,
+                                                           static::REVIEW_SHARE]),
+                          $this->_('Cannot join unknown group %s', $this->getGroupName()));
+  }
+
+
+  public function getHarvestJournalType() {
+    return 'AP_REVIEW_HARVEST_' . $this->getId();
+  }
+}
diff --git a/library/Class/FederationReview.php b/library/Class/FederationReview.php
new file mode 100644
index 0000000000000000000000000000000000000000..0c9eb4b368d63a10ee37f15f6383877cf66ee455
--- /dev/null
+++ b/library/Class/FederationReview.php
@@ -0,0 +1,169 @@
+<?php
+/**
+ * Copyright (c) 2012-2019, 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_FederationReview {
+  use Trait_Singleton, Trait_Translator, Trait_LastMessage;
+
+  const
+    FEATURE_SHARE   = 'SHARE',
+    FEATURE_DISPLAY = 'DISPLAY';
+
+
+  public function enableDisplay() {
+    return $this->_enableFeature(static::FEATURE_DISPLAY);
+  }
+
+
+  public function isDisplayEnabled() {
+    return $this->_isEnabled(static::FEATURE_DISPLAY);
+  }
+
+
+  public function disableDisplay() {
+    return $this->_disableFeature(static::FEATURE_DISPLAY);
+  }
+
+
+  public function enableShare() {
+    return $this->_enableFeature(static::FEATURE_SHARE);
+  }
+
+
+  public function isShareEnabled() {
+    return $this->_isEnabled(static::FEATURE_SHARE);
+  }
+
+
+  public function disableShare() {
+    return $this->_disableFeature(static::FEATURE_SHARE);
+  }
+
+
+  protected function _enableFeature($feature) {
+    if (!$this->_isKnownFeature($feature))
+      return $this->_error($this->_('Impossible d\'activer une fonctionnalité inconnue'));
+
+    if ($this->_isEnabled($feature))
+      return true;
+
+    if (!$community_server = $this->_getCommunityServer())
+      return $this->_error($this->_('L\'adresse du serveur communautaire n\'est pas paramétrée'));
+
+    $service = (new Class_WebService_ActivityPub($community_server));
+    //->setLogger(new Zend_Log(new Zend_Log_Writer_Stream(PATH_TEMP . 'activitypub_client.log')));
+
+    if (!$service->isValid())
+      return $this->_error($this->_('Le serveur communautaire paramétré n\'est pas compatible'));
+
+    if (!$service->join('REVIEW_' . $feature ))
+      return $this->_error($this->_('Une erreur est survenue : %s', $service->getLastMessage()));
+
+    Class_Journal::newInstance(['type' => 'AP_REVIEW_' . $feature . '_JOIN'])->save();
+    return true;
+  }
+
+
+  protected function _isEnabled($feature) {
+    if (!$this->_isKnownFeature($feature))
+      return false;
+
+    $join  = Class_Journal::lastOf('AP_REVIEW_' . $feature . '_JOIN');
+    $leave  = Class_Journal::lastOf('AP_REVIEW_' . $feature . '_LEAVE');
+
+    if (!$join)
+      return false;
+
+    return $leave
+      ? $join->isAfter($leave)
+      : true;
+  }
+
+
+  protected function _disableFeature($feature) {
+    if (!$this->_isKnownFeature($feature))
+      return $this->_error($this->_('Impossible de désactiver une fonctionnalité inconnue'));
+
+    if (!$this->_isEnabled($feature))
+      return;
+
+    Class_Journal::newInstance(['type' => 'AP_REVIEW_' . $feature . '_LEAVE'])->save();
+
+    if (!$community_server = $this->_getCommunityServer())
+      return;
+
+    $service = new Class_WebService_ActivityPub($community_server);
+    if (!$service->isValid())
+      return;
+
+    $service->leave('REVIEW_' . $feature);
+  }
+
+
+  protected function _isKnownFeature($feature) {
+    return in_array($feature, [static::FEATURE_DISPLAY, static::FEATURE_SHARE]);
+  }
+
+
+  /**
+   * @param $record Class_Notice
+   * @param $page int
+   * @return Class_Notice_ReviewsSet
+   */
+  public function getAvis($record, $page) {
+    if (!$this->isDisplayEnabled() || (!$service = $this->_getCommunityService()))
+      return Class_Notice_ReviewsSet::emptyInstance();
+
+    //$service->setLogger(new Zend_Log(new Zend_Log_Writer_Stream(PATH_TEMP . 'activitypub_client.log')));
+    if (!$collection_page = $service->reviews($record, $page))
+      return Class_Notice_ReviewsSet::emptyInstance();
+
+    $reviews = array_map([$this, '_receiveReview'], $collection_page->items());
+
+    return new Class_Notice_ReviewsSet($this->_('Avis communautaires'),
+                                       $reviews,
+                                       Class_AvisNotice::getNoteAverage($reviews),
+                                       $collection_page->totalItems(),
+                                       ceil($collection_page->totalItems()/5));
+  }
+
+
+  protected function _receiveReview($review) {
+    unset($review['id']);
+    return (new Class_AvisNotice())->updateAttributes($review);
+  }
+
+
+  protected function _getCommunityService() {
+    if (!$community_server = $this->_getCommunityServer())
+      return;
+
+    $service = new Class_WebService_ActivityPub($community_server);
+    return $service->isValid()
+      ? $service
+      : null;
+  }
+
+
+  protected function _getCommunityServer() {
+    return Class_AdminVar::getValueOrDefault('FEDERATION_COMMUNITY_SERVER');
+  }
+}
diff --git a/library/Class/HttpSignature.php b/library/Class/HttpSignature.php
new file mode 100644
index 0000000000000000000000000000000000000000..e42e25ef0ab3d7ed3d98256c4e351cb44ad894bd
--- /dev/null
+++ b/library/Class/HttpSignature.php
@@ -0,0 +1,194 @@
+<?php
+/**
+ * Copyright (c) 2012-2019, 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
+ */
+
+require_once 'library/phpseclib/autoload.php';
+
+
+class Class_HttpSignature extends Class_Entity {
+  const REQUEST_TARGET = '(request-target)';
+
+  protected $_logger;
+
+  public function setLogger($logger) {
+    $this->_logger = $logger;
+    return $this;
+  }
+
+
+  protected function _log($message) {
+    if ($this->_logger)
+      $this->_logger->info($message);
+
+    return $this;
+  }
+
+
+  public function __construct($content) {
+    $map = ['keyId', 'signature', 'headers', 'algorithm'];
+
+    $parts = explode(',', $content);
+    foreach($parts as $part) {
+      if (!preg_match('/([^=]+)="([^"]+)"/', $part, $matches))
+        continue;
+
+      if (!in_array(trim($matches[1]), $map))
+        continue;
+
+      $this->set(ucfirst(trim($matches[1])), trim($matches[2]));
+    }
+  }
+
+
+  public function getHeaders() {
+    $headers = $this->get('Headers', '');
+    if (static::REQUEST_TARGET != substr($headers, 0, strlen(static::REQUEST_TARGET)))
+      $headers = static::REQUEST_TARGET . ' ' . $headers;
+    return $headers;
+  }
+
+
+  public function verify($headers, $key) {
+    if ('hmac' == substr($this->getAlgorithm(), 0, 4))
+      return $this->_verifyHmac($headers, $key);
+
+    if ('rsa' == substr($this->getAlgorithm(), 0, 3))
+      return $this->_verifyRsa($headers, $key);
+  }
+
+
+  protected function _verifyRsa($headers, $key) {
+    $rsa = new \phpseclib\Crypt\RSA();
+    $rsa->loadKey($key);
+    $this->_log(sprintf('Will verify rsa : %s', $this->_messageFrom($headers, $this->getHeaders())));
+    return @$rsa->verify($this->_messageFrom($headers, $this->getHeaders()),
+                         base64_decode($this->getSignature()));
+  }
+
+
+  protected function _verifyHmac($headers, $key) {
+    return hash_equals($this->getSignature(),
+                       $this->sign($headers, $this->getHeaders(), $key, $this->getAlgorithm()));
+  }
+
+
+  public function signResponseTo($request, $headers, $key) {
+    $signature = $this->sign($this->injectRequestTargetIn($request, $headers),
+                             $this->getHeaders(),
+                             $key,
+                             $this->getAlgorithm());
+    return $this->_assemble($signature);
+  }
+
+
+  public function signRequest($headers, $key) {
+    $signature = $this->sign($headers, $this->getHeaders(), $key, $this->getAlgorithm());
+    return $this->_assemble($signature);
+  }
+
+
+  protected function _assemble($signature) {
+    $signature = ['keyId="' . Class_Url::absolute(['module' => 'activitypub',
+                                                   'controller' => 'review',
+                                                   'action' => 'pubkey'], null, true) . '"',
+                  'algorithm="'. $this->getAlgorithm() . '"',
+                  'headers="' . $this->getHeaders() . '"',
+                  'signature="' . $signature . '"'];
+
+    return implode(', ', $signature);
+  }
+
+
+  public function sign($headers, $headers_to_sign, $key, $algorithm) {
+    if ('hmac' == substr($algorithm, 0, 4))
+      return $this->_signHmac($headers, $headers_to_sign, $key, substr($algorithm, 5));
+
+    if ('rsa' == substr($algorithm, 0, 3))
+      return $this->_signRsa($headers, $headers_to_sign, $key);
+  }
+
+
+  protected function _signHmac($headers, $headers_to_sign, $key, $algorithm) {
+    return base64_encode(hash_hmac($algorithm,
+                                   $this->_messageFrom($headers, $headers_to_sign), $key, true));
+  }
+
+
+  protected function _signRsa($headers, $headers_to_sign, $key) {
+    $rsa = new \phpseclib\Crypt\RSA();
+    $rsa->loadKey($key);
+    $this->_log(sprintf('Will sign rsa : %s', $this->_messageFrom($headers, $headers_to_sign)));
+    return base64_encode($rsa->sign($this->_messageFrom($headers, $headers_to_sign)));
+  }
+
+
+  protected function _messageFrom($headers, $headers_to_sign) {
+    $headers_to_sign = str_replace(static::REQUEST_TARGET, '', $headers_to_sign);
+    $parts = array_filter(explode(' ', $headers_to_sign));
+
+    /* $this->_log('Headers to sign: ' . json_encode($parts, JSON_PRETTY_PRINT)); */
+    /* $this->_log('Headers: ' . json_encode($headers, JSON_PRETTY_PRINT)); */
+
+    $datas = [ $this->_getHeader($headers, static::REQUEST_TARGET) ];
+
+    foreach($parts as $part) {
+      if (!$part)
+        continue;
+
+      if ($data = $this->_getHeader($headers, $part))
+        $datas[] = $data;
+    }
+
+    //$this->_log('Datas: ' . json_encode($datas, JSON_PRETTY_PRINT));
+
+    return implode("\n", $datas);
+  }
+
+
+  protected function _getHeader($headers, $header) {
+    $headers = array_change_key_case($headers);
+    $data = array_key_exists($header, $headers)
+      ? trim($headers[$header])
+      : '';
+
+    return strtolower($header) . ': ' . $data;
+  }
+
+
+  public function injectRequestHeadersIn($request, $datas) {
+    foreach(explode(' ', $this->getHeaders()) as $header) {
+      if (!$header || static::REQUEST_TARGET == $header)
+        continue;
+
+      $datas[$header] = $request->getHeader($header);
+    }
+
+    return $this->injectRequestTargetIn($request, $datas);
+  }
+
+
+  public function injectRequestTargetIn($request, $datas) {
+    $datas[static::REQUEST_TARGET] = strtolower($request->getMethod())
+      . ' '
+      . $request->getBaseUrl() . $request->getPathInfo();
+
+    return $datas;
+  }
+}
diff --git a/library/Class/Indexation/File.php b/library/Class/Indexation/File.php
new file mode 100644
index 0000000000000000000000000000000000000000..3d71f70aa91e0409ef10f7cb8d05bbaf675dea53
--- /dev/null
+++ b/library/Class/Indexation/File.php
@@ -0,0 +1,69 @@
+<?php
+/**
+ * Copyright (c) 2012-2019, 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_Indexation_File {
+  protected
+    $_extractors,
+    $_content_source;
+
+  public function __construct() {
+    $this->_extractors = ['Class_Indexation_File_Html',
+                          'Class_Indexation_File_PDF'];
+  }
+
+
+  public function getContent($paths) {
+    $this->_content_source = null;
+
+    if (!$paths)
+      return '';
+
+    $by_extractors = [];
+    foreach($this->_extractors as $extractor)
+      $by_extractors[$extractor] = array_filter($paths,
+                                                function($path) use($extractor)
+                                                {
+                                                  return (new $extractor())->shouldHandle($path);
+                                                });
+
+
+    foreach($by_extractors as $extractor => $paths)
+      if ($content = $this->_extractWith(new $extractor(), $paths))
+        return $content;
+
+    return '';
+  }
+
+
+  public function getContentSource() {
+    return $this->_content_source;
+  }
+
+
+  protected function _extractWith($extractor, $paths) {
+    foreach($paths as $path)
+      if ($content = $extractor->getContent($path)) {
+        $this->_content_source = $path;
+        return $content;
+      }
+  }
+}
diff --git a/library/Class/Indexation/File/Extractor.php b/library/Class/Indexation/File/Extractor.php
new file mode 100644
index 0000000000000000000000000000000000000000..15a51833e8dfc71814b3afc2e106ce9053a98166
--- /dev/null
+++ b/library/Class/Indexation/File/Extractor.php
@@ -0,0 +1,46 @@
+<?php
+/**
+ * Copyright (c) 2012-2019, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+abstract class Class_Indexation_File_Extractor {
+  protected static $_file_info_factory;
+
+  /** @category testing */
+  public static function setFileInfoFactory($file_info_factory) {
+    static::$_file_info_factory = $file_info_factory;
+  }
+
+
+  protected static function _getFileInfo($path) {
+    return ($file_info_factory = static::$_file_info_factory)
+      ? $file_info_factory($path)
+      : new Class_Indexation_File_FileInfo($path);
+  }
+
+
+  public function shouldHandle($path) {
+    $file_info = $this->_getFileInfo($path);
+    return $file_info->isFile() && $file_info->isReadable();
+  }
+
+
+  abstract public function getContent($path);
+}
\ No newline at end of file
diff --git a/library/Class/Indexation/File/FileInfo.php b/library/Class/Indexation/File/FileInfo.php
new file mode 100644
index 0000000000000000000000000000000000000000..9dfb073eaae8111072b8b4f2386c8fd5eedce872
--- /dev/null
+++ b/library/Class/Indexation/File/FileInfo.php
@@ -0,0 +1,26 @@
+<?php
+/**
+ * Copyright (c) 2012-2019, 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_Indexation_File_FileInfo extends SplFileInfo {
+  public function getContents() {
+    return file_get_contents($this->getPathname());
+  }
+}
diff --git a/library/Class/Indexation/File/Html.php b/library/Class/Indexation/File/Html.php
new file mode 100644
index 0000000000000000000000000000000000000000..28f79f16030574b51d604611e988bfcd6975b71a
--- /dev/null
+++ b/library/Class/Indexation/File/Html.php
@@ -0,0 +1,36 @@
+<?php
+/**
+ * Copyright (c) 2012-2019, 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_Indexation_File_Html extends Class_Indexation_File_Extractor {
+  public function shouldHandle($path) {
+    return parent::shouldHandle($path)
+      && in_array($this->_getFileInfo($path)->getExtension(), ['htm', 'html']);
+  }
+
+
+  public function getContent($path) {
+    if (false === $content = $this->_getFileInfo($path)->getContents())
+      return '';
+
+    return strip_tags(str_replace(['<br>', '<br/>'], ' ', $content));
+  }
+}
\ No newline at end of file
diff --git a/library/Class/Indexation/File/PDF.php b/library/Class/Indexation/File/PDF.php
new file mode 100644
index 0000000000000000000000000000000000000000..2ed958ff9b3eb2450b251febec1c794ae51f5aab
--- /dev/null
+++ b/library/Class/Indexation/File/PDF.php
@@ -0,0 +1,40 @@
+<?php
+/**
+ * Copyright (c) 2012-2019, 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_Indexation_File_PDF extends Class_Indexation_File_Extractor {
+  use Trait_StaticCommand;
+
+  public function shouldHandle($path) {
+    $path = urldecode($path);
+
+    return parent::shouldHandle($path)
+      && 'pdf' == $this->_getFileInfo($path)->getExtension();
+  }
+
+
+  public function getContent($path) {
+    $command = $this->getCommand();
+    $command->exec('pdftotext -nopgbrk -raw ' . escapeshellarg(urldecode($path)) . ' -');
+
+    return implode(' ', $command->getOutput());
+  }
+}
diff --git a/library/Class/IntProfilDonnees.php b/library/Class/IntProfilDonnees.php
index 066498d0b4a525b495315dabdcbce4205404a87d..a4bf5e1be520ec83899df54730e000b926975ba2 100644
--- a/library/Class/IntProfilDonnees.php
+++ b/library/Class/IntProfilDonnees.php
@@ -42,6 +42,7 @@ class IntProfilDonneesLoader extends Storm_Model_Loader {
             Class_IntProfilDonnees::FIELD_ITEM_SECTION => '',
             Class_IntProfilDonnees::FIELD_ITEM_EMPLACEMENT => '',
             Class_IntProfilDonnees::FIELD_ITEM_ANNEXE => '',
+            Class_IntProfilDonnees::FIELD_ITEM_BUNDLE_ID => '',
             Class_IntProfilDonnees::FIELD_ITEM_AVAILABILITY => ''];
   }
 
@@ -228,12 +229,19 @@ class IntProfilDonneesLoader extends Storm_Model_Loader {
 
 
   public function getNewsSerialIds() {
-    return Class_CosmoVar::getList('id_article_periodique');
+    return [
+            Class_IntProfilDonnees::SERIAL_FORMAT_NONE => $this->_('Aucun'),
+            Class_IntProfilDonnees::SERIAL_FORMAT_PERGAME => $this->_('Pergame'),
+            Class_IntProfilDonnees::SERIAL_FORMAT_ALOES_INDEXPRESS => $this->_('Indexpresse'),
+            Class_IntProfilDonnees::SERIAL_FORMAT_KOHA => $this->_('Koha'),
+            Class_IntProfilDonnees::SERIAL_FORMAT_ORPHEE => $this->_('Orphée'),
+            Class_IntProfilDonnees::SERIAL_FORMAT_PMB => $this->_('PMB')
+    ];
   }
 
 
   public function getAllZoneAndFields() {
-    $values = $keys = array_merge(range(1,9), range('a', 'z'));
+    $values = $keys = array_merge(range(1,9), range('a', 'z'), range('A','Z'));
     array_walk($values, function(&$value) { $value = '$' . $value; });
 
     return ['' => '', '#' => '$0'] + array_combine($keys, $values);
@@ -273,6 +281,11 @@ class IntProfilDonneesLoader extends Storm_Model_Loader {
   public function findAllOfTypeAuthority() {
     return Class_IntProfilDonnees::findAllBy(['type_fichier' => Class_IntProfilDonnees::FT_AUTHORITY]);
   }
+
+
+  public function findAllOfTypeBibliographic() {
+    return Class_IntProfilDonnees::findAllBy(['type_fichier' => Class_IntProfilDonnees::FT_RECORDS]);
+  }
 }
 
 
@@ -312,6 +325,7 @@ class Class_IntProfilDonnees extends Storm_Model_Abstract {
     SERIAL_FORMAT_ALOES_INDEXPRESS = 2,
     SERIAL_FORMAT_KOHA = 3,
     SERIAL_FORMAT_ORPHEE = 4,
+    SERIAL_FORMAT_PMB = 5,
 
     NOVELTY_DATE_FORMAT_NONE = 0,
     NOVELTY_DATE_FORMAT_AAAA_MM_JJ = 1,
@@ -329,6 +343,7 @@ class Class_IntProfilDonnees extends Storm_Model_Abstract {
     FIELD_ITEM_SECTION = 'champ_section',
     FIELD_ITEM_EMPLACEMENT = 'champ_emplacement',
     FIELD_ITEM_ANNEXE = 'champ_annexe',
+    FIELD_ITEM_BUNDLE_ID = 'champ_bundle_id',
     FIELD_ITEM_AVAILABILITY = 'champ_availability',
     FIELD_ITEM_URL = 'champ_url',
     FIELD_ITEM_ID_ORIGINE = 'champ_id_origine',
@@ -765,14 +780,14 @@ class Class_IntProfilDonnees extends Storm_Model_Abstract {
       newInstance(['libelle' => 'Unimarc PMB',
                    'accents' => self::ENCODING_ISO2709,
                    'rejet_periodiques' => '0',
-                   'id_article_periodique' => self::SERIAL_FORMAT_NONE,
+                   'id_article_periodique' => self::SERIAL_FORMAT_PMB,
                    'type_fichier' => self::FT_RECORDS,
                    'format' => self::FORMAT_UNIMARC,
                    'attributs' =>
                    [['type_doc' =>
                      [[ 'code' => '0', 'label' => '', 'zone_995' => '' ],
                       [ 'code' => '1', 'label' => 'am;na', 'zone_995' => '' ],
-                      [ 'code' => '2', 'label' => 'as', 'zone_995' => ''],
+                      [ 'code' => '2', 'label' => 'as;aa', 'zone_995' => ''],
                       [ 'code' => '3', 'label' => 'i;j', 'zone_995' => ''],
                       [ 'code' => '4', 'label' => 'g','zone_995' => ''],
                       [ 'code' => '5', 'label' => 'l;m', 'zone_995' => ''],
@@ -859,6 +874,32 @@ class Class_IntProfilDonnees extends Storm_Model_Abstract {
   }
 
 
+  public static function forIsbnHomogenization() {
+    return self
+      ::newInstance(['libelle' => 'Homogénéisation d\'isbn',
+                     'accents' => self::ENCODING_ISO2709,
+                     'rejet_periodiques' => 0,
+                     'id_article_periodique' => self::SERIAL_FORMAT_NONE,
+                     'type_fichier' => self::FT_RECORDS,
+                     'format' => self::FORMAT_TABBED_ASCII,
+                     'attributs' => [ 1 => ['champs' => 'isbn'] ]
+                     ]);
+  }
+
+
+  public static function forEanHomogenization() {
+    return self
+      ::newInstance(['libelle' => 'Homogénéisation d\'ean',
+                     'accents' => self::ENCODING_ISO2709,
+                     'rejet_periodiques' => 0,
+                     'id_article_periodique' => self::SERIAL_FORMAT_NONE,
+                     'type_fichier' => self::FT_RECORDS,
+                     'format' => self::FORMAT_TABBED_ASCII,
+                     'attributs' => [ 1 => ['champs' => 'ean'] ]
+                     ]);
+  }
+
+
   public function setAttributs($array_or_string) {
     return $this->_set('attributs',
                        is_array($array_or_string)
@@ -876,6 +917,15 @@ class Class_IntProfilDonnees extends Storm_Model_Abstract {
   }
 
 
+  public function setIndexFile($zone, $field, $regex) {
+    $config = unserialize($this->getAttributs());
+    $config[7] = ['index_file_zone' => [$zone],
+                  'index_file_field' => [$field],
+                  'index_file_uri_regex' => [$regex]];
+    return $this->setAttributs($config);
+  }
+
+
   public function setItemField($name, $value) {
     $config = unserialize($this->getAttributs());
     $config[0][$name] = $value;
@@ -1015,6 +1065,11 @@ class Class_IntProfilDonnees extends Storm_Model_Abstract {
   }
 
 
+  public function getBundleIdField() {
+    return $this->getProfilePrefs()->getItemBundleIdField();
+  }
+
+
   public function getNoveltyFormat() {
     return $this->getProfilePrefs()->getItemNoveltyFormat();
   }
@@ -1085,6 +1140,22 @@ class Class_IntProfilDonnees extends Storm_Model_Abstract {
   }
 
 
+  public function getIndexFileZone() {
+    return $this->getProfilePrefs()->getIndexFileZone();
+  }
+
+
+  public function getIndexFileField() {
+    return $this->getProfilePrefs()->getIndexFileField();
+  }
+
+
+  public function getIndexFileUriRegex() {
+    return $this->getProfilePrefs()->getIndexFileUriRegex();
+  }
+
+
+
   public function getProfilePrefs() {
     if(!$this->_profile_prefs)
       $this->_profile_prefs = (new Class_ProfilePrefs())->setDatas($this->toArray());
diff --git a/library/Class/Journal.php b/library/Class/Journal.php
new file mode 100644
index 0000000000000000000000000000000000000000..41f1bd939feed2dde70d13abeb85037dcf7d16a6
--- /dev/null
+++ b/library/Class/Journal.php
@@ -0,0 +1,88 @@
+<?php
+/**
+ * Copyright (c) 2012-2017, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+class Class_JournalLoader extends Storm_Model_Loader {
+  protected $_current_journal;
+
+  public function factory($type, $model=null) {
+    $journal = Class_Journal::newInstance(['type' => $type]);
+    if (!$journal->save())
+      return;
+
+    if (!$model)
+      return $journal;
+
+    $this->_current_journal = $journal;
+    $model->acceptJournalVisitor($this);
+    $this->_current_journal = null;
+
+    return $journal;
+  }
+
+
+  public function visitDetail($type, $value) {
+    Class_JournalDetail::newInstance(['type' => $type,
+                                      'value' => $value,
+                                      'journal' => $this->_current_journal])
+      ->save();
+  }
+
+
+  public function lastOf($type) {
+    return Class_Journal::findFirstBy(['type' => $type, 'order' => 'created_at desc']);
+  }
+}
+
+
+
+class Class_Journal extends Storm_Model_Abstract {
+  use Trait_TimeSource;
+
+  protected $_table_name = 'journal';
+  protected $_loader_class = 'Class_JournalLoader';
+  protected $_has_many = ['details' => ['model' => 'Class_JournalDetail',
+                                        'role' => 'journal',
+                                        'dependents' => 'delete']];
+  protected $_default_attribute_values = ['type' => '',
+                                          'created_at' => ''];
+
+  public function beforeSave() {
+    if ($this->isNew())
+      $this->setCreatedAt($this->getTimeSource()->dateDayAndHours());
+  }
+
+
+  public function getLibelle() {
+    return $this->getType() . ' ' . $this->getCreatedAt();
+  }
+
+
+  public function getDateTime() {
+    return new DateTime($this->getCreatedAt());
+  }
+
+
+  public function isAfter($other) {
+    return $other
+      ? $this->getDateTime() > $other->getDateTime()
+      : true;
+  }
+}
diff --git a/library/Class/JournalDetail.php b/library/Class/JournalDetail.php
new file mode 100644
index 0000000000000000000000000000000000000000..5d94028844919000ca703c6f66b61cc9babd2dbc
--- /dev/null
+++ b/library/Class/JournalDetail.php
@@ -0,0 +1,33 @@
+<?php
+/**
+ * Copyright (c) 2012-2017, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class Class_JournalDetail extends Storm_Model_Abstract {
+  use Trait_TimeSource;
+
+  protected $_table_name = 'journal_detail';
+  protected $_belongs_to = ['journal' => ['model' => 'Class_Journal']];
+
+  protected $_default_attribute_values = ['type' => '',
+                                          'value' => ''];
+
+
+}
diff --git a/library/Class/MoteurRecherche.php b/library/Class/MoteurRecherche.php
index 7a898e15c51bb9817cae9fd37aeaca88b47c3804..603d397f7df40a7c3db2454c811056c5aca9d0ce 100644
--- a/library/Class/MoteurRecherche.php
+++ b/library/Class/MoteurRecherche.php
@@ -19,7 +19,6 @@
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
 class Class_MoteurRecherche {
-
   use Trait_Singleton, Trait_Translator, Trait_TimeSource, Trait_SearchCriteriaVisitor;
 
   /** Classe d'indexation */
@@ -133,7 +132,7 @@ class Class_MoteurRecherche {
   }
 
 
-  public function visitExpression($expression, $pertinence, $tri) {
+  public function visitExpression($expression, $pertinence, $tri, $in_files) {
     // Analyse de l'expression
     $expression = trim($expression);
     if ($expression == '*')
@@ -156,18 +155,27 @@ class Class_MoteurRecherche {
     if (trim(Class_CosmoVar::get('other_index_fields')))
       $axes []= 'other_terms';
 
-    $this->setCondition('MATCH(' . implode(', ', $axes) . ')' . $against);
+    $quoted_expression = Zend_Db_Table_Abstract::getDefaultAdapter()->quote($expression);
+    $match_condition = 'MATCH(' . implode(', ', $axes) . ')' . $against;
+    if ($in_files)
+      $match_condition = '(' . $match_condition . ' OR MATCH(file_content) AGAINST(' . $quoted_expression .' IN BOOLEAN MODE))';
+
+    $this->setCondition($match_condition);
 
     if (($tri and $tri !== '*') and !$pertinence)
       return;
 
     $against_titre = $terms->asSelectAgainst();
 
-    $match_weights = ['titres' => '1.5', 'auteurs' => '1'];
+    $match_weights = ['titres' => '1.5',
+                      'auteurs' => '1'];
     $match_weights_string = [];
     foreach($match_weights as $axe => $weight)
       $match_weights_string []= 'MATCH(' . $axe . ') ' . $against_titre . ( $weight === '1' ? '' :' * ' . $weight);
 
+    if ($in_files)
+      $match_weights_string[] = 'MATCH(file_content) AGAINST('. $quoted_expression . ') * 0.5';
+
     $this->order_by = 'order by (' . implode(') + (', $match_weights_string) . ') desc';
   }
 
@@ -461,14 +469,8 @@ class Class_MoteurRecherche {
     if ($this->all_facettes)
       $this->setCondition("MATCH(facettes) AGAINST('" . trim($this->all_facettes)."' IN BOOLEAN MODE)");
 
-    if ($this->criteres_recherche->hasEmptyDomain()
-        && !$this->criteres_recherche->isSearchInBasket())
-      return ['statut' => 'erreur',
-              'erreur' => $this->_('Domaine non paramétré')];
-
-    if ($this->criteres_recherche->isEmptySelection())
-      return ['statut' => 'erreur',
-              'erreur' => $this->_('La sélection courante est vide')];
+    if ($error = $this->criteres_recherche->getStateError())
+      return ['statut' => 'erreur', 'erreur' => $error];
 
     return $this->getConditionsForRequest($this->getConditions(),
                                           $this->_or_conditions,
@@ -714,4 +716,19 @@ class Class_MoteurRecherche {
 
     $this->setCondition('id_notice in (' . implode(',', $selection->values()). ')');
   }
+
+
+  public function visitRecordType($type) {
+    $this->setTypeCondition($type);
+  }
+
+
+  public function setTypeCondition($type) {
+    if (in_array($type, [Class_Notice::TYPE_BIBLIOGRAPHIC,
+                         Class_Notice::TYPE_AUTHORITY]))
+
+      $this->_type_condition = 'type=' . $type;
+
+    return $this;
+  }
 }
\ No newline at end of file
diff --git a/library/Class/Notice.php b/library/Class/Notice.php
index de25dfb09897e099a8c4b29ae8085eea45fecd27..dff85e46d3bb22ae8d187eba7827952f9050f4ca 100644
--- a/library/Class/Notice.php
+++ b/library/Class/Notice.php
@@ -166,7 +166,8 @@ class Class_Notice extends Storm_Model_Abstract {
 
   const
     TYPE_BIBLIOGRAPHIC = 1,
-    TYPE_AUTHORITY = 2;
+    TYPE_AUTHORITY = 2,
+    TYPE_SERIAL_ARTICLE = 3;
 
   protected $_loader_class = 'NoticeLoader';
   protected $_table_name = 'notices';
@@ -211,7 +212,8 @@ class Class_Notice extends Storm_Model_Abstract {
                                           'date_creation' => '',
                                           'created_at' => null,
                                           'type' => self::TYPE_BIBLIOGRAPHIC,
-                                          'z3950_retry' => 0];
+                                          'z3950_retry' => 0,
+                                          'file_content' => ''];
 
 
   public function __construct() {
@@ -227,6 +229,11 @@ class Class_Notice extends Storm_Model_Abstract {
   }
 
 
+  public function getTypeLabel() {
+    return $this->_getDataMap()->getTypeLabel();
+  }
+
+
   public function initializeAttributes($datas) {
     parent::initializeAttributes($datas);
     if (isset($datas['unimarc']))
@@ -235,7 +242,7 @@ class Class_Notice extends Storm_Model_Abstract {
   }
 
 
-  public function getAvisByUser($user)  {
+  public function getAvisByUser($user) {
     return Class_AvisNotice::findAllBy(['clef_oeuvre' => $this->getClefOeuvre(),
                                         'id_user' => $user->getId()]);
   }
@@ -251,13 +258,22 @@ class Class_Notice extends Storm_Model_Abstract {
   }
 
 
+  public function getLocalAvis() {
+    if (!isset($this->_local_avis))
+      $this->_local_avis = Class_AvisNotice::findAllBy(['clef_oeuvre' => $this->getClefOeuvre(),
+                                                        'source_actor_id' => null]);
+
+    return $this->_local_avis;
+  }
+
+
   public function getAvisBibliothecaire() {
-    return Class_AvisNotice::filterByBibliothecaire($this->getAvis());
+    return Class_AvisNotice::filterByBibliothecaire($this->getLocalAvis());
   }
 
 
   public function getAvisAbonne() {
-    return Class_AvisNotice::filterByAbonne($this->getAvis());
+    return Class_AvisNotice::filterByAbonne($this->getLocalAvis());
   }
 
 
@@ -363,19 +379,27 @@ class Class_Notice extends Storm_Model_Abstract {
 
 
   public function getAllAvisPerSource($page = null) {
-    $all_avis = array('bib' => array('liste' => $avis_bib = $this->getAvisBibliothecaires(),
-                                     'note' => $this->getNoteMoyenneAvisBibliothecaires(),
-                                     'nombre' => count($avis_bib),
-                                     'titre' => 'Bibliothécaires'),
-                      'abonne' => array('liste' => $avis_abon = $this->getAvisAbonnes(),
-                                        'note' => $this->getNoteMoyenneAvisAbonnes(),
-                                        'nombre' => count($avis_abon),
-                                        'titre' => 'Lecteurs du portail'));
-
-    foreach (array('Class_WebService_Babelio', 'Class_WebService_Amazon') as $provider_class) {
+    $avis_bib = $this->getAvisBibliothecaires();
+    $avis_abon = $this->getAvisAbonnes();
+
+    $all_avis = ['bib' => new Class_Notice_ReviewsSet($this->_('Bibliothécaires'),
+                                                      $avis_bib,
+                                                      $this->getNoteMoyenneAvisBibliothecaires(),
+                                                      count($avis_bib)),
+
+                 'abonne' => new Class_Notice_ReviewsSet($this->_('Lecteurs du portail'),
+                                                         $avis_abon,
+                                                         $this->getNoteMoyenneAvisAbonnes(),
+                                                         count($avis_abon)),
+    ];
+
+    foreach (['Class_WebService_Babelio', 'Class_WebService_Amazon', 'Class_FederationReview']
+             as $provider_class) {
       $provider = new $provider_class();
       $source = strtolower(array_last(explode('_', $provider_class)));
-      if ($data = $provider->getAvis($this, $page)) $all_avis[$source] = $data;
+      $reviews_set = $provider->getAvis($this, $page);
+      if (!$reviews_set->isEmpty())
+        $all_avis[$source] = $reviews_set;
     }
 
     return $all_avis;
@@ -383,7 +407,7 @@ class Class_Notice extends Storm_Model_Abstract {
 
 
   public function getAvisBibliothecaires() {
-    return Class_AvisNotice::filterByBibliothecaire($this->getAvis());
+    return Class_AvisNotice::filterByBibliothecaire($this->getLocalAvis());
   }
 
 
@@ -393,7 +417,7 @@ class Class_Notice extends Storm_Model_Abstract {
 
 
   public function getAvisAbonnes() {
-    return Class_AvisNotice::filterByAbonne($this->getAvis());
+    return Class_AvisNotice::filterByAbonne($this->getLocalAvis());
   }
 
 
@@ -1705,6 +1729,12 @@ class Class_Notice extends Storm_Model_Abstract {
     return $type_doc->isSonore();
   }
 
+
+  public function isFirstItemTypeSerialArticle() {
+    return ($first_item = $this->getFirstExemplaire()) && $first_item->isTypeSerialArticle();
+  }
+
+
   public function toUnimarcISO2709() {
     $unimarc = new Class_NoticeUnimarc_Writer();
     $unimarc->setNotice($this->getUnimarc());
@@ -1844,4 +1874,9 @@ class Class_Notice extends Storm_Model_Abstract {
   public function getArticle() {
     return Class_Article::findFirstBy(['id_notice' => $this->getId()]);
   }
+
+
+  public function getFileContentFirstWords() {
+    return substr($this->getFileContent(), 0, 180) . '…';
+  }
 }
\ No newline at end of file
diff --git a/library/Class/Notice/AuthorityRelations.php b/library/Class/Notice/AuthorityRelations.php
new file mode 100644
index 0000000000000000000000000000000000000000..4469d034fe7e9c8befb96f1a515672da8950af7a
--- /dev/null
+++ b/library/Class/Notice/AuthorityRelations.php
@@ -0,0 +1,47 @@
+<?php
+/**
+ * Copyright (c) 2019, 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_Notice_AuthorityRelations extends Storm_Collection {
+  public static function allFor($record) {
+    return new static(Class_Notice_AuthorityRelation::allFor($record));
+  }
+
+
+  public function generics() {
+    return $this->select(function($one) { return $one->isGeneric(); });
+  }
+
+
+  public function specifics() {
+    return $this->select(function($one) { return $one->isSpecific(); });
+  }
+
+
+  public function rejects() {
+    return $this->select(function($one) { return $one->isReject(); });
+  }
+
+
+  public function links() {
+    return $this->select(function($one) { return $one->isLink(); });
+  }
+}
diff --git a/library/Class/Notice/AuthorityType.php b/library/Class/Notice/AuthorityType.php
index dbccbe72dc47e1a554c4f0126f4317364d64f72c..fcf8898fdfb406b5dc6e4b21d47295e3e3901a41 100644
--- a/library/Class/Notice/AuthorityType.php
+++ b/library/Class/Notice/AuthorityType.php
@@ -42,20 +42,32 @@ class Class_Notice_AuthorityType {
     ];
 
 
+  public function typeLabels() {
+    return ['a' => $this->_('nom de personne'),
+            'b' => $this->_('nom de collectivité'),
+            'c' => $this->_('nom de territoire ou nom géographique'),
+            'd' => $this->_('marque'),
+            'e' => $this->_('famille'),
+            'f' => $this->_('titre uniforme'),
+            'g' => $this->_('rubrique de classement'),
+            'h' => $this->_('auteur / titre'),
+            'i' => $this->_('auteur / rubrique de classement'),
+            'j' => $this->_('matière nom commun'),
+            'k' => $this->_('lieu d’édition'),
+            'l' => $this->_('forme, genre ou caractéristiques physiques')];
+  }
+
+
+  public function labelOfType($type) {
+    $labels = $this->typeLabels();
+    return isset($labels[$type])
+      ? $labels[$type]
+      : '';
+  }
+
+
   public function withTypesDo($closure) {
-    foreach(['a' => $this->_('nom de personne'),
-             'b' => $this->_('nom de collectivité'),
-             'c' => $this->_('nom de territoire ou nom géographique'),
-             'd' => $this->_('marque'),
-             'e' => $this->_('famille'),
-             'f' => $this->_('titre uniforme'),
-             'g' => $this->_('rubrique de classement'),
-             'h' => $this->_('auteur / titre'),
-             'i' => $this->_('auteur / rubrique de classement'),
-             'j' => $this->_('matière nom commun'),
-             'k' => $this->_('lieu d’édition'),
-             'l' => $this->_('forme, genre ou caractéristiques physiques'),
-             ] as $code => $label)
+    foreach($this->typeLabels() as $code => $label)
       $closure($code, $label);
   }
 
diff --git a/library/Class/Notice/DataMap/Abstract.php b/library/Class/Notice/DataMap/Abstract.php
index 1ee196b5faa53663821b2ca3e144ec0f841a9114..5aa3ac019ae9758d81f3f6b0cb868fe675474751 100644
--- a/library/Class/Notice/DataMap/Abstract.php
+++ b/library/Class/Notice/DataMap/Abstract.php
@@ -33,6 +33,7 @@ abstract class Class_Notice_DataMap_Abstract {
   /** @return string */
   abstract public function getMainTitle();
 
+
   /**
    * @param $field string field facet code
    * @param $facets string
@@ -40,4 +41,8 @@ abstract class Class_Notice_DataMap_Abstract {
    * @return array
    */
   abstract public function getRecordField($field, $facets='');
+
+
+  /** @return string */
+  abstract public function getTypeLabel();
 }
diff --git a/library/Class/Notice/DataMap/UnimarcAuthority.php b/library/Class/Notice/DataMap/UnimarcAuthority.php
index aabd9c86ea18ee2443b478c8cba6034aa50edee6..962407b54536e57eb996e61b1bf07bde6acdfe0e 100644
--- a/library/Class/Notice/DataMap/UnimarcAuthority.php
+++ b/library/Class/Notice/DataMap/UnimarcAuthority.php
@@ -21,12 +21,25 @@
 
 
 class Class_Notice_DataMap_UnimarcAuthority extends Class_Notice_DataMap_Abstract {
+  protected $_authority_type;
+
+
   public function getMainTitle() {
-    $titres = $this->_record->get_subfield('250', 'a');
+    $zone = $this->_authorityType()->zoneForType('2', $this->_record->getTypeDoc());
+    $titres = $this->_record->get_subfield($zone, 'a');
     return $titres ? trim($titres[0]) : '';
   }
 
 
+  public function getTypeLabel() {
+    $label = [$this->_authorityType()->labelOfType($this->_record->getTypeDoc())];
+    if ($system = $this->_record->get_subfield('152', 'b'))
+      $label[] = $system[0];
+
+    return implode(', ', array_filter($label));
+  }
+
+
   public function getRecordField($field, $facets='') {
     if (Class_Codification::CODE_AUTHORITY_DATAS !== $field)
       return [];
@@ -35,6 +48,13 @@ class Class_Notice_DataMap_UnimarcAuthority extends Class_Notice_DataMap_Abstrac
             new Class_Notice_DataMap_UnimarcAuthorityNotes($this->_record),
             new Class_Notice_DataMap_UnimarcAuthorityUsages($this->_record)];
   }
+
+
+  protected function _authorityType() {
+    return $this->_authority_type
+      ? $this->_authority_type
+      : $this->_authority_type = new Class_Notice_AuthorityType();
+  }
 }
 
 
@@ -57,8 +77,7 @@ abstract class Class_Notice_DataMap_UnimarcAuthorityComponent {
 
 class Class_Notice_DataMap_UnimarcAuthorityNotes extends Class_Notice_DataMap_UnimarcAuthorityComponent {
   public function renderOn($view) {
-    if ($notes = $this->_record->get_subfield('330', 'a'))
-      return $view->tag('h3', 'Note d\'application') . $view->tag('p', reset($notes));
+    return $view->AuthoritySearch_RecordNotes($this->_record);
   }
 }
 
@@ -67,83 +86,28 @@ class Class_Notice_DataMap_UnimarcAuthorityNotes extends Class_Notice_DataMap_Un
 
 class Class_Notice_DataMap_UnimarcAuthorityUsages extends Class_Notice_DataMap_UnimarcAuthorityComponent {
   public function renderOn($view) {
-    $facets = array_filter($this->_record->getFacetCodes(),
-                           function($facet) { return $this->_isDynamicFacetValue($facet); });
-
-    if (!$facets)
-      return $this->_('Utilisé dans aucune notice');
-
-    $criterias = (new Class_CriteresRecherche())->setParams(['multifacets' => implode('-', $facets)]);
-
-    if (!$count = $this->_countByCriterias($criterias))
-      return $this->_('Utilisé dans aucune notice');
-
-    return $view->tagAnchor($view->url($criterias->getUrlCriteresWithFacettes(), null, true),
-                            $this->_plural($count,
-                                           'Utilisé dans aucune notice',
-                                           'Utilisé dans 1 notice',
-                                           'Utilisé dans %d notices',
-                                           $count));
-  }
-
-
-  protected function _isDynamicFacetValue($facet) {
-    return Class_CodifThesaurus::CODE_FACETTE === substr($facet, 0, 1)
-      && strlen($facet) === (Class_CodifThesaurus::ID_KEY_LENGTH * 2) + 1;
-  }
-
-
-  protected function _countByCriterias($criterias) {
-    return (new Class_MoteurRecherche)
-      ->beNotExtensible()
-      ->lancerRecherche($criterias)
-      ->getRecordsCount();
+    return $view->AuthoritySearch_RecordUsages($this->_record);
   }
 }
 
 
 
 
-class Class_Notice_DataMap_UnimarcAuthorityRelations extends Class_Notice_DataMap_UnimarcAuthorityComponent{
+class Class_Notice_DataMap_UnimarcAuthorityRelations
+  extends Class_Notice_DataMap_UnimarcAuthorityComponent{
+
   protected $_relations;
 
   public function renderOn($view) {
-    $this->_relations = new Storm_Collection(Class_Notice_AuthorityRelation::allFor($this->_record));
-
-    $render = [];
-    foreach(['isGeneric' => $this->_('Terme générique'),
-             'isSpecific'=> $this->_('Terme spécifique'),
-             'isReject'  => $this->_('Terme rejeté'),
-             'isLink'    => $this->_('Terme associé')]
-            as $selector => $label)
-      $render[] = $this->_renderLabelledList($view, $label, $selector);
-
-    return implode($render);
-  }
-
-
-  protected function _renderLabelledList($view, $label, $selector) {
-    $closure = function($item) use ($selector) { return $item->$selector(); };
-    $relations = $this->_relations->select($closure);
-    if ($relations->isEmpty())
-      return;
-
-    $terms = [];
-    foreach($relations as $relation)
-      $terms[] = $this->_renderLabelledItem($view, $relation);
-
-    return $view->tag('h3', $label) . $view->tag('ul', implode($terms));
-  }
-
-
-  protected function _renderLabelledItem($view, $relation) {
-    $label = $relation->label();
-    if ($item = Class_Exemplaire::findFirstAuthorityItemByOriginId($relation->id()))
-      $label = $view->tagAnchor($view->url(['controller' => 'recherche',
-                                            'action' => 'viewnotice',
-                                            'id' => $item->getIdNotice()],
-                                           null, true), $label);
-
-    return $view->tag('li', $label);
+    $record_url_closure = function($record_id) use($view) {
+      return $view->url(['controller' => 'recherche',
+                         'action' => 'viewnotice',
+                         'id' => $record_id],
+                        null, true);
+    };
+
+    return $view
+      ->AuthoritySearch_RecordRelations($this->_record,
+                                        $record_url_closure);
   }
 }
diff --git a/library/Class/Notice/DataMap/UnimarcBiblio.php b/library/Class/Notice/DataMap/UnimarcBiblio.php
index ad7df0b6664b1c0a500678944f0baa3997fefff1..bd8dc0498d5858a18b956b323653fa76bb877ce1 100644
--- a/library/Class/Notice/DataMap/UnimarcBiblio.php
+++ b/library/Class/Notice/DataMap/UnimarcBiblio.php
@@ -27,6 +27,11 @@ class Class_Notice_DataMap_UnimarcBiblio extends Class_Notice_DataMap_Abstract {
   }
 
 
+  public function getTypeLabel() {
+    return '';
+  }
+
+
   public function getRecordField($field, $facets='') {
     if ($this->_isReboundField($field))
       return $this->_getReboundField($field, $facets);
diff --git a/library/Class/Notice/ReviewsSet.php b/library/Class/Notice/ReviewsSet.php
new file mode 100644
index 0000000000000000000000000000000000000000..c20973ff75afec36c8d7e2abf3f2ed7cb26cc1e9
--- /dev/null
+++ b/library/Class/Notice/ReviewsSet.php
@@ -0,0 +1,73 @@
+<?php
+/**
+ * Copyright (c) 2012-2019, 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_Notice_ReviewsSet {
+  protected
+    $_reviews    = [],
+    $_rating     = 0,
+    $_count      = 0,
+    $_page_count = 0,
+    $_label      = '';
+
+  public static function emptyInstance() {
+    return new static('', [], 0, 0, 0, 0);
+  }
+
+
+  public function __construct($label, $reviews, $rating, $count, $page_count=0) {
+    $this->_label = $label;
+    $this->_reviews = $reviews;
+    $this->_rating = $rating;
+    $this->_count = $count;
+    $this->_page_count = $page_count;
+  }
+
+
+  public function isEmpty() {
+    return 0 == $this->_count;
+  }
+
+
+  public function hasPages() {
+    return !$this->isEmpty() && 0 < $this->_page_count;
+  }
+
+
+  public function getReviews() {
+    return $this->_reviews;
+  }
+
+
+  public function getRating() {
+    return $this->_rating;
+  }
+
+
+  public function getCount() {
+    return $this->_count;
+  }
+
+
+  public function getLabel() {
+    return $this->_label;
+  }
+}
diff --git a/library/Class/Profil/Preferences/SearchResult.php b/library/Class/Profil/Preferences/SearchResult.php
index 3ad82c15b156385bc752448bd5cc05e67ce5f3cf..af5a2c4bfd790ca2ee5fcf3cd59bf0044a4ce19f 100644
--- a/library/Class/Profil/Preferences/SearchResult.php
+++ b/library/Class/Profil/Preferences/SearchResult.php
@@ -132,7 +132,16 @@ class Class_Profil_Preferences_SearchResult {
             ->whenCalledDo('renderOn', function($view, $criteria)
                            {
                              return $view->search_History($criteria);
-                           })];
+                           }),
+
+            (new Class_Entity())
+            ->setId('SearchMode')
+            ->setLabel($this->_('Mode de recherche'))
+            ->whenCalledDo('renderOn', function($view, $criteria)
+                           {
+                             return $view->search_SearchMode($criteria);
+                           }),
+    ];
   }
 
 
diff --git a/library/Class/ProfilePrefs.php b/library/Class/ProfilePrefs.php
index fd8f2d1a5a72f73b63c9c862eb8a2bccabdcd40d..4b404384942c30d50968e30b5d15aac3e649054a 100644
--- a/library/Class/ProfilePrefs.php
+++ b/library/Class/ProfilePrefs.php
@@ -21,7 +21,6 @@
 
 
 class Class_ProfilePrefs extends Class_Entity {
-
   public function getItemZone() {
     $prefs = $this->getItemPrefs();
     return isset($prefs[Class_IntProfilDonnees::FIELD_ITEM_ZONE])
@@ -69,6 +68,11 @@ class Class_ProfilePrefs extends Class_Entity {
   }
 
 
+  public function getItemBundleIdField() {
+    return $this->getItemPrefs()[Class_IntProfilDonnees::FIELD_ITEM_BUNDLE_ID];
+  }
+
+
   public function getPatronXmlField() {
     return $this->getPrefsXml()[Class_IntProfilDonnees::XML_PATRON_FIELD];
   }
@@ -118,13 +122,13 @@ class Class_ProfilePrefs extends Class_Entity {
     $default = ['interest_zone' => ['932'],
                 'interest_champ' => ['a']];
 
-    if(!$interet_prefs = $this->getPrefs()[6])
+    if (!$interet_prefs = $this->getPrefs()[6])
       return $default;
 
-    if(!$interet_zone = $interet_prefs['zone'])
+    if (!$interet_zone = $interet_prefs['zone'])
       return $default;
 
-    if(!$interet_champ = $interet_prefs['champ'])
+    if (!$interet_champ = $interet_prefs['champ'])
       return $default;
 
     return ['interest_zone' => [$interet_zone],
@@ -132,6 +136,33 @@ class Class_ProfilePrefs extends Class_Entity {
   }
 
 
+  public function getFileIndexation() {
+    $default = ['index_file_zone' => [''],
+                'index_file_field' => [''],
+                'index_file_uri_regex' => ['/userfiles/files/[a-zA-Z0-9_\-]+\.pdf']];
+
+    $prefs = $this->getPrefs();
+    return isset($prefs[7]) && ($file_indexation = $prefs[7])
+      ? $file_indexation
+      : $default;
+  }
+
+
+  public function getIndexFileZone() {
+    return $this->getFileIndexation()['index_file_zone'][0];
+  }
+
+
+  public function getIndexFileField() {
+    return $this->getFileIndexation()['index_file_field'][0];
+  }
+
+
+  public function getIndexFileUriRegex() {
+    return $this->getFileIndexation()['index_file_uri_regex'][0];
+  }
+
+
   public function getItemUrl() {
     $url_zone = $this->getItemUrlZone();
     $url_champ = $this->getItemUrlChamp();
diff --git a/library/Class/ProfileSerializer/UnimarcRecord.php b/library/Class/ProfileSerializer/UnimarcRecord.php
index 1bf6d6a7db80cd6af4201f2e6dd47e03525e1f16..f9baf6a91e38cb5fe137bd5652fcc785a1599b63 100644
--- a/library/Class/ProfileSerializer/UnimarcRecord.php
+++ b/library/Class/ProfileSerializer/UnimarcRecord.php
@@ -28,7 +28,8 @@ class Class_ProfileSerializer_UnimarcRecord extends Class_ProfileSerializer_Abst
             3 => [],
             4 => $this->_extractNovelty(),
             5 => [],
-            6 => $this->_extractInterests()];
+            6 => $this->_extractInterests(),
+            7 => $this->_extractFileIndexation()];
   }
 
 
@@ -47,6 +48,7 @@ class Class_ProfileSerializer_UnimarcRecord extends Class_ProfileSerializer_Abst
       ->populateItemSection()
       ->populateItemEmplacement()
       ->populateItemAnnexe()
+      ->populateItemBundleId()
       ->populateItemAvailability()
       ->populateItemNoveltyZoneAndField()
       ->populateItemNoveltyFormat()
@@ -55,7 +57,8 @@ class Class_ProfileSerializer_UnimarcRecord extends Class_ProfileSerializer_Abst
       ->populateItemDocTypes(['label' => 'label', 'zone' => 'zone_995'])
       ->populateItemIdOrigine()
       ->populateItemUrl()
-      ->populateInterests();
+      ->populateInterests()
+      ->populateFileIndexation();
   }
 
 
@@ -65,6 +68,13 @@ class Class_ProfileSerializer_UnimarcRecord extends Class_ProfileSerializer_Abst
   }
 
 
+  protected function _extractFileIndexation() {
+    return ['index_file_zone' => $this->_datas['index_file_zone'],
+            'index_file_field' => $this->_datas['index_file_field'],
+            'index_file_uri_regex' => $this->_datas['index_file_uri_regex']];
+  }
+
+
   protected function _extractRecords() {
     return array_merge($this->_extractDocTypes(),
                        $this->_extractItemFields(),
diff --git a/library/Class/RendezVous.php b/library/Class/RendezVous.php
index 69c9fcc43f4e9c69588f52c832da72a2accb52a4..761436dea5f3d4d77831b6c162a009a9b793e3d0 100644
--- a/library/Class/RendezVous.php
+++ b/library/Class/RendezVous.php
@@ -140,7 +140,7 @@ class Class_RendezVous extends Storm_Model_Abstract {
     if (false === strpos($value, '/'))
       return $value;
 
-    return implode('-', array_reverse(explode('/', $value)));
+    return Class_Date::frToIso($value);
   }
 
 
diff --git a/library/Class/SearchCriteria/DateRange.php b/library/Class/SearchCriteria/DateRange.php
index 94c6f74c1772093caddf824cfafe86c1d31d5a41..8c04e412830b7d58fae9dcd198199b096cf151cd 100644
--- a/library/Class/SearchCriteria/DateRange.php
+++ b/library/Class/SearchCriteria/DateRange.php
@@ -93,7 +93,7 @@ class Class_SearchCriteria_DateRange extends Class_SearchCriteria_Abstract {
 
 
   protected function _sqlFormat($value) {
-    return implode('-', array_reverse(explode('/', $value)));
+    return Class_Date::frToIso($value);
   }
 
 
diff --git a/library/Class/Systeme/ModulesAccueil/RechercheSimple.php b/library/Class/Systeme/ModulesAccueil/RechercheSimple.php
index 5658673715b313a00e8476709b60731d4e42cb0b..c935007a0063592caded7c163b04866420040ed8 100644
--- a/library/Class/Systeme/ModulesAccueil/RechercheSimple.php
+++ b/library/Class/Systeme/ModulesAccueil/RechercheSimple.php
@@ -46,6 +46,7 @@ class Class_Systeme_ModulesAccueil_RechercheSimple extends Class_Systeme_Modules
                              'recherche_avancee' => 1,
                              'type_doc' => 0,
                              'tri' => '*',
+                             'in_files' => 0,
                              'profil_redirect' => 0,
                              'placeholder' => '',
                              'search_button' => '',
diff --git a/library/Class/Systeme/ModulesAppli.php b/library/Class/Systeme/ModulesAppli.php
index 7146bc7c3f8357fd1c04cb54c0ee5e162f01b5b7..e1968acc746d905c39dca58d13dd633cdf30ab72 100644
--- a/library/Class/Systeme/ModulesAppli.php
+++ b/library/Class/Systeme/ModulesAppli.php
@@ -29,6 +29,7 @@ class Class_Systeme_ModulesAppli extends Class_Systeme_ModulesAbstract {
   const LISTE_FORMAT_VIGNETTES = 3;
   const LISTE_FORMAT_MUR = 4;
   const LISTE_FORMAT_CHRONO = 5;
+  const LISTE_FORMAT_LINKS = 6;
 
   const
     CVS_NONE = 0,
@@ -144,7 +145,10 @@ class Class_Systeme_ModulesAppli extends Class_Systeme_ModulesAbstract {
 
                       'catalogue' => ['*' => ['libelle' => 'Catalogues',
                                               'popup_width' => 710,
-                                              'popup_height' => 620]]
+                                              'popup_height' => 620]],
+                      'authority-search' => ['*' => ['libelle' => 'Parcours des autorités',
+                                                     'popup_width' => 500,
+                                                     'popup_height' => 260]]
   ];
 
 
@@ -194,26 +198,25 @@ class Class_Systeme_ModulesAppli extends Class_Systeme_ModulesAbstract {
    * @return array | false
    */
   public function getModule($type_module=false, $action=false) {
-    if (false !== $type_module) {
-      $type_module = (string)$type_module;
+    if (false === $type_module)
+      return $this->modules;
 
-      if (!array_key_exists($type_module, $this->modules))
-        return false;
+    $type_module = (string)$type_module;
 
-      if (
-          (false === $action)
-          || (!array_key_exists((string)$action, $this->modules[$type_module]))
-      ) {
-        $action = '*';
-      }
+    if (!array_key_exists($type_module, $this->modules))
+      return false;
 
-      if (!isset($this->modules[$type_module][$action]))
-        return [];
-      return $this->modules[$type_module][$action];
+    if (
+        (false === $action)
+        || (!array_key_exists((string)$action, $this->modules[$type_module]))
+    ) {
+      $action = '*';
     }
 
-    return $this->modules;
+    if (!isset($this->modules[$type_module][$action]))
+      return [];
 
+    return $this->modules[$type_module][$action];
   }
 
   /**
diff --git a/library/Class/Systeme/ModulesAppli/Default.php b/library/Class/Systeme/ModulesAppli/Default.php
index 15d39132226611ffb8042fa32d24ed88f50e8c23..ef193f32c4d3ca81d1d3104c69dd6309b7657867 100644
--- a/library/Class/Systeme/ModulesAppli/Default.php
+++ b/library/Class/Systeme/ModulesAppli/Default.php
@@ -21,6 +21,8 @@
 
 
 class Class_Systeme_ModulesAppli_Default {
+  use Trait_Translator;
+
   protected
     $_controller,
     $_action,
@@ -99,6 +101,9 @@ class Class_Systeme_ModulesAppli_Default {
       case 'domains':
         $valeurs = (new Class_Systeme_ModulesAccueil_DomainBrowser())->getDefaultValues();
         break;
+      case 'authority-search':
+        $valeurs = ['titre' => $this->_('Parcours des autorités')];
+        break;
 
       default : $valeurs = [];
     }
diff --git a/library/Class/TableDescription/CosmoCodification.php b/library/Class/TableDescription/CosmoCodification.php
new file mode 100644
index 0000000000000000000000000000000000000000..6da49b9b385047a64abc00c0132fb41bc1703cd4
--- /dev/null
+++ b/library/Class/TableDescription/CosmoCodification.php
@@ -0,0 +1,40 @@
+<?php
+/**
+ * Copyright (c) 2012-2019, 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_TableDescription_CosmoCodification extends Class_TableDescription {
+  public function init() {
+    $this->addColumn($this->_('Libellé'), 'libelle')
+         ->addColumn($this->_('Règles de reconnaissance'), 'regles')
+         ->_addSpecifics()
+         ->addRowPluginsActions();
+  }
+
+
+  /**
+   * hook to handle specifics columns
+   *
+   * @return Class_TableDescription
+   */
+  protected function _addSpecifics() {
+    return $this;
+  }
+}
diff --git a/library/Class/TableDescription/CosmoEmplacement.php b/library/Class/TableDescription/CosmoEmplacement.php
new file mode 100644
index 0000000000000000000000000000000000000000..b2cd1ba1bcc594d92ae2b654b4db5f94597023b1
--- /dev/null
+++ b/library/Class/TableDescription/CosmoEmplacement.php
@@ -0,0 +1,37 @@
+<?php
+/**
+ * Copyright (c) 2012-2019, 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_TableDescription_CosmoEmplacement extends Class_TableDescription_CosmoCodification {
+  /**
+   * hook to handle specifics columns
+   *
+   * @return Class_TableDescription
+   */
+  protected function _addSpecifics() {
+    return $this->addColumn($this->_('Affichage des exemplaires'),
+                            function($model)
+                            {
+                              return $model->getNePasAfficher() ? $this->_('Non') : $this->_('Oui');
+                            })
+      ;
+  }
+}
diff --git a/library/Class/TableDescription/CosmoSection.php b/library/Class/TableDescription/CosmoSection.php
new file mode 100644
index 0000000000000000000000000000000000000000..7c12efd33603641d6440b0475f5f4c326de8b6a1
--- /dev/null
+++ b/library/Class/TableDescription/CosmoSection.php
@@ -0,0 +1,37 @@
+<?php
+/**
+ * Copyright (c) 2012-2019, 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_TableDescription_CosmoSection extends Class_TableDescription_CosmoCodification {
+  /**
+   * hook to handle specifics columns
+   *
+   * @return Class_TableDescription
+   */
+  protected function _addSpecifics() {
+    return $this->addColumn($this->_('Rejeter les exemplaires'),
+                            function($model)
+                            {
+                              return $model->getInvisible() ? $this->_('Oui') : $this->_('Non');
+                            })
+      ;
+  }
+}
diff --git a/library/Class/TimeSource.php b/library/Class/TimeSource.php
index 2ebc5bd9421a4cb71f76e1ae233f978496457600..fa7bfa2bd640dcb8d1907f91015f657e44c1d3a2 100644
--- a/library/Class/TimeSource.php
+++ b/library/Class/TimeSource.php
@@ -52,6 +52,11 @@ class Class_TimeSource {
   }
 
 
+  public function dateHttpHeader() {
+    return gmdate('D, d M Y H:i:s \G\M\T', $this->time());
+  }
+
+
   public function date() {
     $time = $this->time();
     return $this->midnightTime(date('n', $time), date('j', $time), date('Y', $time));
diff --git a/library/Class/Users.php b/library/Class/Users.php
index bc3a6e3b63bbca6e34eaf800529ee7e6e5ea92b4..baf49c9b792de556ac3843455595031227ba2368 100644
--- a/library/Class/Users.php
+++ b/library/Class/Users.php
@@ -1198,8 +1198,8 @@ class Class_Users extends Storm_Model_Abstract {
 
     $loans->uasort(function($a, $b)
                    {
-                     return strcmp(implode('/', array_reverse(explode('/', $a->getIssueDate()))),
-                                   implode('/', array_reverse(explode('/', $b->getIssueDate()))));
+                     return strcmp(Class_Date::frToIso($a->getIssueDate()),
+                                   Class_Date::frToIso($b->getIssueDate()));
                    });
     return Class_User_CardsOperationDecorator::decorateAll($loans, $this);
   }
diff --git a/library/Class/WebService/ActivityPub.php b/library/Class/WebService/ActivityPub.php
new file mode 100644
index 0000000000000000000000000000000000000000..2f85af98942b68d448cae24db30a456ed9cc8e7a
--- /dev/null
+++ b/library/Class/WebService/ActivityPub.php
@@ -0,0 +1,414 @@
+<?php
+/**
+ * Copyright (c) 2012-2017, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+require_once __DIR__ . '/../../activitystreams/autoload.php';
+
+use
+  Patbator\ActivityStreams\Model\Base,
+  Patbator\ActivityStreams\Model\Factory,
+  Patbator\ActivityStreams\Model\Service,
+  Patbator\ActivityStreams\Model\Accept,
+  Patbator\ActivityStreams\Model\Reject,
+  Patbator\ActivityStreams\Model\Group,
+  Patbator\ActivityStreams\Model\Join,
+  Patbator\ActivityStreams\Model\Leave,
+  Patbator\ActivityStreams\Model\CollectionPage,
+  Patbator\ActivityStreams\Stream;
+
+
+class Class_WebService_ActivityPub {
+  use Trait_SimpleWebClient, Trait_TimeSource, Trait_LastMessage, Trait_Translator;
+
+  const
+    MIME_TYPE = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
+    EMPTY_DIGEST = 'ZDQxZDhjZDk4ZjAwYjIwNGU5ODAwOTk4ZWNmODQyN2U=';
+
+  protected static
+    $_throw_errors = false,
+    $_signer;
+
+  protected
+    $_endpoint,
+    $_identity_cache,
+    $_logger;
+
+
+  /** @category testing */
+  public static function setThrowErrors($flag) {
+    static::$_throw_errors = (bool)$flag;
+  }
+
+
+  /** @category testing */
+  public static function setSigner($signer) {
+    static::$_signer = $signer;
+  }
+
+
+  protected function _signerFor($signature) {
+    if (static::$_signer)
+      return static::$_signer;
+
+    return (new Class_HttpSignature($signature))
+      ->setLogger($this->_logger);
+  }
+
+
+  public static function identityOf($actor) {
+    if (!$actor || !$actor->id())
+      return;
+
+    return (new static($actor->id()))->identify();
+  }
+
+
+  public static function publicKeyOf($actor) {
+    if (!$actor || !$actor->publicKey())
+      return;
+
+    return (new static($actor->publicKey()))->identify();
+  }
+
+
+  public function __construct($endpoint) {
+    $this->_endpoint = (substr($endpoint, -1) == '/'
+                        ? substr($endpoint, 0, strlen($endpoint) - 1)
+                        : $endpoint);
+
+    Base::setFactory((new Factory)
+                     ->mapTypeToClass('Service', 'Class_ActivityPub_Service')
+                     ->mapTypeToClass('Key', 'Class_ActivityPub_PublicKey'));
+  }
+
+
+  public function setLogger($logger) {
+    $this->_logger = $logger;
+    return $this;
+  }
+
+
+  protected function _log($message) {
+    if ($this->_logger)
+      $this->_logger->info($message);
+
+    return $this;
+  }
+
+
+  public function isValid() {
+    return (bool) $this->_inboxUrl();
+  }
+
+
+  public function identify() {
+    if (($stream = Stream::fromJson($this->_identity()))
+        && ($object = $stream->getRoot()))
+      return $object;
+
+    $this->_log('Cannot fetch valid identity from ' . $this->_endpoint);
+  }
+
+
+  protected function _identity() {
+    if ($this->_identity_cache)
+      return $this->_identity_cache;
+
+    try {
+      return $this->_identity_cache = $this
+        ->getWebClient()
+        ->open_url($this->_endpoint, ['headers' => ['Accept' => static::MIME_TYPE]]);
+    } catch(Exception $e) {
+      $this->_log($e->getMessage());
+      if (static::$_throw_errors)
+        throw $e;
+    }
+  }
+
+
+  protected function _inboxUrl() {
+    return (($service = $this->identify())
+            && ($service instanceof Service))
+      ? $service->inbox()
+      : null;
+  }
+
+
+  public function name() {
+    return (($service = $this->identify())
+            && ($service instanceof Service))
+      ? $service->name()
+      : null;
+  }
+
+
+  /**
+   * @param $group_name string
+   * @return bool
+   *
+   * Join a group by its name
+   * Return true on Accept or false on error or Reject
+   * Rejection reason can be retreived with $this->getLastMessage()
+   */
+  public function join($group_name) {
+    return ($group_name = trim($group_name))
+      ? $this->_postActivityWithValidation($this->_groupActivityWith(new Join(), $group_name))
+      : $this->_error($this->_('Impossible de rejoindre un groupe vide'));
+  }
+
+
+  /**
+   * @param $group_name string
+   * @return bool
+   *
+   * Leave a group by its name
+   * Return true on Accept or false on error or Reject
+   * Rejection reason can be retreived with $this->getLastMessage()
+   */
+  public function leave($group_name) {
+    return ($group_name = trim($group_name))
+      ? $this->_postActivityWithValidation($this->_groupActivityWith(new Leave(), $group_name))
+      : $this->_error($this->_('Impossible de quitter un groupe vide'));
+  }
+
+
+  protected function _groupActivityWith($activity, $group_name) {
+    return $activity
+      ->actor((new Service())->name((new Class_Federation)->getActorName())
+              ->id(Class_Url::absolute(['module' => 'activitypub',
+                                        'controller' => 'review'], null, true)))
+      ->object((new Group())->name($group_name));
+  }
+
+
+  protected function _postActivityWithValidation($activity) {
+    if (!$inbox = $this->_inboxUrl())
+      return $this->_error($this->_('Serveur invalide'));
+
+    $request_target = 'post ' . Zend_Uri::factory($inbox)->getPath();
+
+    $response = $this->_activityPost($inbox, (new Stream($activity))->render(), $request_target);
+    $this->_log('Received response with headers : ' . json_encode($response->getHeaders(), JSON_PRETTY_PRINT));
+    if (!$response_body = $this->_validateResponseAndBody($response, $request_target))
+      return false;
+
+    if ((!$stream = Stream::fromJson($response_body)) || (!$response_activity = $stream->getRoot()))
+      return $this->_error($this->_('La réponse du serveur n\'était pas valide'));
+
+    if ($response_activity instanceof Accept)
+      return true;
+
+    $message = $this->_('Le serveur a refusé votre demande');
+    if (($response_activity instanceof Reject)
+        && ($summary = $response_activity->summary()))
+      $message .= ': ' . $summary;
+
+    return $this->_error($message);
+  }
+
+
+  public function reviews($notice, $page) {
+    return $this->_getReviewsPage(['key' => $notice->getClefOeuvre(),
+                                   'page' => $page]);
+  }
+
+
+  public function harvestReviews($from) {
+    $reviews = new Storm_Collection();
+    $page = 1;
+    while (($collection_page = $this->_getReviewsPage(['from' => $from,
+                                                       'page' => $page]))
+           && ($items = $collection_page->items())) {
+      $reviews->addAll($items);
+      $page++;
+    }
+
+    return $reviews;
+  }
+
+
+  protected function _getReviewsPage($query_params) {
+    if (!$outbox = $this->_outboxUrl())
+      return;
+
+    $outbox .= '?' . http_build_query($query_params);
+    $request_target = 'get ' . Zend_Uri::factory($outbox)->getPath();
+
+    $response = $this->_activityGet($outbox, $request_target);
+
+    if (!$body = $this->_validateResponseAndBody($response, $request_target))
+      return;
+
+    if ((!$stream = Stream::fromJson($body)) || (!$activity = $stream->getRoot()))
+      return;
+
+    return $activity instanceof CollectionPage
+      ? $activity
+      : null;
+  }
+
+
+  protected function _activityGet($url, $request_target) {
+    $headers = ['date' => $this->getTimeSource()->dateHttpHeader(),
+                'digest' => 'MD5=' . static::EMPTY_DIGEST,
+                Class_HttpSignature::REQUEST_TARGET => $request_target];
+
+    return $this
+      ->getWebClient()
+      ->getResponse($url,
+                    ['headers' => ['Date' => $headers['date'],
+                                   'Digest' => $headers['digest'],
+                                   'Accept' => static::MIME_TYPE,
+                                   'Signature' => $this->_sign($headers),
+                                   'Authorization' => 'Bearer ' . Class_Url::absolute(['module' => 'activitypub',
+                                                                                       'controller' => 'review'], null, true)]]);
+  }
+
+
+  protected function _outboxUrl() {
+    return (($service = $this->identify())
+            && ($service instanceof Service))
+      ? $service->outbox()
+      : null;
+  }
+
+
+  protected function _activityPost($url, $content, $request_target) {
+    $headers = ['date' => $this->getTimeSource()->dateHttpHeader(),
+                'digest' => 'MD5=' . base64_encode(md5($content)),
+                Class_HttpSignature::REQUEST_TARGET => $request_target];
+
+    return $this
+      ->getWebClient()
+      ->postRawDataResponse($url, $content, static::MIME_TYPE,
+                            ['headers' => ['Date' => $headers['date'],
+                                           'Digest' => $headers['digest'],
+                                           'Signature' => $this->_sign($headers)]]);
+  }
+
+
+  public function validateRequest($request) {
+    if (!$signature = $request->getHeader('Signature')) {
+      $this->_log('Request without Signature header');
+      return false;
+    }
+    $this->_log('Received request with signature : ' . $signature);
+
+    if (!$actor = $this->identify())
+      return false;
+
+    if (!$actor_key = $actor->publicKey()) {
+      $this->_log('Cannot verify signature without publickey of ' . $this->_endpoint);
+      return false;
+    }
+
+    $signer = $this->_signerFor($signature);
+    if ($actor_key != $signer->getKeyId()) {
+      $this->_log(sprintf('Signature keyId differs from actor keyId : %s <---> %s',
+                          $signer->getKeyId(), $actor_key));
+      return false;
+    }
+
+    if (!$key = (new static($actor_key))->setLogger($this->_logger)->identify()) {
+      $this->_log('Cannot get actor key details from ' . $actor_key);
+      return false;
+    }
+
+    if ($key->owner() != $actor->id()) {
+      $this->_log(sprintf('Key owner is not actor : %s <---> %s',
+                          $key->owner(), $actor->id()));
+      return false;
+    }
+
+    $headers = $signer->injectRequestHeadersIn($request, []);
+    if (!$signer->verify($headers, $key->publicKeyPem())) {
+      $this->_log(sprintf('Invalid signature : %s does not valid %s',
+                          $key->publicKeyPem(), $signature));
+      return false;
+    }
+
+    return true;
+  }
+
+
+  protected function _validateResponseAndBody($response, $request_target) {
+    if (!$this->_validateResponse($response, $request_target))
+      return false;
+
+    if (!$body = $response->getBody())
+      return $this->_error($this->_('La réponse du serveur était vide'));
+
+    return $body;
+  }
+
+
+  protected function _validateResponse($response, $request_target) {
+    if ($response->isError())
+      return $this->_error($response->getMessage());
+
+    if (!$signature = $response->getHeader('Signature'))
+      return $this->_error($this->_('Le serveur n\'a pas signé sa réponse'));
+
+    if (!$identity = $this->identify())
+      return $this->_error($this->_('Le serveur ne fournit pas son identité'));
+
+    $signer = $this->_signerFor($signature);
+    if ($identity->publicKey() != $signer->getKeyId())
+      return $this->_error($this->_('La signature de la réponse n\'est pas la signature du serveur'));
+
+    $headers = $response->getHeaders();
+    $headers[Class_HttpSignature::REQUEST_TARGET] = $request_target;
+
+    if (!$key = (new static($identity->publicKey()))->setLogger($this->_logger)->identify()) {
+      $this->_log('Cannot get actor key details from ' . $identity->publicKey());
+      return $this->_error($this->_('Impossible de récupérer les détails de la clé publique du serveur'));
+    }
+
+    if ($key->owner() != $identity->id()) {
+      $this->_log(sprintf('Key owner is not actor : %s <---> %s',
+                          $key->owner(), $identity->id()));
+      return $this->_error($this->_('Le serveur n\'est pas le propriétaire de la clé qu\'il fourni'));
+    }
+
+    if (!$signer->verify($headers, $key->publicKeyPem())) {
+      $this->_log(sprintf('Invalid signature : %s does not valid %s',
+                          $key->publicKeyPem(), $signature));
+      return $this->_error($this->_('La signature ne semble pas provenir du serveur'));
+    }
+
+    return true;
+  }
+
+
+  protected function _sign($headers) {
+    return $this->_signerFor('algorithm="rsa-sha256", headers="date digest"')
+                ->signRequest($headers, $this->_privateKey());
+  }
+
+
+  protected function _privateKey() {
+    return (new Class_Federation())->getPrivateKey();
+  }
+
+
+  public function getEndpoint() {
+    return $this->_endpoint;
+  }
+}
diff --git a/library/Class/WebService/ActivityPubServer.php b/library/Class/WebService/ActivityPubServer.php
new file mode 100644
index 0000000000000000000000000000000000000000..09c38cbd5142ef46de2b77e9960a6bd3d2d72ef5
--- /dev/null
+++ b/library/Class/WebService/ActivityPubServer.php
@@ -0,0 +1,41 @@
+<?php
+/**
+ * Copyright (c) 2012-2017, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+require_once __DIR__ . '/../../activitystreams/autoload.php';
+
+use Patbator\ActivityStreams\Stream;
+
+
+class Class_WebService_ActivityPubServer extends Class_WebService_ActivityPub {
+  public function respondTo($request, $response, $activity) {
+    $body = (new Stream($activity))->render();
+    $digest = 'MD5=' . base64_encode(md5($body));
+    $signer = $this->_signerFor('algorithm="rsa-sha256", headers="digest"');
+
+    $response
+      ->setHeader('Content-Type', static::MIME_TYPE, true)
+      ->setHeader('Digest', $digest)
+      ->setHeader('Signature',
+                  $signer->signResponseTo($request, ['digest' => $digest], $this->_privateKey()))
+      ->setBody($body)
+      ;
+  }
+}
diff --git a/library/Class/WebService/Amazon.php b/library/Class/WebService/Amazon.php
index 3a5d4e1267fdfd829c284a099e995ad52ebb7368..9e8a52463585dca9362aa40ff0d740784d7bdf7c 100644
--- a/library/Class/WebService/Amazon.php
+++ b/library/Class/WebService/Amazon.php
@@ -18,33 +18,24 @@
  * along with BOKEH; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
-//////////////////////////////////////////////////////////////////////////////////////////
-// OPAC3 - WEB-SERVICE AMAZON
-//////////////////////////////////////////////////////////////////////////////////////////
 
-class Class_WebService_Amazon
-{
-  var $xml;                                                         // Pointeur sur la classe xml de base
-  private $req;                                                     // Racine requete http
-  private $id_afi="AKIAINZSICEPECFZ4RPQ";                           // ID afi chez amazon dans cfg
-  private $secret_key="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";   // Clé secrete chez amazon
 
-//------------------------------------------------------------------------------------------------------
-// Constructeur
-//------------------------------------------------------------------------------------------------------
-  function __construct()
-  {
+class Class_WebService_Amazon {
+  use Trait_Translator;
+
+  public  $xml;
+  private $req;
+  private $id_afi="AKIAINZSICEPECFZ4RPQ";
+  private $secret_key="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";
+
+  public function __construct()  {
     $this->xml= new Class_Xml();
     $this->req="http://webservices.amazon.fr/onca/xml?Service=AWSECommerceService";
     $this->req.="&AWSAccessKeyId=".$this->id_afi;
   }
 
 
-//------------------------------------------------------------------------------------------------------
-// Execution requete http et test erreur
-//------------------------------------------------------------------------------------------------------
-  function requete($req)
-  {
+  public function requete($req) {
     $url=$this->req.$req;
 
     // Ajout de signature AMAZON
@@ -68,11 +59,8 @@ class Class_WebService_Amazon
     return $this->test_erreur();
   }
 
-//------------------------------------------------------------------------------------------------------
-// Retourne la notice d'après un noeud de type item
-//------------------------------------------------------------------------------------------------------
-  function rend_notice($node)
-  {
+
+  public function rend_notice($node) {
     $notice["asin"]=$this->xml->get_child_value($node,"asin");
     $img=$this->xml->get_child_node($node,"smallimage");
     if($img)
@@ -106,48 +94,42 @@ class Class_WebService_Amazon
   }
 
 
-  // pour rendre le service polymorphique avec Babelio, Amazon, Notices ....
   public function getAvis($notice, $page) {
-    if (! $notice->isLivre()) return false;
-    $avis = $this->rend_avis($notice, $page);
-    if ($avis == false) return false;
-
-    $avis['titre'] = 'Lecteurs Amazon';
-    return $avis;
+    return $notice->isLivre()
+      ? $this->rend_avis($notice, $page)
+      : Class_Notice_ReviewsSet::emptyInstance();
   }
 
-//------------------------------------------------------------------------------------------------------
-// Avis des lecteurs
-//------------------------------------------------------------------------------------------------------
-  function rend_avis($notice, $page)  {
-    if ($notice instanceof Class_Notice)
-      $isbn = $notice->getIsbn();
-    else
-      $isbn = $notice;
-
-    if(!trim($isbn)){
-      return false;
-    }
-    if($page>0){
+
+  public function rend_avis($notice, $page)  {
+    $isbn = $notice instanceof Class_Notice
+      ? $notice->getIsbn()
+      : $notice;
+
+    if (!trim($isbn))
+      return Class_Notice_ReviewsSet::emptyInstance();
+
+    if ($page > 0)
       $page="&ReviewPage=".$page;
-    }
-    $req=$this->req_isbn($isbn)."&ResponseGroup=Reviews".$page;
-    if(!$this->requete($req)){
-      return false;
-    }
-    $item=$this->xml->getNode("customerreviews");
-    $avis["note"]=$this->xml->get_child_value($item,"averagerating");
 
-    if(!$avis["note"]){
-      return false;
-    }
-    $avis["nombre"]=$this->xml->get_child_value($item,"totalreviews");
-    $avis["nb_pages"]=$this->xml->get_child_value($item,"totalreviewpages");
-    $item=$this->xml->get_child_node($item,"review");
+    $req = $this->req_isbn($isbn) . "&ResponseGroup=Reviews" . $page;
+    if (!$this->requete($req))
+      return Class_Notice_ReviewsSet::emptyInstance();
+
+    $item = $this->xml->getNode("customerreviews");
+    $rating = $this->xml->get_child_value($item, "averagerating");
+    if (!$rating)
+      return Class_Notice_ReviewsSet::emptyInstance();
+
+    $count = $this->xml->get_child_value($item, "totalreviews");
+    $page_count = $this->xml->get_child_value($item, "totalreviewpages");
+
+    $item = $this->xml->get_child_node($item, "review");
     $dateClass = new Class_Date();
-    while( $item ) {
-      $avis_notice = new Class_AvisNotice();
-      $avis_notice
+    $reviews = [];
+
+    while($item) {
+      $reviews[] = (new Class_AvisNotice())
         ->setNote($this->xml->get_child_value($item,"rating"))
         ->setDateAvis($dateClass->LocalizedDate($this->xml->get_child_value($item,"date"), 'yyyy-MM-dd'))
         ->setEntete(utf8_encode($this->xml->get_child_value($item,"summary")))
@@ -155,17 +137,17 @@ class Class_WebService_Amazon
         ->setNotice($notice)
         ->setUser(null);
 
-      $index=count($avis["liste"]);
-      $avis["liste"][$index] = $avis_notice;
-      $item=$this->xml->get_sibling($item);
+      $item = $this->xml->get_sibling($item);
     }
 
-    return $avis;
+    return new Class_Notice_ReviewsSet($this->_('Lecteurs Amazon'),
+                                       $reviews,
+                                       $rating,
+                                       $count,
+                                       $page_count);
   }
 
-//------------------------------------------------------------------------------------------------------
-// Résumés et analyses
-//------------------------------------------------------------------------------------------------------
+
   public function getResumes($notice) {
     if (!$service = $notice->getIsbnOrEan())
       return array();
@@ -174,7 +156,7 @@ class Class_WebService_Amazon
   }
 
 
-  function rend_analyses($isbn) {
+  public function rend_analyses($isbn) {
     if(!trim($isbn))
       return array();
 
diff --git a/library/Class/WebService/Babelio.php b/library/Class/WebService/Babelio.php
index 993d271fa245f87c929baa114154a775a03ef0a3..e51bf47d370e12aa2f65fc21e2f5becdae910f43 100644
--- a/library/Class/WebService/Babelio.php
+++ b/library/Class/WebService/Babelio.php
@@ -19,6 +19,8 @@
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
 class Class_WebService_Babelio {
+  use Trait_Translator;
+
   const USER = 'afi_test';
   const PASS = 'af_45_POQ_d';
 
@@ -130,18 +132,9 @@ class Class_WebService_Babelio {
    * @return mixed
    */
   public function getAvis($notice, $page) {
-    if (! $notice->isLivre())
-      return false;
-
-    if (! $this->_serviceActivated())
-      return false;
-
-    $avis = $this->getCritiques($notice);
-    if ($avis == false)
-      return false;
-
-    $avis['titre'] = 'Lecteurs Babelio';
-    return $avis;
+    return $notice->isLivre() && $this->_serviceActivated()
+      ? $this->getCritiques($notice)
+      : Class_Notice_ReviewsSet::emptyInstance();
   }
 
 
@@ -162,7 +155,7 @@ class Class_WebService_Babelio {
     $isbn = $notice->getIsbn();
     $this->requete($isbn);
     if (!$this->_xml)
-      return false;
+      return Class_Notice_ReviewsSet::emptyInstance();
 
     $liste_avis = array();
     foreach($this->_xml->url as $avis)  {
@@ -185,9 +178,10 @@ class Class_WebService_Babelio {
       $liste_avis[] = $avis_notice;
     }
 
-    return array("liste" => $liste_avis,
-                 "nombre" => count($liste_avis),
-                 "note" => Class_AvisNotice::getNoteAverage($liste_avis));
+    return new Class_Notice_ReviewsSet($this->_('Lecteurs Babelio'),
+                                       $liste_avis,
+                                       Class_AvisNotice::getNoteAverage($liste_avis),
+                                       count($liste_avis));
   }
 
 
diff --git a/library/Class/WebService/Redmine.php b/library/Class/WebService/Redmine.php
index 89c41bab2b3117bea6b1facaf465967e778b1087..3ae9be3ffa6f2966dcbbb0ad1da739471aefe67a 100644
--- a/library/Class/WebService/Redmine.php
+++ b/library/Class/WebService/Redmine.php
@@ -20,7 +20,7 @@
  */
 
 class Class_WebService_Redmine extends Class_WebService_Abstract {
-   use Trait_Translator;
+  use Trait_Translator;
 
    const CUSTOM_PRIORITY_ID = 5;
    const CUSTOM_MODULE_ID = 37;
@@ -381,8 +381,12 @@ class Class_WebService_Redmine extends Class_WebService_Abstract {
 
 
   public function uploadFile($json) {
-    return $this->_getAttachmentApi()
-                ->upload($json);
+    return $this->_getAttachmentApi()->upload($json);
+  }
+
+
+  public function downloadFile($fileid) {
+    return $this->_getAttachmentApi()->download($fileid);
   }
 
 
diff --git a/library/Class/WebService/Redmine/Issue.php b/library/Class/WebService/Redmine/Issue.php
index e3bccad28f6e0c8973cab5ad2ee50175a60d4622..bfb49055070fe2fed501b7b57d3cf35c40f63e1f 100644
--- a/library/Class/WebService/Redmine/Issue.php
+++ b/library/Class/WebService/Redmine/Issue.php
@@ -139,10 +139,12 @@ class Class_WebService_Redmine_Issue extends Class_Entity {
       return $view->tag('del',
                         $this->_('Pièce jointe : "%s"', $attachment->getold_value()));
 
-    $show_url = sprintf('%s/attachments/download/%s/%s',
-                   Class_AdminVar::get('REDMINE_SERVER_URL'),
-                   $attachment->getname(),
-                   $attachment->getnew_value());
+    $show_url = Class_Url::absolute(['module' => 'admin',
+                                     'controller' => 'redmine',
+                                     'action' => 'download-file',
+                                     'id_lib' => $this->getLibrary()->getId(),
+                                     'fileid' => $attachment->getname(),
+                                     'filename' => $attachment->getnew_value()],null,true);
 
     return $view->tagAnchor($show_url,
                             $this->_('Pièce jointe : "%s"', $attachment->getnew_value()), ['target' => '_blank']);
diff --git a/library/Class/WebService/SIGB/AbstractILSDIPatronInfoReader.php b/library/Class/WebService/SIGB/AbstractILSDIPatronInfoReader.php
index e6559fde165e6ba29f2ad536ca7bd002a194547f..a220101919cdf980e66b49360b73f69da9f24979 100644
--- a/library/Class/WebService/SIGB/AbstractILSDIPatronInfoReader.php
+++ b/library/Class/WebService/SIGB/AbstractILSDIPatronInfoReader.php
@@ -161,7 +161,14 @@ abstract class Class_WebService_SIGB_AbstractILSDIPatronInfoReader {
    * @param array $attributes
    */
   public function startHold($attributes) {
-    $this->_current_operation = $this->_currentHold = Class_WebService_SIGB_Reservation::newInstanceWithEmptyExemplaire();
+    $this->_current_operation = $this->_currentHold = call_user_func([$this->getHoldClassName(),
+                                                                      'newInstanceWithEmptyExemplaire']);
+  }
+
+
+
+  public function getHoldClassName() {
+    return 'Class_WebService_SIGB_Reservation';
   }
 
 
diff --git a/library/Class/WebService/SIGB/Emprunt.php b/library/Class/WebService/SIGB/Emprunt.php
index ab0ec1fb122728f595b1ed3dc92b9e14efa345ff..fe8ac12be57ff2248580f70882f91d27d2fafc8e 100644
--- a/library/Class/WebService/SIGB/Emprunt.php
+++ b/library/Class/WebService/SIGB/Emprunt.php
@@ -33,7 +33,7 @@ class Class_WebService_SIGB_Emprunt extends Class_WebService_SIGB_ExemplaireOper
 
 
   public function getDateRetourISO8601() {
-    return implode('-', array_reverse(explode('/', $this->getDateRetour())));
+    return Class_Date::frToIso($this->getDateRetour());
   }
 
 
diff --git a/library/Class/WebService/SIGB/Exemplaire.php b/library/Class/WebService/SIGB/Exemplaire.php
index 6584cfa50c03eac064368265d919e6b6bffb96c2..e81633a89647cb70f03171a1e032be07ab2ea2d3 100644
--- a/library/Class/WebService/SIGB/Exemplaire.php
+++ b/library/Class/WebService/SIGB/Exemplaire.php
@@ -22,38 +22,37 @@
 class Class_WebService_SIGB_Exemplaire {
   use Trait_Translator;
 
-
-  protected $id;
-
-  /** @var Class_WebService_SIGB_Notice */
-  protected $notice;
-  protected $nb_resas;
-  protected $edition;
-  protected $titre;
-  protected $reservable;
-  protected $bibliotheque;
-  protected $section;
-  protected $auteur;
-  protected $no_notice;
-  protected $code_barre;
-  protected $date_retour;
-  protected $visible_opac;
-  protected $code_annexe;
-  protected $_notice_opac;
-  protected $_exemplaire_opac;
-  protected $_disponibiliteLabel;
-  protected $_isEnPret;
-  protected $_cote;
-  protected $_emplacement;
-  protected $_issue_date;
-  protected $consultation = false;
-  protected $onhold = false;
-  protected $renewals = 0;
+  protected
+    $id,
+    $notice,   /** @var Class_WebService_SIGB_Notice */
+    $nb_resas,
+    $edition,
+    $titre,
+    $reservable,
+    $bibliotheque,
+    $section,
+    $auteur,
+    $no_notice,
+    $code_barre,
+    $date_retour,
+    $visible_opac,
+    $code_annexe,
+    $_notice_opac,
+    $_exemplaire_opac,
+    $_disponibiliteLabel,
+    $_isEnPret,
+    $_cote,
+    $_emplacement,
+    $_issue_date,
+    $consultation = false,
+    $onhold = false,
+    $renewals = 0;
 
   public static function newInstance() {
     return new self(null);
   }
 
+
   public  function message($code) {
     $messages = [
                  'DISPO_EN_PRET' =>  $this->_('En prêt'),
@@ -94,9 +93,6 @@ class Class_WebService_SIGB_Exemplaire {
   }
 
 
-
-
-
   public function setId($id) {
     $this->id = $id;
     return $this;
@@ -108,6 +104,11 @@ class Class_WebService_SIGB_Exemplaire {
   }
 
 
+  public function requiresCalendarHold() {
+    return false;
+  }
+
+
   /**
    * @return Class_Exemplaire
    */
@@ -465,10 +466,12 @@ class Class_WebService_SIGB_Exemplaire {
     return false;
   }
 
+
   public function isAvailableForConsultation() {
     return $this->consultation;
   }
 
+
   public function setConsultationAvailable($bool = true) {
     $this->consultation=$bool;
   }
diff --git a/library/Class/WebService/SIGB/Koha.php b/library/Class/WebService/SIGB/Koha.php
index ce1712fcfabfd497f861d80e524495b418586b76..d14d4defb6f486dd84354b101a7bddfa856fdcbb 100644
--- a/library/Class/WebService/SIGB/Koha.php
+++ b/library/Class/WebService/SIGB/Koha.php
@@ -34,7 +34,11 @@ class Class_WebService_SIGB_Koha {
                              'Codification_disponibilites' => '',
                              'restful' => '',
                              'pre-registration' => '',
-                             'use_card_number' => ''],
+                             'use_card_number' => '',
+                             'withdrawn_mapping' => '',
+                             'grouped_holds_itypes' => '',
+                             'bundled_holds_minimal_duration' => 0,
+                             'bundled_holds_maximal_duration' => 0],
                             $params);
 
       $service_class = trim($params['use_card_number'])
@@ -43,7 +47,13 @@ class Class_WebService_SIGB_Koha {
 
       $service = $service_class::getService($params);
 
-      $service->setCodificationDisponibilites(static::decodeCodificationDisponibilites($params['Codification_disponibilites']));
+      $service
+        ->setCodificationDisponibilites(static::decodeMapping($params['Codification_disponibilites']))
+        ->setWithdrawnMapping(static::decodeMapping($params['withdrawn_mapping']))
+        ->setGroupedHoldsITypes(array_filter(explode("\n", $params['grouped_holds_itypes'])))
+        ->setBundledHoldsMinimalDuration($params['bundled_holds_minimal_duration'])
+        ->setBundledHoldsMaximalDuration($params['bundled_holds_maximal_duration']);;
+
       static::$services[$key] = $service;
     }
 
@@ -51,7 +61,7 @@ class Class_WebService_SIGB_Koha {
   }
 
 
-  public static function decodeCodificationDisponibilites($str_codif) {
+  public static function decodeMapping($str_codif) {
     $codifs = [];
     $lines = array_filter(explode("\n", trim($str_codif)));
     foreach($lines as $line) {
diff --git a/library/Class/WebService/SIGB/Koha/Exemplaire.php b/library/Class/WebService/SIGB/Koha/Exemplaire.php
index dcac15af7a530d9e2eb7ab6d15c129f7358bc77e..84d2ccf278237ba59ee33be622f25e4640b9b710 100644
--- a/library/Class/WebService/SIGB/Koha/Exemplaire.php
+++ b/library/Class/WebService/SIGB/Koha/Exemplaire.php
@@ -19,10 +19,12 @@
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
 class Class_WebService_SIGB_Koha_Exemplaire extends Class_WebService_SIGB_Exemplaire {
-  protected $_endommage = false;
-  protected $_retire    = false;
-  protected $_perdu     = false;
-  protected $_loanable = true;
+  protected
+    $_endommage = false,
+    $_retire    = false,
+    $_perdu     = false,
+    $_loanable = true,
+    $_require_calendar_hold = false;
 
   /**
    * @param bool $flag
@@ -34,6 +36,20 @@ class Class_WebService_SIGB_Koha_Exemplaire extends Class_WebService_SIGB_Exempl
   }
 
 
+  /**
+   * @return bool
+   */
+  public function requiresCalendarHold() {
+    return $this->_require_calendar_hold;
+  }
+
+
+  public function doRequiresCalendarHold() {
+    $this->_require_calendar_hold = true;
+    return $this;
+  }
+
+
   /**
    *
    * @return bool
diff --git a/library/Class/WebService/SIGB/Koha/GetRecordsResponseReader.php b/library/Class/WebService/SIGB/Koha/GetRecordsResponseReader.php
index 6caa347f7fa4056773f1bb2e95bd1781bb1fd610..45f9323693063abc6347db0dec6cd296c24c80b0 100644
--- a/library/Class/WebService/SIGB/Koha/GetRecordsResponseReader.php
+++ b/library/Class/WebService/SIGB/Koha/GetRecordsResponseReader.php
@@ -22,11 +22,17 @@
 class Class_WebService_SIGB_Koha_GetRecordsResponseReader {
   use Class_WebService_SIGB_Koha_TraitFormat;
   use Trait_Translator;
+
   static protected $_default_reader;
 
-  protected $_record, $_item, $_holds = [],
-    $_not_for_loan_status = [];
-  protected $_cannot_hold_available = false;
+  protected
+    $_record,
+    $_item,
+    $_holds = [],
+    $_not_for_loan_status = [],
+    $_grouped_holds_itypes = [],
+    $_cannot_hold_available = false;
+
 
   public static function newInstance() {
     return (new self())->setNotForLoanStatus();
@@ -65,6 +71,18 @@ class Class_WebService_SIGB_Koha_GetRecordsResponseReader {
   }
 
 
+  public function setWithdrawnMapping($mapping) {
+    $this->_withdrawn_mapping = $mapping;
+    return $this;
+  }
+
+
+  public function setGroupedHoldsITypes($itypes) {
+    $this->_grouped_holds_itypes = $itypes;
+    return $this;
+  }
+
+
   public function allowAvailableDocumentReservation() {
     return !$this->_cannot_hold_available;
   }
@@ -112,12 +130,24 @@ class Class_WebService_SIGB_Koha_GetRecordsResponseReader {
       ->_handleItemLost()
       ->_handleItemDamaged()
       ->_handleTransfer()
+      ->_handleIType()
       ;
 
     $this->_record->addExemplaire($this->_item);
   }
 
 
+  protected function _handleIType() {
+    if (!$this->_hasChild('itype', $this->_xml_item))
+      return $this;
+
+    if (in_array((string)$this->_xml_item->itype, $this->_grouped_holds_itypes))
+      $this->_item->doRequiresCalendarHold();
+
+    return $this;
+  }
+
+
   protected function _handleItemBarCode() {
     if ($this->_hasChild('barcode', $this->_xml_item))
       $this->_item->setCodeBarre((string)$this->_xml_item->barcode);
@@ -175,14 +205,23 @@ class Class_WebService_SIGB_Koha_GetRecordsResponseReader {
     return $this;
   }
 
+
   protected function _handleWithdrawnNamed($name) {
     if (!$this->_hasChild($name, $this->_xml_item))
       return $this;
 
-    if ('0' != (string)$this->_xml_item->{$name})
-      $this->_item
-        ->setRetire(true)
-        ->setDisponibilitePilonne();
+    $withdrawn = (string)$this->_xml_item->{$name};
+    if ('0' === $withdrawn)
+      return $this;
+
+    $this->_item->setRetire(true);
+
+    if (isset($this->_withdrawn_mapping[$withdrawn])) {
+      $this->_item->setDisponibilite($this->_withdrawn_mapping[$withdrawn]);
+      return $this;
+    }
+
+    $this->_item->setDisponibilitePilonne();
 
     return $this;
   }
diff --git a/library/Class/WebService/SIGB/Koha/HoldsReader.php b/library/Class/WebService/SIGB/Koha/HoldsReader.php
new file mode 100644
index 0000000000000000000000000000000000000000..82192e62bf6e135a711f7ee8bd707fd621e9a74a
--- /dev/null
+++ b/library/Class/WebService/SIGB/Koha/HoldsReader.php
@@ -0,0 +1,43 @@
+<?php
+/**
+ * Copyright (c) 2012-2019, 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_WebService_SIGB_Koha_HoldsReader {
+  protected $_item;
+
+  public function __construct($item) {
+    $this->_item = $item;
+  }
+
+
+  public function parse($json) {
+    return ($datas = json_decode($json)) ?
+      array_map([$this, '_parseOne'], $datas->holds) : [];
+  }
+
+
+  protected function _parseOne($data) {
+    return (new Class_WebService_SIGB_Koha_Reservation($data->reserve_id,
+                                                      $this->_item))
+      ->setReserveDate($data->reservedate)
+      ->setExpirationDate($data->expirationdate);
+  }
+}
diff --git a/library/Class/WebService/SIGB/Koha/PatronInfoReader.php b/library/Class/WebService/SIGB/Koha/PatronInfoReader.php
index 4e2af03a50dc45dcff5a10a4f0451be4408186e6..a841c99fa855ba79269befdfefccc21781b359ae 100644
--- a/library/Class/WebService/SIGB/Koha/PatronInfoReader.php
+++ b/library/Class/WebService/SIGB/Koha/PatronInfoReader.php
@@ -22,19 +22,36 @@
 class Class_WebService_SIGB_Koha_PatronInfoReader extends Class_WebService_SIGB_AbstractILSDIPatronInfoReader {
   use Class_WebService_SIGB_Koha_TraitFormat, Trait_Translator;
 
-  protected $_current_exemplaire_operation;
+  protected
+    $_current_exemplaire_operation,
+    $_grouped_holds_itypes = [];
 
   public static function newInstance() {
     return new self();
   }
 
+
+  public function getHoldClassName() {
+    return 'Class_WebService_SIGB_Koha_Reservation';
+  }
+
+
+  public function setGroupedHoldsITypes($itypes) {
+    $this->_grouped_holds_itypes = $itypes;
+    return $this;
+  }
+
+
   public function endBorrowerNumber($id) {
     $this->getEmprunteur()->setId($id);
   }
 
+
   public function startHold($attributes) {
     parent::startHold($attributes);
-    $this->_currentHold->setEtat($this->_('En attente'));
+
+    $this->_currentHold
+      ->setGroupedHoldsITypes($this->_grouped_holds_itypes);
   }
 
 
@@ -130,11 +147,32 @@ class Class_WebService_SIGB_Koha_PatronInfoReader extends Class_WebService_SIGB_
     $this->_currentHold->setId((int)$data);
   }
 
+
+
+  public function endStatus($data) {
+    $this->_currentHold->setKohaStatus($data);
+  }
+
+
   public function endFound($data) {
-    if ($data == 'W') {
-      $this->_currentHold->setEtat($this->_('Exemplaire mis de côté'));
+    if ($data == 'W')
       $this->_currentHold->setWaitingToBePulled();
-    }
+  }
+
+
+  public function endReserveDate($data) {
+    $this->_currentHold->setReserveDate($data);
+  }
+
+
+  public function endExpirationDate($data) {
+    $this->_currentHold->setExpirationDate($data);
+  }
+
+
+  public function endIType($data) {
+    if ($this->_xml_parser->inParents('hold'))
+      $this->_currentHold->setIType($data);
   }
 }
 
diff --git a/library/Class/WebService/SIGB/Koha/Reservation.php b/library/Class/WebService/SIGB/Koha/Reservation.php
new file mode 100644
index 0000000000000000000000000000000000000000..2da8526820acf8f041021e8eeedc527a805b7818
--- /dev/null
+++ b/library/Class/WebService/SIGB/Koha/Reservation.php
@@ -0,0 +1,97 @@
+<?php
+/**
+ * Copyright (c) 2012-2019, 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_WebService_SIGB_Koha_Reservation extends Class_WebService_SIGB_Reservation {
+  use Trait_Translator;
+  protected
+    $_grouped_holds_itypes = [],
+    $_koha_status,
+    $_reserve_date,
+    $_expiration_date,
+    $_itype;
+
+  public function setGroupedHoldsITypes($itypes) {
+    $this->_grouped_holds_itypes = $itypes;
+    return $this;
+  }
+
+
+  public function setKohaStatus($status) {
+    $this->_koha_status = $status;
+    return $this;
+  }
+
+
+  public function setIType($itype) {
+    $this->_itype = $itype;
+    return $this;
+  }
+
+
+  public function setReserveDate($date) {
+    $this->_reserve_date = $date;
+    return $this;
+  }
+
+
+  public function getReserveDate() {
+    return $this->_reserve_date;
+  }
+
+
+  public function setExpirationDate($date) {
+    $this->_expiration_date = $date;
+    return $this;
+  }
+
+
+  public function getExpirationDate() {
+    return $this->_expiration_date;
+  }
+
+
+  public function isGroupedHold() {
+    return in_array($this->_itype, $this->_grouped_holds_itypes);
+  }
+
+
+  public function getEtat() {
+    if ($this->waiting_to_be_pulled)
+      return $this->_('Exemplaire mis de côté');
+
+    if (!$this->isGroupedHold())
+      return $this->_('En attente');
+
+    $message = ($this->_koha_status === '1')
+      ? $this->_('En attente')
+      : $this->_('En attente de validation');
+
+    return $message . '. ' . $this->renderRange();
+  }
+
+
+  public function renderRange() {
+    return $this->_('Du %s au %s',
+                    strftime('%d %B %Y', strtotime($this->_reserve_date)),
+                    strftime('%d %B %Y', strtotime($this->_expiration_date)));
+  }
+}
diff --git a/library/Class/WebService/SIGB/Koha/RestfulService.php b/library/Class/WebService/SIGB/Koha/RestfulService.php
index 6101926c49341e7eadf11dd0b8d6fb661f482695..585d47ff830192b1eecb15ab587e5ff5999972fd 100644
--- a/library/Class/WebService/SIGB/Koha/RestfulService.php
+++ b/library/Class/WebService/SIGB/Koha/RestfulService.php
@@ -99,6 +99,24 @@ class Class_WebService_SIGB_Koha_RestfulService
   }
 
 
+  public function holdsForItem($item) {
+    $json = $this->restfulGet('biblio/' . $item->getIdOrigine() . '/holds');
+
+
+    if (false === strpos($json, 'holds')) {
+      $response =  $this->_error($this->_('Échec de la connexion au webservice, le SIGB a répondu "%s"',
+                                    trim($json)));
+      $response['holds'] = [];
+      return $response;
+    }
+
+    $holds = (new Class_WebService_SIGB_Koha_HoldsReader($item))->parse($json);
+    $response = $this->_success();
+    $response['holds'] = $holds;
+    return $response;
+  }
+
+
   public function suggest($suggestion) {
     $doc_type = Class_TypeDoc::find($suggestion->getDocType());
     $doctype_label = $doc_type
diff --git a/library/Class/WebService/SIGB/Koha/Service.php b/library/Class/WebService/SIGB/Koha/Service.php
index ea5e374369dda3cd05400fdcaa7b524fc25946d7..951825747e2da80fb7a440b55e0114b4e3389337 100644
--- a/library/Class/WebService/SIGB/Koha/Service.php
+++ b/library/Class/WebService/SIGB/Koha/Service.php
@@ -25,7 +25,11 @@ class Class_WebService_SIGB_Koha_Service extends Class_WebService_SIGB_AbstractR
     $pre_registration = false,
     $interdire_resa_doc_dispo = false,
     $restful = false,
-    $codification_disponibilites = [];
+    $codification_disponibilites = [],
+    $_withdrawn_mapping = [],
+    $_grouped_holds_itypes = [],
+    $_bundled_holds_minimal_duration = 0,
+    $_bundled_holds_maximal_duration = 0;
 
   public static function newInstance() {
     return new static();
@@ -97,6 +101,34 @@ class Class_WebService_SIGB_Koha_Service extends Class_WebService_SIGB_AbstractR
    */
   public function setCodificationDisponibilites($codif) {
     $this->codification_disponibilites = $codif;
+    return $this;
+  }
+
+
+  public function setWithdrawnMapping($mapping) {
+    $this->_withdrawn_mapping = $mapping;
+    return $this;
+  }
+
+
+  public function setBundledHoldsMinimalDuration($duration) {
+    $this->_bundled_holds_minimal_duration = $duration;
+    return $this;
+  }
+
+
+  public function setBundledHoldsMaximalDuration($duration) {
+    $this->_bundled_holds_maximal_duration = $duration;
+    return $this;
+  }
+
+
+  /**
+   * @param array itypes
+   */
+  public function setGroupedHoldsITypes($itypes) {
+    $this->_grouped_holds_itypes = $itypes;
+    return $this;
   }
 
 
@@ -105,7 +137,8 @@ class Class_WebService_SIGB_Koha_Service extends Class_WebService_SIGB_AbstractR
                                       'show_contact' => 1,
                                       'show_loans' => 1,
                                       'show_holds' => 1],
-                                     Class_WebService_SIGB_Koha_PatronInfoReader::newInstance());
+                                     Class_WebService_SIGB_Koha_PatronInfoReader::newInstance()
+                                     ->setGroupedHoldsITypes($this->_grouped_holds_itypes));
   }
 
 
@@ -151,7 +184,10 @@ class Class_WebService_SIGB_Koha_Service extends Class_WebService_SIGB_AbstractR
   }
 
 
-  public function reserverExemplaire($user, $exemplaire, $code_annexe) {
+  public function reserverExemplaire($user, $exemplaire, $code_annexe, $reservedate = null , $expirationdate = null) {
+    if ($exemplaire->getSigbExemplaire()->requiresCalendarHold())
+      return $this->_holdCalendar($user, $exemplaire, $code_annexe, $reservedate, $expirationdate);
+
     if (1 == Class_AdminVar::get('KOHA_MULTI_SITES'))
       return $this->holdItem($user, $exemplaire, $code_annexe);
 
@@ -162,6 +198,46 @@ class Class_WebService_SIGB_Koha_Service extends Class_WebService_SIGB_AbstractR
   }
 
 
+  public function holdsForItem($item) {
+    if (!$this->restful) {
+      $response = $this->_error($this->_('Koha Restful désactivé'));
+      $response['holds'] = [];
+      return $response;
+    }
+
+    return $this
+      ->getRestfulService()
+      ->holdsForItem($item);
+  }
+
+
+  protected function _holdCalendar( $user, $exemplaire, $code_annexe, $reservedate, $expirationdate) {
+    if (!$reservedate || !$expirationdate)
+      throw new Class_WebService_SIGB_RequiresCalendarHoldException();
+
+    $duration = strtotime($expirationdate) - strtotime($reservedate);
+    if ($duration < strtotime($this->_bundled_holds_minimal_duration . ' days', 0))
+      return ['statut' => false,
+              'erreur' => $this->_('Durée minimale requise: %s jours',
+                                   $this->_bundled_holds_minimal_duration)];
+
+    if ($duration > strtotime($this->_bundled_holds_maximal_duration . ' days', 0))
+      return ['statut' => false,
+              'erreur' => $this->_('Durée maximale autorisée: %s jours',
+                                   $this->_bundled_holds_maximal_duration)];
+
+    return $this->ilsdiHoldItem(
+                                $this->_setPickupLocation(['patron_id' => $this->_getUserPatronId($user),
+                                                           'bib_id' => $exemplaire->getIdOrigine(),
+                                                           'item_id' => $exemplaire->getSubfield(9),
+                                                           'needed_before_date' => $reservedate,
+                                                           'pickup_expiry_date' => $expirationdate],
+                                                          $code_annexe),
+                                 'code');
+
+  }
+
+
   protected function _tryHoldItem($user, $exemplaire, $code_annexe) {
     $response = $this->holdItem($user, $exemplaire, $code_annexe);
 
@@ -178,6 +254,7 @@ class Class_WebService_SIGB_Koha_Service extends Class_WebService_SIGB_AbstractR
   }
 
 
+
   protected function _holdTitle($user, $exemplaire, $code_annexe) {
     return $this->ilsdiHoldTitle(
                                  $this->_setPickupLocation(['patron_id' => $this->_getUserPatronId($user),
@@ -307,7 +384,9 @@ class Class_WebService_SIGB_Koha_Service extends Class_WebService_SIGB_AbstractR
   protected function _getReader() {
     return Class_WebService_SIGB_Koha_GetRecordsResponseReader::newInstance()
       ->setCodificationDisponibilites($this->codification_disponibilites)
-      ->setInterdireResaDocDispo($this->interdire_resa_doc_dispo);
+      ->setWithdrawnMapping($this->_withdrawn_mapping)
+      ->setInterdireResaDocDispo($this->interdire_resa_doc_dispo)
+      ->setGroupedHoldsITypes($this->_grouped_holds_itypes);
   }
 
 
diff --git a/library/Class/WebService/SIGB/Opsys/Service.php b/library/Class/WebService/SIGB/Opsys/Service.php
index 29eb6cdecb33592176edb5696ee59b5b125134d9..862ac158ddc466a556178bb48ffb4099afdaf452 100644
--- a/library/Class/WebService/SIGB/Opsys/Service.php
+++ b/library/Class/WebService/SIGB/Opsys/Service.php
@@ -249,12 +249,12 @@ class Class_WebService_SIGB_Opsys_Service extends Class_WebService_SIGB_Abstract
     $entite_result = $this->search_client->EmprListerEntite(EmprListerEntite::infos($this->guid));
 
     if ($date_fin_abonnement = $entite_result->findAttribute('FinAbo')) {
-      $date_fin_abonnement = implode('-', array_reverse(explode('/', $date_fin_abonnement)));
+      $date_fin_abonnement = Class_Date::frToIso($date_fin_abonnement);
       $emprunteur->setEndDate($date_fin_abonnement);
     }
 
     if ($date_naissance = $entite_result->findAttribute('DateNaissance')) {
-      $date_naissance = implode('-', array_reverse(explode('/', $date_naissance)));
+      $date_naissance = Class_Date::frToIso($date_naissance);
       $emprunteur->setDateNaissance($date_naissance);
     }
 
diff --git a/library/Class/WebService/SIGB/Orphee.php b/library/Class/WebService/SIGB/Orphee.php
index c302bc725822a09d2c5e1618863fc84ea1a2a10b..d9ceca0f22007831cb1ebe2361d8e0e39547b6fe 100644
--- a/library/Class/WebService/SIGB/Orphee.php
+++ b/library/Class/WebService/SIGB/Orphee.php
@@ -16,7 +16,7 @@
  *
  * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
  * along with BOKEH; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA 
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
 
 class Class_WebService_SIGB_Orphee {
@@ -25,9 +25,12 @@ class Class_WebService_SIGB_Orphee {
   public static function getService($params){
     if (!isset(self::$service)) {
       $instance = new self();
-      self::$service = Class_WebService_SIGB_Orphee_Service::getService($params['url_serveur'], 
-      isset($params['key']) ? $params['key'] : null,
-      $params['allow_hold_available_items'] ? $params['allow_hold_available_items'] : null);
+      self::$service = Class_WebService_SIGB_Orphee_Service::getService($params['url_serveur'],
+                                                                        isset($params['key']) ? $params['key'] : null,
+                                                                        $params['allow_hold_available_items'] ? $params['allow_hold_available_items'] : null);
+
+      if (isset($params['hold_mode']) && (Class_WebService_SIGB_Orphee_Service::HOLD_MODE_ITEM === (int)$params['hold_mode']))
+        self::$service->beHoldModeItem();
     }
 
     return self::$service;
diff --git a/library/Class/WebService/SIGB/Orphee/GetLstRsvResponseReader.php b/library/Class/WebService/SIGB/Orphee/GetLstRsvResponseReader.php
index 106e0f054cc97bcf9311d7152c29fa38237d542b..6c90e6c3c69222cc9f6204b3ef6c001edb55bd80 100644
--- a/library/Class/WebService/SIGB/Orphee/GetLstRsvResponseReader.php
+++ b/library/Class/WebService/SIGB/Orphee/GetLstRsvResponseReader.php
@@ -20,6 +20,8 @@
  */
 
 class Class_WebService_SIGB_Orphee_GetLstRsvResponseReader extends Class_WebService_SIGB_AbstractXMLNoticeReader {
+  use Trait_Translator;
+
   /** @var Class_WebService_XMLParser */
   protected $_xml_parser;
 
@@ -57,6 +59,15 @@ class Class_WebService_SIGB_Orphee_GetLstRsvResponseReader extends Class_WebServ
   }
 
 
+  public function endDocument() {
+    if ($this->_date_fin)
+      $this->_etat = $this->_etat . ' ' . $this->_('jusqu\'au %s', $this->_date_fin);
+
+    $this->_current_reservation->setEtat($this->_etat);
+    $this->_date_fin = $this->_etat = '';
+  }
+
+
   public function endNo_Ntc($data) {
     $this->_current_reservation->setId(trim($data));
     $this->_current_reservation->getExemplaire()->setNoNotice(sprintf('frOr%010d', trim($data)));
@@ -92,7 +103,7 @@ class Class_WebService_SIGB_Orphee_GetLstRsvResponseReader extends Class_WebServ
   public function endSit($data) {
     $status = trim($data);
     if (!is_numeric($status)) {//compatibility with Orphée WS version < version K
-      $this->_current_reservation->setEtat($status);
+      $this->_etat = $status;
     }
 
     if ($status == 'affectée' || $status == '2')
@@ -100,10 +111,16 @@ class Class_WebService_SIGB_Orphee_GetLstRsvResponseReader extends Class_WebServ
   }
 
 
+  public function endDate_Fin($data) {
+    $this->_date_fin = $data;
+  }
+
+
   public function endLib_Sit($data) {
-    $this->_current_reservation->setEtat(trim($data));
+    $this->_etat = trim($data);
   }
 
+
   public function endRang($data) {
     $this->_current_reservation->setRang(trim($data));
   }
diff --git a/library/Class/WebService/SIGB/Orphee/Service.php b/library/Class/WebService/SIGB/Orphee/Service.php
index 1441ba487ad41272a8f19c24b752cf2e620cf1a6..af814ded2498d1016bc068f461f67e30df1aff70 100644
--- a/library/Class/WebService/SIGB/Orphee/Service.php
+++ b/library/Class/WebService/SIGB/Orphee/Service.php
@@ -20,12 +20,18 @@
  */
 
 class Class_WebService_SIGB_Orphee_Service extends Class_WebService_SIGB_AbstractService {
-  protected $_search_client;
-  protected $_session_strategy;
-  protected $_wsdl;
-  protected $_key;
-  protected $_allow_hold_available_items;
-  protected $_soap_options;
+  const
+    HOLD_MODE_TITLE = 0,
+    HOLD_MODE_ITEM = 1;
+
+  protected
+    $_search_client,
+    $_session_strategy,
+    $_wsdl,
+    $_key,
+    $_allow_hold_available_items,
+    $_soap_options,
+    $_hold_mode;
 
 
   public static function getService($wsdl, $key=null, $allow_hold_available_items=null) {
@@ -44,6 +50,18 @@ class Class_WebService_SIGB_Orphee_Service extends Class_WebService_SIGB_Abstrac
     $this->_key = $key;
     $this->_allow_hold_available_items = $allow_hold_available_items;
     $this->_soap_options = $soap_options;
+    $this->_hold_mode = static::HOLD_MODE_TITLE;
+  }
+
+
+  public function beHoldModeItem() {
+    $this->_hold_mode = static::HOLD_MODE_ITEM;
+    return $this;
+  }
+
+
+  public function isHoldModeItem() {
+    return $this->_hold_mode === static::HOLD_MODE_ITEM;
   }
 
 
@@ -245,24 +263,42 @@ class Class_WebService_SIGB_Orphee_Service extends Class_WebService_SIGB_Abstrac
   public function reserverExemplaire($user, $exemplaire, $code_annexe) {
     $notice_id = $this->removeOrpheeNoticePrefix($exemplaire->getIdOrigine());
 
+
+    return $this
+      ->withUserDo(
+                   $user,
+
+                   $notice_id,
+
+                   function ($id, $emprunteur) use ($code_annexe, $exemplaire) {
+                     return $this->_holdRecordOrItem($id, $emprunteur, $code_annexe, $exemplaire);
+                   },
+
+                   function ($result) {
+                     $datas = simplexml_load_string($result->getXml());
+                     return ($datas->msg->code == 1) ? false : (string)$datas->msg->libelle;
+                   });
+  }
+
+
+  protected function _holdRecordOrItem($id, $emprunteur, $code_annexe, $exemplaire) {
     $tome = $exemplaire->isPeriodique()
       ? $exemplaire->getSubfield('u')
       : 0;
 
-    return $this->withUserDo(
-      $user, $notice_id,
-      function ($id, $emprunteur) use ($code_annexe, $tome) {
-        if ($this->hasSetAdhDispoAnx() && (null !== $code_annexe))
-          $this->getSearchClient()
-            ->setAdhDispoAnx(setAdhDispoAnx::with($code_annexe));
+    $client = $this->getSearchClient();
 
-        return $this->getSearchClient()
-          ->RsvNtcAdh(RsvNtcAdh::withNoticeUserNo($id, $emprunteur->getId(), $tome));
-      },
-      function ($result) {
-        $datas = simplexml_load_string($result->getXml());
-        return ($datas->msg->code == 1) ? false : (string)$datas->msg->libelle;
-      });
+    if ($this->hasSetAdhDispoAnx() && (null !== $code_annexe))
+      $client->setAdhDispoAnx(setAdhDispoAnx::with($code_annexe));
+
+    return $this->isHoldModeItem()
+      ? $client->RsvDmtAdh(RsvDmtAdh::withNoticeItemUser($id,
+                                                         $exemplaire->getSigbExemplaire()->getId(),
+                                                         $emprunteur->getId()))
+
+      : $client->RsvNtcAdh(RsvNtcAdh::withNoticeUserNo($id,
+                                                       $emprunteur->getId(),
+                                                       $tome));
   }
 
 
@@ -567,6 +603,21 @@ class RsvNtcAdh {
 }
 
 
+class RsvDmtAdh {
+  public $sntc; // string
+  public $dmt; // int
+  public $adh; // int
+
+  public static function withNoticeItemUser($sntc, $dmt, $adh) {
+    $instance = new self();
+    $instance->sntc = $sntc;
+    $instance->dmt = $dmt;
+    $instance->adh = $adh;
+    return $instance;
+  }
+}
+
+
 class RsvNtcAdhResponse {
   public $RsvNtcAdhResult; // string
 
@@ -583,6 +634,20 @@ class RsvNtcAdhResponse {
 }
 
 
+class RsvDmtAdhResponse {
+  public $RsvDmtAdhResult; // string
+
+  public static function withResult($xml) {
+    $instance = new self();
+    $instance->RsvDmtAdhResult = $xml;
+    return $instance;
+  }
+
+
+  public function getXml() {
+    return Class_WebService_SIGB_Orphee_XMLFilter::filter($this->RsvDmtAdhResult);
+  }
+}
 
 
 class SetPwdAdh {
diff --git a/library/Class/WebService/SIGB/RequiresCalendarHoldException.php b/library/Class/WebService/SIGB/RequiresCalendarHoldException.php
new file mode 100644
index 0000000000000000000000000000000000000000..e7df4da73507ab5b23bcf414cb64478738fe0e23
--- /dev/null
+++ b/library/Class/WebService/SIGB/RequiresCalendarHoldException.php
@@ -0,0 +1,23 @@
+<?php
+/**
+ * Copyright (c) 2012-2019, 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_WebService_SIGB_RequiresCalendarHoldException extends Exception {}
diff --git a/library/Class/WebService/SimpleWebClient.php b/library/Class/WebService/SimpleWebClient.php
index 5ec8ffc117230e3f8539a530c83e639aef7172f5..247b85e1e34890da985d86465d969dc158518433 100644
--- a/library/Class/WebService/SimpleWebClient.php
+++ b/library/Class/WebService/SimpleWebClient.php
@@ -78,6 +78,12 @@ class Class_WebService_SimpleWebClient {
 
 
   public function postRawData($url, $datas, $encoding, $options = []) {
+    return $this->_postRawData($url, $datas, $encoding, $options)
+                ->getBody();
+  }
+
+
+  protected function _postRawData($url, $datas, $encoding, $options) {
     $httpClient = $this->getHttpClient();
     $httpClient->resetParameters();
     $httpClient->setUri($url);
@@ -87,7 +93,12 @@ class Class_WebService_SimpleWebClient {
     if (isset($options['headers']))
       $httpClient->setHeaders($options['headers']);
 
-    return $httpClient->request()->getBody();
+    return $httpClient->request();
+  }
+
+
+  public function postRawDataResponse($url, $datas, $encoding, $options = []) {
+    return $this->_postRawData($url, $datas, $encoding, $options);
   }
 
 
@@ -104,5 +115,3 @@ class Class_WebService_SimpleWebClient {
       : null;
   }
 }
-
-?>
\ No newline at end of file
diff --git a/library/Trait/LastMessage.php b/library/Trait/LastMessage.php
new file mode 100644
index 0000000000000000000000000000000000000000..48b81119d0f8763cd49b49e724d02f30c2f3170d
--- /dev/null
+++ b/library/Trait/LastMessage.php
@@ -0,0 +1,35 @@
+<?php
+/**
+ * Copyright (c) 2012-2019, 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
+ */
+
+
+trait Trait_LastMessage {
+  protected $_last_message = '';
+
+  public function getLastMessage() {
+    return $this->_last_message;
+  }
+
+
+  protected function _error($message) {
+    $this->_last_message = $message;
+    return false;
+  }
+}
diff --git a/library/Trait/Observer.php b/library/Trait/Observer.php
new file mode 100644
index 0000000000000000000000000000000000000000..a102766285d4c308e4fab63f3464893be7661fbf
--- /dev/null
+++ b/library/Trait/Observer.php
@@ -0,0 +1,31 @@
+<?php
+/**
+ * Copyright (c) 2012-2019, 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
+ */
+
+
+trait Trait_Observer {
+  public function __call($name, $args) {
+    // allow call to any notifyXXXXX method without error
+    if ('notify' === substr($name, 0, 6))
+      return $this;
+
+    throw new RuntimeException('Call to unknown method ' . get_class($this) . '::' . $name);
+  }
+}
diff --git a/library/Trait/SearchCriteriaVisitor.php b/library/Trait/SearchCriteriaVisitor.php
index f52cd242d2a5cc3e7c15c54ff4adbaa0fab29cb9..71478b94c177e9ac0f7b5d6977e9e76467b522e6 100644
--- a/library/Trait/SearchCriteriaVisitor.php
+++ b/library/Trait/SearchCriteriaVisitor.php
@@ -55,4 +55,6 @@ trait Trait_SearchCriteriaVisitor {
   public function visitLimit($limit) {}
 
   public function visitBookmarkedSearch($bookmark, $version) {}
+
+  public function visitRecordType($type) {}
 }
\ No newline at end of file
diff --git a/library/ZendAfi/Acl/AdminControllerGroup.php b/library/ZendAfi/Acl/AdminControllerGroup.php
index ba103b09e36bf4e0c5abc73459dd913a39f31729..08c2b62b14c1b0596df128a5ec8ca5e8507c79e4 100644
--- a/library/ZendAfi/Acl/AdminControllerGroup.php
+++ b/library/ZendAfi/Acl/AdminControllerGroup.php
@@ -85,6 +85,7 @@ class ZendAfi_Acl_AdminControllerGroup {
                           'custom-fields-report' => Class_AdminVar::isCustomFieldsReportEnabled(),
                           'usergroup-agenda' => Class_AdminVar::isRendezVousEnabled(),
                           'rendez-vous' => Class_AdminVar::isRendezVousEnabled(),
+                          'federation-reviews' => Class_AdminVar::isFederationEnabled(),
                           ];
 
     $this->_activated = array_merge($this->_activated,
diff --git a/library/ZendAfi/Acl/AdminControllerRoles.php b/library/ZendAfi/Acl/AdminControllerRoles.php
index 28e56a51fabf5231a8f54123941c3c32352294af..a891142242831ed029413c75e70e6ef875bf4a0d 100644
--- a/library/ZendAfi/Acl/AdminControllerRoles.php
+++ b/library/ZendAfi/Acl/AdminControllerRoles.php
@@ -100,6 +100,7 @@ class ZendAfi_Acl_AdminControllerRoles extends Zend_Acl {
     $this->add(new Zend_Acl_Resource('search-form'));
     $this->add(new Zend_Acl_Resource('usergroup-agenda'));
     $this->add(new Zend_Acl_Resource('rendez-vous'));
+    $this->add(new Zend_Acl_Resource('journal'));
 
     $codifications = ['codification-browser',
                       'thesauri',
@@ -204,6 +205,7 @@ class ZendAfi_Acl_AdminControllerRoles extends Zend_Acl {
     $this->deny('modo_portail','systeme/phpinfo');
     $this->deny('modo_portail','usergroup-agenda');
     $this->deny('modo_portail','rendez-vous');
+    $this->deny('modo_portail','journal');
     foreach($codifications as $controller)
       $this->deny('modo_portail', $controller);
 
diff --git a/library/ZendAfi/Controller/Action.php b/library/ZendAfi/Controller/Action.php
index f0f9a612ff01c28aded62d2aaffedc759392ac5e..7656aa5dbf25d0be44d7ab8461d493cda96591ec 100644
--- a/library/ZendAfi/Controller/Action.php
+++ b/library/ZendAfi/Controller/Action.php
@@ -24,7 +24,8 @@ class ZendAfi_Controller_Action extends Zend_Controller_Action {
 
   protected
     $_definitions,
-    $_plugins;
+    $_plugins,
+    $_inspector;
 
 
   protected function _getActionPlugins() {
@@ -376,4 +377,48 @@ class ZendAfi_Controller_Action extends Zend_Controller_Action {
   public function getPluginsCollection() {
     return $this->_plugins;
   }
+
+
+  protected function _getActionPreferences() {
+    return Class_Profil::getCurrentProfil()
+      ->getCfgModulesPreferences($this->_request->getControllerName(),
+                                 $this->_request->getActionName());
+  }
+
+
+  protected function _inspectorButton($definition) {
+    $this->_getInspector()->addButton($definition);
+    return $this;
+  }
+
+
+  protected function _isInspecting() {
+    return $this->_getInspector()->isEnabled();
+  }
+
+
+  protected function _getInspector() {
+    if ($this->_inspector)
+      return $this->_inspector;
+
+    $ig = Zend_Controller_Front::getInstance()->getPlugin('ZendAfi_Controller_Plugin_InspectorGadget');
+
+    return $this->_inspector = $ig
+      ? $ig
+      : new ZendAfi_Controller_Action_NullInspector();
+  }
+}
+
+
+
+
+class ZendAfi_Controller_Action_NullInspector {
+  public function isEnabled() {
+    return false;
+  }
+
+
+  public function addButton($definition) {
+    return $this;
+  }
 }
diff --git a/library/ZendAfi/Controller/Action/Helper/Journal.php b/library/ZendAfi/Controller/Action/Helper/Journal.php
new file mode 100644
index 0000000000000000000000000000000000000000..307bd958153ccdaabf6fa49863ce9967cd33237a
--- /dev/null
+++ b/library/ZendAfi/Controller/Action/Helper/Journal.php
@@ -0,0 +1,34 @@
+<?php
+/**
+ * Copyright (c) 2012-2017, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class ZendAfi_Controller_Action_Helper_Journal
+  extends Zend_Controller_Action_Helper_Abstract{
+
+  public function journal($type, $model=null) {
+    return Class_Journal::factory($type, $model);
+  }
+
+
+  public function direct($type, $model=null) {
+    return $this->journal($type, $model);
+  }
+}
diff --git a/library/ZendAfi/Controller/Action/Helper/ListViewMode/Abstract.php b/library/ZendAfi/Controller/Action/Helper/ListViewMode/Abstract.php
index 6d983cca9ae77825ea10696b9d07260ca2323677..566dbe8e6e8bcc09218b33c11263b8c267504d1d 100644
--- a/library/ZendAfi/Controller/Action/Helper/ListViewMode/Abstract.php
+++ b/library/ZendAfi/Controller/Action/Helper/ListViewMode/Abstract.php
@@ -43,6 +43,12 @@ abstract class ZendAfi_Controller_Action_Helper_ListViewMode_Abstract
   }
 
 
+  protected function _initParams($params) {
+    $this->_params = $params;
+    return $this;
+  }
+
+
   public function getName() {
     return str_replace('ZendAfi_Controller_Action_Helper_', '', get_class($this));
   }
diff --git a/library/ZendAfi/Controller/Action/Helper/ListViewMode/Codification/Flat.php b/library/ZendAfi/Controller/Action/Helper/ListViewMode/Codification/Flat.php
index 8f69470b496cbc17834c8ef895e8a960c36a4541..2c6c5d958b18bee3bcd764130f1ba4019b04225d 100644
--- a/library/ZendAfi/Controller/Action/Helper/ListViewMode/Codification/Flat.php
+++ b/library/ZendAfi/Controller/Action/Helper/ListViewMode/Codification/Flat.php
@@ -26,7 +26,7 @@ class ZendAfi_Controller_Action_Helper_ListViewMode_Codification_Flat
   protected $_model_class;
 
   protected function _initParams($params) {
-    $this->_params = $params;
+    parent::_initParams($params);
     $this->_model_class = get_class($this->getModel());
   }
 
diff --git a/library/ZendAfi/Controller/Action/Helper/ListViewMode/Journal.php b/library/ZendAfi/Controller/Action/Helper/ListViewMode/Journal.php
new file mode 100644
index 0000000000000000000000000000000000000000..b58106630f71297f996a5c4ad058d3c2e1dbe980
--- /dev/null
+++ b/library/ZendAfi/Controller/Action/Helper/ListViewMode/Journal.php
@@ -0,0 +1,69 @@
+<?php
+/**
+ * Copyright (c) 2012-2017, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class ZendAfi_Controller_Action_Helper_ListViewMode_Journal
+  extends ZendAfi_Controller_Action_Helper_ListViewMode_Abstract {
+
+  public function ListViewMode_Journal($params) {
+    return parent::_initParams($params);
+  }
+
+
+  public function direct($params) {
+    return $this->ListViewMode_Journal($params);
+  }
+
+
+  public function isSearchEnabled() {
+    return false;
+  }
+
+
+  protected function _describeCategoriesIn($description) {
+    return $description;
+  }
+
+
+  protected function _describeItemsIn($description) {
+    return $description
+      ->addColumn($this->_('Date'), 'created_at')
+      ->addColumn($this->_('Type'), 'type')
+      ->setSorterServer();
+  }
+
+
+  public function getItems() {
+    $params = ['limitPage' => [$this->getPage(),  $this->_items_by_page],
+               'order' => $this->getOrder()];
+    return Class_Journal::findAllBy($params);
+  }
+
+
+  protected function getOrder() {
+    return $this->getParam('order', 'created_at desc');
+  }
+
+
+  protected function enabledSorter() {
+    return false;
+  }
+}
diff --git a/library/ZendAfi/Controller/Action/Helper/ListViewMode/Library.php b/library/ZendAfi/Controller/Action/Helper/ListViewMode/Library.php
index 0427097d0e9d33cfbf0bee1678bf994304d4d830..373cd3f43f6de3d1a8372decb941ecf9bd0964b8 100644
--- a/library/ZendAfi/Controller/Action/Helper/ListViewMode/Library.php
+++ b/library/ZendAfi/Controller/Action/Helper/ListViewMode/Library.php
@@ -23,8 +23,7 @@
 class ZendAfi_Controller_Action_Helper_ListViewMode_Library extends ZendAfi_Controller_Action_Helper_ListViewMode_Abstract {
 
   public function ListViewMode_Library($params) {
-    $this->_params = $params;
-    return $this;
+    return parent::_initParams($params);
   }
 
 
diff --git a/library/ZendAfi/Controller/Plugin/AdminAuth.php b/library/ZendAfi/Controller/Plugin/AdminAuth.php
index 871b912c036eeb26b836922f90947fe59766a943..7bd9242d2b366fa6a33e1a6c00f65fe8b097b193 100644
--- a/library/ZendAfi/Controller/Plugin/AdminAuth.php
+++ b/library/ZendAfi/Controller/Plugin/AdminAuth.php
@@ -46,6 +46,15 @@ class ZendAfi_Controller_Plugin_AdminAuth extends Zend_Controller_Plugin_Abstrac
       return;
     }
 
+    // activitypub
+    if ('activitypub' == $module
+        && !Class_AdminVar::isFederationServiceAvailable()) {
+      $request->setModuleName('activitypub')
+              ->setControllerName('review')
+              ->setActionName('unavailable');
+      return;
+    }
+
     // Entree dans opac on teste si le site a été désactivé
     if (Class_AdminVar::get("SITE_OK") == "0" and $module == 'opac')  {
       $controller = 'index';
diff --git a/library/ZendAfi/Controller/Plugin/InspectorGadget.php b/library/ZendAfi/Controller/Plugin/InspectorGadget.php
index e139117a6d93d03b3a61bfd27fd85e1c6c0e5e48..d1a0a4bc6d74f0b7cc46b41436ca1822a5529779 100644
--- a/library/ZendAfi/Controller/Plugin/InspectorGadget.php
+++ b/library/ZendAfi/Controller/Plugin/InspectorGadget.php
@@ -68,7 +68,7 @@ class ZendAfi_Controller_Plugin_InspectorGadget extends Zend_Controller_Plugin_A
     if (!$this->isEnabled())
       return $params;
 
-    $params[self::PARAM_NAME] = 1;
+    $params[self::PARAM_NAME] = $this->_keep ? 'keep' : 1;
     return $params;
   }
 
diff --git a/library/ZendAfi/Controller/Plugin/Manager/Album.php b/library/ZendAfi/Controller/Plugin/Manager/Album.php
index cbb6d598b135ca996c923ede5ce23146b22be949..9c995329eb6e2992d036f4e59a7bb14ef9065b4d 100644
--- a/library/ZendAfi/Controller/Plugin/Manager/Album.php
+++ b/library/ZendAfi/Controller/Plugin/Manager/Album.php
@@ -445,7 +445,7 @@ class ZendAfi_Controller_Plugin_Manager_Album extends ZendAfi_Controller_Plugin_
     $frbr_multi = $values['frbr_multi'];
     unset($values['frbr_multi']);
 
-    $values['created_at'] = implode('-', array_reverse(explode('/', $values['created_at'])));
+    $values['created_at'] = Class_Date::frToIso($values['created_at']);
 
     $album->updateAttributes($values);
 
diff --git a/library/ZendAfi/Controller/Plugin/Manager/Section.php b/library/ZendAfi/Controller/Plugin/Manager/CosmoCodification.php
similarity index 76%
rename from library/ZendAfi/Controller/Plugin/Manager/Section.php
rename to library/ZendAfi/Controller/Plugin/Manager/CosmoCodification.php
index 2d6b5f38e7b5f8580c098db7f034281fb076083f..326dc4735d7a7ce27de166d118f6b1f100670712 100644
--- a/library/ZendAfi/Controller/Plugin/Manager/Section.php
+++ b/library/ZendAfi/Controller/Plugin/Manager/CosmoCodification.php
@@ -20,7 +20,9 @@
  */
 
 
-class ZendAfi_Controller_Plugin_Manager_Section extends ZendAfi_Controller_Plugin_Manager_ExternalManager {
+class ZendAfi_Controller_Plugin_Manager_CosmoCodification
+  extends ZendAfi_Controller_Plugin_Manager_ExternalManager {
+
   public function getActions($model) {
     return [['url' => ['action' => 'edit',
                        'id' => $model->getId()],
@@ -32,4 +34,17 @@ class ZendAfi_Controller_Plugin_Manager_Section extends ZendAfi_Controller_Plugi
              'icon' => 'delete',
              'label' => $this->_('Supprimer')]];
   }
+
+
+  protected function _getPost($key = null, $default = null) {
+    $post = parent::_getPost($key, $default);
+
+    if ($key)
+      return $post;
+
+    $rules = Class_Codification_Rules::newFromPost($post);
+    $post['regles'] = $rules->asString();
+
+    return $post;
+  }
 }
\ No newline at end of file
diff --git a/library/ZendAfi/Controller/Plugin/Manager/Emplacement.php b/library/ZendAfi/Controller/Plugin/Manager/Emplacement.php
new file mode 100644
index 0000000000000000000000000000000000000000..5e42f2841b864f2c259da30e65a55f92bf8bc513
--- /dev/null
+++ b/library/ZendAfi/Controller/Plugin/Manager/Emplacement.php
@@ -0,0 +1,29 @@
+<?php
+/**
+ * Copyright (c) 2012-2017, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class ZendAfi_Controller_Plugin_Manager_Emplacement
+  extends ZendAfi_Controller_Plugin_Manager_CosmoCodification {
+
+  protected function _updateNewModel($model) {
+    return $model->setRegles('995$k/R');
+  }
+}
\ No newline at end of file
diff --git a/library/ZendAfi/Controller/Plugin/Manager/ExternalManager.php b/library/ZendAfi/Controller/Plugin/Manager/ExternalManager.php
index faf4b3abc5f0b8cf147c5a47fe1cb30a36650714..edec3d0768cbffe9b4b079efc6c809939f99e156 100644
--- a/library/ZendAfi/Controller/Plugin/Manager/ExternalManager.php
+++ b/library/ZendAfi/Controller/Plugin/Manager/ExternalManager.php
@@ -20,7 +20,16 @@
  */
 
 
-class ZendAfi_Controller_Plugin_Manager_ExternalManager extends ZendAfi_Controller_Plugin_Manager_Manager {
+class ZendAfi_Controller_Plugin_Manager_ExternalManager
+  extends ZendAfi_Controller_Plugin_Manager_Manager {
+
   protected function _addQuery() {
   }
+
+  protected function _redirectToEdit($model) {
+    $url = '/cosmo/'.$this->_request->getControllerName().'/edit/id/' . $model->getId();
+    return ('1' === $this->_getParam('styles_reload'))
+      ? $this->_redirect($url)
+      : $this->_redirectClose($url);
+  }
 }
\ No newline at end of file
diff --git a/library/ZendAfi/Controller/Plugin/Manager/Opening.php b/library/ZendAfi/Controller/Plugin/Manager/Opening.php
index d599b3760ff7f7056ee78ce01572c9506d6446b3..60e7a96b1a2bec92236301740a0d7a9d2ff5bf89 100644
--- a/library/ZendAfi/Controller/Plugin/Manager/Opening.php
+++ b/library/ZendAfi/Controller/Plugin/Manager/Opening.php
@@ -80,7 +80,7 @@ class ZendAfi_Controller_Plugin_Manager_Opening extends ZendAfi_Controller_Plugi
 
 
   protected function _getSQLDateFrom($human_date) {
-    $date = implode('-', array_reverse(explode('/', $human_date)));
+    $date = Class_Date::frToIso($human_date);
     return strtotime($date) > 0
       ? $date
       : null;
diff --git a/library/ZendAfi/Controller/Plugin/Manager/SessionActivity.php b/library/ZendAfi/Controller/Plugin/Manager/SessionActivity.php
index aa1778ffcd8fe13bff590472e4b914fed3f9dabc..6b3493861c97f84ad67799eda7186c09dfe795a4 100644
--- a/library/ZendAfi/Controller/Plugin/Manager/SessionActivity.php
+++ b/library/ZendAfi/Controller/Plugin/Manager/SessionActivity.php
@@ -30,7 +30,7 @@ class ZendAfi_Controller_Plugin_Manager_SessionActivity extends ZendAfi_Controll
 
 
   protected function _readPostDate($date) {
-    return implode('-', array_reverse(explode('/', $date)));
+    return Class_Date::frToIso($date);
   }
 
 
diff --git a/library/ZendAfi/Controller/Plugin/ResourceDefinition/Emplacement.php b/library/ZendAfi/Controller/Plugin/ResourceDefinition/Emplacement.php
new file mode 100644
index 0000000000000000000000000000000000000000..64843118882baf52b83ef78b3f259bd0793d3f6b
--- /dev/null
+++ b/library/ZendAfi/Controller/Plugin/ResourceDefinition/Emplacement.php
@@ -0,0 +1,67 @@
+<?php
+/**
+ * Copyright (c) 2012-2017, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class ZendAfi_Controller_Plugin_ResourceDefinition_Emplacement
+  extends ZendAfi_Controller_Plugin_ResourceDefinition_Abstract {
+
+  public function getDefinitions() {
+    return
+      ['model' => ['class' => 'Class_CodifEmplacement',
+                   'name' => 'emplacement',
+                   'order' => 'libelle',
+                   'model_id' => 'id_emplacement'],
+
+       'listViewMode' => ['helper_method' => 'ListViewMode_Codification_Flat',
+                          'label' => $this->_('Emplacements'),
+                          'controller' => 'emplacement-browser'],
+
+       'messages' => ['successful_save' => $this->_('L\'emplacement "%s" a été modifé'),
+                      'successful_add' => $this->_('L\'emplacement "%s" a été ajouté'),
+                      'successful_delete' => $this->_('L\'emplacement "%s" a été supprimé')],
+
+       'actions' => ['index' => ['title' => $this->_('Parcourir les emplacements')],
+                     'add' => ['title' => $this->_('Ajouter un emplacement')],
+                     'edit' => ['title' => $this->_('Modifier un emplacement')]],
+
+
+       'form_class_name' => 'ZendAfi_Form_Admin_Emplacement'];
+  }
+
+
+  public function getActions($model) {
+    if (!$model || 'cosmo' == $this->_request->getModuleName())
+      return [];
+
+    return [
+            ['url' => $this->_view->url(['module' => 'opac',
+                                         'controller' => 'recherche',
+                                         'action' => 'simple',
+                                         'facette' =>  $model->getFacetCode()
+                                         ],
+                                        null,
+                                        true),
+             'icon' => 'view',
+             'label' => $this->_('Voir les documents qui ont la facette "%s"',$model->getFacetCode()),
+             'anchorOptions' => ['target' => '_blank']
+      ]];
+  }
+}
\ No newline at end of file
diff --git a/library/ZendAfi/Controller/Plugin/ResourceDefinition/Genre.php b/library/ZendAfi/Controller/Plugin/ResourceDefinition/Genre.php
index 2167780236d0cbb38ff629318d05bff7c1d76497..ccc7a306486c762010ab6f4f6c3451be19c99f4e 100644
--- a/library/ZendAfi/Controller/Plugin/ResourceDefinition/Genre.php
+++ b/library/ZendAfi/Controller/Plugin/ResourceDefinition/Genre.php
@@ -27,18 +27,27 @@ class ZendAfi_Controller_Plugin_ResourceDefinition_Genre
     return
       ['model' => ['class' => 'Class_CodifGenre',
                    'name' => 'genre',
-                   'order' => 'libelle'],
+                   'order' => 'libelle',
+                   'model_id' => 'id_genre'],
 
        'listViewMode' => ['helper_method' => 'ListViewMode_Codification_Flat',
                           'label' => $this->_('Genres'),
                           'controller' => 'genre-browser'],
 
-       'actions' => ['index' => ['title' => $this->_('Parcourir les genres')]]];
+       'messages' => ['successful_save' => $this->_('Le genre "%s" a été modifé'),
+                      'successful_add' => $this->_('Le genre "%s" a été ajouté'),
+                      'successful_delete' => $this->_('Le genre "%s" a été supprimé')],
+
+       'actions' => ['index' => ['title' => $this->_('Parcourir les genres')],
+                     'add' => ['title' => $this->_('Ajouter un genre')],
+                     'edit' => ['title' => $this->_('Modifier un genre')]],
+
+       'form_class_name' => 'ZendAfi_Form_Admin_Genre'];
   }
 
 
   public function getActions($model) {
-    if(!$model)
+    if (!$model || 'cosmo' == $this->_request->getModuleName())
       return [];
 
     return [
diff --git a/library/ZendAfi/Controller/Plugin/ResourceDefinition/Journal.php b/library/ZendAfi/Controller/Plugin/ResourceDefinition/Journal.php
new file mode 100644
index 0000000000000000000000000000000000000000..15f210c74c4c96901e62d88660178762b6c93029
--- /dev/null
+++ b/library/ZendAfi/Controller/Plugin/ResourceDefinition/Journal.php
@@ -0,0 +1,36 @@
+<?php
+/**
+ * Copyright (c) 2012-2017, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class ZendAfi_Controller_Plugin_ResourceDefinition_Journal
+  extends ZendAfi_Controller_Plugin_ResourceDefinition_Abstract {
+
+  public function getDefinitions() {
+    return
+      ['model' => ['class' => 'Class_Journal',
+                   'name' => 'journal',
+                   'order' => 'created_at desc'],
+
+       'listViewMode' => ['helper_method' => 'ListViewMode_Journal'],
+
+       'actions' => ['index' => ['title' => $this->_('Journal d\'évènements')]]];
+  }
+}
\ No newline at end of file
diff --git a/library/ZendAfi/Controller/Plugin/ResourceDefinition/Section.php b/library/ZendAfi/Controller/Plugin/ResourceDefinition/Section.php
index 97d05585fb2202e598146c515e1bbe5b0bae9a99..733353ad917dc31452c30d050bcfe984673eede7 100644
--- a/library/ZendAfi/Controller/Plugin/ResourceDefinition/Section.php
+++ b/library/ZendAfi/Controller/Plugin/ResourceDefinition/Section.php
@@ -36,6 +36,10 @@ class ZendAfi_Controller_Plugin_ResourceDefinition_Section extends ZendAfi_Contr
                            'successful_add' => $this->_('La section "%s" a été ajoutée'),
                            'successful_delete' => $this->_('La section "%s" a été suppriméee')],
 
+            'actions' => ['index' => ['title' => $this->_('Parcourir les sections')],
+                          'add' => ['title' => $this->_('Ajouter une section')],
+                          'edit' => ['title' => $this->_('Modifier une section')]],
+
             'form_class_name' => 'ZendAfi_Form_Admin_Section'];
   }
 
diff --git a/library/ZendAfi/Form/Admin/AdminVarFactory.php b/library/ZendAfi/Form/Admin/AdminVarFactory.php
index 60faeae3124e217e761a539cb96db2f65fe979a1..8d45130554e8252c565c409a306c9d7cef57a59c 100644
--- a/library/ZendAfi/Form/Admin/AdminVarFactory.php
+++ b/library/ZendAfi/Form/Admin/AdminVarFactory.php
@@ -33,7 +33,8 @@ class ZendAfi_Form_Admin_AdminVarFactory {
      Class_AdminVar_Meta::TYPE_ENCODED_DATA => 'ZendAfi_Form_Admin_AdminVar_EncodedData',
      Class_AdminVar_Meta::TYPE_RAW_TEXT => 'ZendAfi_Form_Admin_AdminVar_RawText',
      Class_AdminVar_Meta::TYPE_COMBO => 'ZendAfi_Form_Admin_AdminVar_Combo',
-     Class_AdminVar_Meta::TYPE_EDITOR => 'ZendAfi_Form_Admin_AdminVar_Editor'
+     Class_AdminVar_Meta::TYPE_EDITOR => 'ZendAfi_Form_Admin_AdminVar_Editor',
+     Class_AdminVar_Meta::TYPE_CRYPT_KEY => 'ZendAfi_Form_Admin_AdminVar'
     ];
 
 
diff --git a/library/ZendAfi/Form/Admin/Codification.php b/library/ZendAfi/Form/Admin/Codification.php
new file mode 100644
index 0000000000000000000000000000000000000000..b9ffb2f1d590bcc137c6fd78fa71e2319efb7cc3
--- /dev/null
+++ b/library/ZendAfi/Form/Admin/Codification.php
@@ -0,0 +1,57 @@
+<?php
+/**
+ * Copyright (c) 2012-2017, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class ZendAfi_Form_Admin_Codification extends ZendAfi_Form {
+  public function init() {
+    parent::init();
+
+    $rules = new Class_Codification_Rules(null);
+
+    $this
+      ->addElement('text',
+                   'libelle',
+                   ['label' => $this->_('Libellé'),
+                    'required' => true,
+                    'allowEmpty' => false])
+
+      ->addElement('multiInput',
+                   'regles',
+                   ['label' => $this->_('Règles de reconnaissance'),
+                    'fields' => $rules->multiInputFields(),
+                    'deleteMessage' => $this->_('cette règle'),
+                    'fieldsValidators' => $rules->multiInputValidators(),
+                   ]);
+
+    $this->regles->setValidators([new ZendAfi_Validate_MultiAllOrNothing($this->regles)]);
+  }
+
+
+  public function populate($datas) {
+    parent::populate($datas);
+    if (!isset($datas['regles']))
+      return $this;
+
+    $this->regles->setValues(Class_Codification_Rules::multiInputValues($datas['regles']));
+
+    return $this;
+  }
+}
diff --git a/library/ZendAfi/Form/Admin/Emplacement.php b/library/ZendAfi/Form/Admin/Emplacement.php
new file mode 100644
index 0000000000000000000000000000000000000000..56376a1ea3518ad0f83bc14c11e3c05be8d81d80
--- /dev/null
+++ b/library/ZendAfi/Form/Admin/Emplacement.php
@@ -0,0 +1,36 @@
+<?php
+/**
+ * Copyright (c) 2012-2017, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class ZendAfi_Form_Admin_Emplacement extends ZendAfi_Form_Admin_Codification {
+  public function init() {
+    parent::init();
+
+    $this
+      ->addElement('select',
+                   'ne_pas_afficher',
+                   ['label' => $this->_('Affichage des exemplaires'),
+                    'multiOptions' => [0 => $this->_('Oui'),
+                                       1 => $this->_('Non')]])
+
+      ->addUniqDisplayGroup('emplacement');
+  }
+}
diff --git a/library/ZendAfi/Form/Admin/Genre.php b/library/ZendAfi/Form/Admin/Genre.php
new file mode 100644
index 0000000000000000000000000000000000000000..2673c52748c578398b8b826fe27731a552fd6a7c
--- /dev/null
+++ b/library/ZendAfi/Form/Admin/Genre.php
@@ -0,0 +1,29 @@
+<?php
+/**
+ * Copyright (c) 2012-2017, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class ZendAfi_Form_Admin_Genre extends ZendAfi_Form_Admin_Codification {
+  public function init() {
+    parent::init();
+
+    $this->addUniqDisplayGroup('genre');
+  }
+}
diff --git a/library/ZendAfi/Form/Admin/Section.php b/library/ZendAfi/Form/Admin/Section.php
index 3ba993e32b6f16acef79cf48b40f0f8f56dbb935..596679b4b6e5917fd9c8cd0c89d94a47602fd8b1 100644
--- a/library/ZendAfi/Form/Admin/Section.php
+++ b/library/ZendAfi/Form/Admin/Section.php
@@ -20,28 +20,15 @@
  */
 
 
-class ZendAfi_Form_Admin_Section extends ZendAfi_Form {
+class ZendAfi_Form_Admin_Section extends ZendAfi_Form_Admin_Codification {
   public function init() {
     parent::init();
 
     $this
-      ->addElement('text',
-                   'libelle',
-                   ['label' => $this->_('Libellé'),
-                    'required' => true,
-                    'allowEmpty' => false])
-      ->addElement('textarea',
-                   'regles',
-                   ['label' => $this->_('Règles de reconnaissance : Syntaxe : [zone$champ][signe][valeur1;valeur2;etc...] - Ex : 995$a = a . Signes : "=" égal - "/" commence par - "*" contient'),
-                    'required' => true,
-                    'allowEmpty' => false])
       ->addElement('checkbox',
                    'invisible',
                    ['label' => $this->_('Rejeter les exemplaires')])
-      ->addUniqDisplayGroup('section',
-                            ['legend' => $this->_('Formulaire de section')]);
-  }
-
 
+      ->addUniqDisplayGroup('section');
+  }
 }
-?>
\ No newline at end of file
diff --git a/library/ZendAfi/Form/AuthoritySearch.php b/library/ZendAfi/Form/AuthoritySearch.php
new file mode 100644
index 0000000000000000000000000000000000000000..59366814ef6af7ec8f615613c36ea33be0427c0a
--- /dev/null
+++ b/library/ZendAfi/Form/AuthoritySearch.php
@@ -0,0 +1,30 @@
+<?php
+/**
+ * Copyright (c) 2012-2019, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class ZendAfi_Form_AuthoritySearch extends ZendAfi_Form {
+  public function init() {
+    parent::init();
+    $this
+      ->addElement('text', 'expressionRecherche', [])
+      ->addElement('submit', 'run', ['label' => $this->_('Rechercher')]);
+  }
+}
diff --git a/library/ZendAfi/Form/Configuration/Widget/Search.php b/library/ZendAfi/Form/Configuration/Widget/Search.php
index 0fbf2901df14e89b6330727d25c42ddd3165530f..2bdf76d3bbad36c0cd3f75fab02cf5f8b491010b 100644
--- a/library/ZendAfi/Form/Configuration/Widget/Search.php
+++ b/library/ZendAfi/Form/Configuration/Widget/Search.php
@@ -82,6 +82,11 @@ class ZendAfi_Form_Configuration_Widget_Search
                    ['label' => $this->_('Tri du résultat de recherche'),
                     'multiOptions' => (new Class_CriteresRecherche())->getListeTris()])
 
+      ->addElement('select',
+                   'in_files',
+                   ['label' => $this->_('Mode de recherche'),
+                    'multiOptions' => (new Class_CriteresRecherche())->getSearchModeList()])
+
       ->addElement('comboProfils',
                    'profil_redirect',
                    ['label' => $this->_('Basculer automatiquement sur le profil'),
@@ -112,6 +117,7 @@ class ZendAfi_Form_Configuration_Widget_Search
     $this
       ->addToSelectionGroup(['type_doc',
                              'tri',
+                             'in_files',
                              'select_bib',
                              'select_annexe',
                              'select_doc',
diff --git a/library/ZendAfi/Form/ConsultationPickup.php b/library/ZendAfi/Form/ConsultationPickup.php
index f770833a714a980dfada6185d50c06b6cf7f0a4e..705c02284c672ddd2f669c58aec97f7e7741536c 100644
--- a/library/ZendAfi/Form/ConsultationPickup.php
+++ b/library/ZendAfi/Form/ConsultationPickup.php
@@ -27,8 +27,8 @@ class ZendAfi_Form_ConsultationPickup extends ZendAfi_Form {
     $this->setAction($this->getView()->url(['controller' => 'recherche', 'action' => 'consultation-pickup-ajax']))
          ->addElement('Radio', 'location', ['required' => true, 'allowEmpty' => false])
          ->addDisplayGroup(['location'], 'group', ['legend' => $this->_('Site de consultation')])
-         ->addElement('Submit', 'Valider')
-         ->addElement('Button', 'Annuler',
+         ->addElement('Submit', $this->_('Valider'))
+         ->addElement('Button', $this->_('Annuler'),
                    ['onclick' => 'opacDialogClose();return false;']);
 
   }
diff --git a/library/ZendAfi/Form/Cosmo/DataProfile.php b/library/ZendAfi/Form/Cosmo/DataProfile.php
index 887fbc0923149e96c91573e3e39211b1b472b070..5ec6bdfcea0c6c4455a1448bdf0d8d82e3e6883f 100644
--- a/library/ZendAfi/Form/Cosmo/DataProfile.php
+++ b/library/ZendAfi/Form/Cosmo/DataProfile.php
@@ -147,7 +147,7 @@ class ZendAfi_Form_Cosmo_DataProfile extends ZendAfi_Form {
       ->addDocTypeGroup(['label' => $this->_('Label'), 'zone' => $this->_('Zone exemplaire')])
       ->_recordNoveltyDate()
       ->_recordItemSerial()
-      ->_recordItemInterest();
+      ->_recordIndexation();
   }
 
 
@@ -197,7 +197,7 @@ class ZendAfi_Form_Cosmo_DataProfile extends ZendAfi_Form {
   }
 
 
-  protected function _recordItemInterest() {
+  protected function _recordIndexation() {
     return $this->addElement('multiInput',
                              'interests',
                              ['label' => $this->_('Prendre la zone centre d\'intérêts en'),
@@ -205,8 +205,17 @@ class ZendAfi_Form_Cosmo_DataProfile extends ZendAfi_Form {
                                            ['name' => 'interest_champ', 'label' => $this->_('Champ')]],
                               'fixed' => true])
 
-                ->addDisplayGroup(['interests'],
-                                  'item_interests_group',
+
+                ->addElement('multiInput',
+                             'index_files',
+                             ['label' => $this->_('Indexer le contenu des fichiers spécifiés en'),
+                              'fields' => [['name' => 'index_file_zone', 'label' => $this->_('Zone')],
+                                           ['name' => 'index_file_field', 'label' => $this->_('Champ')],
+                                           ['name' => 'index_file_uri_regex', 'label' => $this->_('Chemin du fichier (expression régulière)')]],
+                              'fixed' => true])
+
+                ->addDisplayGroup(['interests', 'index_files'],
+                                  'indexation_group',
                                   ['legend' => $this->_('Indexation')]);
   }
 
@@ -217,7 +226,8 @@ class ZendAfi_Form_Cosmo_DataProfile extends ZendAfi_Form {
                                  'champ_genre' => $this->_('genre'),
                                  'champ_emplacement' => $this->_('emplacement'),
                                  'champ_annexe' => $this->_('annexe'),
-                                 'champ_availability' => $this->_('disponilité')];
+                                 'champ_availability' => $this->_('disponibilité'),
+                                 'champ_bundle_id' => $this->_('identifiant de la notice de lot')];
 
     return $this
       ->addElement('text',
@@ -355,6 +365,12 @@ class ZendAfi_Form_Cosmo_DataProfile extends ZendAfi_Form {
   }
 
 
+  public function populateItemBundleId() {
+    $this->champ_bundle_id->setValue($this->_profile_prefs->getItemBundleIdField());
+    return $this;
+  }
+
+
   public function populateItemAvailability() {
     $this->champ_availability->setValue($this->_profile_prefs->getItemAvailability());
     return $this;
@@ -465,6 +481,12 @@ class ZendAfi_Form_Cosmo_DataProfile extends ZendAfi_Form {
   }
 
 
+  public function populateFileIndexation() {
+    $this->index_files->setValues($this->_profile_prefs->getFileIndexation());
+    return $this;
+  }
+
+
   public function setProfilePrefs($preferences) {
     $this->_profile_prefs = $preferences;
   }
diff --git a/library/ZendAfi/Form/Element/DateRangePicker.php b/library/ZendAfi/Form/Element/DateRangePicker.php
index 440b02e8146aca7470258e401dce3000cea09e50..6ea216d80d7225c8d330e7dede7cff5a7e3c0952 100644
--- a/library/ZendAfi/Form/Element/DateRangePicker.php
+++ b/library/ZendAfi/Form/Element/DateRangePicker.php
@@ -81,13 +81,16 @@ class ZendAfi_Form_Element_DateRangePicker extends Zend_Form_Element_Xhtml {
 
 
   public function isValid($value, $context=null) {
-    $datas = func_get_args()[1];
-
-    if(!isset($datas[$this->_start->getName()]) && !isset($datas[$this->_end->getName()]))
+    if (!isset($context[$this->_start->getName()]) && !isset($context[$this->_end->getName()]))
       return true;
 
-    return $this->_start->isValid($datas[$this->_start->getName()])
-      && $this->_end->isValid($datas[$this->_end->getName()]);
+    $start = $context[$this->_start->getName()];
+    $end = $context[$this->_end->getName()];
+
+    $start_valid = $this->_start->isValid($start);
+    $end_valid = $this->_end->isValid($end);
+
+    return $start_valid && $end_valid;
   }
 
 
diff --git a/library/ZendAfi/Form/Element/MultiInput.php b/library/ZendAfi/Form/Element/MultiInput.php
index 2784d04bc018b9b46a1b26fdb17690d9130e2707..bff5656b8428417d8d341b2e87bc3b200c8d4f53 100644
--- a/library/ZendAfi/Form/Element/MultiInput.php
+++ b/library/ZendAfi/Form/Element/MultiInput.php
@@ -57,7 +57,7 @@ class ZendAfi_Form_Element_MultiInput extends Zend_Form_Element {
   protected function validateFields($datas) {
     $valid = true;
     foreach($this->getFieldsValidators() as $field_name => $validators)
-      $valid = $valid && $this->_validateField($field_name, $validators, $datas);
+      $valid = $this->_validateField($field_name, $validators, $datas) && $valid;
 
     return $valid;
   }
@@ -66,7 +66,7 @@ class ZendAfi_Form_Element_MultiInput extends Zend_Form_Element {
   protected function _validateField($name, $validators, $datas) {
     $valid = true;
     foreach($this->_values[$name] as $k => $value)
-      $valid = $valid && $this->_validateValue($value, $k, $validators, $datas);
+      $valid = $this->_validateValue($value, $k, $validators, $datas) && $valid;
 
     return $valid;
   }
diff --git a/library/ZendAfi/Validate/ActivityPubEndpoint.php b/library/ZendAfi/Validate/ActivityPubEndpoint.php
new file mode 100644
index 0000000000000000000000000000000000000000..cdc823f6a47481e345c2df5371cd6f6b1335e40c
--- /dev/null
+++ b/library/ZendAfi/Validate/ActivityPubEndpoint.php
@@ -0,0 +1,40 @@
+<?php
+/**
+ * Copyright (c) 2012-2017, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class ZendAfi_Validate_ActivityPubEndpoint extends Zend_Validate_Abstract {
+  const INVALID_VALUE = 'invalidValue';
+
+  protected $_messageTemplates =
+    [
+     self::INVALID_VALUE => "'%value%' n'est pas un service de fédération valide."
+    ];
+
+  public function isValid($value) {
+    $this->_setValue((string)$value);
+
+    $service = new Class_WebService_ActivityPub((string)$value);
+    if ($service->isValid())
+      return true;
+
+    $this->_error(static::INVALID_VALUE);
+  }
+}
diff --git a/library/ZendAfi/Validate/MarcZone.php b/library/ZendAfi/Validate/MarcZone.php
new file mode 100644
index 0000000000000000000000000000000000000000..f98712c0275b6575b829cb7fbe3d91d6b79f82a0
--- /dev/null
+++ b/library/ZendAfi/Validate/MarcZone.php
@@ -0,0 +1,40 @@
+<?php
+/**
+ * Copyright (c) 2012-2019, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class ZendAfi_Validate_MarcZone extends Zend_Validate_Abstract {
+  const INVALID = 'invalid';
+
+  protected $_messageTemplates = [self::INVALID => '"%value%" n\'est pas une zone marc valide (de 001 à 999)'];
+
+
+  public function isValid($value) {
+    if ('' == trim($value))
+      return true;
+
+    $this->_setValue($value);
+
+    if (!(new Zend_Validate_Regex('/^[0-9]{3}$/'))->isValid($value))
+      $this->_error(static::INVALID);
+
+    return empty($this->_errors);
+  }
+}
diff --git a/library/ZendAfi/Validate/Url.php b/library/ZendAfi/Validate/Url.php
index 8d20c40c254cd26701e3a2b1da88a5e97c5c8b50..eb56e38ff342304b2c1fcdc2985a25245d6c77da 100644
--- a/library/ZendAfi/Validate/Url.php
+++ b/library/ZendAfi/Validate/Url.php
@@ -18,6 +18,8 @@
  * along with BOKEH; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
+
+
 class ZendAfi_Validate_Url extends Zend_Validate_Abstract {
   const INVALID_URL = 'invalidUrl';
 
@@ -39,4 +41,3 @@ class ZendAfi_Validate_Url extends Zend_Validate_Abstract {
     return true;
   }
 }
-?>
\ No newline at end of file
diff --git a/library/ZendAfi/View/Helper/Admin/ContentNav.php b/library/ZendAfi/View/Helper/Admin/ContentNav.php
index e763e735787452eab58df1d92cad33ef9c7b76ee..af75679b6b0e9d53de89b5842c5fcee0e5f11639 100644
--- a/library/ZendAfi/View/Helper/Admin/ContentNav.php
+++ b/library/ZendAfi/View/Helper/Admin/ContentNav.php
@@ -32,6 +32,7 @@ class ZendAfi_View_Helper_Admin_ContentNav extends ZendAfi_View_Helper_BaseHelpe
                 $this->menuMiseEnPage(),
                 $this->menuStats(),
                 $this->menuPortail(),
+                $this->menuFederation(),
                 $this->menuCatalogue(),
                 $this->menuSysteme(),
     ];
@@ -131,6 +132,15 @@ class ZendAfi_View_Helper_Admin_ContentNav extends ZendAfi_View_Helper_BaseHelpe
   }
 
 
+  protected function menuFederation() {
+    return $this
+      ->renderBloc($this->_('Fédération'),
+                   [
+                    ['frbr', $this->_('Avis'),        '/admin/federation-reviews'],
+                   ]);
+  }
+
+
   public function menuSysteme() {
     $is_admin = function($user) { return $user->isAdmin(); };
     $is_super_admin = function($user) { return $user->isSuperAdmin(); };
@@ -160,7 +170,8 @@ class ZendAfi_View_Helper_Admin_ContentNav extends ZendAfi_View_Helper_BaseHelpe
                     ['search_form', $this->_('Formulaires de recherche'), '/admin/search-form'],
 
                     ['customfields', $this->_('Champs personnalisés'), '/admin/custom-fields/index', [], $is_admin],
-                    ['customreports', $this->_('Rapports statistiques'), '/admin/custom-fields-report']
+                    ['customreports', $this->_('Rapports statistiques'), '/admin/custom-fields-report'],
+                    ['variables', $this->_('Journal'), '/admin/journal']
                    ]);
   }
 
diff --git a/library/ZendAfi/View/Helper/Admin/HelpLink.php b/library/ZendAfi/View/Helper/Admin/HelpLink.php
index d275882ec20064ae7959df276b25ba7ae209de2d..521275a5c37810be41524215d59cde2d143d03d3 100644
--- a/library/ZendAfi/View/Helper/Admin/HelpLink.php
+++ b/library/ZendAfi/View/Helper/Admin/HelpLink.php
@@ -123,6 +123,10 @@ class ZendAfi_View_Helper_Admin_HelpLinkBokehWiki {
      'registration'           => ['index' => 'Gérer_les_demandes_d\'inscription'],
      'usergroup-agenda'       => ['index' => 'Rendez-vous'],
      'rendez-vous'            => ['index' => 'Rendez-vous'],
+     'federation-reviews'     => ['index' => 'Avis_communautaires'],
+     'genre'                  => ['index' => 'Codification_des_genres,_emplacements,_annexes,_...'],
+     'emplacement'            => ['index' => 'Codification_des_genres,_emplacements,_annexes,_...'],
+     'section'                => ['index' => 'Codification_des_genres,_emplacements,_annexes,_...'],
     ];
 
 
diff --git a/library/ZendAfi/View/Helper/AuthoritySearch/Header.php b/library/ZendAfi/View/Helper/AuthoritySearch/Header.php
new file mode 100644
index 0000000000000000000000000000000000000000..ef1f39c78aa683d5e5cd2fb601acf016fbe690d4
--- /dev/null
+++ b/library/ZendAfi/View/Helper/AuthoritySearch/Header.php
@@ -0,0 +1,43 @@
+<?php
+/**
+ * Copyright (c) 2012-2017, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class ZendAfi_View_Helper_AuthoritySearch_Header extends ZendAfi_View_Helper_BaseHelper {
+  public function AuthoritySearch_Header($result) {
+    return $result
+      ? $this->_tag('div',
+                    $this->_tag('div',
+                                $this->_renderCount($result),
+                                ['class' => 'info-recherche']),
+                    ['class' => 'resultats_page'])
+      : '';
+  }
+
+
+  protected function _renderCount($result) {
+    return $this->_tag('div',
+                       $this->view->search_ResultCount($result->getRecordsCount(),
+                                                       Class_CriteresRecherche_Authority::getMaxSearchResults())
+                       . ' '
+                       . $this->_tag('small', $this->_('(%1.2f secondes)', $result->getDuration())),
+                       ['class' => 'search-sentence']);
+  }
+}
\ No newline at end of file
diff --git a/library/ZendAfi/View/Helper/AuthoritySearch/Record.php b/library/ZendAfi/View/Helper/AuthoritySearch/Record.php
new file mode 100644
index 0000000000000000000000000000000000000000..109328a27d5a7285e7292d7675479fb484862031
--- /dev/null
+++ b/library/ZendAfi/View/Helper/AuthoritySearch/Record.php
@@ -0,0 +1,35 @@
+<?php
+/**
+ * Copyright (c) 2019, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class ZendAfi_View_Helper_AuthoritySearch_Record extends ZendAfi_View_Helper_BaseHelper {
+  public function AuthoritySearch_Record($record) {
+    if (!$record)
+      return '';
+
+    return
+      $this->_tag('h2', $record->getTitrePrincipal())
+      . $this->view->AuthoritySearch_RecordRelations($record)
+      . $this->view->AuthoritySearch_RecordNotes($record)
+      . $this->view->AuthoritySearch_RecordUsages($record)
+      ;
+  }
+}
\ No newline at end of file
diff --git a/library/ZendAfi/View/Helper/AuthoritySearch/RecordNotes.php b/library/ZendAfi/View/Helper/AuthoritySearch/RecordNotes.php
new file mode 100644
index 0000000000000000000000000000000000000000..f9f3dbd5d9190682b0a123230708f0c625a9a5fd
--- /dev/null
+++ b/library/ZendAfi/View/Helper/AuthoritySearch/RecordNotes.php
@@ -0,0 +1,36 @@
+<?php
+/**
+ * Copyright (c) 2019, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class ZendAfi_View_Helper_AuthoritySearch_RecordNotes extends ZendAfi_View_Helper_BaseHelper {
+  public function AuthoritySearch_RecordNotes($record) {
+    return ($record && ($notes = $record->get_subfield('330', 'a')))
+      ? $this->_renderNotes(reset($notes))
+      : '';
+  }
+
+
+  protected function _renderNotes($notes) {
+    return $notes
+      ? ($this->_tag('h3', $this->_('Note d\'application')) . $this->_tag('p', $notes))
+      : '';
+  }
+}
\ No newline at end of file
diff --git a/library/ZendAfi/View/Helper/AuthoritySearch/RecordRelations.php b/library/ZendAfi/View/Helper/AuthoritySearch/RecordRelations.php
new file mode 100644
index 0000000000000000000000000000000000000000..7b0c8bb9eaedd47df90492b3752c013e5a125c2c
--- /dev/null
+++ b/library/ZendAfi/View/Helper/AuthoritySearch/RecordRelations.php
@@ -0,0 +1,76 @@
+<?php
+/**
+ * Copyright (c) 2019, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class ZendAfi_View_Helper_AuthoritySearch_RecordRelations extends ZendAfi_View_Helper_BaseHelper {
+  protected $_record_url_closure;
+
+  public function AuthoritySearch_RecordRelations($record, $record_url_closure=null) {
+    if (!$record)
+      return '';
+
+    $this->_record_url_closure = $record_url_closure;
+
+    $relations = Class_Notice_AuthorityRelations::allFor($record);
+    $parts = [];
+    foreach([$this->_('Terme générique') => $relations->generics(),
+             $this->_('Terme spécifique') => $relations->specifics(),
+             $this->_('Terme rejeté') => $relations->rejects(),
+             $this->_('Terme associé') => $relations->links()]
+            as $label => $relations)
+      $parts[] = $this->_renderLabelledRelations($label, $relations);
+
+    return implode(array_filter($parts));
+  }
+
+
+  protected function _renderLabelledRelations($label, $relations) {
+    if ($relations->isEmpty())
+      return '';
+
+    $items = $relations
+      ->collect(function($relation)
+                {
+                  return $this->_renderRelation($relation);
+                });
+
+    return
+      $this->_tag('h3', $label)
+      . $this->_tag('ul', implode($items->getArrayCopy()));
+  }
+
+
+  protected function _renderRelation($relation) {
+    $label = $relation->label();
+    if ($item = Class_Exemplaire::findFirstAuthorityItemByOriginId($relation->id()))
+      $label = $this->_tagAnchor($this->_recordUrl($item->getIdNotice()),
+                                 $label);
+
+    return $this->_tag('li', $label);
+  }
+
+
+  protected function _recordUrl($record_id) {
+    return ($closure = $this->_record_url_closure)
+      ? $closure($record_id)
+      : ['record_id' => $record_id];
+  }
+}
\ No newline at end of file
diff --git a/library/ZendAfi/View/Helper/AuthoritySearch/RecordUsages.php b/library/ZendAfi/View/Helper/AuthoritySearch/RecordUsages.php
new file mode 100644
index 0000000000000000000000000000000000000000..b459f158f0360b8a56580e47d8bff98c327d63c5
--- /dev/null
+++ b/library/ZendAfi/View/Helper/AuthoritySearch/RecordUsages.php
@@ -0,0 +1,66 @@
+<?php
+/**
+ * Copyright (c) 2019, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class ZendAfi_View_Helper_AuthoritySearch_RecordUsages extends ZendAfi_View_Helper_BaseHelper {
+  public function AuthoritySearch_RecordUsages($record) {
+    if (!$record)
+      return '';
+
+    return $this->_tag('p', $this->_renderUsage($record));
+  }
+
+
+  protected function _renderUsage($record) {
+    $facets = array_filter($record->getFacetCodes(),
+                           function($facet) { return $this->_isDynamicFacetValue($facet); });
+
+    if (!$facets)
+      return $this->_('Utilisé dans aucune notice');
+
+    $criterias = (new Class_CriteresRecherche())
+      ->setParams(['multifacets' => implode('-', $facets)]);
+
+    if (!$count = $this->_countByCriterias($criterias))
+      return $this->_('Utilisé dans aucune notice');
+
+    return $this->_tagAnchor($this->_url($criterias->getUrlCriteresWithFacettes(), null, true),
+                             $this->_plural($count,
+                                           'Utilisé dans aucune notice',
+                                           'Utilisé dans 1 notice',
+                                           'Utilisé dans %d notices',
+                                           $count));
+  }
+
+
+  protected function _isDynamicFacetValue($facet) {
+    return Class_CodifThesaurus::CODE_FACETTE === substr($facet, 0, 1)
+      && strlen($facet) === (Class_CodifThesaurus::ID_KEY_LENGTH * 2) + 1;
+  }
+
+
+  protected function _countByCriterias($criterias) {
+    return (new Class_MoteurRecherche)
+      ->beNotExtensible()
+      ->lancerRecherche($criterias)
+      ->getRecordsCount();
+  }
+}
\ No newline at end of file
diff --git a/library/ZendAfi/View/Helper/AuthoritySearch/Result.php b/library/ZendAfi/View/Helper/AuthoritySearch/Result.php
new file mode 100644
index 0000000000000000000000000000000000000000..cffde8f0256d1245de1bfa50c5f310e5aafeb153
--- /dev/null
+++ b/library/ZendAfi/View/Helper/AuthoritySearch/Result.php
@@ -0,0 +1,140 @@
+<?php
+/**
+ * Copyright (c) 2012-2017, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class ZendAfi_View_Helper_AuthoritySearch_Result extends ZendAfi_View_Helper_BaseHelper {
+  protected
+    $_result,
+    $_record,
+    $_record_path = [];
+
+  public function AuthoritySearch_Result($result, $record, $tree_roots) {
+    $this->_result = $result;
+    $this->_record = $record;
+
+    return $this->_tag('div',
+                       $this->_renderRecordOrResult()
+                       . $this->_renderTree($tree_roots)
+                       . $this->_tag('div', '', ['class' => 'clear']),
+                       ['class' => 'conteneur_simple']);
+  }
+
+
+  protected function _renderRecordOrResult() {
+    $html = ($record = $this->view->AuthoritySearch_Record($this->_record))
+      ? $record
+      : $this->_renderRecords();
+
+    return $this->_tag('div', $html, ['class' => 'resultat_recherche']);
+  }
+
+
+  protected function _renderRecords() {
+    return $this->_result
+      ? $this->view->listeNotices($this->_result)
+      : '';
+  }
+
+
+  protected function _renderTree($roots) {
+    if (!$roots || (!$roots = $this->_treeRootRecords($roots)))
+      return '';
+
+    $this->_prepareRecordPath();
+
+    return $this->_tag('div',
+                       $this->_tag('div',
+                                   $this->_tag('ul',
+                                               implode(array_map([$this, '_treeNodefor'],
+                                                                 $roots))),
+                                   ['class' => 'facette_outer']),
+                       ['class' => 'filtre_recherche']);
+  }
+
+
+  protected function _treeRootRecords($roots) {
+    return array_filter(array_map([$this, '_recordFromIdOrigine'], explode('-', $roots)));
+  }
+
+
+  protected function _treeNodeFor($record) {
+    $attribs = in_array($record->getId(), $this->_record_path)
+      ? ['class' => 'bold']
+      : [];
+
+    return $this->_tag('li',
+                       $this->_tagAnchor($this->_url(['record_id' => $record->getId()]),
+                                         $record->getTitrePrincipal(),
+                                         $attribs)
+                       . $this->_treeNodeSpecificsFor($record));
+  }
+
+
+  protected function _treeNodeSpecificsFor($record) {
+    if (!in_array($record->getId(), $this->_record_path))
+      return '';
+
+    $specifics = Class_Notice_AuthorityRelations::allFor($record)
+      ->specifics()
+      ->injectInto('',
+                   function($html, $one)
+                   {
+                     return $html
+                       . (($record = $this->_recordFromIdOrigine($one->id()))
+                          ? $this->_treeNodeFor($record)
+                          : '');
+                   });
+
+    return $specifics
+      ? $this->_tag('ul', $specifics)
+      : '';
+  }
+
+
+  protected function _recordFromIdOrigine($id) {
+    return $id && ($item = Class_Exemplaire::findFirstAuthorityItemByOriginId($id))
+      ? $item->getNotice()
+      : null;
+  }
+
+
+  protected function _prepareRecordPath() {
+    if (!$this->_record_path && $this->_record)
+      $this->_addRecordPathOf($this->_record);
+  }
+
+
+  protected function _addRecordPathOf($record) {
+    $this->_record_path[] = $record->getId();
+
+    $generic = Class_Notice_AuthorityRelations::allFor($record)
+      ->generics()
+      ->first();
+
+    if (!$generic
+        || (!$id = $generic->id())
+        || (!$generic_record = $this->_recordFromIdOrigine($id))
+        || in_array($generic_record->getId(), $this->_record_path))
+      return;
+
+    $this->_addRecordPathOf($generic_record);
+  }
+}
\ No newline at end of file
diff --git a/library/ZendAfi/View/Helper/Avis.php b/library/ZendAfi/View/Helper/Avis.php
index 1253a451cb3af9c0037810672df34a94728ad648..2cfe418a97a7aa306bd77b4567201cb772a24d49 100644
--- a/library/ZendAfi/View/Helper/Avis.php
+++ b/library/ZendAfi/View/Helper/Avis.php
@@ -127,7 +127,7 @@ class ZendAfi_View_Helper_Avis extends ZendAfi_View_Helper_BaseHelper {
                                        $this->view->tagImg($url_vignette,
                                                            ['alt' => $this->_('vignette de \'%s\'', $title),
                                                             'title' => $type_doc_label . ' : ' . $title]),
-                                     ['href' => $this->_getUrlClickVignette($avis)])
+                                       ['href' => $this->_getUrlClickVignette($avis)])
                          . $this->_tag('a', $this->_('Voir la notice'),
                                        ['href' => $this->_getUrlNotice($avis)]),
                          ['class' => 'vignette_notice'])
@@ -170,8 +170,11 @@ class ZendAfi_View_Helper_Avis extends ZendAfi_View_Helper_BaseHelper {
 
 
   protected function _renderAuthor($avis) {
+    if ($avis->hasSourceAuthor())
+      return $this->_renderFederationAuthor($avis);
+
     $auteur = $this->view->escape($avis->getUserName());
-    $url_auteur = $this->_url($this->_getUrlAuthor($avis));
+    $url_auteur = $this->_urlWithContext($this->_getUrlAuthor($avis));
 
     $html = '' != $auteur ?
       ' ' . $this->_tag('a', $auteur, ['href' => $url_auteur])
@@ -186,6 +189,16 @@ class ZendAfi_View_Helper_Avis extends ZendAfi_View_Helper_BaseHelper {
   }
 
 
+  protected function _renderFederationAuthor($avis) {
+    $html = $avis->getSourceAuthor()
+      . ' '
+      . $this->_tag('span', '- ' . $avis->getReadableDateAvis());
+
+    return $this->_tag('span', $html,
+                       ['class' => 'auteur_critique']);
+  }
+
+
   protected function _getUrlAuthor($avis) {
     if ($avis->isAvisNotice())
       return ['module' => 'opac',
@@ -214,8 +227,8 @@ class ZendAfi_View_Helper_Avis extends ZendAfi_View_Helper_BaseHelper {
 
 
   /** @params parts array*/
-  public function _url($parts) {
-    return $this->view->url(array_merge($parts, $this->_url_context));
+  public function _urlWithContext($parts) {
+    return $this->_url(array_merge($parts, $this->_url_context));
   }
 
 
@@ -231,10 +244,10 @@ class ZendAfi_View_Helper_Avis extends ZendAfi_View_Helper_BaseHelper {
     $html_actions = '';
     $suffix = ($avis->isAvisNotice()) ? 'avisnotice' : '-aviscms';
     foreach($this->_actions as $action) {
-      $link = $this->view->tagAnchor($this->_url(['action' => $action . $suffix,
-                                                  'id' => $avis->getId(),
-                                                  'active_tab' => $this->_active_tab,
-                                                  'page' => $this->_page]),
+      $link = $this->view->tagAnchor($this->_urlWithContext(['action' => $action . $suffix,
+                                                             'id' => $avis->getId(),
+                                                             'active_tab' => $this->_active_tab,
+                                                             'page' => $this->_page]),
                                      $this->view->boutonIco('type=' . $action));
 
       $html_actions .= $this->_tag('span', $link, ['rel' => $action]);
@@ -253,11 +266,11 @@ class ZendAfi_View_Helper_Avis extends ZendAfi_View_Helper_BaseHelper {
   protected function _getAdminActionsTag($avis) {
     $html_actions = '';
     foreach($this->_admin_actions as $action) {
-      $link = $this->view->tagAnchor($this->_url(['controller' => 'abonne',
-                                                  'action' => $action . 'avisnotice',
-                                                  'id' => $avis->getId(),
-                                                  'active_tab' => $this->_active_tab,
-                                                  'page' => $this->_page]),
+      $link = $this->view->tagAnchor($this->_urlWithContext(['controller' => 'abonne',
+                                                             'action' => $action . 'avisnotice',
+                                                             'id' => $avis->getId(),
+                                                             'active_tab' => $this->_active_tab,
+                                                             'page' => $this->_page]),
                                      $this->view->boutonIco('type=' .$action),
                                      ['data-popup' => 'true']);
       $html_actions .= $this->_tag('span', $link, ['rel' => $action]);
@@ -273,20 +286,20 @@ class ZendAfi_View_Helper_Avis extends ZendAfi_View_Helper_BaseHelper {
     if (null == $avis->getId() || !$avis->isAvisNotice())
       return '#';
 
-    return $this->_url(['module' => 'opac',
-                        'controller' => 'blog',
-                        'action' => 'viewavis',
-                        'id' => $avis->getId()]);
+    return $this->_urlWithContext(['module' => 'opac',
+                                   'controller' => 'blog',
+                                   'action' => 'viewavis',
+                                   'id' => $avis->getId()]);
   }
 
 
   protected function _getUrlNotice($avis) {
     if (null !== $notice = $avis->getFirstNotice())
-      return $this->_url(['module' => 'opac',
-                          'controller' => 'recherche',
-                          'action' => 'viewnotice',
-                          'id' => $notice->getId(),
-                          'clef' => $notice->getClefAlpha()]);
+      return $this->_urlWithContext(['module' => 'opac',
+                                     'controller' => 'recherche',
+                                     'action' => 'viewnotice',
+                                     'id' => $notice->getId(),
+                                     'clef' => $notice->getClefAlpha()]);
 
     return '';
   }
@@ -325,4 +338,4 @@ class ZendAfi_View_Helper_Avis extends ZendAfi_View_Helper_BaseHelper {
     return ['text_avis' => nl2br($content),
             'lire_la_suite' => $read_link];
   }
-}
\ No newline at end of file
+}
diff --git a/library/ZendAfi/View/Helper/BaseHelper.php b/library/ZendAfi/View/Helper/BaseHelper.php
index 1b09abaefe85d606ae8e1501f126f9d0fcdeb8cc..1742041c9f67ea7be2d5bdd222d087ff8fb65310 100644
--- a/library/ZendAfi/View/Helper/BaseHelper.php
+++ b/library/ZendAfi/View/Helper/BaseHelper.php
@@ -76,4 +76,9 @@ class ZendAfi_View_Helper_BaseHelper extends Zend_View_Helper_HtmlElement {
   protected function _div($attributes = [], $content = '') {
     return call_user_func_array([$this->view, 'div'], func_get_args());
   }
+
+
+  protected function _url(array $urlOptions = [], $name = null, $reset = false, $encode = true) {
+    return call_user_func_array([$this->view, 'url'], func_get_args());
+  }
 }
\ No newline at end of file
diff --git a/library/ZendAfi/View/Helper/CosmoEmplacement.php b/library/ZendAfi/View/Helper/CosmoEmplacement.php
deleted file mode 100644
index d09a0c7f467aa541ffe53bc08295d4288b8cda9f..0000000000000000000000000000000000000000
--- a/library/ZendAfi/View/Helper/CosmoEmplacement.php
+++ /dev/null
@@ -1,131 +0,0 @@
-<?php
-/**
- * Copyright (c) 2012-2014, Agence Française Informatique (AFI). All rights reserved.
- *
- * BOKEH is free software; you can redistribute it and/or modify
- * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
- * the Free Software Foundation.
- *
- * There are special exceptions to the terms and conditions of the AGPL as it
- * is applied to this software (see README file).
- *
- * BOKEH is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
- *
- * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
- * along with BOKEH; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA 
- */
-
-
-class ZendAfi_View_Helper_CosmoEmplacement extends Zend_View_Helper_Abstract {
-  public function cosmoEmplacement($model, $display) {
-    $this->model = $model;
-    return $this->view
-      ->tag('div', $this->_getForm(), [
-        'class' => 'form', 
-        'id' => 'emplacement'. $model->getId(), 
-        'style' => 'width:600px;margin-left:20px;' 
-        . ((!$display) ? 'display:none;': '')]);
-  }
-
-
-  protected function _getForm() {
-    return $this->view
-      ->tag('form', $this->_getFields(), [
-        'method' => 'post', 
-        'action' => $this->view->url([
-          'module' => 'cosmo',
-          'controller' => 'emplacement', 
-          'action' => 'validate', 
-          'id' => $this->model->getId()], null, true)]);
-  }
-
-
-  protected function _getFields() {
-    return $this->view
-      ->tag(
-        'table', 
-        $this->_getTitle()
-        . $this->_getLabel()
-        . $this->_getHelp()
-        . $this->_getRules()
-        . $this->_getDisplay()
-        . $this->_getActions(), 
-        ['class' => 'form', 'cellspacing' => 0, 'cellpadding' => 5]);
-  }
-
-
-  protected function _getTitle() {
-    return $this->view->tag('tr', $this->view->tag('th', 'Création d\'emplacement', [
-      'class' => 'form', 'colspan' => 2, 'align' => 'left'
-    ]));
-  }
-
-
-  protected function _getLabel() {
-    return $this->view->tag(
-      'tr', 
-      $this->view->tag('td', 'Libellé', [
-        'class' => 'form_first', 'align' => 'right', 'width' => '35%']) . 
-      $this->view->tag('td', $this->view->formText('libelle', $this->model->getLibelle(), ['size' => 43]), ['class' => 'form_first']));
-  }
-
-
-  protected function _getHelp() {
-    return '<tr>
-          <td class="form_first" align="center" colspan="2">
-            <div class="commentaire">Syntaxe : [zone$champ][signe][valeur1;valeur2;etc...] - Ex : 995$a = a<br>Signes : "=" égal - "/" commence par - "*" contient</div>
-          </td>
-        </tr>';
-  }
-
-
-  protected function _getRules() {
-    return $this->_getSimpleInput(
-      'Règles de reconnaissance',
-      $this->view->formTextarea('regles', $this->model->getRegles(), [
-        'cols' => 40, 'rows' => 5])
-    );
-  }
-
-
-  protected function _getDisplay() {
-    return $this->_getSimpleInput(
-      'Affichage des exemplaires', 
-      $this->view->formSelect('ne_pas_afficher', $this->model->getNePasAfficher(), null, [
-        '0' => 'Afficher les exemplaires',
-        '1' => 'Ne pas afficher les exemplaires'])
-    );
-  }
-
-
-  protected function _getSimpleInput($label, $input) {
-    return $this->view->tag('tr', 
-      $this->view->tag('td', $label, [
-        'class' => 'form', 'align' => 'right', 'valign' => 'top']) . 
-      $this->view->tag('td', $input, ['class' => 'form']));
-  }
-
-
-  protected function _getActions() {
-    return $this->view->tag('tr', 
-      $this->view->tag(
-        'th', 
-        $this->view->formSubmit('emplacementSubmit', 'Valider', ['class' => 'bouton']) .
-        ($this->model->getId() ? str_repeat('&nbsp;', 5) .
-        $this->view->cosmoButton(
-          'Supprimer', 
-          $this->view->url([
-            'module' => 'cosmo',
-            'controller' => 'emplacement', 
-            'action' => 'delete',
-            'id' => $this->model->getId()], null, true),
-          'Voulez-vous vraiment supprimer cet emplacement ?') : ''), 
-        ['class' => 'form', 'align' => 'center', 'colspan' => 2]
-      )
-    );
-  }
-}
diff --git a/library/ZendAfi/View/Helper/DatePicker.php b/library/ZendAfi/View/Helper/DatePicker.php
index fb5c1589b6c285abde74663154bb186a48dfb333..f346a0e2ce26ae85aeadc5804f35a5b286469598 100644
--- a/library/ZendAfi/View/Helper/DatePicker.php
+++ b/library/ZendAfi/View/Helper/DatePicker.php
@@ -26,7 +26,7 @@ class ZendAfi_View_Helper_DatePicker extends ZendAfi_View_Helper_BaseHelper {
    * @param $maxYear maximum year to display
    * @return html for the date picker
    */
-  public function datePicker($name, $varDate, $dateOnly=true, $allDaySwitch='',$disabled=false) {
+  public function datePicker($name, $varDate, $dateOnly=true, $allDaySwitch='', $disabled = false) {
     $locale = Zend_Registry::get('locale');
 
     $optionsdateonly = $optionswithhours = ['dateFormat' => 'DD/MM/YYYY',
diff --git a/library/ZendAfi/View/Helper/ListeNotices.php b/library/ZendAfi/View/Helper/ListeNotices.php
index dd630173d730eed2440bd6a149efe763b139465b..2230626f4673cd2abcdb2acde7d4d6a1a5ba36ac 100644
--- a/library/ZendAfi/View/Helper/ListeNotices.php
+++ b/library/ZendAfi/View/Helper/ListeNotices.php
@@ -39,10 +39,7 @@ class ZendAfi_View_Helper_ListeNotices extends ZendAfi_View_Helper_BaseHelper {
     $pager = $this->view->pager($nombre_resultats,
                                 $criteres_recherche->getPageSize(),
                                 $criteres_recherche->getPage(),
-                                array_merge($criteres_recherche->getUrlRetourListe(),
-                                            ['controller' => 'recherche',
-                                             'action' => 'simple']));
-
+                                $criteres_recherche->getUrlPager());
 
     if (Class_AdminVar::displayPagerOnTop())
       $html = $pager . $html;
@@ -81,7 +78,8 @@ class ZendAfi_View_Helper_ListeNotices extends ZendAfi_View_Helper_BaseHelper {
     $helpers = [Class_Systeme_ModulesAppli::LISTE_FORMAT_TABLEAU => 'ListeNotices_Tableau',
                 Class_Systeme_ModulesAppli::LISTE_FORMAT_VIGNETTES => 'ListeNotices_Vignettes',
                 Class_Systeme_ModulesAppli::LISTE_FORMAT_MUR => 'ListeNotices_Mur',
-                Class_Systeme_ModulesAppli::LISTE_FORMAT_CHRONO => 'ListeNotices_Chrono'];
+                Class_Systeme_ModulesAppli::LISTE_FORMAT_CHRONO => 'ListeNotices_Chrono',
+                Class_Systeme_ModulesAppli::LISTE_FORMAT_LINKS => 'ListeNotices_Links'];
 
     $helper = $helpers[$preferences['liste_format']];
     return call_user_func_array([$this->view, $helper], [$notices, $preferences]);
diff --git a/library/ZendAfi/View/Helper/ListeNotices/Links.php b/library/ZendAfi/View/Helper/ListeNotices/Links.php
new file mode 100644
index 0000000000000000000000000000000000000000..8f4c49a97a0c032cc4f0a27733835bd75c853702
--- /dev/null
+++ b/library/ZendAfi/View/Helper/ListeNotices/Links.php
@@ -0,0 +1,49 @@
+<?php
+/**
+ * Copyright (c) 2019, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class ZendAfi_View_Helper_ListeNotices_Links extends ZendAfi_View_Helper_BaseHelper {
+  protected $_prefs;
+
+  public function listeNotices_Links($records, $preferences=[]) {
+    $this->_prefs = $preferences;
+
+    return $this->_tag('ul',
+                       implode(array_map([$this, '_renderOne'], $records)),
+                       ['class' => 'liste_links']);
+  }
+
+
+  protected function _renderOne($record) {
+    return $this
+      ->_tag('li',
+             $this->_tag('a',
+                         $record->getTitrePrincipal() . $this->_renderTypeLabel($record),
+                         ['href' => $this->view->url(['record_id' => $record->getId()]),
+                          'data-record-id' => $record->getId()]));
+  }
+
+
+  protected function _renderTypeLabel($record) {
+    return $this->_tag('span', ' (' . $record->getTypeLabel() . ')',
+                       ['style' => 'font-size:smaller']);
+  }
+}
diff --git a/library/ZendAfi/View/Helper/Notice/Avis.php b/library/ZendAfi/View/Helper/Notice/Avis.php
index 6af6890666640a4a5d81ddacc27d369b273b0553..0cb70c40746196de892a1282c86d2f59b7f4831f 100644
--- a/library/ZendAfi/View/Helper/Notice/Avis.php
+++ b/library/ZendAfi/View/Helper/Notice/Avis.php
@@ -18,9 +18,7 @@
  * along with BOKEH; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
-class ZendAfi_View_Helper_Notice_Avis extends Zend_View_Helper_HtmlElement {
-  use Trait_Translator;
-
+class ZendAfi_View_Helper_Notice_Avis extends ZendAfi_View_Helper_BaseHelper {
   protected $_notice, $_avis, $_params;
 
   public function Notice_Avis($notice, $avis, $params)  {
@@ -31,8 +29,7 @@ class ZendAfi_View_Helper_Notice_Avis extends Zend_View_Helper_HtmlElement {
     $user = Class_Users::getIdentity();
 
     return $this->_tag('table',
-                       $this->_header($user)
-                       . $this->_advices($user),
+                       $this->_header($user) . $this->_advices($user),
                        ['cellspacing' => 0, 'width' => '100%']);
   }
 
@@ -45,43 +42,40 @@ class ZendAfi_View_Helper_Notice_Avis extends Zend_View_Helper_HtmlElement {
     if ($user && $user->isBibliothecaire())
       $avis_helper->setAdminActions(['edit', 'del']);
 
-    $html = $this->_tag('tr',
-                        $this->_tag('td', '&nbsp;', ['colspan' => 3]))
-      . $this->_tag('tr',
-                        $this->_tag('td', $this->_avis[$source]["titre"],
-                                    ['class' => 'notice_info_ligne_titre',
-                                     'align' => 'left',
-                                     'colspan' => 3]));
-
-    if (0 == $this->_avis[$source]['nombre']) {
-      return $html .= $this->_tag('tr',
-                                  $this->_tag('td', '&nbsp;', ['colspan' => 3]))
-        . $this->_tag('tr',
-                      $this->_tag('td', $this->_('Aucun avis pour le moment'),
-                                  ['colspan' => 3,
-                                   'class' => 'notice_info',
-                                   'style' => 'text-align:left']))
-        . $this->_tag('tr',
-                      $this->_tag('td', '&nbsp;', ['colspan' => 3]));
-    }
+    $reviews_set = array_key_exists($source, $this->_avis)
+      ? $this->_avis[$source]
+      : Class_Notice_ReviewsSet::emptyInstance();
+
+    $html = $this->_tag('tr', $this->_tag('td', '&nbsp;', ['colspan' => 3]))
+      . $this->_tag('tr', $this->_tag('td', $reviews_set->getLabel(),
+                                      ['class' => 'notice_info_ligne_titre',
+                                       'align' => 'left',
+                                       'colspan' => 3]));
 
-    foreach($this->_avis[$source]["liste"] as $detail) {
+    if ($reviews_set->isEmpty())
+      return $html .= $this->_tag('tr', $this->_tag('td', '&nbsp;', ['colspan' => 3]))
+        . $this->_tag('tr', $this->_tag('td', $this->_('Aucun avis pour le moment'),
+                                        ['colspan' => 3,
+                                         'class' => 'notice_info',
+                                         'style' => 'text-align:left']))
+        . $this->_tag('tr', $this->_tag('td', '&nbsp;', ['colspan' => 3]));
+
+
+    foreach($reviews_set->getReviews() as $detail) {
       if ($detail->isVisibleForUser($user))
         $html .= $this->_tag('tr',
                              $this->_tag('td', $avis_helper->contenu_avis($detail),
                                          ['colspan' => 3]));
     }
 
-    if (isset($this->_avis[$source]["nb_pages"])
-        && ($this->_avis[$source]["nb_pages"] > 1)) {
+    if ($reviews_set->hasPages()) {
       $pager = $this->view->getHelper('Pager');
-      $lien = "javascript:".$fct."('".$this->_params["onglet"]."','".$this->_notice->getId()."','avis','".$source."',1,@PAGE@)";
-      $urlPagesHtml = $pager->Pager($this->_avis[$source]["nombre"], 5, $this->_params["page"], $lien);
-      $html .= $this->_tag('tr',
-                           $this->_tag('td', $urlPagesHtml,
-                                       ['colspan' => 3,
-                                        'class' => 'notice_info',
-                                        'style' => 'text-align:center']));
+      $lien = "javascript:" . $this->_jsFunction() . "('".$this->_params["onglet"]."','".$this->_notice->getId()."','avis','".$source."',1,@PAGE@)";
+      $urlPagesHtml = $pager->Pager($reviews_set->getCount(), 5, $this->_params["page"], $lien);
+      $html .= $this->_tag('tr', $this->_tag('td', $urlPagesHtml,
+                                             ['colspan' => 3,
+                                              'class' => 'notice_info',
+                                              'style' => 'text-align:center']));
     }
 
     return $html;
@@ -92,11 +86,12 @@ class ZendAfi_View_Helper_Notice_Avis extends Zend_View_Helper_HtmlElement {
     if (array_key_exists('cherche', $this->_params))
       return $this->_params['cherche'];
 
-    if($this->_avis["bib"]["nombre"] > 0)
+    if (!$this->_avis['bib']->isEmpty())
       return 'bib';
 
-    if($this->_avis["abonne"]["nombre"] > 0)
+    if (!$this->_avis['abonne']->isEmpty())
       return 'abonne';
+
     return null;
   }
 
@@ -117,20 +112,15 @@ class ZendAfi_View_Helper_Notice_Avis extends Zend_View_Helper_HtmlElement {
 
   protected function _headerSources() {
     $html = '';
-    foreach($this->_avis as $source => $ligne) {
-      $count = $this->_avis[$source]['nombre'];
-      if (0 == $count)
+    foreach($this->_avis as $key => $reviews_set) {
+      if ($reviews_set->isEmpty())
         continue;
 
-      $fct = (isset($this->_params['onglet'])
-              && (substr($this->_params['onglet'], 0, 4)) == 'bloc') ?
-        'infos_bloc' : 'infos_onglet';
-
-      $url_site = "javascript:".$fct."('" . $this->_params["onglet"]."','".$this->_notice->getId() . "','avis','" . $source . "',1,1)";
+      $url_site = "javascript:" . $this->_jsFunction() . "('" . $this->_params["onglet"]."','".$this->_notice->getId() . "','avis','" . $key . "',1,1)";
 
-      $html .= $this->_headerSource($this->_avis[$source]["titre"],
-                                    $this->_getAdviceCountLabel($count),
-                                    $ligne['note'],
+      $html .= $this->_headerSource($reviews_set->getLabel(),
+                                    $this->_getAdviceCountLabel($reviews_set->getCount()),
+                                    $reviews_set->getRating(),
                                     $url_site);
     }
 
@@ -138,6 +128,14 @@ class ZendAfi_View_Helper_Notice_Avis extends Zend_View_Helper_HtmlElement {
   }
 
 
+  protected function _jsFunction() {
+    return (isset($this->_params['onglet'])
+            && (substr($this->_params['onglet'], 0, 4)) == 'bloc')
+      ? 'infos_bloc'
+      : 'infos_onglet';
+  }
+
+
   protected function _headerSource($label, $count, $note, $url) {
     return $this->_tag('li',
                        $this->view->NoteImg($note) . '&nbsp;&nbsp;'
@@ -171,6 +169,7 @@ class ZendAfi_View_Helper_Notice_Avis extends Zend_View_Helper_HtmlElement {
     return '';
   }
 
+
   protected function getLink($id_notice) {
     return $this->view->tagAnchor($this->view->url(['controller' => 'noticeajax',
                                                     'action' => 'add-avis',
@@ -179,10 +178,4 @@ class ZendAfi_View_Helper_Notice_Avis extends Zend_View_Helper_HtmlElement {
                                   ['class' => 'notice',
                                    'data-popup' => 'true']);
   }
-
-
-  protected function _tag() {
-    return call_user_func_array([$this->view, 'tag'], func_get_args());
-  }
 }
-?>
\ No newline at end of file
diff --git a/library/ZendAfi/View/Helper/Notice/ExemplairesTable.php b/library/ZendAfi/View/Helper/Notice/ExemplairesTable.php
index 48f18a284a0afdcd0282745aa697d4ebc90f25f5..57f8a40c0cf179681b55edfc0180961056eecb22 100644
--- a/library/ZendAfi/View/Helper/Notice/ExemplairesTable.php
+++ b/library/ZendAfi/View/Helper/Notice/ExemplairesTable.php
@@ -248,42 +248,54 @@ class ZendAfi_View_Helper_Notice_Exemplaires_Dispo extends ZendAfi_View_Helper_N
     return $this->_('Disponibilité');
   }
 
+
   public function renderContent($exemplaire) {
-    $class_dispo='';
-    $libelle = $exemplaire->getDisponibilite();
+    $libelle = $this->_getAvailabilityLabel($exemplaire);
+
     $class_dispo = ($libelle == Class_WebService_SIGB_Exemplaire::newInstance()->message('DISPO_LIBRE'))
       ? 'disponible'
       : '';
 
-    if ($exemplaire->getUrl())
-      $libelle = $this->view->tagAnchor($exemplaire->getUrl(),
-                                        $this->view->_('Description en ligne'));
-
-    if((new Class_Notice_Sso($exemplaire->getNotice()))->isValid())
-      $libelle = $this->view->tagAnchor($this->view->url(['module' => 'opac',
-                                                          'controller' => 'modules',
-                                                          'action' => 'sso',
-                                                          'id' => $exemplaire->getIdNotice()],
-                                                         null,
-                                                         true),
-                                        $this->view->_('Consulter en ligne'));
-
     if ($exemplaire->getNbResas() > 0)
       $libelle.=
         '<span>'
-        .$this->view->_plural($exemplaire->getNbResas(),
-                              '',$this->view->_('Nb résa: %s '),
-                              $this->view->_('Nb résas: %s '),
-                              $exemplaire->getNbResas())
+        . $this->view->_plural($exemplaire->getNbResas(),
+                               '',$this->view->_('Nb résa: %s '),
+                               $this->view->_('Nb résas: %s '),
+                               $exemplaire->getNbResas())
         .'</span>';
 
 
     return '<td class="dispo '.$class_dispo.'">'.$libelle.'</td>';
   }
+
+
+  protected function _getAvailabilityLabel($exemplaire) {
+    if ($exemplaire->getUrl())
+      return $this->view->tagAnchor($exemplaire->getUrl(),
+                                    $this->view->_('Description en ligne'));
+
+    if ((new Class_Notice_Sso($exemplaire->getNotice()))->isValid())
+      return $this->view->tagAnchor($this->view->url(['module' => 'opac',
+                                                      'controller' => 'modules',
+                                                      'action' => 'sso',
+                                                      'id' => $exemplaire->getIdNotice()],
+                                                     null,
+                                                     true),
+                                    $this->view->_('Consulter en ligne'));
+
+    if ($bundle = $exemplaire->getBundle())
+      return $this->view->tagAnchor($this->view->urlNotice($bundle),
+                                    $this->view->_('Fait partie du lot "%s"',
+                                                   $bundle->getTitrePrincipal()));
+
+    return $exemplaire->getDisponibilite();
+  }
 }
 
 
 
+
 class ZendAfi_View_Helper_Notice_Exemplaires_Cote extends ZendAfi_View_Helper_Notice_ExemplairesTable {
   public function getLibelle() {
     return $this->_('Cote').$this->editionLabel($this->exemplaires);
diff --git a/library/ZendAfi/View/Helper/Notice/Unimarc.php b/library/ZendAfi/View/Helper/Notice/Unimarc.php
index 20c0baa7a8bddaf1dbc7e644e32015a34f1dccfa..143266d4c2054368fc74664e52c5895f76eea4c4 100644
--- a/library/ZendAfi/View/Helper/Notice/Unimarc.php
+++ b/library/ZendAfi/View/Helper/Notice/Unimarc.php
@@ -60,6 +60,7 @@ class ZendAfi_View_Helper_Notice_Unimarc extends Zend_View_Helper_HtmlElement {
        'dewey' => $this->_('Dewey'),
        'collection' => $this->_('Collection'),
        'other_terms' => $this->_('Autres termes'),
+       'file_content_first_words' => $this->_('Contenu du fichier'),
        'raw_editeur' => $this->_('Éditeurs'),
        'facettes' => $this->_('Facettes'),
        'clef_alpha' => $this->_('Clé alpha'),
@@ -132,7 +133,8 @@ class ZendAfi_View_Helper_Notice_Unimarc extends Zend_View_Helper_HtmlElement {
                 'getLocationLabel' => $this->_('Emplacement'),
                 'getActivite' => $this->_('Activité'),
                 'getIdOrigine' => $this->_('Id origine'),
-                'getDateNouveaute' => $this->_('Date nouveaté')];
+                'getIdDataProfile' => $this->_('Profil de données'),
+                'getDateNouveaute' => $this->_('Date nouveauté')];
 
     $html = '';
     foreach($notice->getExemplaires() as $item)
diff --git a/library/ZendAfi/View/Helper/Search/ResultCount.php b/library/ZendAfi/View/Helper/Search/ResultCount.php
new file mode 100644
index 0000000000000000000000000000000000000000..225b5442c326906f0aa80c38bc7312a187dad991
--- /dev/null
+++ b/library/ZendAfi/View/Helper/Search/ResultCount.php
@@ -0,0 +1,37 @@
+<?php
+/**
+ * Copyright (c) 2012-2019, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class ZendAfi_View_Helper_Search_ResultCount extends ZendAfi_View_Helper_BaseHelper {
+  public function search_ResultCount($count, $limit) {
+    return ($limit && ($count == $limit))
+      ? $this->_('Il y a plus de %s résultats',
+                 $this->_tag('span',
+                             ' ' . $count . ' ',
+                             ['class' => 'nombre-recherche']))
+      : $this->_plural($count,
+                       'Aucun résultat trouvé',
+                       'Il y a %s résultat',
+                       'Il y a %s résultats',
+                       $this->_tag('span', ' ' . $count . ' ',
+                                   ['class' => 'nombre-recherche']));
+  }
+}
diff --git a/library/ZendAfi/View/Helper/Search/SearchMode.php b/library/ZendAfi/View/Helper/Search/SearchMode.php
new file mode 100644
index 0000000000000000000000000000000000000000..b8b7387c47f478448e4f2fdb14a6c2f7945190b9
--- /dev/null
+++ b/library/ZendAfi/View/Helper/Search/SearchMode.php
@@ -0,0 +1,34 @@
+<?php
+/**
+ * Copyright (c) 2012-2014, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class ZendAfi_View_Helper_Search_SearchMode extends ZendAfi_View_Helper_BaseHelper {
+  public function search_SearchMode($search_criteria) {
+    $instance = (new Class_Entity())
+      ->setKey('in_files')
+      ->setLabel($this->_('Mode de recherche'))
+      ->setValue($search_criteria->getInFiles() ? 1 : 0)
+      ->setAvailables($search_criteria->getSearchModeList());
+
+    return $this->view->selectWidget($instance);
+  }
+}
+?>
\ No newline at end of file
diff --git a/library/ZendAfi/View/Helper/TagRechercheSimple.php b/library/ZendAfi/View/Helper/TagRechercheSimple.php
index d4c480a2ddbbf98c459d696be3c16414e7038408..eeb39a16e35458d0c4bd5212b7cf979839916515 100644
--- a/library/ZendAfi/View/Helper/TagRechercheSimple.php
+++ b/library/ZendAfi/View/Helper/TagRechercheSimple.php
@@ -30,6 +30,7 @@ class ZendAfi_View_Helper_TagRechercheSimple extends ZendAfi_View_Helper_BaseHel
 
     $form = implode([$this->_renderInputTypeDoc(),
                      $this->_renderInputTri(),
+                     $this->_renderInputInFiles(),
                      $this->_renderChampSaisie(),
                      $this->renderDomainSelect(),
                      $this->renderSelectionBib()]);
@@ -231,6 +232,15 @@ class ZendAfi_View_Helper_TagRechercheSimple extends ZendAfi_View_Helper_BaseHel
   }
 
 
+  protected function _renderInputInFiles() {
+    return $this->_tag('input',
+                       null,
+                       ['type' => 'hidden',
+                        'name' => 'in_files',
+                        'value' => (int)$this->preferences["in_files"]]);
+  }
+
+
   public function renderForm($content) {
     $action_url = ['controller' => 'recherche',
                    'action' => 'simple'];
diff --git a/library/ZendAfi/View/Helper/TagTitreEtNombreDeResultats.php b/library/ZendAfi/View/Helper/TagTitreEtNombreDeResultats.php
index 2f1d2e13ffa98add6bf7f7a024b0013559bd6b3a..219646909673b233f3614bb7ecfbb73e3d646c35 100644
--- a/library/ZendAfi/View/Helper/TagTitreEtNombreDeResultats.php
+++ b/library/ZendAfi/View/Helper/TagTitreEtNombreDeResultats.php
@@ -20,27 +20,16 @@
  */
 class ZendAfi_View_Helper_TagTitreEtNombreDeResultats extends ZendAfi_View_Helper_BaseHelper {
 
-  public function tagTitreEtNombreDeResultats($search_result){
+  public function tagTitreEtNombreDeResultats($search_result) {
     $search_duration = $search_result->getDuration();
     $criteres_recherche = $search_result->getCriteresRecherche();
     $expression_recherche = $this->_getSearchTerm($criteres_recherche);
 
     $nombre_resultats = $search_result->getRecordsCount();
 
-    $plural_expression = $this->_islimited($nombre_resultats, $criteres_recherche->getMaxSearchResults())
-      ? $this->view->_("Il y a plus de %s résultats",
-                       $this->_tag('span',
-                                   ' ' . $nombre_resultats . ' ',
-                                   ['class' => 'nombre-recherche']))
-      : $this->view->_plural( $nombre_resultats,
-                             "Aucun résultat trouvé",
-                             "Il y a %s résultat",
-                             "Il y a %s résultats",
-                             $this->_tag('span',
-                                         ' ' . $nombre_resultats . ' ',
-                                         ['class' => 'nombre-recherche']));
-
-    $url_rss = $criteres_recherche->getUrlCriteresWithFacettes();
+    $plural_expression = $this->view->search_ResultCount($nombre_resultats,
+                                                         $criteres_recherche->getMaxSearchResults());
+    $url_rss = $criteres_recherche->getUrlCriteres();
     $url_rss['format'] = 'atom';
     $html = $this->view->tagRss($this->view->url($url_rss, null, true),
                                  $this->view->_('S\'abonner à cette recherche'));
@@ -58,11 +47,6 @@ class ZendAfi_View_Helper_TagTitreEtNombreDeResultats extends ZendAfi_View_Helpe
   }
 
 
-  protected function _islimited($count, $limit) {
-    return $limit && ($count == $limit);
-  }
-
-
   protected function _getSearchTerm($criteria) {
     if (($term = $criteria->getExpressionRecherche())
         && Class_Profil::getCurrentProfil()->isSearchTermEditable())
diff --git a/library/activitystreams b/library/activitystreams
new file mode 160000
index 0000000000000000000000000000000000000000..fb573517b032e10ad2630b7d1e2440b9e6e28a63
--- /dev/null
+++ b/library/activitystreams
@@ -0,0 +1 @@
+Subproject commit fb573517b032e10ad2630b7d1e2440b9e6e28a63
diff --git a/library/digital_resources/Cvs/Service.php b/library/digital_resources/Cvs/Service.php
index d5999866928a4104adb6826c93b7fe132656d785..108132686c315446170b14cae958c6389592eec0 100644
--- a/library/digital_resources/Cvs/Service.php
+++ b/library/digital_resources/Cvs/Service.php
@@ -234,10 +234,10 @@ class Cvs_Service {
                                'password' => $user->getPassword(),
                                'email' => $user->getMail(),
                                'dnaiss' => (($naissance = $user->getNaissance())
-                                            ? implode('-', array_reverse(explode('/', $naissance)))
+                                            ? Class_Date::frToIso($naissance)
                                             : ''),
                                'datout' => (($finabo = $user->getDateFin())
-                                            ? implode('-', array_reverse(explode('/', $finabo)))
+                                            ? Class_Date::frToIso($finabo)
                                             : ''),
                                'bibliotheque' => (($label = $this->_var('BMLABEL'))
                                                   ? $label : $user->getLibelleBib())]);
diff --git a/library/phpseclib b/library/phpseclib
new file mode 160000
index 0000000000000000000000000000000000000000..bc6ac8edfdea41760743acedd8e431677ebef75d
--- /dev/null
+++ b/library/phpseclib
@@ -0,0 +1 @@
+Subproject commit bc6ac8edfdea41760743acedd8e431677ebef75d
diff --git a/library/startup.php b/library/startup.php
index 0bbb965ae7a7258ce720a5e2f7488cf4e31784f1..ba43a06811d9bb522d6a083226ccc0f27d598174 100644
--- a/library/startup.php
+++ b/library/startup.php
@@ -81,7 +81,7 @@ class Bokeh_Engine {
 
   function setupConstants() {
     defineConstant('BOKEH_MAJOR_VERSION','8.0');
-    defineConstant('BOKEH_RELEASE_NUMBER', BOKEH_MAJOR_VERSION . '.23');
+    defineConstant('BOKEH_RELEASE_NUMBER', BOKEH_MAJOR_VERSION . '.24');
 
     defineConstant('BOKEH_REMOTE_FILES', 'http://git.afi-sa.fr/afi/opacce/');
 
@@ -177,8 +177,15 @@ class Bokeh_Engine {
       : ['cache_dir' => PATH_TEMP ];
 
     // getting a Zend_Cache_Core object
+    $backend = 'File';
+
+    if ($use_memcached) {
+      $backend = extension_loaded('memcached')
+        ? 'Memcached'
+        : 'Memcache';
+    }
     $cache = Zend_Cache::factory('Core',
-                                 $use_memcached ? 'Memcached' : 'File',
+                                 $backend,
                                  $frontendOptions,
                                  $backendOptions);
 
diff --git a/library/storm b/library/storm
index 8e752b86bc642f6563eb4b42b20fcbe8ece45b5c..03db1d52ff87846f60a4c42085811ef14b880ab9 160000
--- a/library/storm
+++ b/library/storm
@@ -1 +1 @@
-Subproject commit 8e752b86bc642f6563eb4b42b20fcbe8ece45b5c
+Subproject commit 03db1d52ff87846f60a4c42085811ef14b880ab9
diff --git a/library/translation/ca.mo b/library/translation/ca.mo
index 2fa8933ddde9c20a2bbd3e68dd19835763275455..5b695fcdab101a6ad716db945a5aedaa8bfb6a74 100644
Binary files a/library/translation/ca.mo and b/library/translation/ca.mo differ
diff --git a/library/translation/en.mo b/library/translation/en.mo
index 838a638ccb386ebf6802905bd44c01665af75909..ef188c2ead07714a6809c452a2ed75785a0779f1 100644
Binary files a/library/translation/en.mo and b/library/translation/en.mo differ
diff --git a/library/translation/es.mo b/library/translation/es.mo
index 66aead7cb1ccba9846f7a1072ab5e5af54568e27..d422ce751915f39a748d649e3387961cd6e16e06 100644
Binary files a/library/translation/es.mo and b/library/translation/es.mo differ
diff --git a/library/translation/fr.mo b/library/translation/fr.mo
index cb3505f3aec12d0c9461d3aa0ea42f0f024dadc6..562de0a4b1e939bb3ea62bf3f2aa5dc0f458751a 100644
Binary files a/library/translation/fr.mo and b/library/translation/fr.mo differ
diff --git a/library/translation/fr.po b/library/translation/fr.po
index 03f203d75fa488e6e50ee555a6618f9453d17fac..98b422086df64ad774fb8b4d060c20e8ef455fa0 100644
--- a/library/translation/fr.po
+++ b/library/translation/fr.po
@@ -780,6 +780,10 @@ msgstr "'%value%' contient plus de %max% caractères"
 msgid "'%value%' is less than %min% characters long"
 msgstr "'%value%' contient moins de %min% caractères"
 
+
+msgid "'%value%' does not fit given date format"
+msgstr "'%value%' ne correspond pas au format de date attendu"
+
 msgid ""
 "'%value%' is not a valid email address in the basic format local-"
 "part@hostname"
@@ -34755,6 +34759,7 @@ msgstr ""
 msgid "id_users invalides"
 msgstr ""
 
+
 #: ../../library/Class/IntProfilDonnees.php:173
 #: ../../library/Class/IntProfilDonnees.php:173
 #: ../../library/Class/IntProfilDonnees.php:174
diff --git a/library/translation/ro.mo b/library/translation/ro.mo
index 0dfe5318ccd79fb0d2b3530f0a5abbac791165d6..a53f705262c0cedf670ed28e829077918368a143 100644
Binary files a/library/translation/ro.mo and b/library/translation/ro.mo differ
diff --git a/public/admin/js/jquery_ui_datepicker_i18n/datepicker-en-GB.js b/public/admin/js/jquery_ui_datepicker_i18n/datepicker-en-GB.js
new file mode 100644
index 0000000000000000000000000000000000000000..c961c18659e7bc0ca418c058a1ee519da835287c
--- /dev/null
+++ b/public/admin/js/jquery_ui_datepicker_i18n/datepicker-en-GB.js
@@ -0,0 +1,37 @@
+/* English/UK initialisation for the jQuery UI date picker plugin. */
+/* Written by Stuart. */
+( function( factory ) {
+	if ( typeof define === "function" && define.amd ) {
+
+		// AMD. Register as an anonymous module.
+		define( [ "../widgets/datepicker" ], factory );
+	} else {
+
+		// Browser globals
+		factory( jQuery.datepicker );
+	}
+}( function( datepicker ) {
+
+datepicker.regional[ "en-GB" ] = {
+	closeText: "Done",
+	prevText: "Prev",
+	nextText: "Next",
+	currentText: "Today",
+	monthNames: [ "January","February","March","April","May","June",
+	"July","August","September","October","November","December" ],
+	monthNamesShort: [ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+	"Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ],
+	dayNames: [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ],
+	dayNamesShort: [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ],
+	dayNamesMin: [ "Su","Mo","Tu","We","Th","Fr","Sa" ],
+	weekHeader: "Wk",
+	dateFormat: "dd/mm/yy",
+	firstDay: 1,
+	isRTL: false,
+	showMonthAfterYear: false,
+	yearSuffix: "" };
+datepicker.setDefaults( datepicker.regional[ "en-GB" ] );
+
+return datepicker.regional[ "en-GB" ];
+
+} ) );
diff --git a/public/admin/js/jquery_ui_datepicker_i18n/datepicker-es.js b/public/admin/js/jquery_ui_datepicker_i18n/datepicker-es.js
new file mode 100644
index 0000000000000000000000000000000000000000..afb472af7f27cd745ceaafaf00db7e72c8ee2283
--- /dev/null
+++ b/public/admin/js/jquery_ui_datepicker_i18n/datepicker-es.js
@@ -0,0 +1,1030 @@
+
+
+
+
+
+
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8">
+  <link rel="dns-prefetch" href="https://github.githubassets.com">
+  <link rel="dns-prefetch" href="https://avatars0.githubusercontent.com">
+  <link rel="dns-prefetch" href="https://avatars1.githubusercontent.com">
+  <link rel="dns-prefetch" href="https://avatars2.githubusercontent.com">
+  <link rel="dns-prefetch" href="https://avatars3.githubusercontent.com">
+  <link rel="dns-prefetch" href="https://github-cloud.s3.amazonaws.com">
+  <link rel="dns-prefetch" href="https://user-images.githubusercontent.com/">
+
+
+
+  <link crossorigin="anonymous" media="all" integrity="sha512-aVn2DoCuXdXX9G3sp/Luupl/Ui00/iXrUh7Ke3geLlkigQY8GHBky7kKRSuyeKxGApWDdCQUy+6gTF1ZmYHWkw==" rel="stylesheet" href="https://github.githubassets.com/assets/frameworks-6b8b7859c4b8fbe3ab45f8ab0905a9f8.css" />
+  <link crossorigin="anonymous" media="all" integrity="sha512-Myp6HIV6QpUnPxl7XbmQTeyGZboMMugCA3QK9vka+wV9W6ZE0NoHtZh0nWjowNoOB34X1nAgcEgfhhzhy+5u4g==" rel="stylesheet" href="https://github.githubassets.com/assets/site-b046b27487428b94fc20941868838997.css" />
+    <link crossorigin="anonymous" media="all" integrity="sha512-QhuwqMmfVbf7hBKQiI06Hhr+N3pwbFEoddGW/mB4FL2g0/hxjDyFOZQfa8NF2wymI8UnTVRHFU1zeG0x+wLbQw==" rel="stylesheet" href="https://github.githubassets.com/assets/github-74ca376c8e9adc68a252986ce12b0857.css" />
+    
+    
+    
+    
+
+  <meta name="viewport" content="width=device-width">
+  
+  <title>jquery-ui/datepicker-es.js at master · jquery/jquery-ui · GitHub</title>
+    <meta name="description" content="The official jQuery user interface library. Contribute to jquery/jquery-ui development by creating an account on GitHub.">
+    <link rel="search" type="application/opensearchdescription+xml" href="/opensearch.xml" title="GitHub">
+  <link rel="fluid-icon" href="https://github.com/fluidicon.png" title="GitHub">
+  <meta property="fb:app_id" content="1401488693436528">
+
+    <meta name="twitter:image:src" content="https://avatars2.githubusercontent.com/u/70142?s=400&amp;v=4" /><meta name="twitter:site" content="@github" /><meta name="twitter:card" content="summary" /><meta name="twitter:title" content="jquery/jquery-ui" /><meta name="twitter:description" content="The official jQuery user interface library. Contribute to jquery/jquery-ui development by creating an account on GitHub." />
+    <meta property="og:image" content="https://avatars2.githubusercontent.com/u/70142?s=400&amp;v=4" /><meta property="og:site_name" content="GitHub" /><meta property="og:type" content="object" /><meta property="og:title" content="jquery/jquery-ui" /><meta property="og:url" content="https://github.com/jquery/jquery-ui" /><meta property="og:description" content="The official jQuery user interface library. Contribute to jquery/jquery-ui development by creating an account on GitHub." />
+
+  <link rel="assets" href="https://github.githubassets.com/">
+  
+  <meta name="pjax-timeout" content="1000">
+  
+  <meta name="request-id" content="24AC:179AF:1F77047:302989C:5D7227FF" data-pjax-transient>
+
+
+  
+
+  <meta name="selected-link" value="repo_source" data-pjax-transient>
+
+      <meta name="google-site-verification" content="KT5gs8h0wvaagLKAVWq8bbeNwnZZK1r1XQysX3xurLU">
+    <meta name="google-site-verification" content="ZzhVyEFwb7w3e0-uOTltm8Jsck2F5StVihD0exw2fsA">
+    <meta name="google-site-verification" content="GXs5KoUUkNCoaAZn7wPN-t01Pywp9M3sEjnt_3_ZWPc">
+
+  <meta name="octolytics-host" content="collector.githubapp.com" /><meta name="octolytics-app-id" content="github" /><meta name="octolytics-event-url" content="https://collector.githubapp.com/github-external/browser_event" /><meta name="octolytics-dimension-request_id" content="24AC:179AF:1F77047:302989C:5D7227FF" /><meta name="octolytics-dimension-region_edge" content="ams" /><meta name="octolytics-dimension-region_render" content="iad" /><meta name="octolytics-dimension-ga_id" content="" class="js-octo-ga-id" /><meta name="octolytics-dimension-visitor_id" content="1614443839805859839" />
+<meta name="analytics-location" content="/&lt;user-name&gt;/&lt;repo-name&gt;/blob/show" data-pjax-transient="true" />
+
+
+
+    <meta name="google-analytics" content="UA-3769691-2">
+
+
+<meta class="js-ga-set" name="dimension1" content="Logged Out">
+
+
+
+  
+
+      <meta name="hostname" content="github.com">
+    <meta name="user-login" content="">
+
+      <meta name="expected-hostname" content="github.com">
+    <meta name="js-proxy-site-detection-payload" content="OGJlMWFhNGM1NmMzOGRiNjlmOWNhNmNhNGZmMTExYTBjYzcxYWYzYTQyMTZhMDg2ZTJkZjdjMDg4Y2Q1YzUwMnx7InJlbW90ZV9hZGRyZXNzIjoiMTA5LjMuMjIwLjE3MCIsInJlcXVlc3RfaWQiOiIyNEFDOjE3OUFGOjFGNzcwNDc6MzAyOTg5Qzo1RDcyMjdGRiIsInRpbWVzdGFtcCI6MTU2Nzc2MjQzMSwiaG9zdCI6ImdpdGh1Yi5jb20ifQ==">
+
+    <meta name="enabled-features" content="ACTIONS_V2_ON_MARKETPLACE,MARKETPLACE_FEATURED_BLOG_POSTS,MARKETPLACE_INVOICED_BILLING,MARKETPLACE_SOCIAL_PROOF_CUSTOMERS,MARKETPLACE_TRENDING_SOCIAL_PROOF,MARKETPLACE_RECOMMENDATIONS,MARKETPLACE_PENDING_INSTALLATIONS">
+
+  <meta name="html-safe-nonce" content="021f1a46323844bfec7399c068ba9ce84097edb3">
+
+  <meta http-equiv="x-pjax-version" content="d237c2b71e49a92d4a6366cb711255df">
+  
+
+      <link href="https://github.com/jquery/jquery-ui/commits/master.atom" rel="alternate" title="Recent Commits to jquery-ui:master" type="application/atom+xml">
+
+  <meta name="go-import" content="github.com/jquery/jquery-ui git https://github.com/jquery/jquery-ui.git">
+
+  <meta name="octolytics-dimension-user_id" content="70142" /><meta name="octolytics-dimension-user_login" content="jquery" /><meta name="octolytics-dimension-repository_id" content="478996" /><meta name="octolytics-dimension-repository_nwo" content="jquery/jquery-ui" /><meta name="octolytics-dimension-repository_public" content="true" /><meta name="octolytics-dimension-repository_is_fork" content="false" /><meta name="octolytics-dimension-repository_network_root_id" content="478996" /><meta name="octolytics-dimension-repository_network_root_nwo" content="jquery/jquery-ui" /><meta name="octolytics-dimension-repository_explore_github_marketplace_ci_cta_shown" content="false" />
+
+
+    <link rel="canonical" href="https://github.com/jquery/jquery-ui/blob/master/ui/i18n/datepicker-es.js" data-pjax-transient>
+
+
+  <meta name="browser-stats-url" content="https://api.github.com/_private/browser/stats">
+
+  <meta name="browser-errors-url" content="https://api.github.com/_private/browser/errors">
+
+  <link rel="mask-icon" href="https://github.githubassets.com/pinned-octocat.svg" color="#000000">
+  <link rel="icon" type="image/x-icon" class="js-site-favicon" href="https://github.githubassets.com/favicon.ico">
+
+<meta name="theme-color" content="#1e2327">
+
+
+
+
+
+  <link rel="manifest" href="/manifest.json" crossOrigin="use-credentials">
+
+  </head>
+
+  <body class="logged-out env-production page-responsive page-blob">
+    
+
+  <div class="position-relative js-header-wrapper ">
+    <a href="#start-of-content" tabindex="1" class="px-2 py-4 bg-blue text-white show-on-focus js-skip-to-content">Skip to content</a>
+    <div id="js-pjax-loader-bar" class="pjax-loader-bar"><div class="progress"></div></div>
+
+    
+    
+    
+
+
+        <header class="Header-old header-logged-out js-details-container Details position-relative f4 py-2" role="banner">
+  <div class="container-lg d-lg-flex flex-items-center p-responsive">
+    <div class="d-flex flex-justify-between flex-items-center">
+        <a class="mr-4" href="https://github.com/" aria-label="Homepage" data-ga-click="(Logged out) Header, go to homepage, icon:logo-wordmark">
+          <svg height="32" class="octicon octicon-mark-github text-white" viewBox="0 0 16 16" version="1.1" width="32" aria-hidden="true"><path fill-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z"/></svg>
+        </a>
+
+          <div class="d-lg-none css-truncate css-truncate-target width-fit p-2">
+            
+              <svg class="octicon octicon-repo" viewBox="0 0 12 16" version="1.1" width="12" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9H3V8h1v1zm0-3H3v1h1V6zm0-2H3v1h1V4zm0-2H3v1h1V2zm8-1v12c0 .55-.45 1-1 1H6v2l-1.5-1.5L3 16v-2H1c-.55 0-1-.45-1-1V1c0-.55.45-1 1-1h10c.55 0 1 .45 1 1zm-1 10H1v2h2v-1h3v1h5v-2zm0-10H2v9h9V1z"/></svg>
+    <a class="Header-link" href="/jquery">jquery</a>
+    /
+    <a class="Header-link" href="/jquery/jquery-ui">jquery-ui</a>
+
+
+          </div>
+
+        <div class="d-flex flex-items-center">
+            <a href="/join?source=header-repo"
+              class="d-inline-block d-lg-none f5 text-white no-underline border border-gray-dark rounded-2 px-2 py-1 mr-3 mr-sm-5"
+              data-hydro-click="{&quot;event_type&quot;:&quot;authentication.click&quot;,&quot;payload&quot;:{&quot;location_in_page&quot;:&quot;site header&quot;,&quot;repository_id&quot;:null,&quot;auth_type&quot;:&quot;SIGN_UP&quot;,&quot;client_id&quot;:null,&quot;originating_request_id&quot;:&quot;24AC:179AF:1F77047:302989C:5D7227FF&quot;,&quot;originating_url&quot;:&quot;https://github.com/jquery/jquery-ui/blob/master/ui/i18n/datepicker-es.js&quot;,&quot;referrer&quot;:null,&quot;user_id&quot;:null}}" data-hydro-click-hmac="94134e7bb345cf4c8407e1b32798accadd9b0c8141f627b764cdba3eced754b5"
+              data-ga-click="(Logged out) Header, clicked Sign up, text:sign-up">
+              Sign&nbsp;up
+            </a>
+
+          <button class="btn-link d-lg-none mt-1 js-details-target" type="button" aria-label="Toggle navigation" aria-expanded="false">
+            <svg height="24" class="octicon octicon-three-bars text-white" viewBox="0 0 12 16" version="1.1" width="18" aria-hidden="true"><path fill-rule="evenodd" d="M11.41 9H.59C0 9 0 8.59 0 8c0-.59 0-1 .59-1H11.4c.59 0 .59.41.59 1 0 .59 0 1-.59 1h.01zm0-4H.59C0 5 0 4.59 0 4c0-.59 0-1 .59-1H11.4c.59 0 .59.41.59 1 0 .59 0 1-.59 1h.01zM.59 11H11.4c.59 0 .59.41.59 1 0 .59 0 1-.59 1H.59C0 13 0 12.59 0 12c0-.59 0-1 .59-1z"/></svg>
+          </button>
+        </div>
+    </div>
+
+    <div class="HeaderMenu HeaderMenu--logged-out position-fixed top-0 right-0 bottom-0 height-fit position-lg-relative d-lg-flex flex-justify-between flex-items-center flex-auto">
+      <div class="d-flex d-lg-none flex-justify-end border-bottom bg-gray-light p-3">
+        <button class="btn-link js-details-target" type="button" aria-label="Toggle navigation" aria-expanded="false">
+          <svg height="24" class="octicon octicon-x text-gray" viewBox="0 0 12 16" version="1.1" width="18" aria-hidden="true"><path fill-rule="evenodd" d="M7.48 8l3.75 3.75-1.48 1.48L6 9.48l-3.75 3.75-1.48-1.48L4.52 8 .77 4.25l1.48-1.48L6 6.52l3.75-3.75 1.48 1.48L7.48 8z"/></svg>
+        </button>
+      </div>
+
+        <nav class="mt-0 px-3 px-lg-0 mb-5 mb-lg-0" aria-label="Global">
+          <ul class="d-lg-flex list-style-none">
+              <li class="d-block d-lg-flex flex-lg-nowrap flex-lg-items-center border-bottom border-lg-bottom-0 mr-0 mr-lg-3 edge-item-fix position-relative flex-wrap flex-justify-between d-flex flex-items-center ">
+                <details class="HeaderMenu-details details-overlay details-reset width-full">
+                  <summary class="HeaderMenu-summary HeaderMenu-link px-0 py-3 border-0 no-wrap d-block d-lg-inline-block">
+                    Why GitHub?
+                    <svg x="0px" y="0px" viewBox="0 0 14 8" xml:space="preserve" fill="none" class="icon-chevon-down-mktg position-absolute position-lg-relative">
+                      <path d="M1,1l6.2,6L13,1"></path>
+                    </svg>
+                  </summary>
+                  <div class="dropdown-menu flex-auto rounded-1 bg-white px-0 mt-0 pb-4 p-lg-4 position-relative position-lg-absolute left-0 left-lg-n4">
+                    <a href="/features" class="py-2 lh-condensed-ultra d-block link-gray-dark no-underline h5 Bump-link--hover" data-ga-click="(Logged out) Header, go to Features">Features <span class="Bump-link-symbol float-right text-normal text-gray-light">&rarr;</span></a>
+                    <ul class="list-style-none f5 pb-3">
+                      <li class="edge-item-fix"><a href="/features/code-review/" class="py-2 lh-condensed-ultra d-block link-gray no-underline f5" data-ga-click="(Logged out) Header, go to Code review">Code review</a></li>
+                      <li class="edge-item-fix"><a href="/features/project-management/" class="py-2 lh-condensed-ultra d-block link-gray no-underline f5" data-ga-click="(Logged out) Header, go to Project management">Project management</a></li>
+                      <li class="edge-item-fix"><a href="/features/integrations" class="py-2 lh-condensed-ultra d-block link-gray no-underline f5" data-ga-click="(Logged out) Header, go to Integrations">Integrations</a></li>
+                      <li class="edge-item-fix"><a href="/features/actions" class="py-2 lh-condensed-ultra d-block link-gray no-underline f5" data-ga-click="(Logged out) Header, go to Actions">Actions</a>
+                          <li class="edge-item-fix"><a href="/features/package-registry" class="py-2 lh-condensed-ultra d-block link-gray no-underline f5" data-ga-click="(Logged out) Header, go to Package Registry">Package registry</a>
+                      <li class="edge-item-fix"><a href="/features#team-management" class="py-2 lh-condensed-ultra d-block link-gray no-underline f5" data-ga-click="(Logged out) Header, go to Team management">Team management</a></li>
+                      <li class="edge-item-fix"><a href="/features#social-coding" class="py-2 lh-condensed-ultra d-block link-gray no-underline f5" data-ga-click="(Logged out) Header, go to Social coding">Social coding</a></li>
+                      <li class="edge-item-fix"><a href="/features#documentation" class="py-2 lh-condensed-ultra d-block link-gray no-underline f5" data-ga-click="(Logged out) Header, go to Documentation">Documentation</a></li>
+                      <li class="edge-item-fix"><a href="/features#code-hosting" class="py-2 lh-condensed-ultra d-block link-gray no-underline f5" data-ga-click="(Logged out) Header, go to Code hosting">Code hosting</a></li>
+                    </ul>
+
+                    <ul class="list-style-none mb-0 border-lg-top pt-lg-3">
+                      <li class="edge-item-fix"><a href="/customer-stories" class="py-2 lh-condensed-ultra d-block no-underline link-gray-dark no-underline h5 Bump-link--hover" data-ga-click="(Logged out) Header, go to Customer stories">Customer stories <span class="Bump-link-symbol float-right text-normal text-gray-light">&rarr;</span></a></li>
+                      <li class="edge-item-fix"><a href="/security" class="py-2 lh-condensed-ultra d-block no-underline link-gray-dark no-underline h5 Bump-link--hover" data-ga-click="(Logged out) Header, go to Security">Security <span class="Bump-link-symbol float-right text-normal text-gray-light">&rarr;</span></a></li>
+                    </ul>
+                  </div>
+                </details>
+              </li>
+              <li class="border-bottom border-lg-bottom-0 mr-0 mr-lg-3">
+                <a href="/enterprise" class="HeaderMenu-link no-underline py-3 d-block d-lg-inline-block" data-ga-click="(Logged out) Header, go to Enterprise">Enterprise</a>
+              </li>
+
+              <li class="d-block d-lg-flex flex-lg-nowrap flex-lg-items-center border-bottom border-lg-bottom-0 mr-0 mr-lg-3 edge-item-fix position-relative flex-wrap flex-justify-between d-flex flex-items-center ">
+                <details class="HeaderMenu-details details-overlay details-reset width-full">
+                  <summary class="HeaderMenu-summary HeaderMenu-link px-0 py-3 border-0 no-wrap d-block d-lg-inline-block">
+                    Explore
+                    <svg x="0px" y="0px" viewBox="0 0 14 8" xml:space="preserve" fill="none" class="icon-chevon-down-mktg position-absolute position-lg-relative">
+                      <path d="M1,1l6.2,6L13,1"></path>
+                    </svg>
+                  </summary>
+
+                  <div class="dropdown-menu flex-auto rounded-1 bg-white px-0 pt-2 pb-0 mt-0 pb-4 p-lg-4 position-relative position-lg-absolute left-0 left-lg-n4">
+                    <ul class="list-style-none mb-3">
+                      <li class="edge-item-fix"><a href="/explore" class="py-2 lh-condensed-ultra d-block link-gray-dark no-underline h5 Bump-link--hover" data-ga-click="(Logged out) Header, go to Explore">Explore GitHub <span class="Bump-link-symbol float-right text-normal text-gray-light">&rarr;</span></a></li>
+                    </ul>
+
+                    <h4 class="text-gray-light text-normal text-mono f5 mb-2 border-lg-top pt-lg-3">Learn &amp; contribute</h4>
+                    <ul class="list-style-none mb-3">
+                      <li class="edge-item-fix"><a href="/topics" class="py-2 lh-condensed-ultra d-block link-gray no-underline f5" data-ga-click="(Logged out) Header, go to Topics">Topics</a></li>
+                        <li class="edge-item-fix"><a href="/collections" class="py-2 lh-condensed-ultra d-block link-gray no-underline f5" data-ga-click="(Logged out) Header, go to Collections">Collections</a></li>
+                      <li class="edge-item-fix"><a href="/trending" class="py-2 lh-condensed-ultra d-block link-gray no-underline f5" data-ga-click="(Logged out) Header, go to Trending">Trending</a></li>
+                      <li class="edge-item-fix"><a href="https://lab.github.com/" class="py-2 lh-condensed-ultra d-block link-gray no-underline f5" data-ga-click="(Logged out) Header, go to Learning lab">Learning Lab</a></li>
+                      <li class="edge-item-fix"><a href="https://opensource.guide" class="py-2 lh-condensed-ultra d-block link-gray no-underline f5" data-ga-click="(Logged out) Header, go to Open source guides">Open source guides</a></li>
+                    </ul>
+
+                    <h4 class="text-gray-light text-normal text-mono f5 mb-2 border-lg-top pt-lg-3">Connect with others</h4>
+                    <ul class="list-style-none mb-0">
+                      <li class="edge-item-fix"><a href="https://github.com/events" class="py-2 lh-condensed-ultra d-block link-gray no-underline f5" data-ga-click="(Logged out) Header, go to Events">Events</a></li>
+                      <li class="edge-item-fix"><a href="https://github.community" class="py-2 lh-condensed-ultra d-block link-gray no-underline f5" data-ga-click="(Logged out) Header, go to Community forum">Community forum</a></li>
+                      <li class="edge-item-fix"><a href="https://education.github.com" class="py-2 pb-0 lh-condensed-ultra d-block link-gray no-underline f5" data-ga-click="(Logged out) Header, go to GitHub Education">GitHub Education</a></li>
+                    </ul>
+                  </div>
+                </details>
+              </li>
+
+              <li class="border-bottom border-lg-bottom-0 mr-0 mr-lg-3">
+                <a href="/marketplace" class="HeaderMenu-link no-underline py-3 d-block d-lg-inline-block" data-ga-click="(Logged out) Header, go to Marketplace">Marketplace</a>
+              </li>
+
+              <li class="d-block d-lg-flex flex-lg-nowrap flex-lg-items-center border-bottom border-lg-bottom-0 mr-0 mr-lg-3 edge-item-fix position-relative flex-wrap flex-justify-between d-flex flex-items-center ">
+                <details class="HeaderMenu-details details-overlay details-reset width-full">
+                  <summary class="HeaderMenu-summary HeaderMenu-link px-0 py-3 border-0 no-wrap d-block d-lg-inline-block">
+                    Pricing
+                    <svg x="0px" y="0px" viewBox="0 0 14 8" xml:space="preserve" fill="none" class="icon-chevon-down-mktg position-absolute position-lg-relative">
+                       <path d="M1,1l6.2,6L13,1"></path>
+                    </svg>
+                  </summary>
+
+                  <div class="dropdown-menu flex-auto rounded-1 bg-white px-0 pt-2 pb-4 mt-0 p-lg-4 position-relative position-lg-absolute left-0 left-lg-n4">
+                    <a href="/pricing" class="pb-2 lh-condensed-ultra d-block link-gray-dark no-underline h5 Bump-link--hover" data-ga-click="(Logged out) Header, go to Pricing">Plans <span class="Bump-link-symbol float-right text-normal text-gray-light">&rarr;</span></a>
+
+                    <ul class="list-style-none mb-3">
+                      <li class="edge-item-fix"><a href="/pricing#feature-comparison" class="py-2 lh-condensed-ultra d-block link-gray no-underline f5" data-ga-click="(Logged out) Header, go to Compare plans">Compare plans</a></li>
+                      <li class="edge-item-fix"><a href="https://enterprise.github.com/contact" class="py-2 lh-condensed-ultra d-block link-gray no-underline f5" data-ga-click="(Logged out) Header, go to Contact Sales">Contact Sales</a></li>
+                    </ul>
+
+                    <ul class="list-style-none mb-0 border-lg-top pt-lg-3">
+                      <li class="edge-item-fix"><a href="/nonprofit" class="py-2 lh-condensed-ultra d-block no-underline link-gray-dark no-underline h5 Bump-link--hover" data-ga-click="(Logged out) Header, go to Nonprofits">Nonprofit <span class="Bump-link-symbol float-right text-normal text-gray-light">&rarr;</span></a></li>
+                      <li class="edge-item-fix"><a href="https://education.github.com" class="py-2 pb-0 lh-condensed-ultra d-block no-underline link-gray-dark no-underline h5 Bump-link--hover"  data-ga-click="(Logged out) Header, go to Education">Education <span class="Bump-link-symbol float-right text-normal text-gray-light">&rarr;</span></a></li>
+                    </ul>
+                  </div>
+                </details>
+              </li>
+          </ul>
+        </nav>
+
+      <div class="d-lg-flex flex-items-center px-3 px-lg-0 text-center text-lg-left">
+          <div class="d-lg-flex mb-3 mb-lg-0">
+            <div class="header-search flex-self-stretch flex-lg-self-auto mr-0 mr-lg-3 mb-3 mb-lg-0 scoped-search site-scoped-search js-site-search position-relative js-jump-to"
+  role="combobox"
+  aria-owns="jump-to-results"
+  aria-label="Search or jump to"
+  aria-haspopup="listbox"
+  aria-expanded="false"
+>
+  <div class="position-relative">
+    <!-- '"` --><!-- </textarea></xmp> --></option></form><form class="js-site-search-form" role="search" aria-label="Site" data-scope-type="Repository" data-scope-id="478996" data-scoped-search-url="/jquery/jquery-ui/search" data-unscoped-search-url="/search" action="/jquery/jquery-ui/search" accept-charset="UTF-8" method="get"><input name="utf8" type="hidden" value="&#x2713;" />
+      <label class="form-control input-sm header-search-wrapper p-0 header-search-wrapper-jump-to position-relative d-flex flex-justify-between flex-items-center js-chromeless-input-container">
+        <input type="text"
+          class="form-control input-sm header-search-input jump-to-field js-jump-to-field js-site-search-focus js-site-search-field is-clearable"
+          data-hotkey="s,/"
+          name="q"
+          value=""
+          placeholder="Search"
+          data-unscoped-placeholder="Search GitHub"
+          data-scoped-placeholder="Search"
+          autocapitalize="off"
+          aria-autocomplete="list"
+          aria-controls="jump-to-results"
+          aria-label="Search"
+          data-jump-to-suggestions-path="/_graphql/GetSuggestedNavigationDestinations#csrf-token=U0//l7tBXaRzq5o0D1xGpLdwZUAgybtGhWS8h4gdzb1jsjKOIhr3SVT5SfqHPozH5yaIm1wchNDeGnT5KPp+UA=="
+          spellcheck="false"
+          autocomplete="off"
+          >
+          <input type="hidden" class="js-site-search-type-field" name="type" >
+            <img src="https://github.githubassets.com/images/search-key-slash.svg" alt="" class="mr-2 header-search-key-slash">
+
+            <div class="Box position-absolute overflow-hidden d-none jump-to-suggestions js-jump-to-suggestions-container">
+              
+<ul class="d-none js-jump-to-suggestions-template-container">
+  
+
+<li class="d-flex flex-justify-start flex-items-center p-0 f5 navigation-item js-navigation-item js-jump-to-suggestion" role="option">
+  <a tabindex="-1" class="no-underline d-flex flex-auto flex-items-center jump-to-suggestions-path js-jump-to-suggestion-path js-navigation-open p-2" href="">
+    <div class="jump-to-octicon js-jump-to-octicon flex-shrink-0 mr-2 text-center d-none">
+      <svg height="16" width="16" class="octicon octicon-repo flex-shrink-0 js-jump-to-octicon-repo d-none" title="Repository" aria-label="Repository" viewBox="0 0 12 16" version="1.1" role="img"><path fill-rule="evenodd" d="M4 9H3V8h1v1zm0-3H3v1h1V6zm0-2H3v1h1V4zm0-2H3v1h1V2zm8-1v12c0 .55-.45 1-1 1H6v2l-1.5-1.5L3 16v-2H1c-.55 0-1-.45-1-1V1c0-.55.45-1 1-1h10c.55 0 1 .45 1 1zm-1 10H1v2h2v-1h3v1h5v-2zm0-10H2v9h9V1z"/></svg>
+      <svg height="16" width="16" class="octicon octicon-project flex-shrink-0 js-jump-to-octicon-project d-none" title="Project" aria-label="Project" viewBox="0 0 15 16" version="1.1" role="img"><path fill-rule="evenodd" d="M10 12h3V2h-3v10zm-4-2h3V2H6v8zm-4 4h3V2H2v12zm-1 1h13V1H1v14zM14 0H1a1 1 0 0 0-1 1v14a1 1 0 0 0 1 1h13a1 1 0 0 0 1-1V1a1 1 0 0 0-1-1z"/></svg>
+      <svg height="16" width="16" class="octicon octicon-search flex-shrink-0 js-jump-to-octicon-search d-none" title="Search" aria-label="Search" viewBox="0 0 16 16" version="1.1" role="img"><path fill-rule="evenodd" d="M15.7 13.3l-3.81-3.83A5.93 5.93 0 0 0 13 6c0-3.31-2.69-6-6-6S1 2.69 1 6s2.69 6 6 6c1.3 0 2.48-.41 3.47-1.11l3.83 3.81c.19.2.45.3.7.3.25 0 .52-.09.7-.3a.996.996 0 0 0 0-1.41v.01zM7 10.7c-2.59 0-4.7-2.11-4.7-4.7 0-2.59 2.11-4.7 4.7-4.7 2.59 0 4.7 2.11 4.7 4.7 0 2.59-2.11 4.7-4.7 4.7z"/></svg>
+    </div>
+
+    <img class="avatar mr-2 flex-shrink-0 js-jump-to-suggestion-avatar d-none" alt="" aria-label="Team" src="" width="28" height="28">
+
+    <div class="jump-to-suggestion-name js-jump-to-suggestion-name flex-auto overflow-hidden text-left no-wrap css-truncate css-truncate-target">
+    </div>
+
+    <div class="border rounded-1 flex-shrink-0 bg-gray px-1 text-gray-light ml-1 f6 d-none js-jump-to-badge-search">
+      <span class="js-jump-to-badge-search-text-default d-none" aria-label="in this repository">
+        In this repository
+      </span>
+      <span class="js-jump-to-badge-search-text-global d-none" aria-label="in all of GitHub">
+        All GitHub
+      </span>
+      <span aria-hidden="true" class="d-inline-block ml-1 v-align-middle">↵</span>
+    </div>
+
+    <div aria-hidden="true" class="border rounded-1 flex-shrink-0 bg-gray px-1 text-gray-light ml-1 f6 d-none d-on-nav-focus js-jump-to-badge-jump">
+      Jump to
+      <span class="d-inline-block ml-1 v-align-middle">↵</span>
+    </div>
+  </a>
+</li>
+
+</ul>
+
+<ul class="d-none js-jump-to-no-results-template-container">
+  <li class="d-flex flex-justify-center flex-items-center f5 d-none js-jump-to-suggestion p-2">
+    <span class="text-gray">No suggested jump to results</span>
+  </li>
+</ul>
+
+<ul id="jump-to-results" role="listbox" class="p-0 m-0 js-navigation-container jump-to-suggestions-results-container js-jump-to-suggestions-results-container">
+  
+
+<li class="d-flex flex-justify-start flex-items-center p-0 f5 navigation-item js-navigation-item js-jump-to-scoped-search d-none" role="option">
+  <a tabindex="-1" class="no-underline d-flex flex-auto flex-items-center jump-to-suggestions-path js-jump-to-suggestion-path js-navigation-open p-2" href="">
+    <div class="jump-to-octicon js-jump-to-octicon flex-shrink-0 mr-2 text-center d-none">
+      <svg height="16" width="16" class="octicon octicon-repo flex-shrink-0 js-jump-to-octicon-repo d-none" title="Repository" aria-label="Repository" viewBox="0 0 12 16" version="1.1" role="img"><path fill-rule="evenodd" d="M4 9H3V8h1v1zm0-3H3v1h1V6zm0-2H3v1h1V4zm0-2H3v1h1V2zm8-1v12c0 .55-.45 1-1 1H6v2l-1.5-1.5L3 16v-2H1c-.55 0-1-.45-1-1V1c0-.55.45-1 1-1h10c.55 0 1 .45 1 1zm-1 10H1v2h2v-1h3v1h5v-2zm0-10H2v9h9V1z"/></svg>
+      <svg height="16" width="16" class="octicon octicon-project flex-shrink-0 js-jump-to-octicon-project d-none" title="Project" aria-label="Project" viewBox="0 0 15 16" version="1.1" role="img"><path fill-rule="evenodd" d="M10 12h3V2h-3v10zm-4-2h3V2H6v8zm-4 4h3V2H2v12zm-1 1h13V1H1v14zM14 0H1a1 1 0 0 0-1 1v14a1 1 0 0 0 1 1h13a1 1 0 0 0 1-1V1a1 1 0 0 0-1-1z"/></svg>
+      <svg height="16" width="16" class="octicon octicon-search flex-shrink-0 js-jump-to-octicon-search d-none" title="Search" aria-label="Search" viewBox="0 0 16 16" version="1.1" role="img"><path fill-rule="evenodd" d="M15.7 13.3l-3.81-3.83A5.93 5.93 0 0 0 13 6c0-3.31-2.69-6-6-6S1 2.69 1 6s2.69 6 6 6c1.3 0 2.48-.41 3.47-1.11l3.83 3.81c.19.2.45.3.7.3.25 0 .52-.09.7-.3a.996.996 0 0 0 0-1.41v.01zM7 10.7c-2.59 0-4.7-2.11-4.7-4.7 0-2.59 2.11-4.7 4.7-4.7 2.59 0 4.7 2.11 4.7 4.7 0 2.59-2.11 4.7-4.7 4.7z"/></svg>
+    </div>
+
+    <img class="avatar mr-2 flex-shrink-0 js-jump-to-suggestion-avatar d-none" alt="" aria-label="Team" src="" width="28" height="28">
+
+    <div class="jump-to-suggestion-name js-jump-to-suggestion-name flex-auto overflow-hidden text-left no-wrap css-truncate css-truncate-target">
+    </div>
+
+    <div class="border rounded-1 flex-shrink-0 bg-gray px-1 text-gray-light ml-1 f6 d-none js-jump-to-badge-search">
+      <span class="js-jump-to-badge-search-text-default d-none" aria-label="in this repository">
+        In this repository
+      </span>
+      <span class="js-jump-to-badge-search-text-global d-none" aria-label="in all of GitHub">
+        All GitHub
+      </span>
+      <span aria-hidden="true" class="d-inline-block ml-1 v-align-middle">↵</span>
+    </div>
+
+    <div aria-hidden="true" class="border rounded-1 flex-shrink-0 bg-gray px-1 text-gray-light ml-1 f6 d-none d-on-nav-focus js-jump-to-badge-jump">
+      Jump to
+      <span class="d-inline-block ml-1 v-align-middle">↵</span>
+    </div>
+  </a>
+</li>
+
+  
+
+<li class="d-flex flex-justify-start flex-items-center p-0 f5 navigation-item js-navigation-item js-jump-to-global-search d-none" role="option">
+  <a tabindex="-1" class="no-underline d-flex flex-auto flex-items-center jump-to-suggestions-path js-jump-to-suggestion-path js-navigation-open p-2" href="">
+    <div class="jump-to-octicon js-jump-to-octicon flex-shrink-0 mr-2 text-center d-none">
+      <svg height="16" width="16" class="octicon octicon-repo flex-shrink-0 js-jump-to-octicon-repo d-none" title="Repository" aria-label="Repository" viewBox="0 0 12 16" version="1.1" role="img"><path fill-rule="evenodd" d="M4 9H3V8h1v1zm0-3H3v1h1V6zm0-2H3v1h1V4zm0-2H3v1h1V2zm8-1v12c0 .55-.45 1-1 1H6v2l-1.5-1.5L3 16v-2H1c-.55 0-1-.45-1-1V1c0-.55.45-1 1-1h10c.55 0 1 .45 1 1zm-1 10H1v2h2v-1h3v1h5v-2zm0-10H2v9h9V1z"/></svg>
+      <svg height="16" width="16" class="octicon octicon-project flex-shrink-0 js-jump-to-octicon-project d-none" title="Project" aria-label="Project" viewBox="0 0 15 16" version="1.1" role="img"><path fill-rule="evenodd" d="M10 12h3V2h-3v10zm-4-2h3V2H6v8zm-4 4h3V2H2v12zm-1 1h13V1H1v14zM14 0H1a1 1 0 0 0-1 1v14a1 1 0 0 0 1 1h13a1 1 0 0 0 1-1V1a1 1 0 0 0-1-1z"/></svg>
+      <svg height="16" width="16" class="octicon octicon-search flex-shrink-0 js-jump-to-octicon-search d-none" title="Search" aria-label="Search" viewBox="0 0 16 16" version="1.1" role="img"><path fill-rule="evenodd" d="M15.7 13.3l-3.81-3.83A5.93 5.93 0 0 0 13 6c0-3.31-2.69-6-6-6S1 2.69 1 6s2.69 6 6 6c1.3 0 2.48-.41 3.47-1.11l3.83 3.81c.19.2.45.3.7.3.25 0 .52-.09.7-.3a.996.996 0 0 0 0-1.41v.01zM7 10.7c-2.59 0-4.7-2.11-4.7-4.7 0-2.59 2.11-4.7 4.7-4.7 2.59 0 4.7 2.11 4.7 4.7 0 2.59-2.11 4.7-4.7 4.7z"/></svg>
+    </div>
+
+    <img class="avatar mr-2 flex-shrink-0 js-jump-to-suggestion-avatar d-none" alt="" aria-label="Team" src="" width="28" height="28">
+
+    <div class="jump-to-suggestion-name js-jump-to-suggestion-name flex-auto overflow-hidden text-left no-wrap css-truncate css-truncate-target">
+    </div>
+
+    <div class="border rounded-1 flex-shrink-0 bg-gray px-1 text-gray-light ml-1 f6 d-none js-jump-to-badge-search">
+      <span class="js-jump-to-badge-search-text-default d-none" aria-label="in this repository">
+        In this repository
+      </span>
+      <span class="js-jump-to-badge-search-text-global d-none" aria-label="in all of GitHub">
+        All GitHub
+      </span>
+      <span aria-hidden="true" class="d-inline-block ml-1 v-align-middle">↵</span>
+    </div>
+
+    <div aria-hidden="true" class="border rounded-1 flex-shrink-0 bg-gray px-1 text-gray-light ml-1 f6 d-none d-on-nav-focus js-jump-to-badge-jump">
+      Jump to
+      <span class="d-inline-block ml-1 v-align-middle">↵</span>
+    </div>
+  </a>
+</li>
+
+
+</ul>
+
+            </div>
+      </label>
+</form>  </div>
+</div>
+
+          </div>
+
+        <a href="/login?return_to=%2Fjquery%2Fjquery-ui%2Fblob%2Fmaster%2Fui%2Fi18n%2Fdatepicker-es.js"
+          class="HeaderMenu-link no-underline mr-3"
+          data-hydro-click="{&quot;event_type&quot;:&quot;authentication.click&quot;,&quot;payload&quot;:{&quot;location_in_page&quot;:&quot;site header menu&quot;,&quot;repository_id&quot;:null,&quot;auth_type&quot;:&quot;SIGN_UP&quot;,&quot;client_id&quot;:null,&quot;originating_request_id&quot;:&quot;24AC:179AF:1F77047:302989C:5D7227FF&quot;,&quot;originating_url&quot;:&quot;https://github.com/jquery/jquery-ui/blob/master/ui/i18n/datepicker-es.js&quot;,&quot;referrer&quot;:null,&quot;user_id&quot;:null}}" data-hydro-click-hmac="ad2074c57803012129b5a1014d73c9d5eab4f83ecde2c9ca76bef487432a8eba"
+          data-ga-click="(Logged out) Header, clicked Sign in, text:sign-in">
+          Sign&nbsp;in
+        </a>
+          <a href="/join?source=header-repo"
+            class="HeaderMenu-link d-inline-block no-underline border border-gray-dark rounded-1 px-2 py-1"
+            data-hydro-click="{&quot;event_type&quot;:&quot;authentication.click&quot;,&quot;payload&quot;:{&quot;location_in_page&quot;:&quot;site header menu&quot;,&quot;repository_id&quot;:null,&quot;auth_type&quot;:&quot;SIGN_UP&quot;,&quot;client_id&quot;:null,&quot;originating_request_id&quot;:&quot;24AC:179AF:1F77047:302989C:5D7227FF&quot;,&quot;originating_url&quot;:&quot;https://github.com/jquery/jquery-ui/blob/master/ui/i18n/datepicker-es.js&quot;,&quot;referrer&quot;:null,&quot;user_id&quot;:null}}" data-hydro-click-hmac="ad2074c57803012129b5a1014d73c9d5eab4f83ecde2c9ca76bef487432a8eba"
+            data-ga-click="(Logged out) Header, clicked Sign up, text:sign-up">
+            Sign&nbsp;up
+          </a>
+      </div>
+    </div>
+  </div>
+</header>
+
+  </div>
+
+  <div id="start-of-content" class="show-on-focus"></div>
+
+
+    <div id="js-flash-container">
+
+</div>
+
+
+
+  <div class="application-main " data-commit-hovercards-enabled>
+        <div itemscope itemtype="http://schema.org/SoftwareSourceCode" class="">
+    <main  >
+      
+
+
+  
+
+
+
+  
+
+
+
+
+
+
+
+
+  <div class="pagehead repohead instapaper_ignore readability-menu experiment-repo-nav pt-0 pt-lg-4 ">
+    <div class="repohead-details-container clearfix container-lg p-responsive d-none d-lg-block">
+
+      <ul class="pagehead-actions">
+
+
+
+
+  <li>
+    
+  <a class="tooltipped tooltipped-s btn btn-sm btn-with-count" aria-label="You must be signed in to watch a repository" rel="nofollow" data-hydro-click="{&quot;event_type&quot;:&quot;authentication.click&quot;,&quot;payload&quot;:{&quot;location_in_page&quot;:&quot;notification subscription menu watch&quot;,&quot;repository_id&quot;:null,&quot;auth_type&quot;:&quot;LOG_IN&quot;,&quot;client_id&quot;:null,&quot;originating_request_id&quot;:&quot;24AC:179AF:1F77047:302989C:5D7227FF&quot;,&quot;originating_url&quot;:&quot;https://github.com/jquery/jquery-ui/blob/master/ui/i18n/datepicker-es.js&quot;,&quot;referrer&quot;:null,&quot;user_id&quot;:null}}" data-hydro-click-hmac="f919b58a58ba7d667c22ed08a13619378c6dbeeec86a2c2cec25dce135ec6132" href="/login?return_to=%2Fjquery%2Fjquery-ui">
+    <svg class="octicon octicon-eye v-align-text-bottom" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M8.06 2C3 2 0 8 0 8s3 6 8.06 6C13 14 16 8 16 8s-3-6-7.94-6zM8 12c-2.2 0-4-1.78-4-4 0-2.2 1.8-4 4-4 2.22 0 4 1.8 4 4 0 2.22-1.78 4-4 4zm2-4c0 1.11-.89 2-2 2-1.11 0-2-.89-2-2 0-1.11.89-2 2-2 1.11 0 2 .89 2 2z"/></svg>
+    Watch
+</a>    <a class="social-count" href="/jquery/jquery-ui/watchers"
+       aria-label="688 users are watching this repository">
+      688
+    </a>
+
+  </li>
+
+  <li>
+        <a class="btn btn-sm btn-with-count tooltipped tooltipped-s" aria-label="You must be signed in to star a repository" rel="nofollow" data-hydro-click="{&quot;event_type&quot;:&quot;authentication.click&quot;,&quot;payload&quot;:{&quot;location_in_page&quot;:&quot;star button&quot;,&quot;repository_id&quot;:478996,&quot;auth_type&quot;:&quot;LOG_IN&quot;,&quot;client_id&quot;:null,&quot;originating_request_id&quot;:&quot;24AC:179AF:1F77047:302989C:5D7227FF&quot;,&quot;originating_url&quot;:&quot;https://github.com/jquery/jquery-ui/blob/master/ui/i18n/datepicker-es.js&quot;,&quot;referrer&quot;:null,&quot;user_id&quot;:null}}" data-hydro-click-hmac="aa53ba455c340713aeda0addf6140aab2ab946538a25bc31d5557f665a836bc5" href="/login?return_to=%2Fjquery%2Fjquery-ui">
+      <svg class="octicon octicon-star v-align-text-bottom" viewBox="0 0 14 16" version="1.1" width="14" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M14 6l-4.9-.64L7 1 4.9 5.36 0 6l3.6 3.26L2.67 14 7 11.67 11.33 14l-.93-4.74L14 6z"/></svg>
+      Star
+</a>
+    <a class="social-count js-social-count" href="/jquery/jquery-ui/stargazers"
+      aria-label="10701 users starred this repository">
+      10,701
+    </a>
+
+  </li>
+
+  <li>
+      <a class="btn btn-sm btn-with-count tooltipped tooltipped-s" aria-label="You must be signed in to fork a repository" rel="nofollow" data-hydro-click="{&quot;event_type&quot;:&quot;authentication.click&quot;,&quot;payload&quot;:{&quot;location_in_page&quot;:&quot;repo details fork button&quot;,&quot;repository_id&quot;:478996,&quot;auth_type&quot;:&quot;LOG_IN&quot;,&quot;client_id&quot;:null,&quot;originating_request_id&quot;:&quot;24AC:179AF:1F77047:302989C:5D7227FF&quot;,&quot;originating_url&quot;:&quot;https://github.com/jquery/jquery-ui/blob/master/ui/i18n/datepicker-es.js&quot;,&quot;referrer&quot;:null,&quot;user_id&quot;:null}}" data-hydro-click-hmac="0728e11359f9b95a9d76d9f40bc21bed3f4a9d9164c2efc1b40ba292f0548b17" href="/login?return_to=%2Fjquery%2Fjquery-ui">
+        <svg class="octicon octicon-repo-forked v-align-text-bottom" viewBox="0 0 10 16" version="1.1" width="10" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M8 1a1.993 1.993 0 0 0-1 3.72V6L5 8 3 6V4.72A1.993 1.993 0 0 0 2 1a1.993 1.993 0 0 0-1 3.72V6.5l3 3v1.78A1.993 1.993 0 0 0 5 15a1.993 1.993 0 0 0 1-3.72V9.5l3-3V4.72A1.993 1.993 0 0 0 8 1zM2 4.2C1.34 4.2.8 3.65.8 3c0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2zm3 10c-.66 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2zm3-10c-.66 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2z"/></svg>
+        Fork
+</a>
+    <a href="/jquery/jquery-ui/network/members" class="social-count"
+       aria-label="5098 users forked this repository">
+      5,098
+    </a>
+  </li>
+</ul>
+
+      <h1 class="public ">
+    <svg class="octicon octicon-repo" viewBox="0 0 12 16" version="1.1" width="12" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9H3V8h1v1zm0-3H3v1h1V6zm0-2H3v1h1V4zm0-2H3v1h1V2zm8-1v12c0 .55-.45 1-1 1H6v2l-1.5-1.5L3 16v-2H1c-.55 0-1-.45-1-1V1c0-.55.45-1 1-1h10c.55 0 1 .45 1 1zm-1 10H1v2h2v-1h3v1h5v-2zm0-10H2v9h9V1z"/></svg>
+  <span class="author" itemprop="author"><a class="url fn" rel="author" data-hovercard-type="organization" data-hovercard-url="/orgs/jquery/hovercard" href="/jquery">jquery</a></span><!--
+--><span class="path-divider">/</span><!--
+--><strong itemprop="name"><a data-pjax="#js-repo-pjax-container" href="/jquery/jquery-ui">jquery-ui</a></strong>
+  
+
+</h1>
+
+    </div>
+    
+<nav class="hx_reponav reponav js-repo-nav js-sidenav-container-pjax container-lg p-responsive d-none d-lg-block"
+     itemscope
+     itemtype="http://schema.org/BreadcrumbList"
+    aria-label="Repository"
+     data-pjax="#js-repo-pjax-container">
+
+  <span itemscope itemtype="http://schema.org/ListItem" itemprop="itemListElement">
+    <a class="js-selected-navigation-item selected reponav-item" itemprop="url" data-hotkey="g c" aria-current="page" data-selected-links="repo_source repo_downloads repo_commits repo_releases repo_tags repo_branches repo_packages /jquery/jquery-ui" href="/jquery/jquery-ui">
+      <svg class="octicon octicon-code" viewBox="0 0 14 16" version="1.1" width="14" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M9.5 3L8 4.5 11.5 8 8 11.5 9.5 13 14 8 9.5 3zm-5 0L0 8l4.5 5L6 11.5 2.5 8 6 4.5 4.5 3z"/></svg>
+      <span itemprop="name">Code</span>
+      <meta itemprop="position" content="1">
+</a>  </span>
+
+
+  <span itemscope itemtype="http://schema.org/ListItem" itemprop="itemListElement">
+    <a data-hotkey="g p" itemprop="url" class="js-selected-navigation-item reponav-item" data-selected-links="repo_pulls checks /jquery/jquery-ui/pulls" href="/jquery/jquery-ui/pulls">
+      <svg class="octicon octicon-git-pull-request" viewBox="0 0 12 16" version="1.1" width="12" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M11 11.28V5c-.03-.78-.34-1.47-.94-2.06C9.46 2.35 8.78 2.03 8 2H7V0L4 3l3 3V4h1c.27.02.48.11.69.31.21.2.3.42.31.69v6.28A1.993 1.993 0 0 0 10 15a1.993 1.993 0 0 0 1-3.72zm-1 2.92c-.66 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2zM4 3c0-1.11-.89-2-2-2a1.993 1.993 0 0 0-1 3.72v6.56A1.993 1.993 0 0 0 2 15a1.993 1.993 0 0 0 1-3.72V4.72c.59-.34 1-.98 1-1.72zm-.8 10c0 .66-.55 1.2-1.2 1.2-.65 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2zM2 4.2C1.34 4.2.8 3.65.8 3c0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2z"/></svg>
+      <span itemprop="name">Pull requests</span>
+      <span class="Counter">34</span>
+      <meta itemprop="position" content="3">
+</a>  </span>
+
+
+    <a data-hotkey="g b" class="js-selected-navigation-item reponav-item" data-selected-links="repo_projects new_repo_project repo_project /jquery/jquery-ui/projects" href="/jquery/jquery-ui/projects">
+      <svg class="octicon octicon-project" viewBox="0 0 15 16" version="1.1" width="15" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M10 12h3V2h-3v10zm-4-2h3V2H6v8zm-4 4h3V2H2v12zm-1 1h13V1H1v14zM14 0H1a1 1 0 0 0-1 1v14a1 1 0 0 0 1 1h13a1 1 0 0 0 1-1V1a1 1 0 0 0-1-1z"/></svg>
+      Projects
+      <span class="Counter" >0</span>
+</a>
+
+
+    <a data-skip-pjax="true" class="js-selected-navigation-item reponav-item" data-selected-links="security alerts policy code_scanning /jquery/jquery-ui/security/advisories" href="/jquery/jquery-ui/security/advisories">
+      <svg class="octicon octicon-shield" viewBox="0 0 14 16" version="1.1" width="14" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M0 2l7-2 7 2v6.02C14 12.69 8.69 16 7 16c-1.69 0-7-3.31-7-7.98V2zm1 .75L7 1l6 1.75v5.268C13 12.104 8.449 15 7 15c-1.449 0-6-2.896-6-6.982V2.75zm1 .75L7 2v12c-1.207 0-5-2.482-5-5.985V3.5z"/></svg>
+      Security
+</a>
+    <a class="js-selected-navigation-item reponav-item" data-selected-links="repo_graphs repo_contributors dependency_graph pulse people /jquery/jquery-ui/pulse" href="/jquery/jquery-ui/pulse">
+      <svg class="octicon octicon-graph" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M16 14v1H0V0h1v14h15zM5 13H3V8h2v5zm4 0H7V3h2v10zm4 0h-2V6h2v7z"/></svg>
+      Insights
+</a>
+
+</nav>
+
+  <div class="reponav-wrapper reponav-small d-lg-none">
+  <nav class="reponav js-reponav text-center no-wrap"
+       itemscope
+       itemtype="http://schema.org/BreadcrumbList">
+
+    <span itemscope itemtype="http://schema.org/ListItem" itemprop="itemListElement">
+      <a class="js-selected-navigation-item selected reponav-item" itemprop="url" aria-current="page" data-selected-links="repo_source repo_downloads repo_commits repo_releases repo_tags repo_branches repo_packages /jquery/jquery-ui" href="/jquery/jquery-ui">
+        <span itemprop="name">Code</span>
+        <meta itemprop="position" content="1">
+</a>    </span>
+
+
+    <span itemscope itemtype="http://schema.org/ListItem" itemprop="itemListElement">
+      <a itemprop="url" class="js-selected-navigation-item reponav-item" data-selected-links="repo_pulls checks /jquery/jquery-ui/pulls" href="/jquery/jquery-ui/pulls">
+        <span itemprop="name">Pull requests</span>
+        <span class="Counter">34</span>
+        <meta itemprop="position" content="3">
+</a>    </span>
+
+      <span itemscope itemtype="http://schema.org/ListItem" itemprop="itemListElement">
+        <a itemprop="url" class="js-selected-navigation-item reponav-item" data-selected-links="repo_projects new_repo_project repo_project /jquery/jquery-ui/projects" href="/jquery/jquery-ui/projects">
+          <span itemprop="name">Projects</span>
+          <span class="Counter">0</span>
+          <meta itemprop="position" content="4">
+</a>      </span>
+
+
+      <a itemprop="url" class="js-selected-navigation-item reponav-item" data-selected-links="security alerts policy code_scanning /jquery/jquery-ui/security/advisories" href="/jquery/jquery-ui/security/advisories">
+        <span itemprop="name">Security</span>
+        <meta itemprop="position" content="6">
+</a>
+      <a class="js-selected-navigation-item reponav-item" data-selected-links="pulse /jquery/jquery-ui/pulse" href="/jquery/jquery-ui/pulse">
+        Pulse
+</a>
+
+  </nav>
+</div>
+
+
+  </div>
+<div class="container-lg clearfix new-discussion-timeline experiment-repo-nav  p-responsive">
+  <div class="repository-content ">
+
+    
+    
+
+
+  
+
+
+    <a class="d-none js-permalink-shortcut" data-hotkey="y" href="/jquery/jquery-ui/blob/74f8a0ac952f6f45f773312292baef1c26d81300/ui/i18n/datepicker-es.js">Permalink</a>
+
+    <!-- blob contrib key: blob_contributors:v21:187e259de5277208b08c3d5aa5e6f3b4 -->
+          <div class="signup-prompt-bg rounded-1">
+      <div class="signup-prompt p-4 text-center mb-4 rounded-1">
+        <div class="position-relative">
+          <!-- '"` --><!-- </textarea></xmp> --></option></form><form action="/prompt_dismissals/signup" accept-charset="UTF-8" method="post"><input name="utf8" type="hidden" value="&#x2713;" /><input type="hidden" name="_method" value="put" /><input type="hidden" name="authenticity_token" value="gcNKRSqpy76QP0Gjf6DZtqLQu+QXxXl7tESUQPuT+6kb77xIS0HQZh+XIB2Yg9ssZnGjia1zWgJo8e3s/fpPXQ==" />
+            <button type="submit" class="position-absolute top-0 right-0 btn-link link-gray" data-ga-click="(Logged out) Sign up prompt, clicked Dismiss, text:dismiss">
+              Dismiss
+            </button>
+</form>          <h3 class="pt-2">Join GitHub today</h3>
+          <p class="col-6 mx-auto">GitHub is home to over 40 million developers working together to host and review code, manage projects, and build software together.</p>
+          <a class="btn btn-primary" data-hydro-click="{&quot;event_type&quot;:&quot;authentication.click&quot;,&quot;payload&quot;:{&quot;location_in_page&quot;:&quot;files signup prompt&quot;,&quot;repository_id&quot;:null,&quot;auth_type&quot;:&quot;SIGN_UP&quot;,&quot;client_id&quot;:null,&quot;originating_request_id&quot;:&quot;24AC:179AF:1F77047:302989C:5D7227FF&quot;,&quot;originating_url&quot;:&quot;https://github.com/jquery/jquery-ui/blob/master/ui/i18n/datepicker-es.js&quot;,&quot;referrer&quot;:null,&quot;user_id&quot;:null}}" data-hydro-click-hmac="e03fa22b50f39447c948a46e69b41a0c962edb507ad314130a805ccc2e032874" data-ga-click="(Logged out) Sign up prompt, clicked Sign up, text:sign-up" href="/join?source=prompt-blob-show">Sign up</a>
+        </div>
+      </div>
+    </div>
+
+
+    <div class="d-flex flex-items-start flex-shrink-0 pb-3 flex-column flex-md-row">
+      <span class="d-flex flex-justify-between width-full width-md-auto">
+        
+<details class="details-reset details-overlay select-menu branch-select-menu  hx_rsm" id="branch-select-menu">
+  <summary class="btn btn-sm select-menu-button css-truncate"
+           data-hotkey="w"
+           title="Switch branches or tags">
+    <i>Branch:</i>
+    <span class="css-truncate-target" data-menu-button>master</span>
+  </summary>
+
+  <details-menu class="select-menu-modal hx_rsm-modal position-absolute" style="z-index: 99;" src="/jquery/jquery-ui/ref-list/master/ui/i18n/datepicker-es.js?source_action=show&amp;source_controller=blob" preload>
+    <include-fragment class="select-menu-loading-overlay anim-pulse">
+      <svg height="32" class="octicon octicon-octoface" viewBox="0 0 16 16" version="1.1" width="32" aria-hidden="true"><path fill-rule="evenodd" d="M14.7 5.34c.13-.32.55-1.59-.13-3.31 0 0-1.05-.33-3.44 1.3-1-.28-2.07-.32-3.13-.32s-2.13.04-3.13.32c-2.39-1.64-3.44-1.3-3.44-1.3-.68 1.72-.26 2.99-.13 3.31C.49 6.21 0 7.33 0 8.69 0 13.84 3.33 15 7.98 15S16 13.84 16 8.69c0-1.36-.49-2.48-1.3-3.35zM8 14.02c-3.3 0-5.98-.15-5.98-3.35 0-.76.38-1.48 1.02-2.07 1.07-.98 2.9-.46 4.96-.46 2.07 0 3.88-.52 4.96.46.65.59 1.02 1.3 1.02 2.07 0 3.19-2.68 3.35-5.98 3.35zM5.49 9.01c-.66 0-1.2.8-1.2 1.78s.54 1.79 1.2 1.79c.66 0 1.2-.8 1.2-1.79s-.54-1.78-1.2-1.78zm5.02 0c-.66 0-1.2.79-1.2 1.78s.54 1.79 1.2 1.79c.66 0 1.2-.8 1.2-1.79s-.53-1.78-1.2-1.78z"/></svg>
+    </include-fragment>
+  </details-menu>
+</details>
+
+        <div class="BtnGroup flex-shrink-0 d-md-none">
+          <a href="/jquery/jquery-ui/find/master"
+                class="js-pjax-capture-input btn btn-sm BtnGroup-item"
+                data-pjax
+                data-hotkey="t">
+            Find file
+          </a>
+          <clipboard-copy value="ui/i18n/datepicker-es.js" class="btn btn-sm BtnGroup-item">
+            Copy path
+          </clipboard-copy>
+        </div>
+      </span>
+      <h2 id="blob-path" class="breadcrumb flex-auto min-width-0 text-normal flex-md-self-center ml-md-2 mr-md-3 my-2 my-md-0">
+        <span class="js-repo-root text-bold"><span class="js-path-segment"><a data-pjax="true" href="/jquery/jquery-ui"><span>jquery-ui</span></a></span></span><span class="separator">/</span><span class="js-path-segment"><a data-pjax="true" href="/jquery/jquery-ui/tree/master/ui"><span>ui</span></a></span><span class="separator">/</span><span class="js-path-segment"><a data-pjax="true" href="/jquery/jquery-ui/tree/master/ui/i18n"><span>i18n</span></a></span><span class="separator">/</span><strong class="final-path">datepicker-es.js</strong>
+      </h2>
+
+      <div class="BtnGroup flex-shrink-0 d-none d-md-inline-block">
+        <a href="/jquery/jquery-ui/find/master"
+              class="js-pjax-capture-input btn btn-sm BtnGroup-item"
+              data-pjax
+              data-hotkey="t">
+          Find file
+        </a>
+        <clipboard-copy value="ui/i18n/datepicker-es.js" class="btn btn-sm BtnGroup-item">
+          Copy path
+        </clipboard-copy>
+      </div>
+    </div>
+
+
+
+    
+  <div class="Box Box--condensed d-flex flex-column flex-shrink-0">
+      <div class="Box-body d-flex flex-justify-between bg-blue-light flex-column flex-md-row flex-items-start flex-md-items-center">
+        <span class="pr-md-4 f6">
+          <a rel="contributor" data-skip-pjax="true" data-hovercard-type="user" data-hovercard-url="/hovercards?user_id=462993" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="/arschmitz"><img class="avatar" src="https://avatars2.githubusercontent.com/u/462993?s=40&amp;v=4" width="20" height="20" alt="@arschmitz" /></a>
+          <a class="text-bold link-gray-dark lh-default v-align-middle" rel="contributor" data-hovercard-type="user" data-hovercard-url="/hovercards?user_id=462993" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="/arschmitz">arschmitz</a>
+            <span class="lh-default v-align-middle">
+              <a data-pjax="true" title="Datepicker: Style updates
+
+Ref #14246" class="link-gray" href="/jquery/jquery-ui/commit/a380d2d09e244983288878cc3117c2c4988a1a56">Datepicker: Style updates</a>
+            </span>
+        </span>
+        <span class="d-inline-block flex-shrink-0 v-align-bottom f6 mt-2 mt-md-0">
+          <a class="pr-2 text-mono link-gray" href="/jquery/jquery-ui/commit/a380d2d09e244983288878cc3117c2c4988a1a56" data-pjax>a380d2d</a>
+          <relative-time datetime="2015-09-11T12:36:40Z">Sep 11, 2015</relative-time>
+        </span>
+      </div>
+
+    <div class="Box-body d-flex flex-items-center flex-auto f6 border-bottom-0 flex-wrap" >
+      <details class="details-reset details-overlay details-overlay-dark lh-default text-gray-dark float-left mr-2" id="blob_contributors_box">
+        <summary class="btn-link">
+          <span><strong>2</strong> contributors</span>
+        </summary>
+        <details-dialog
+          class="Box Box--overlay d-flex flex-column anim-fade-in fast"
+          aria-label="Users who have contributed to this file"
+          src="/jquery/jquery-ui/contributors/master/ui/i18n/datepicker-es.js/list" preload>
+          <div class="Box-header">
+            <button class="Box-btn-octicon btn-octicon float-right" type="button" aria-label="Close dialog" data-close-dialog>
+              <svg class="octicon octicon-x" viewBox="0 0 12 16" version="1.1" width="12" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M7.48 8l3.75 3.75-1.48 1.48L6 9.48l-3.75 3.75-1.48-1.48L4.52 8 .77 4.25l1.48-1.48L6 6.52l3.75-3.75 1.48 1.48L7.48 8z"/></svg>
+            </button>
+            <h3 class="Box-title">
+              Users who have contributed to this file
+            </h3>
+          </div>
+          <include-fragment class="octocat-spinner my-3" aria-label="Loading..."></include-fragment>
+        </details-dialog>
+      </details>
+        <span class="">
+    <a class="avatar-link" data-hovercard-type="user" data-hovercard-url="/hovercards?user_id=967155" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="/jquery/jquery-ui/commits/master/ui/i18n/datepicker-es.js?author=rxaviers">
+      <img class="avatar mr-1" src="https://avatars3.githubusercontent.com/u/967155?s=40&amp;v=4" width="20" height="20" alt="@rxaviers" /> 
+</a>    <a class="avatar-link" data-hovercard-type="user" data-hovercard-url="/hovercards?user_id=462993" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="/jquery/jquery-ui/commits/master/ui/i18n/datepicker-es.js?author=arschmitz">
+      <img class="avatar mr-1" src="https://avatars2.githubusercontent.com/u/462993?s=40&amp;v=4" width="20" height="20" alt="@arschmitz" /> 
+</a>
+</span>
+
+    </div>
+  </div>
+
+
+
+
+
+    <div class="Box mt-3 position-relative">
+      
+<div class="Box-header py-2 d-flex flex-column flex-shrink-0 flex-md-row flex-md-items-center">
+
+  <div class="text-mono f6 flex-auto pr-3 flex-order-2 flex-md-order-1 mt-2 mt-md-0">
+      38 lines (32 sloc)
+      <span class="file-info-divider"></span>
+    1.13 KB
+  </div>
+
+  <div class="d-flex py-1 py-md-0 flex-auto flex-order-1 flex-md-order-2 flex-sm-grow-0 flex-justify-between">
+
+    <div class="BtnGroup">
+      <a id="raw-url" class="btn btn-sm BtnGroup-item" href="/jquery/jquery-ui/raw/master/ui/i18n/datepicker-es.js">Raw</a>
+        <a class="btn btn-sm js-update-url-with-hash BtnGroup-item" data-hotkey="b" href="/jquery/jquery-ui/blame/master/ui/i18n/datepicker-es.js">Blame</a>
+      <a rel="nofollow" class="btn btn-sm BtnGroup-item" href="/jquery/jquery-ui/commits/master/ui/i18n/datepicker-es.js">History</a>
+    </div>
+
+
+    <div>
+
+          <button type="button" class="btn-octicon disabled tooltipped tooltipped-nw"
+            aria-label="You must be signed in to make or propose changes">
+            <svg class="octicon octicon-pencil" viewBox="0 0 14 16" version="1.1" width="14" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M0 12v3h3l8-8-3-3-8 8zm3 2H1v-2h1v1h1v1zm10.3-9.3L12 6 9 3l1.3-1.3a.996.996 0 0 1 1.41 0l1.59 1.59c.39.39.39 1.02 0 1.41z"/></svg>
+          </button>
+          <button type="button" class="btn-octicon btn-octicon-danger disabled tooltipped tooltipped-nw"
+            aria-label="You must be signed in to make or propose changes">
+            <svg class="octicon octicon-trashcan" viewBox="0 0 12 16" version="1.1" width="12" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M11 2H9c0-.55-.45-1-1-1H5c-.55 0-1 .45-1 1H2c-.55 0-1 .45-1 1v1c0 .55.45 1 1 1v9c0 .55.45 1 1 1h7c.55 0 1-.45 1-1V5c.55 0 1-.45 1-1V3c0-.55-.45-1-1-1zm-1 12H3V5h1v8h1V5h1v8h1V5h1v8h1V5h1v9zm1-10H2V3h9v1z"/></svg>
+          </button>
+    </div>
+  </div>
+</div>
+
+
+
+
+      
+
+  <div itemprop="text" class="Box-body p-0 blob-wrapper data type-javascript ">
+      
+<table class="highlight tab-size js-file-line-container" data-tab-size="8">
+      <tr>
+        <td id="L1" class="blob-num js-line-number" data-line-number="1"></td>
+        <td id="LC1" class="blob-code blob-code-inner js-file-line"><span class="pl-c"><span class="pl-c">/*</span> Inicialización en español para la extensión &#39;UI date picker&#39; para jQuery. <span class="pl-c">*/</span></span></td>
+      </tr>
+      <tr>
+        <td id="L2" class="blob-num js-line-number" data-line-number="2"></td>
+        <td id="LC2" class="blob-code blob-code-inner js-file-line"><span class="pl-c"><span class="pl-c">/*</span> Traducido por Vester (xvester@gmail.com). <span class="pl-c">*/</span></span></td>
+      </tr>
+      <tr>
+        <td id="L3" class="blob-num js-line-number" data-line-number="3"></td>
+        <td id="LC3" class="blob-code blob-code-inner js-file-line">( <span class="pl-k">function</span>( <span class="pl-smi">factory</span> ) {</td>
+      </tr>
+      <tr>
+        <td id="L4" class="blob-num js-line-number" data-line-number="4"></td>
+        <td id="LC4" class="blob-code blob-code-inner js-file-line">	<span class="pl-k">if</span> ( <span class="pl-k">typeof</span> define <span class="pl-k">===</span> <span class="pl-s"><span class="pl-pds">&quot;</span>function<span class="pl-pds">&quot;</span></span> <span class="pl-k">&amp;&amp;</span> <span class="pl-smi">define</span>.<span class="pl-smi">amd</span> ) {</td>
+      </tr>
+      <tr>
+        <td id="L5" class="blob-num js-line-number" data-line-number="5"></td>
+        <td id="LC5" class="blob-code blob-code-inner js-file-line">
+</td>
+      </tr>
+      <tr>
+        <td id="L6" class="blob-num js-line-number" data-line-number="6"></td>
+        <td id="LC6" class="blob-code blob-code-inner js-file-line">		<span class="pl-c"><span class="pl-c">//</span> AMD. Register as an anonymous module.</span></td>
+      </tr>
+      <tr>
+        <td id="L7" class="blob-num js-line-number" data-line-number="7"></td>
+        <td id="LC7" class="blob-code blob-code-inner js-file-line">		<span class="pl-en">define</span>( [ <span class="pl-s"><span class="pl-pds">&quot;</span>../widgets/datepicker<span class="pl-pds">&quot;</span></span> ], factory );</td>
+      </tr>
+      <tr>
+        <td id="L8" class="blob-num js-line-number" data-line-number="8"></td>
+        <td id="LC8" class="blob-code blob-code-inner js-file-line">	} <span class="pl-k">else</span> {</td>
+      </tr>
+      <tr>
+        <td id="L9" class="blob-num js-line-number" data-line-number="9"></td>
+        <td id="LC9" class="blob-code blob-code-inner js-file-line">
+</td>
+      </tr>
+      <tr>
+        <td id="L10" class="blob-num js-line-number" data-line-number="10"></td>
+        <td id="LC10" class="blob-code blob-code-inner js-file-line">		<span class="pl-c"><span class="pl-c">//</span> Browser globals</span></td>
+      </tr>
+      <tr>
+        <td id="L11" class="blob-num js-line-number" data-line-number="11"></td>
+        <td id="LC11" class="blob-code blob-code-inner js-file-line">		<span class="pl-en">factory</span>( <span class="pl-smi">jQuery</span>.<span class="pl-smi">datepicker</span> );</td>
+      </tr>
+      <tr>
+        <td id="L12" class="blob-num js-line-number" data-line-number="12"></td>
+        <td id="LC12" class="blob-code blob-code-inner js-file-line">	}</td>
+      </tr>
+      <tr>
+        <td id="L13" class="blob-num js-line-number" data-line-number="13"></td>
+        <td id="LC13" class="blob-code blob-code-inner js-file-line">}( <span class="pl-k">function</span>( <span class="pl-smi">datepicker</span> ) {</td>
+      </tr>
+      <tr>
+        <td id="L14" class="blob-num js-line-number" data-line-number="14"></td>
+        <td id="LC14" class="blob-code blob-code-inner js-file-line">
+</td>
+      </tr>
+      <tr>
+        <td id="L15" class="blob-num js-line-number" data-line-number="15"></td>
+        <td id="LC15" class="blob-code blob-code-inner js-file-line"><span class="pl-smi">datepicker</span>.<span class="pl-smi">regional</span>.<span class="pl-smi">es</span> <span class="pl-k">=</span> {</td>
+      </tr>
+      <tr>
+        <td id="L16" class="blob-num js-line-number" data-line-number="16"></td>
+        <td id="LC16" class="blob-code blob-code-inner js-file-line">	closeText<span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">&quot;</span>Cerrar<span class="pl-pds">&quot;</span></span>,</td>
+      </tr>
+      <tr>
+        <td id="L17" class="blob-num js-line-number" data-line-number="17"></td>
+        <td id="LC17" class="blob-code blob-code-inner js-file-line">	prevText<span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">&quot;</span>&amp;#x3C;Ant<span class="pl-pds">&quot;</span></span>,</td>
+      </tr>
+      <tr>
+        <td id="L18" class="blob-num js-line-number" data-line-number="18"></td>
+        <td id="LC18" class="blob-code blob-code-inner js-file-line">	nextText<span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">&quot;</span>Sig&amp;#x3E;<span class="pl-pds">&quot;</span></span>,</td>
+      </tr>
+      <tr>
+        <td id="L19" class="blob-num js-line-number" data-line-number="19"></td>
+        <td id="LC19" class="blob-code blob-code-inner js-file-line">	currentText<span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">&quot;</span>Hoy<span class="pl-pds">&quot;</span></span>,</td>
+      </tr>
+      <tr>
+        <td id="L20" class="blob-num js-line-number" data-line-number="20"></td>
+        <td id="LC20" class="blob-code blob-code-inner js-file-line">	monthNames<span class="pl-k">:</span> [ <span class="pl-s"><span class="pl-pds">&quot;</span>enero<span class="pl-pds">&quot;</span></span>,<span class="pl-s"><span class="pl-pds">&quot;</span>febrero<span class="pl-pds">&quot;</span></span>,<span class="pl-s"><span class="pl-pds">&quot;</span>marzo<span class="pl-pds">&quot;</span></span>,<span class="pl-s"><span class="pl-pds">&quot;</span>abril<span class="pl-pds">&quot;</span></span>,<span class="pl-s"><span class="pl-pds">&quot;</span>mayo<span class="pl-pds">&quot;</span></span>,<span class="pl-s"><span class="pl-pds">&quot;</span>junio<span class="pl-pds">&quot;</span></span>,</td>
+      </tr>
+      <tr>
+        <td id="L21" class="blob-num js-line-number" data-line-number="21"></td>
+        <td id="LC21" class="blob-code blob-code-inner js-file-line">	<span class="pl-s"><span class="pl-pds">&quot;</span>julio<span class="pl-pds">&quot;</span></span>,<span class="pl-s"><span class="pl-pds">&quot;</span>agosto<span class="pl-pds">&quot;</span></span>,<span class="pl-s"><span class="pl-pds">&quot;</span>septiembre<span class="pl-pds">&quot;</span></span>,<span class="pl-s"><span class="pl-pds">&quot;</span>octubre<span class="pl-pds">&quot;</span></span>,<span class="pl-s"><span class="pl-pds">&quot;</span>noviembre<span class="pl-pds">&quot;</span></span>,<span class="pl-s"><span class="pl-pds">&quot;</span>diciembre<span class="pl-pds">&quot;</span></span> ],</td>
+      </tr>
+      <tr>
+        <td id="L22" class="blob-num js-line-number" data-line-number="22"></td>
+        <td id="LC22" class="blob-code blob-code-inner js-file-line">	monthNamesShort<span class="pl-k">:</span> [ <span class="pl-s"><span class="pl-pds">&quot;</span>ene<span class="pl-pds">&quot;</span></span>,<span class="pl-s"><span class="pl-pds">&quot;</span>feb<span class="pl-pds">&quot;</span></span>,<span class="pl-s"><span class="pl-pds">&quot;</span>mar<span class="pl-pds">&quot;</span></span>,<span class="pl-s"><span class="pl-pds">&quot;</span>abr<span class="pl-pds">&quot;</span></span>,<span class="pl-s"><span class="pl-pds">&quot;</span>may<span class="pl-pds">&quot;</span></span>,<span class="pl-s"><span class="pl-pds">&quot;</span>jun<span class="pl-pds">&quot;</span></span>,</td>
+      </tr>
+      <tr>
+        <td id="L23" class="blob-num js-line-number" data-line-number="23"></td>
+        <td id="LC23" class="blob-code blob-code-inner js-file-line">	<span class="pl-s"><span class="pl-pds">&quot;</span>jul<span class="pl-pds">&quot;</span></span>,<span class="pl-s"><span class="pl-pds">&quot;</span>ago<span class="pl-pds">&quot;</span></span>,<span class="pl-s"><span class="pl-pds">&quot;</span>sep<span class="pl-pds">&quot;</span></span>,<span class="pl-s"><span class="pl-pds">&quot;</span>oct<span class="pl-pds">&quot;</span></span>,<span class="pl-s"><span class="pl-pds">&quot;</span>nov<span class="pl-pds">&quot;</span></span>,<span class="pl-s"><span class="pl-pds">&quot;</span>dic<span class="pl-pds">&quot;</span></span> ],</td>
+      </tr>
+      <tr>
+        <td id="L24" class="blob-num js-line-number" data-line-number="24"></td>
+        <td id="LC24" class="blob-code blob-code-inner js-file-line">	dayNames<span class="pl-k">:</span> [ <span class="pl-s"><span class="pl-pds">&quot;</span>domingo<span class="pl-pds">&quot;</span></span>,<span class="pl-s"><span class="pl-pds">&quot;</span>lunes<span class="pl-pds">&quot;</span></span>,<span class="pl-s"><span class="pl-pds">&quot;</span>martes<span class="pl-pds">&quot;</span></span>,<span class="pl-s"><span class="pl-pds">&quot;</span>miércoles<span class="pl-pds">&quot;</span></span>,<span class="pl-s"><span class="pl-pds">&quot;</span>jueves<span class="pl-pds">&quot;</span></span>,<span class="pl-s"><span class="pl-pds">&quot;</span>viernes<span class="pl-pds">&quot;</span></span>,<span class="pl-s"><span class="pl-pds">&quot;</span>sábado<span class="pl-pds">&quot;</span></span> ],</td>
+      </tr>
+      <tr>
+        <td id="L25" class="blob-num js-line-number" data-line-number="25"></td>
+        <td id="LC25" class="blob-code blob-code-inner js-file-line">	dayNamesShort<span class="pl-k">:</span> [ <span class="pl-s"><span class="pl-pds">&quot;</span>dom<span class="pl-pds">&quot;</span></span>,<span class="pl-s"><span class="pl-pds">&quot;</span>lun<span class="pl-pds">&quot;</span></span>,<span class="pl-s"><span class="pl-pds">&quot;</span>mar<span class="pl-pds">&quot;</span></span>,<span class="pl-s"><span class="pl-pds">&quot;</span>mié<span class="pl-pds">&quot;</span></span>,<span class="pl-s"><span class="pl-pds">&quot;</span>jue<span class="pl-pds">&quot;</span></span>,<span class="pl-s"><span class="pl-pds">&quot;</span>vie<span class="pl-pds">&quot;</span></span>,<span class="pl-s"><span class="pl-pds">&quot;</span>sáb<span class="pl-pds">&quot;</span></span> ],</td>
+      </tr>
+      <tr>
+        <td id="L26" class="blob-num js-line-number" data-line-number="26"></td>
+        <td id="LC26" class="blob-code blob-code-inner js-file-line">	dayNamesMin<span class="pl-k">:</span> [ <span class="pl-s"><span class="pl-pds">&quot;</span>D<span class="pl-pds">&quot;</span></span>,<span class="pl-s"><span class="pl-pds">&quot;</span>L<span class="pl-pds">&quot;</span></span>,<span class="pl-s"><span class="pl-pds">&quot;</span>M<span class="pl-pds">&quot;</span></span>,<span class="pl-s"><span class="pl-pds">&quot;</span>X<span class="pl-pds">&quot;</span></span>,<span class="pl-s"><span class="pl-pds">&quot;</span>J<span class="pl-pds">&quot;</span></span>,<span class="pl-s"><span class="pl-pds">&quot;</span>V<span class="pl-pds">&quot;</span></span>,<span class="pl-s"><span class="pl-pds">&quot;</span>S<span class="pl-pds">&quot;</span></span> ],</td>
+      </tr>
+      <tr>
+        <td id="L27" class="blob-num js-line-number" data-line-number="27"></td>
+        <td id="LC27" class="blob-code blob-code-inner js-file-line">	weekHeader<span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">&quot;</span>Sm<span class="pl-pds">&quot;</span></span>,</td>
+      </tr>
+      <tr>
+        <td id="L28" class="blob-num js-line-number" data-line-number="28"></td>
+        <td id="LC28" class="blob-code blob-code-inner js-file-line">	dateFormat<span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">&quot;</span>dd/mm/yy<span class="pl-pds">&quot;</span></span>,</td>
+      </tr>
+      <tr>
+        <td id="L29" class="blob-num js-line-number" data-line-number="29"></td>
+        <td id="LC29" class="blob-code blob-code-inner js-file-line">	firstDay<span class="pl-k">:</span> <span class="pl-c1">1</span>,</td>
+      </tr>
+      <tr>
+        <td id="L30" class="blob-num js-line-number" data-line-number="30"></td>
+        <td id="LC30" class="blob-code blob-code-inner js-file-line">	isRTL<span class="pl-k">:</span> <span class="pl-c1">false</span>,</td>
+      </tr>
+      <tr>
+        <td id="L31" class="blob-num js-line-number" data-line-number="31"></td>
+        <td id="LC31" class="blob-code blob-code-inner js-file-line">	showMonthAfterYear<span class="pl-k">:</span> <span class="pl-c1">false</span>,</td>
+      </tr>
+      <tr>
+        <td id="L32" class="blob-num js-line-number" data-line-number="32"></td>
+        <td id="LC32" class="blob-code blob-code-inner js-file-line">	yearSuffix<span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">&quot;</span><span class="pl-pds">&quot;</span></span> };</td>
+      </tr>
+      <tr>
+        <td id="L33" class="blob-num js-line-number" data-line-number="33"></td>
+        <td id="LC33" class="blob-code blob-code-inner js-file-line"><span class="pl-smi">datepicker</span>.<span class="pl-en">setDefaults</span>( <span class="pl-smi">datepicker</span>.<span class="pl-smi">regional</span>.<span class="pl-smi">es</span> );</td>
+      </tr>
+      <tr>
+        <td id="L34" class="blob-num js-line-number" data-line-number="34"></td>
+        <td id="LC34" class="blob-code blob-code-inner js-file-line">
+</td>
+      </tr>
+      <tr>
+        <td id="L35" class="blob-num js-line-number" data-line-number="35"></td>
+        <td id="LC35" class="blob-code blob-code-inner js-file-line"><span class="pl-k">return</span> <span class="pl-smi">datepicker</span>.<span class="pl-smi">regional</span>.<span class="pl-smi">es</span>;</td>
+      </tr>
+      <tr>
+        <td id="L36" class="blob-num js-line-number" data-line-number="36"></td>
+        <td id="LC36" class="blob-code blob-code-inner js-file-line">
+</td>
+      </tr>
+      <tr>
+        <td id="L37" class="blob-num js-line-number" data-line-number="37"></td>
+        <td id="LC37" class="blob-code blob-code-inner js-file-line">} ) );</td>
+      </tr>
+</table>
+
+  <details class="details-reset details-overlay BlobToolbar position-absolute js-file-line-actions dropdown d-none" aria-hidden="true">
+    <summary class="btn-octicon ml-0 px-2 p-0 bg-white border border-gray-dark rounded-1" aria-label="Inline file action toolbar">
+      <svg class="octicon octicon-kebab-horizontal" viewBox="0 0 13 16" version="1.1" width="13" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M1.5 9a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3zm5 0a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3zM13 7.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0z"/></svg>
+    </summary>
+    <details-menu>
+      <ul class="BlobToolbar-dropdown dropdown-menu dropdown-menu-se mt-2" style="width:185px">
+        <li><clipboard-copy role="menuitem" class="dropdown-item" id="js-copy-lines" style="cursor:pointer;" data-original-text="Copy lines">Copy lines</clipboard-copy></li>
+        <li><clipboard-copy role="menuitem" class="dropdown-item" id="js-copy-permalink" style="cursor:pointer;" data-original-text="Copy permalink">Copy permalink</clipboard-copy></li>
+        <li><a class="dropdown-item js-update-url-with-hash" id="js-view-git-blame" role="menuitem" href="/jquery/jquery-ui/blame/74f8a0ac952f6f45f773312292baef1c26d81300/ui/i18n/datepicker-es.js">View git blame</a></li>
+      </ul>
+    </details-menu>
+  </details>
+
+  </div>
+
+    </div>
+
+  
+
+  <details class="details-reset details-overlay details-overlay-dark">
+    <summary data-hotkey="l" aria-label="Jump to line"></summary>
+    <details-dialog class="Box Box--overlay d-flex flex-column anim-fade-in fast linejump" aria-label="Jump to line">
+      <!-- '"` --><!-- </textarea></xmp> --></option></form><form class="js-jump-to-line-form Box-body d-flex" action="" accept-charset="UTF-8" method="get"><input name="utf8" type="hidden" value="&#x2713;" />
+        <input class="form-control flex-auto mr-3 linejump-input js-jump-to-line-field" type="text" placeholder="Jump to line&hellip;" aria-label="Jump to line" autofocus>
+        <button type="submit" class="btn" data-close-dialog>Go</button>
+</form>    </details-dialog>
+  </details>
+
+
+
+  </div>
+</div>
+
+    </main>
+  </div>
+  
+
+  </div>
+
+        
+<div class="footer container-lg width-full p-responsive" role="contentinfo">
+  <div class="position-relative d-flex flex-row-reverse flex-lg-row flex-wrap flex-lg-nowrap flex-justify-center flex-lg-justify-between pt-6 pb-2 mt-6 f6 text-gray border-top border-gray-light ">
+    <ul class="list-style-none d-flex flex-wrap col-12 col-lg-5 flex-justify-center flex-lg-justify-between mb-2 mb-lg-0">
+      <li class="mr-3 mr-lg-0">&copy; 2019 <span title="0.23772s from unicorn-canary-846df6bd86-4wqgg">GitHub</span>, Inc.</li>
+        <li class="mr-3 mr-lg-0"><a data-ga-click="Footer, go to terms, text:terms" href="https://github.com/site/terms">Terms</a></li>
+        <li class="mr-3 mr-lg-0"><a data-ga-click="Footer, go to privacy, text:privacy" href="https://github.com/site/privacy">Privacy</a></li>
+        <li class="mr-3 mr-lg-0"><a data-ga-click="Footer, go to security, text:security" href="https://github.com/security">Security</a></li>
+        <li class="mr-3 mr-lg-0"><a href="https://githubstatus.com/" data-ga-click="Footer, go to status, text:status">Status</a></li>
+        <li><a data-ga-click="Footer, go to help, text:help" href="https://help.github.com">Help</a></li>
+    </ul>
+
+    <a aria-label="Homepage" title="GitHub" class="footer-octicon d-none d-lg-block mx-lg-4" href="https://github.com">
+      <svg height="24" class="octicon octicon-mark-github" viewBox="0 0 16 16" version="1.1" width="24" aria-hidden="true"><path fill-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z"/></svg>
+</a>
+   <ul class="list-style-none d-flex flex-wrap col-12 col-lg-5 flex-justify-center flex-lg-justify-between mb-2 mb-lg-0">
+        <li class="mr-3 mr-lg-0"><a data-ga-click="Footer, go to contact, text:contact" href="https://github.com/contact">Contact GitHub</a></li>
+        <li class="mr-3 mr-lg-0"><a href="https://github.com/pricing" data-ga-click="Footer, go to Pricing, text:Pricing">Pricing</a></li>
+      <li class="mr-3 mr-lg-0"><a href="https://developer.github.com" data-ga-click="Footer, go to api, text:api">API</a></li>
+      <li class="mr-3 mr-lg-0"><a href="https://training.github.com" data-ga-click="Footer, go to training, text:training">Training</a></li>
+        <li class="mr-3 mr-lg-0"><a href="https://github.blog" data-ga-click="Footer, go to blog, text:blog">Blog</a></li>
+        <li><a data-ga-click="Footer, go to about, text:about" href="https://github.com/about">About</a></li>
+
+    </ul>
+  </div>
+  <div class="d-flex flex-justify-center pb-6">
+    <span class="f6 text-gray-light"></span>
+  </div>
+</div>
+
+
+
+  <div id="ajax-error-message" class="ajax-error-message flash flash-error">
+    <svg class="octicon octicon-alert" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"/></svg>
+    <button type="button" class="flash-close js-ajax-error-dismiss" aria-label="Dismiss error">
+      <svg class="octicon octicon-x" viewBox="0 0 12 16" version="1.1" width="12" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M7.48 8l3.75 3.75-1.48 1.48L6 9.48l-3.75 3.75-1.48-1.48L4.52 8 .77 4.25l1.48-1.48L6 6.52l3.75-3.75 1.48 1.48L7.48 8z"/></svg>
+    </button>
+    You can’t perform that action at this time.
+  </div>
+
+
+    <script crossorigin="anonymous" integrity="sha512-7DgnKTvrXv8ipUdIi9xz0FXboKiq95B6EB028aPspG4JRZsvvwSbe2mLifBycxKZWNnQLZEwHqGAgw48jgpLLg==" type="application/javascript" src="https://github.githubassets.com/assets/compat-bootstrap-21b609a5.js"></script>
+    <script crossorigin="anonymous" integrity="sha512-66yPJ4JAblq1zDn/+3IKWzUuzR0Oin7dKMBwDVuflRlcQHmegTtqoyANZqWlTompijP0bSNSRyk09xnPzusbjw==" type="application/javascript" src="https://github.githubassets.com/assets/frameworks-7cd2951d.js"></script>
+    
+    <script crossorigin="anonymous" async="async" integrity="sha512-CDqh3OIJfVsfaqNpzmuSj4NKQ8A/QNp6fWGd/OzIKRzjCRW7J2UhDWslWlqdfvRFWoBmL59UI+9nIqVX68FrCQ==" type="application/javascript" src="https://github.githubassets.com/assets/github-bootstrap-5d33ad3d.js"></script>
+    
+    
+    
+  <div class="js-stale-session-flash stale-session-flash flash flash-warn flash-banner" hidden
+    >
+    <svg class="octicon octicon-alert" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"/></svg>
+    <span class="signed-in-tab-flash">You signed in with another tab or window. <a href="">Reload</a> to refresh your session.</span>
+    <span class="signed-out-tab-flash">You signed out in another tab or window. <a href="">Reload</a> to refresh your session.</span>
+  </div>
+  <template id="site-details-dialog">
+  <details class="details-reset details-overlay details-overlay-dark lh-default text-gray-dark hx_rsm" open>
+    <summary role="button" aria-label="Close dialog"></summary>
+    <details-dialog class="Box Box--overlay d-flex flex-column anim-fade-in fast hx_rsm-dialog hx_rsm-modal">
+      <button class="Box-btn-octicon m-0 btn-octicon position-absolute right-0 top-0" type="button" aria-label="Close dialog" data-close-dialog>
+        <svg class="octicon octicon-x" viewBox="0 0 12 16" version="1.1" width="12" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M7.48 8l3.75 3.75-1.48 1.48L6 9.48l-3.75 3.75-1.48-1.48L4.52 8 .77 4.25l1.48-1.48L6 6.52l3.75-3.75 1.48 1.48L7.48 8z"/></svg>
+      </button>
+      <div class="octocat-spinner my-6 js-details-dialog-spinner"></div>
+    </details-dialog>
+  </details>
+</template>
+
+  <div class="Popover js-hovercard-content position-absolute" style="display: none; outline: none;" tabindex="0">
+  <div class="Popover-message Popover-message--bottom-left Popover-message--large Box box-shadow-large" style="width:360px;">
+  </div>
+</div>
+
+  <div aria-live="polite" class="js-global-screen-reader-notice sr-only"></div>
+
+  </body>
+</html>
+
diff --git a/public/admin/js/jquery_ui_datepicker_i18n/datepicker-fr.js b/public/admin/js/jquery_ui_datepicker_i18n/datepicker-fr.js
new file mode 100644
index 0000000000000000000000000000000000000000..9e39fbd68f56d4844cf758f0c78f1fff8b58e43e
--- /dev/null
+++ b/public/admin/js/jquery_ui_datepicker_i18n/datepicker-fr.js
@@ -0,0 +1,39 @@
+/* French initialisation for the jQuery UI date picker plugin. */
+/* Written by Keith Wood (kbwood{at}iinet.com.au),
+			  Stéphane Nahmani (sholby@sholby.net),
+			  Stéphane Raimbault <stephane.raimbault@gmail.com> */
+( function( factory ) {
+	if ( typeof define === "function" && define.amd ) {
+
+		// AMD. Register as an anonymous module.
+		define( [ "../widgets/datepicker" ], factory );
+	} else {
+
+		// Browser globals
+		factory( jQuery.datepicker );
+	}
+}( function( datepicker ) {
+
+datepicker.regional.fr = {
+	closeText: "Fermer",
+	prevText: "Précédent",
+	nextText: "Suivant",
+	currentText: "Aujourd'hui",
+	monthNames: [ "janvier", "février", "mars", "avril", "mai", "juin",
+		"juillet", "août", "septembre", "octobre", "novembre", "décembre" ],
+	monthNamesShort: [ "janv.", "févr.", "mars", "avr.", "mai", "juin",
+		"juil.", "août", "sept.", "oct.", "nov.", "déc." ],
+	dayNames: [ "dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi" ],
+	dayNamesShort: [ "dim.", "lun.", "mar.", "mer.", "jeu.", "ven.", "sam." ],
+	dayNamesMin: [ "D","L","M","M","J","V","S" ],
+	weekHeader: "Sem.",
+	dateFormat: "dd/mm/yy",
+	firstDay: 1,
+	isRTL: false,
+	showMonthAfterYear: false,
+	yearSuffix: "" };
+datepicker.setDefaults( datepicker.regional.fr );
+
+return datepicker.regional.fr;
+
+} ) );
diff --git a/public/opac/css/global.css b/public/opac/css/global.css
index 190e01a01286d85d634f553c3e860b27910ea585..68f09d0a80520a573eae9978b708c995e1d6a370 100644
--- a/public/opac/css/global.css
+++ b/public/opac/css/global.css
@@ -3767,4 +3767,25 @@ a[href*="bookmarked-searches/notify"] img {
 .search_axe_input {
     display: inline-block;
     min-width: 150px;
+}
+
+/** bundled holds dialog **/
+#holds_view .day-with-hold,
+#holds_view .day-without-hold {
+    opacity: 1;
+}
+
+#holds_view .day-with-hold span { background-color: orange !important; }
+
+#holds_view .day-without-hold span { background-color: #9bd99b !important; }
+
+#holds_view .ui-datepicker-inline.ui-widget-content { border-width: 0px }
+
+#holds_view .ui-datepicker-inline {
+    margin: 0 auto;
+}
+
+#holds_view + ul {
+    justify-content: center;
+    display: flex;
 }
\ No newline at end of file
diff --git a/scripts/import_avis_csv.php b/scripts/import_avis_csv.php
index 13d762a83c78b88064941f83cff953e581a4d947..5d9956f37323dd7ca98fda5bec8e98f954fc9fd8 100644
--- a/scripts/import_avis_csv.php
+++ b/scripts/import_avis_csv.php
@@ -16,7 +16,7 @@
  *
  * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
  * along with BOKEH; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA 
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
 define("BASE_URL", "/");
 
@@ -40,17 +40,17 @@ while (($line = fgetcsv($handle, 0)) !== FALSE) {
 	if (!$exemplaire = Class_Exemplaire::findFirstBy(['id_origine' => $id_origine]))
 		continue;
 	$id_notice = $exemplaire->getIdNotice();
-	
+
 	$datas=['id_user' => 2,
 					'clef_oeuvre' => $exemplaire->getNotice()->getClefOeuvre(),
 					'id_notice' => $id_notice,
-					'date_avis' => implode('-', array_reverse(explode('/', $date))),
+					'date_avis' => Class_Date::frToIso($date),
 					'note' => 5,
 					'entete' => $entete,
 					'avis' => str_replace('"', '\"', $avis),
 					'statut' => 1,
 					'abon_ou_bib' => 1];
-					
+
 
 	echo "insert into notices_avis(id_user, clef_oeuvre, id_notice, date_avis, note, entete, avis, statut, abon_ou_bib) values(\"".implode("\",\"", array_values($datas))."\");\n";
 }
diff --git a/scripts/import_pod_ratings.php b/scripts/import_pod_ratings.php
index 1f51b755d84ffe175f8e9c4153a84ab03a22dea3..dcc89e3e076aa501d4765d4dcbc609b1c14fd829 100644
--- a/scripts/import_pod_ratings.php
+++ b/scripts/import_pod_ratings.php
@@ -91,7 +91,7 @@ class PODCommentImport {
 
     return (new Class_AvisNotice())
       ->setClefOeuvre($record->getClefOeuvre())
-      ->setDateAvis(implode('-', array_reverse(explode('/', $creation_date))))
+      ->setDateAvis(Class_Date::frToIso($creation_date))
       ->setEntete($head)
       ->setAvis(iconv('CP850', 'UTF-8', $comment_content))
       ->setNote($rating)
diff --git a/scripts/reindex_local_file_content.php b/scripts/reindex_local_file_content.php
new file mode 100644
index 0000000000000000000000000000000000000000..c37821b94cf9f6895a2b527a18e02f5486973048
--- /dev/null
+++ b/scripts/reindex_local_file_content.php
@@ -0,0 +1,152 @@
+<?php
+error_reporting(E_ERROR | E_PARSE);
+require(__DIR__.'/../console.php');
+
+
+echo "\n\nWelcome to the iReindex Files content 4K 3D tool by @patbator\n\n";
+
+$data_profile = null;
+
+$profiles = Class_IntProfilDonnees::findAllOfTypeBibliographic();
+$message = "Tell me which data profile to use: \n\n";
+$possibles = [];
+
+foreach($profiles as $profile) {
+  $message .= "\t" . sprintf("[%s] %s", $profile->getId(), $profile->getLibelle()) . "\n";
+  $possibles[] = $profile->getId();
+}
+$message .= "\n";
+
+while(!$data_profile) {
+  echo $message;
+
+  $id = (int)readline("Please iReindex use : ");
+  if (!in_array($id, $possibles)) {
+    echo $id . " is not an existing profile, try again \n\n";
+    continue;
+  }
+
+  if (!$data_profile = Class_IntProfilDonnees::find($id)) {
+    echo "data profile not found try again \n\n";
+    continue;
+  }
+
+  if ('' === $data_profile->getIndexFileZone()
+      || '' === $data_profile->getIndexFileField()) {
+    $data_profile = null;
+    echo "data profile does not have file content configuration, try again \n\n";
+    continue;
+  }
+
+  echo "File content will be detected with profile : " . $data_profile->getLibelle() . "\n\n";
+}
+
+echo sprintf("\n\n==== %s records to handle ====\n\n",
+             Class_Notice::countBy(['type' => Class_Notice::TYPE_BIBLIOGRAPHIC]));
+
+
+class Scripts_Local_File_Content_Indexer {
+  use Trait_Observer;
+
+  protected
+    $_profile,
+    $_total_count = 0,
+    $_matches = [],
+    $_empties = [],
+    $_sources = [],
+    $_start_time,
+    $_end_time;
+
+  public function __construct($profile) {
+    $this->_profile = $profile;
+    $this->_start_time = new DateTime();
+  }
+
+
+  public function index($record) {
+    $this->_total_count++;
+    echo '.';
+    $record->setFileContent((new Class_Cosmogramme_Integration_Record_FileContent($this))
+                            ->getContent($record, $this->_profile));
+    $record->save();
+    $this->_end_time = new DateTime();
+  }
+
+
+  public function notifyMatch($record, $paths) {
+    $this->_matches[$record->getId()] = $paths;
+    return $this;
+  }
+
+
+  public function notifyEmpty($record) {
+    $this->_empties[] = $record->getId();
+    return $this;
+  }
+
+
+  public function notifySource($record, $path) {
+    $this->_sources[$record->getId()] = $path;
+    return $this;
+  }
+
+
+  public function summarize() {
+    return "\t" . implode("\n\t", $this->_summaryParts());
+  }
+
+
+  public function report() {
+    $report_path = PATH_TEMP . 'reindex_local_file_content_report_' . $this->_start_time->format('Ymd_His') . '.json';
+    $wrote = file_put_contents($report_path,
+                               json_encode(['summary' => $this->_summaryParts(),
+                                            'matches' => $this->_matches,
+                                            'sources' => $this->_sources,
+                                            'empties' => $this->_empties],
+                                           JSON_PRETTY_PRINT));
+    return (false !== $wrote)
+      ? 'See full report in ' . $report_path
+      : 'Could not write report in' . $report_path;
+  }
+
+  protected function _summaryParts() {
+    $elapsed_minutes = $this->_elapsedMinutes();
+
+    $parts = [count($this->_matches) . ' records with matching URIs'];
+    if ($this->_empties)
+      $parts[] = 'In which ' . count($this->_empties) . ' produced empty content';
+
+    if (0 < $elapsed_minutes && 0 < $this->_total_count)
+      $parts[] = 'Took ' . $elapsed_minutes . ' minutes to handle ' . $this->_total_count . ' records (~' . floor($this->_total_count/$elapsed_minutes) . '/mn)';
+
+    return $parts;
+  }
+
+
+  protected function _elapsedMinutes() {
+    if (!$this->_end_time)
+      return 0;
+
+    $interval = $this->_end_time->diff($this->_start_time);
+    return $interval->i;
+  }
+}
+
+
+
+$indexer = new Scripts_Local_File_Content_Indexer($data_profile);
+$page = 1;
+while ($records = Class_Notice::findAllBy(['type' => Class_Notice::TYPE_BIBLIOGRAPHIC,
+                                           'limitPage' => [$page, 1000]])) {
+  echo "\npage: $page\n";
+  $page ++;
+  array_map([$indexer, 'index'], $records);
+
+  Storm_Model_Abstract::unsetLoaders();
+  Storm_Model_Loader::resetCache();
+  gc_collect_cycles();
+}
+
+echo "\n\n" . $indexer->summarize();
+echo "\n\n" . $indexer->report();
+echo "\n\nDONE !!!!\n\n";
\ No newline at end of file
diff --git a/tests/application/modules/AbstractControllerTestCase.php b/tests/application/modules/AbstractControllerTestCase.php
index 612ecce99607d199fd04bae4238e867598f3c985..40e8e4d11bcd9260581eb05964fc3cf98d48c31a 100644
--- a/tests/application/modules/AbstractControllerTestCase.php
+++ b/tests/application/modules/AbstractControllerTestCase.php
@@ -197,6 +197,17 @@ abstract class AbstractControllerTestCase extends Zend_Test_PHPUnit_ControllerTe
     return $this->dispatch($url, true);
   }
 
+
+  public function postDispatchRaw($url, $data, $headers=[]) {
+    $this->getRequest()
+         ->setMethod('POST')
+         ->setRawBody($data)
+         ->setHeaders($headers);
+
+    return $this->dispatch($url, true);
+  }
+
+
   /**
    * Retourne la valeur du header Location
    * @return String
@@ -467,6 +478,13 @@ abstract class AbstractControllerTestCase extends Zend_Test_PHPUnit_ControllerTe
   }
 
 
+  public function assertRedirectRegex($pattern, $message = '') {
+    $this->assertRedirect($message);
+    $location = $this->response->getHeaders()[0]['value'];
+    parent::assertRedirectRegex($pattern, 'Headers location : ' . $location);
+  }
+
+
   public function xhprofile($closure) {
     xhprof_enable();
 
@@ -481,5 +499,3 @@ abstract class AbstractControllerTestCase extends Zend_Test_PHPUnit_ControllerTe
     var_dump('XHProfile data: ' . BASE_URL."/xhprof/xhprof_html/index.php?run={$run_id}&source=xhprof_testing");
   }
 }
-
-?>
\ No newline at end of file
diff --git a/tests/application/modules/admin/controllers/AdminAvisModerationControllerTest.php b/tests/application/modules/admin/controllers/AdminAvisModerationControllerTest.php
index 3cbedea6db63b24887a68ca650206547b7342b85..1751f5bce64de6444296ee4ddbd734e23d7502b5 100644
--- a/tests/application/modules/admin/controllers/AdminAvisModerationControllerTest.php
+++ b/tests/application/modules/admin/controllers/AdminAvisModerationControllerTest.php
@@ -70,7 +70,8 @@ abstract class AdminAvisModerationControllerTestCase extends AbstractControllerT
                                             'date_avis' => '2005-03-27',
                                             'abon_ou_bib' => 0,
                                             'statut' => 0,
-                                            'note' => 4]);
+                                            'note' => 4,
+                                            'source_author' => null]);
 
     $this->avis_marcus_routard = $this->fixture('Class_AvisNotice',
                                                 ['id' => 42,
@@ -81,7 +82,8 @@ abstract class AdminAvisModerationControllerTestCase extends AbstractControllerT
                                                  'date_avis' => '2010-07-21',
                                                  'abon_ou_bib' => 0,
                                                  'statut' => 0,
-                                                 'note' => 2]);
+                                                 'note' => 2,
+                                                 'source_author' => null]);
 
   }
 }
diff --git a/tests/application/modules/admin/controllers/CmsControllerTest.php b/tests/application/modules/admin/controllers/CmsControllerTest.php
index 6e49b08209ca3b9384b218bcd07840f7a35b0d3c..fb918f2b0789744fd04b1bc8f84bc0ccbd7dce9b 100644
--- a/tests/application/modules/admin/controllers/CmsControllerTest.php
+++ b/tests/application/modules/admin/controllers/CmsControllerTest.php
@@ -1711,7 +1711,7 @@ class CmsControllerNewsAddActionPostWithWorkflowTest
   /** @test */
   public function sentMailToAuthorWhenNewsIsValidatedShouldContainsDefaultSubject() {
     $this->postArticleValidated();
-    $this->assertEquals('=?utf8?Q?[Bokeh] Validation de l\'article Katsuhiro Otomo en dédicace !?',
+    $this->assertEquals('=?utf8?Q?[Bokeh] Validation de l\'article Katsuhiro Otomo ? =?utf8?Q?en dédicace !?',
                         quoted_printable_decode($this->mock_transport->getSentMails()[0]->getSubject()));
   }
 
@@ -1809,7 +1809,7 @@ class CmsControllerNewsAddActionPostWithWorkflowTest
   /** @test */
   public function sentMailToAdminWhenValidationPendingShouldContainsDefaultTitle() {
     $this->postArticleAValider();
-    $this->assertContains('[Bokeh] Validation d\'article en attente: Katsuhiro Otomo en dédicace !',
+    $this->assertContains('=?utf8?Q?[Bokeh] Validation d\'article en attente: ? =?utf8?Q?Katsuhiro Otomo en dédicace !?',
                           quoted_printable_decode($this->mock_transport->getSentMails()[0]->getSubject()));
   }
 
@@ -1978,7 +1978,7 @@ class CmsControllerWorkflowArticleRefusedTest extends CmsControllerWorkflowTestC
   /** @test */
   public function sentMailToUserWhenRefusedShouldContainsDefaultTitle() {
     $this->postArticleRefuser();
-    $this->assertContains('[Bokeh] Refus de l\'article Katsuhiro Otomo en dédicace !',
+    $this->assertContains('=?utf8?Q?[Bokeh] Refus de l\'article Katsuhiro Otomo en ? =?utf8?Q?dédicace !?',
                           quoted_printable_decode($this->mock_transport->getSentMails()[0]->getSubject()));
   }
 
diff --git a/tests/application/modules/admin/controllers/JournalControllerTest.php b/tests/application/modules/admin/controllers/JournalControllerTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..795032bb8541b68b5c5f15beac612837af04de39
--- /dev/null
+++ b/tests/application/modules/admin/controllers/JournalControllerTest.php
@@ -0,0 +1,59 @@
+<?php
+/**
+ * Copyright (c) 2012, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class Admin_JournalControllerIndexTest extends Admin_AbstractControllerTestCase {
+  protected $_storm_default_to_volatile = true;
+
+  public function setUp() {
+    parent::setUp();
+
+    Class_Journal::setTimeSource(new TimeSourceForTest('2018-07-06 15:18:04'));
+    Class_Journal::newInstance(['type' => 'REVIEW_SAVE'])->assertSave();
+
+    $this->dispatch('/admin/journal', true);
+  }
+
+
+  public function tearDown() {
+    Class_Journal::setTimeSource(null);
+    parent::tearDown();
+  }
+
+
+  /** @test */
+  public function titleShouldContainsJournal() {
+    $this->assertXPathContentContains('//h1', 'Journal d\'évènements');
+  }
+
+
+  /** @test */
+  public function firstEventTypeShouldBePresent() {
+    $this->assertXPathContentContains('//td', 'REVIEW_SAVE');
+  }
+
+
+  /** @test */
+  public function firstEventCreatedAtShouldBePresent() {
+    $this->assertXPathContentContains('//td', '2018-07-06 15:18:04',
+                                      $this->_response->getBody());
+  }
+}
\ No newline at end of file
diff --git a/tests/application/modules/admin/controllers/ModoControllerTest.php b/tests/application/modules/admin/controllers/ModoControllerTest.php
index 91df915f932228b6fcfa8af3afa2cfa65ea3dc70..e5fe26e09ec117fba6726d929124ce0e32f1195d 100644
--- a/tests/application/modules/admin/controllers/ModoControllerTest.php
+++ b/tests/application/modules/admin/controllers/ModoControllerTest.php
@@ -56,7 +56,8 @@ abstract class ModoControllerIndexActionTestCase extends Admin_AbstractControlle
                                         'id_user' => null,
                                         'avis' => 'Ce livre est vraiment bien !',
                                         'statut' => 0,
-                                        'abon_ou_bib' => 1]);
+                                        'abon_ou_bib' => 1,
+                                        'source_author' => null]);
 
     $this->fixture('Class_AvisNotice', ['id' => 223,
                                         'id_notice' => 1002,
@@ -67,7 +68,8 @@ abstract class ModoControllerIndexActionTestCase extends Admin_AbstractControlle
                                         'avis' => ' Pour faire aimer la biere aux enfants!',
                                         'id_notice' => 1032,
                                         'statut' => 1,
-                                        'abon_ou_bib' => 1]);
+                                        'abon_ou_bib' => 1,
+                                        'source_author' => null]);
 
     $this->fixture('Class_Notice', ['id' => 1032,
                                     'titre_principal' => 'B comme bière : la bière expliquée aux (grands) enfants']);
@@ -106,7 +108,8 @@ abstract class ModoControllerIndexActionTestCase extends Admin_AbstractControlle
                                           'abon_ou_bib' => 0,
                                           'date_avis' => $i,
                                           'id_notice' => 1032,
-                                          'statut' => 1]);
+                                          'statut' => 1,
+                                          'source_author' => null]);
   }
 }
 
@@ -745,7 +748,8 @@ class ModoControllerAvisnoticeActionTest extends Admin_AbstractControllerTestCas
                                         'avis' => 'Un bon livre !',
                                         'id_notice' => 1032,
                                         'statut' => 0,
-                                        'abon_ou_bib' => 1]);
+                                        'abon_ou_bib' => 1,
+                                        'source_author' => null]);
 
     $this->dispatch('admin/modo/avisnotice', true);
   }
diff --git a/tests/application/modules/admin/controllers/RedmineControllerTest.php b/tests/application/modules/admin/controllers/RedmineControllerTest.php
index 3b84848c1d754d888c1d66658c3e6a7fecc1e992..a94fef7ab9b30f8dcc8630b702034325bcc693f8 100644
--- a/tests/application/modules/admin/controllers/RedmineControllerTest.php
+++ b/tests/application/modules/admin/controllers/RedmineControllerTest.php
@@ -52,6 +52,7 @@ abstract class Admin_RedmineControllerTestCase extends Admin_AbstractControllerT
 
 
 
+
 class Admin_RedmineControllerTestActionWithNoBibTest extends Admin_RedmineControllerTestCase {
   public function setUp() {
     parent::setUp();
@@ -67,6 +68,7 @@ class Admin_RedmineControllerTestActionWithNoBibTest extends Admin_RedmineContro
 
 
 
+
 abstract class Admin_RedmineControllerWithAnnecyLibraryTestCase extends Admin_RedmineControllerTestCase {
 
   public function setUp() {
@@ -560,6 +562,13 @@ class Admin_RedmineControllerEditIssue34247Test extends Admin_RedmineControllerF
   public function historyShouldBePresent() {
     $this->assertXPathContentContains('//legend', 'Historique');
   }
+
+
+  /** @test */
+  public function attachmentShouldLinkToDownloadFile() {
+    $this->assertXPathContentContains('//a[contains(@href,"download-file/id_lib/1/fileid/456789/filename/proof.jpg")]', 'proof.jpg', $this->_response->getBody());
+  }
+
 }
 
 
@@ -863,3 +872,74 @@ class Admin_RedmineControllerPostEditIssue34247WithAttachmentTest extends Admin_
     $this->assertRedirect();
   }
 }
+
+
+
+class Admin_RedmineControllerTestActionDownloadAttachmentTest
+  extends Admin_RedmineControllerWithAnnecyLibraryTestCase {
+
+  public function setUp() {
+    parent::setUp();
+
+    $redmine_api = $this->mock()
+                        ->whenCalled('download')
+                        ->with(123)
+                        ->answers('toto');
+
+    $redmine_client = $this->mock()
+                           ->whenCalled('api')
+                           ->answers($redmine_api);
+
+    Class_WebService_Redmine::setClient($redmine_client);
+
+    $this->dispatch('/admin/redmine/download-file/id_lib/1/fileid/123/filename/test.txt', true);
+  }
+
+
+  /** @test */
+  public function responseBodyShouldBeToto() {
+    $this->assertEquals('toto', $this->_response->getBody());
+  }
+
+
+  /** @test */
+  public function contentTypeShouldBeApplicationOctetStream() {
+    $this->assertEquals('application/octet-stream; name="test.txt"',
+                        $this->_response->getHeaders()[0]['value']);
+  }
+}
+
+
+
+class Admin_RedmineControllerTestActionDownloadEmptyAttachmentTest
+  extends Admin_RedmineControllerWithAnnecyLibraryTestCase {
+
+  public function setUp() {
+    parent::setUp();
+
+    $redmine_api = $this->mock()
+                        ->whenCalled('download')
+                        ->with(123)
+                        ->answers('');
+
+    $redmine_client = $this->mock()
+                           ->whenCalled('api')
+                           ->answers($redmine_api);
+
+    Class_WebService_Redmine::setClient($redmine_client);
+
+    $this->dispatch('/admin/redmine/download-file/id_lib/1/fileid/123/filename/test.txt', true);
+  }
+
+
+  /** @test */
+  public function shouldNotifyError() {
+    $this->assertFlashMessengerContentContains('Impossible de télécharger cette pièce jointe');
+  }
+
+
+  /** @test */
+  public function shouldRedirect() {
+    $this->assertRedirect();
+  }
+}
\ No newline at end of file
diff --git a/tests/application/modules/admin/controllers/WidgetControllerTest.php b/tests/application/modules/admin/controllers/WidgetControllerTest.php
index 351d4b8586da3db45072bee60b2493150bb522d6..55839256bf7348c1a7fb0a0b7f007c2b635d0db6 100644
--- a/tests/application/modules/admin/controllers/WidgetControllerTest.php
+++ b/tests/application/modules/admin/controllers/WidgetControllerTest.php
@@ -1822,6 +1822,14 @@ class WidgetControllerSearchTest extends WidgetControllerDispatchWidgetConfigura
   }
 
 
+  /** @test */
+  public function inputInFilesShouldHaveIndexOnlySelected() {
+    $this->assertXPathContentContains('//form//select[@name="in_files"]//option[@selected][@value="0"]',
+                                      'Index seulement',
+                                      $this->_response->getBody());
+  }
+
+
   /** @test */
   public function selectProfilRedirectShouldContainsProfilAdulte() {
     $this->assertXPath('//select[@name="profil_redirect"]//option[@value="2"]');
diff --git a/tests/application/modules/opac/controllers/AbonneControllerActivitiesTest.php b/tests/application/modules/opac/controllers/AbonneControllerActivitiesTest.php
index bb93289eeed201ce2b5006b8632ea0e0ed19c8d8..32ac1fb1c79c1572f2602c14fe3ee775429419fa 100644
--- a/tests/application/modules/opac/controllers/AbonneControllerActivitiesTest.php
+++ b/tests/application/modules/opac/controllers/AbonneControllerActivitiesTest.php
@@ -706,8 +706,9 @@ class AbonneControllerActivitiesAmadouInscritSessionFebruaryJavaOpenTest extends
 
   /** @test */
   public function firstMailSubjectShouldBeRegistrationConfirmation() {
-    $this->assertEquals('=?utf8?Q?Confirmation d\'inscription à l\'activité "Learn Java"?',
-                        quoted_printable_decode($this->_mails[0]->getSubject()));
+    $this->assertEquals('=?utf8?Q?Confirmation=20d\'inscription=20=C3=A0=20l\'activit=C3=A9=20?=
+ =?utf8?Q?"Learn=20Java"?=',
+                        $this->_mails[0]->getSubject());
   }
 
 
@@ -943,7 +944,7 @@ class AbonneControllerActivitiesAmadouDesinscritSessionJuilletPythonTest
 
   /** @test */
   public function firstMailSubjectShouldContainsUnregistrationConfirmation() {
-    $this->assertContains('Confirmation de désinscription à l\'activité "Learn Python"',
+    $this->assertContains('=?utf8?Q?Confirmation de désinscription à ? =?utf8?Q?l\'activité "Learn Python"?',
                           quoted_printable_decode($this->_mails[0]->getSubject()));
   }
 
diff --git a/tests/application/modules/opac/controllers/AbonneControllerAvisTest.php b/tests/application/modules/opac/controllers/AbonneControllerAvisTest.php
index 44fe54f783d61da5f369849971f8d7aaa0b92526..07955802c1f1bbfb089e72328d2247eb81e9190e 100644
--- a/tests/application/modules/opac/controllers/AbonneControllerAvisTest.php
+++ b/tests/application/modules/opac/controllers/AbonneControllerAvisTest.php
@@ -51,7 +51,6 @@ abstract class AbonneFlorenceIsLoggedControllerTestCase extends AbstractControll
                                       'fiche_sigb' => ['type_com' => 0]]);
 
     ZendAfi_Auth::getInstance()->logUser($this->florence);
-
   }
 }
 
@@ -61,9 +60,8 @@ abstract class AbonneControllerAvisTestCase extends AbonneFlorenceIsLoggedContro
   public function setUp() {
     parent::setUp();
 
-    $this->potter = Class_Notice::newInstanceWithId(53,
-                                                    ['clef_oeuvre' =>'POTTER']);
-    $this->potter->save();
+    $this->potter = $this->fixture('Class_Notice',
+                                   ['id' => 53, 'clef_oeuvre' => 'POTTER']);
   }
 }
 
@@ -104,24 +102,14 @@ class AbonneControllerAvisNoticeWithoutAvisTest extends AbonneControllerAvisTest
 
 
 
-class AbonneControllerAvisInvalidNoticeAvisSaveTest extends  AbonneControllerAvisTestCase {
+class AbonneControllerAvisInvalidNoticeAvisSaveTest extends AbonneControllerAvisTestCase {
   public $xpath,$json;
+
   public function setUp() {
     parent::setUp();
 
-    $this->avis_min_saisie = new Class_AdminVar();
-    $this->avis_min_saisie
-      ->setId('AVIS_MIN_SAISIE')
-      ->setValeur(10);
-
-    $this->avis_max_saisie = new Class_AdminVar();
-    $this->avis_max_saisie
-      ->setId('AVIS_MAX_SAISIE')
-      ->setValeur(1200);
-
-    Class_AdminVar::getLoader()
-      ->cacheInstance($this->avis_min_saisie)
-      ->cacheInstance($this->avis_max_saisie);
+    Class_AdminVar::set('AVIS_MIN_SAISIE', 10);
+    Class_AdminVar::set('AVIS_MAX_SAISIE', 1200);
   }
 
 
@@ -130,11 +118,10 @@ class AbonneControllerAvisInvalidNoticeAvisSaveTest extends  AbonneControllerAvi
              'avisTexte' => 'On adore',
              'avisNote' => 5,
              'avisSignature' => 'FloCouv'];
+
     $this->xpath = new Storm_Test_XPath();
-    $this->getRequest()
-         ->setMethod('POST')
-         ->setPost($data);
-    $this->dispatch('/opac/abonne/avis/id_notice/53/render/popup');
+    $this->postDispatch('/opac/abonne/avis/id_notice/53/render/popup', $data);
+
     $this->json = json_decode($this->_response->getbody());
     $this->assertController('abonne');
     $this->assertAction('avis');
@@ -144,68 +131,58 @@ class AbonneControllerAvisInvalidNoticeAvisSaveTest extends  AbonneControllerAvi
   }
 
   public function testEmptyEntete() {
-    $data = array('avisEntete' => '',
-                  'avisTexte' => 'On adore la magie',
-                  'avisNote' => 5,
-                  'avisSignature' => '');
+    $data = ['avisEntete' => '',
+             'avisTexte' => 'On adore la magie',
+             'avisNote' => 5,
+             'avisSignature' => ''];
+
     $this->xpath = new Storm_Test_XPath();
-    $this->getRequest()
-         ->setMethod('POST')
-         ->setPost($data);
-    $this->dispatch('/opac/abonne/avis/id_notice/53/render/popup');
+    $this->postDispatch('/opac/abonne/avis/id_notice/53/render/popup', $data);
+
     $this->json = json_decode($this->_response->getbody());
     $this->assertController('abonne');
     $this->assertAction('avis');
-    $this->xpath->assertXPathContentContains($this->json->content,'//p[@class="error"]','Vous devez saisir un titre',$this->_response->getBody());
+    $this->xpath->assertXPathContentContains($this->json->content,
+                                             '//p[@class="error"]','Vous devez saisir un titre',
+                                             $this->_response->getBody());
   }
 }
 
 
 
-class AbonneControllerAvisNoticeAvisSaveTest extends  AbonneControllerAvisTestCase {
-  public function testSaveNewAvis() {
-    $expected_avis = Class_AvisNotice::newInstance();
-    $expected_avis
-      ->setEntete('Sorcellerie')
-      ->setAvis('On adore la magie')
-      ->setNote(5)
-      ->setClefOeuvre('POTTER')
-      ->setUser($this->florence)
-      ->setAbonOuBib(1)
-      ->setStatut(0);
-    $expected_avis->save();
-    $this->postAndAssertAvisIsSaved($expected_avis);
-  }
-
-
-  public function postAndAssertAvisIsSaved($expected_avis) {
+class AbonneControllerAvisNoticeAvisSaveTest extends AbonneControllerAvisTestCase {
+  /** @test */
+  public function newAvisShouldBeSaved() {
     $data = ['avisEntete' => 'Sorcellerie',
              'avisTexte' => 'On adore la magie',
              'avisNote' => 5,
              'avisSignature' => 'FloCouv'];
 
-    $this->getRequest()
-         ->setMethod('POST')
-         ->setPost($data);
-    $this->dispatch('/opac/abonne/avis/id_notice/53');
-
-    $this->assertEquals('FloCouv', $this->florence->getPseudo());
+    $this->postDispatch('/opac/abonne/avis/id_notice/53', $data);
+    $this->assertNotNull(Class_AvisNotice::findFirstBy(['entete' => 'Sorcellerie']));
   }
 
 
-  public function testSaveExistingAvis() {
-
-    $expected_avis = Class_AvisNotice::newInstanceWithId(12);
-    $expected_avis
-      ->setEntete('Sorcellerie')
-      ->setAvis('On adore la magie')
-      ->setNote(5)
-      ->setClefOeuvre('POTTER')
-      ->setUser($this->florence)
-      ->setAbonOuBib(1)
-      ->setStatut(0);
+  /** @test */
+  public function existingAvisShouldBeUpdated() {
+    $this->fixture('Class_AvisNotice',
+                   ['id' => 12,
+                    'entete' => 'Sorcellerie',
+                    'avis' => 'On adore la magie',
+                    'note' => 5,
+                    'clef_oeuvre' => 'POTTER',
+                    'user' => $this->florence,
+                    'abon_ou_bib' => 1,
+                    'statut' => 0,
+                    'source_author' => null]);
+
+    $data = ['avisEntete' => 'Sorcellerie mais pas trop',
+             'avisTexte' => 'On adore la magie',
+             'avisNote' => 4,
+             'avisSignature' => 'FloCouv'];
 
-    $this->postAndAssertAvisIsSaved($expected_avis);
+    $this->postDispatch('/opac/abonne/avis/id_notice/53', $data);
+    $this->assertEquals(4, Class_AvisNotice::find(12)->getNote());
   }
 }
 
@@ -220,7 +197,8 @@ class AbonneControllerAvisNoticeWithAvisTest extends AbonneControllerAvisTestCas
                                                 'Entete' => 'Le sorcier super mimi',
                                                 'note' => 4,
                                                 'clef_oeuvre' => 'POTTER',
-                                                'user' => $this->florence]);
+                                                'user' => $this->florence,
+                                                'source_author' => null]);
     $this->dispatch('/opac/abonne/avis/id_notice/53/render/popup');
     $this->_xpath = new Storm_Test_XPath();
     $this->_json = json_decode($this->_response->getBody());
@@ -308,6 +286,7 @@ abstract class AvisControllersFixturesTestCase extends AbonneFlorenceIsLoggedCon
                                                                  'user' => $this->florence,
                                                                  'statut' => 0,
                                                                  'abon_ou_bib'=>1 ,
+                                                                 'source_author' => null,
                                                                  'notices' => [$this->millenium,
                                                                                $this->millenium_with_vignette] ]);
 
@@ -319,14 +298,15 @@ abstract class AvisControllersFixturesTestCase extends AbonneFlorenceIsLoggedCon
 
     $this->avis_potter = $this->fixture('Class_AvisNotice',
                                         ['id' => 25,
-                                        'entete' => 'Prenant',
-                                        'avis' => "Mais un peu trop naïf",
-                                        'note'=>4,
-                                        'date_avis' => '2010-10-12 10:00:00',
-                                        'user'=>$this->florence,
-                                        'statut' => 1,
-                                        'abon_out_bib' => 1,
-                                        'notices' => [$this->potter]]);
+                                         'entete' => 'Prenant',
+                                         'avis' => "Mais un peu trop naïf",
+                                         'note'=>4,
+                                         'date_avis' => '2010-10-12 10:00:00',
+                                         'user'=>$this->florence,
+                                         'statut' => 1,
+                                         'abon_out_bib' => 1,
+                                         'source_author' => null,
+                                         'notices' => [$this->potter]]);
 
     $lost=$this->fixture('Class_AvisNotice', ['id' => 178,
                                               'entete' => "Lost highway",
@@ -338,6 +318,7 @@ abstract class AvisControllersFixturesTestCase extends AbonneFlorenceIsLoggedCon
                                               'statut' => 1,
                                               'flags' =>  Class_AvisNotice::ORPHAN_FLAG,
                                               'abon_ou_bib'=>1,
+                                              'source_author' => null,
                                               'id_notice' => 30]);
 
 
@@ -351,6 +332,7 @@ abstract class AvisControllersFixturesTestCase extends AbonneFlorenceIsLoggedCon
                                          'statut' => 1,
                                          'abon_out_bib' => 1,
                                          'flags' => 1,
+                                         'source_author' => null,
                                          'notices' => []]);
 
 
@@ -372,6 +354,7 @@ abstract class AvisControllersFixturesTestCase extends AbonneFlorenceIsLoggedCon
                                              'user' => $dupont,
                                              'statut' => 0,
                                              'abon_out_bib' => 1,
+                                             'source_author' => null,
                                              'notices' =>[$this->millenium]
                                             ]);
     Storm_Test_ObjectWrapper::onLoaderOfModel('Class_Notice')
@@ -845,7 +828,8 @@ class AbonneControllerAvisBlogControllerViewReadAvisTest extends  AbonneFlorence
       ->setUser($this->florence)
       ->setAbonOuBib(1)
       ->setStatut(0)
-      ->setNotices([$millenium]);
+      ->setNotices([$millenium])
+      ->setSourceAuthor(null);
   }
 
 
@@ -913,7 +897,8 @@ class AbonneControllerEditAvisNoticeNotAdminLoggedActionTest extends AbstractCon
     parent::setUp();
     $avis = $this->fixture('Class_AvisNotice', ['id' => 54,
                                                 'entete' => 'Bonjour !',
-                                                'avis' => 'Ceci est le contenu de l\'avis']);
+                                                'avis' => 'Ceci est le contenu de l\'avis',
+                                                'source_author' => null]);
     $this->dispatch('/opac/abonne/editavisnotice/id/54', true);
   }
 
@@ -932,7 +917,8 @@ class AbonneControllerEditAvisNoticeAdminLoggedActionTest extends AbstractContro
     parent::setUp();
     $avis = $this->fixture('Class_AvisNotice', ['id' => 54,
                                                 'entete' => 'Bonjour !',
-                                                'avis' => 'Ceci est le contenu de l\'avis']);
+                                                'avis' => 'Ceci est le contenu de l\'avis',
+                                                'source_author' => null]);
 
     $this->dispatch('/opac/abonne/editavisnotice/id/54', true);
     $this->json = json_decode($this->_response->getBody());
@@ -985,6 +971,7 @@ class AbonneControllerEditAvisNoticeAdminLoggedPostActionTest extends AbstractCo
                                         'entete' => 'Bonjour !',
                                         'avis' => 'Ceci est le contenu de l\'avis',
                                         'note' => 1,
+                                        'source_author' => null,
                                         'clef_oeuvre' => 'HUITIEMECOULEURLA-DASTYLE']);
 
     $this->fixture('Class_Notice', ['id' => 1190178,
@@ -1034,7 +1021,8 @@ class AbonneControllerDeleteAvisNoticeAdminLoggedActionTest extends AbstractCont
     $_SERVER['HTTP_REFERER'] ='opac/recherche/viewnotice/id/1';
     $avis = $this->fixture('Class_AvisNotice', ['id' => 54,
                                                 'entete' => 'Bonjour !',
-                                                'avis' => 'Ceci est le contenu de l\'avis']);
+                                                'avis' => 'Ceci est le contenu de l\'avis',
+                                                'source_author' => null]);
     $this->dispatch('/opac/abonne/delavisnotice/id/54/expressionRecherche/1', true);
   }
 
diff --git a/tests/application/modules/opac/controllers/BlogControllerTest.php b/tests/application/modules/opac/controllers/BlogControllerTest.php
index 6144366da1567f14fad2bcb3d12ee21cdf43f6a8..6516c32892c079d1f87c27c830476a50e3d1a901 100644
--- a/tests/application/modules/opac/controllers/BlogControllerTest.php
+++ b/tests/application/modules/opac/controllers/BlogControllerTest.php
@@ -76,6 +76,7 @@ class BlogControllerHierarchicalTest extends AbstractControllerTestCase {
                     'statut' => 1,
                     'date_avis' => '2015-05-18 00:00:00',
                     'id_user' => 3,
+                    'source_author' => null,
                     'clef_oeuvre' => $this->ksp->getClefOeuvre()]);
 
     $this->fixture('Class_AvisNotice',
@@ -86,6 +87,7 @@ class BlogControllerHierarchicalTest extends AbstractControllerTestCase {
                     'statut' => 1,
                     'date_avis' => '2016-05-18 00:00:00',
                     'id_user' => 3,
+                    'source_author' => null,
                     'clef_oeuvre' => $this->ksp->getClefOeuvre()]);
 
     $this->fixture('Class_AvisNotice',
@@ -96,6 +98,7 @@ class BlogControllerHierarchicalTest extends AbstractControllerTestCase {
                     'statut' => 1,
                     'date_avis' => '2014-05-18 00:00:00',
                     'id_user' => 3,
+                    'source_author' => null,
                     'clef_oeuvre' => $this->ksp->getClefOeuvre()]);
 
     $this->fixture('Class_Users',
diff --git a/tests/application/modules/opac/controllers/NoticeAjaxControllerItemsTest.php b/tests/application/modules/opac/controllers/NoticeAjaxControllerItemsTest.php
index 014f64571d9360246071215cb4ea2f3a7ab42b97..9983079f6bab327073dc297fc5652db561c07dda 100644
--- a/tests/application/modules/opac/controllers/NoticeAjaxControllerItemsTest.php
+++ b/tests/application/modules/opac/controllers/NoticeAjaxControllerItemsTest.php
@@ -291,4 +291,114 @@ class NoticeAjaxControllerItemsCustomIconsTest
     $this->assertXPath('//img[contains(@src, "/customized/images/map.gif")]',
                        $this->_response->getBody());
   }
+}
+
+
+
+
+class NoticeAjaxControllerItemBelongsToBundleTest
+  extends NoticeAjaxControllerItemsTestCase {
+
+  protected function _prepareFixtures() {
+    $config = Class_Profil::getCurrentProfil()->getCfgNoticeAsArray();
+    $config['exemplaires']['grouper'] = '1';
+    Class_Profil::getCurrentProfil()->setCfgNotice($config);
+
+
+    $data_profile = Class_IntProfilDonnees::forKoha()->setId(23);
+    $data_profile
+      ->setItemField(Class_IntProfilDonnees::FIELD_ITEM_BUNDLE_ID,
+                     7)
+      ->save();
+
+    $bundled_item = $this->fixture('Class_Exemplaire',
+                                   ['id' => 98,
+                                    'id_bib' => 3,
+                                    'id_int_bib' => 3,
+                                    'section' => 14,
+                                    'id_data_profile' => 23,
+                                    'cote' => 'A',
+                                    'annexe' => 87,
+                                    'dispo' => 'Non',
+                                    'zone995' => serialize([['code' => '7',
+                                                             'valeur' => 87346]])
+                                   ]);
+
+    $no_value_in_itemfield = $this->fixture('Class_Exemplaire',
+                                            ['id' => 102,
+                                             'id_bib' => 3,
+                                             'id_int_bib' => 3,
+                                             'section' => 14,
+                                             'id_data_profile' => 23,
+                                             'cote' => 'A',
+                                             'annexe' => 87,
+                                             'dispo' => 'Non',
+                                             'zone995' => serialize([['code' => '7',
+                                                                      'valeur' => '']])
+                                            ]);
+
+
+    $bundle_not_exists = $this->fixture('Class_Exemplaire',
+                                        ['id' => 103,
+                                         'id_bib' => 3,
+                                         'id_int_bib' => 3,
+                                         'section' => 14,
+                                         'id_data_profile' => 23,
+                                         'cote' => 'A',
+                                         'annexe' => 87,
+                                         'dispo' => 'Non',
+                                         'zone995' => serialize([['code' => '7',
+                                                                  'valeur' => 666]])
+                                        ]);
+
+    $no_bundle_field = $this->fixture('Class_Exemplaire',
+                                      ['id' => 103,
+                                       'id_bib' => 3,
+                                       'id_int_bib' => 3,
+                                       'section' => 14,
+                                       'id_data_profile' => 23,
+                                       'cote' => 'A',
+                                       'annexe' => 87,
+                                       'dispo' => 'Non',
+                                       'zone995' => serialize([[]])
+                                      ]);
+
+
+     $this->fixture('Class_Notice',
+                    ['id' => 34,
+                     'titre_principal' => 'La fausse malle du piège du int_bib',
+                     'exemplaires' => [
+                                       $this->fixture('Class_Exemplaire',
+                                                      ['id' => 89,
+                                                       'id_int_bib' => 2,
+                                                       'id_origine' => 87346])
+                     ]]);
+
+
+     $this->fixture('Class_Notice',
+                    ['id' => 25,
+                     'titre_principal' => 'Se faire la malle',
+                     'exemplaires' => [
+                                       $this->fixture('Class_Exemplaire',
+                                                      ['id' => 82,
+                                                       'id_int_bib' => 3,
+                                                       'id_origine' => 87346])
+                     ]]);
+
+
+     Class_Notice::find(731325)
+       ->addExemplaire($bundled_item)
+       ->addExemplaire($no_value_in_itemfield)
+       ->addExemplaire($bundle_not_exists)
+       ->addExemplaire($no_bundle_field)
+       ->save();
+  }
+
+
+  /** @test */
+  public function tdShouldContainsLinkToRecordSeFaireLaMalleId25() {
+    $this->assertXPathContentContains('//td//a[contains(@href, "/recherche/viewnotice/id/25")]',
+                                      'Fait partie du lot "Se faire la malle"');
+  }
+
 }
\ No newline at end of file
diff --git a/tests/application/modules/opac/controllers/NoticeAjaxControllerPMBTest.php b/tests/application/modules/opac/controllers/NoticeAjaxControllerPMBTest.php
index e74ea46bc178af56a01775455e6bd4a0175aa5d5..8c79f97eb5a45675e21ae3057f4c39c97eb211f8 100644
--- a/tests/application/modules/opac/controllers/NoticeAjaxControllerPMBTest.php
+++ b/tests/application/modules/opac/controllers/NoticeAjaxControllerPMBTest.php
@@ -92,7 +92,7 @@ class NoticeAjaxControllerPMBRecordTest extends AbstractControllerTestCase {
 
 
   /** @test */
-  public function libraryLabelShouldBeLeKiosqueLibrary() {
+  public function libraryLabelShouldBePMBLibrary() {
     $this->assertXPathContentContains('//table//td', 'PMB library');
   }
 
@@ -102,3 +102,80 @@ class NoticeAjaxControllerPMBRecordTest extends AbstractControllerTestCase {
     $this->assertXPathContentContains('//script', 'setupAnchorsTarget');
   }
 }
+
+
+
+
+
+class NoticeAjaxControllerPMBSerialArticleTest extends AbstractControllerTestCase {
+  protected $_storm_default_to_volatile = true;
+
+
+  public function setUp() {
+    parent::setUp();
+
+    $with_item_other_int_bib = $this->fixture('Class_Notice',
+                                              ['id' => '666',
+                                               'exemplaires' => [
+                                                   $this->fixture('Class_Exemplaire',
+                                                                  ['id' => 666,
+                                                                   'id_origine' => '60-bull',
+                                                                   'id_int_bib' => 2,
+                                                                   'code_barres' => '666',
+                                                                   'type' => Class_Notice::TYPE_BIBLIOGRAPHIC,
+                                                                   'cote' => 'THIS IS NOT GOOD'])
+                                               ]
+                                              ]);
+
+    $serial = $this->fixture('Class_Notice',
+                             ['id' => '123',
+                              'type_doc' => Class_TypeDoc::PERIODIQUE,
+                              'exemplaires' => [
+                                  $this->fixture('Class_Exemplaire',
+                                                 ['id' => 1,
+                                                  'id_origine' => '60-bull',
+                                                  'id_int_bib' => 3,
+                                                  'code_barres' => 'ZXY60',
+                                                  'type' => Class_Notice::TYPE_BIBLIOGRAPHIC,
+                                                  'cote' => 'RFI1'])
+                              ]
+                             ]);
+
+
+    $article = $this->fixture('Class_Notice',
+                             ['id' => '234',
+                              'clef_chapeau' => 'REVUE FRANC',
+                              'type_doc' => Class_TypeDoc::PERIODIQUE,
+                              'unimarc' => file_get_contents(ROOT_PATH . '/tests/fixtures/unimarc_article_rfp.txt'),
+                              'exemplaires' => [
+                                  $this->fixture('Class_Exemplaire',
+                                                 ['id' => 2,
+                                                  'id_origine' => '1787',
+                                                  'id_int_bib' => 3,
+                                                  'code_barres' => '1-234',
+                                                  'type' => Class_Notice::TYPE_SERIAL_ARTICLE,
+                                                  'zone995' => serialize([ ['code' => '0',
+                                                                            'valeur' => '60-bull'] ])])
+                              ]
+                             ]);
+  }
+
+
+  /** @test */
+  public function itemsTableShouldContainsCoteRFI1() {
+    $this->dispatch('/opac/noticeajax/exemplaires/id_notice/234', true);
+
+    $this->assertXPathContentContains('//table//td[@class="cote"]',
+                                      'RFI1');
+  }
+
+
+  /** @test */
+  public function recordShouldContainsLinkForSerie() {
+    $this->dispatch('/opac/recherche/viewnotice/id/234', true);
+
+    $this->assertXPathContentContains('//a[contains(@href,"/serie/REVUE+FRANC-2/")]',
+                                      'Voir tous les articles de ce numéro de Revue française de pédagogie',
+                                      $this->_response->getBody());
+  }
+}
\ No newline at end of file
diff --git a/tests/application/modules/opac/controllers/NoticeAjaxControllerTest.php b/tests/application/modules/opac/controllers/NoticeAjaxControllerTest.php
index 49a564d145b07f1986af514bae316a60d905c1a6..7b77a6cccd9e86594844f4ad9821a9d7b38d5a59 100644
--- a/tests/application/modules/opac/controllers/NoticeAjaxControllerTest.php
+++ b/tests/application/modules/opac/controllers/NoticeAjaxControllerTest.php
@@ -1536,6 +1536,8 @@ abstract class NoticeAjaxControllerNoticeWithAvisTestCase
                                     'clef_oeuvre' => 'potter',
                                     'abon_ou_bib' => '0',
                                     'statut' => 1,
+                                    'source_actor_id' => null,
+                                    'source_author' => null,
                                    ]);
 
     $avis_de_francois = Class_AvisNotice::newInstanceWithId(24,
@@ -1545,20 +1547,155 @@ abstract class NoticeAjaxControllerNoticeWithAvisTestCase
                                                              'avis'=>'tres bien',
                                                              'date_avis'=>'16/02/2013',
                                                              'clef_oeuvre'=>'potter',
-                                                             'abon_ou_bib'=>'1'])
+                                                             'abon_ou_bib' => '1',
+                                                             'source_actor_id' => null])
       ->setModerationOk();
 
-    Class_Notice::newInstanceWithId(34,
-                                    ['titre_principal'=>'Potter',
-                                     'clef_oeuvre'=>'potter',
-                                     'avis' => [$avis_de_paul,
-                                                $avis_de_francois]]);
+    $this->fixture('Class_Notice', ['id' => 34,
+                                    'titre_principal' => 'Potter',
+                                    'clef_oeuvre' => 'potter']);
+  }
+}
+
+
+
+
+abstract class NoticeAjaxControllerNoticeWithAvisFederationReviewGuestLoggedTestCase
+  extends NoticeAjaxControllerNoticeWithAvisTestCase {
+
+  protected $_endpoint = 'https://commu.server.io/activitypub/reviews';
+
+  protected function _loginHook($account) {
+    $account->ROLE_LEVEL = ZendAfi_Acl_AdminControllerRoles::INVITE;
+    $account->ROLE = 'invite';
+  }
+
 
+  public function setUp() {
+    parent::setUp();
+
+    $this->fixture('Class_Journal',
+                   ['id' => 150,
+                    'type' => 'AP_REVIEW_DISPLAY_JOIN']);
+
+    $endpoint = $this->_endpoint;
+    Class_WebService_ActivityPub::setThrowErrors(true);
+
+    $signer = $this->mock()
+                   ->whenCalled('signRequest')->answers('MySignature')
+                   ->whenCalled('getKeyId')->answers($endpoint . '/pubkey')
+
+                   ->whenCalled('verify')
+                   ->with(['(request-target)' => 'get /activitypub/reviews/outbox'], 'TheirKey')
+                   ->answers(true);
+
+    Class_WebService_ActivityPub::setSigner($signer);
+
+    $client = $this->mock()
+                   ->whenCalled('open_url')
+                   ->with($endpoint,
+                          ['headers' => ['Accept' => Class_WebService_ActivityPub::MIME_TYPE]])
+                   ->answers(json_encode(['@context' => 'https://www.w3.org/ns/activitystreams',
+                                          'type' => 'Service',
+                                          'id' => $endpoint,
+                                          'inbox' => $endpoint . '/inbox',
+                                          'outbox' => $endpoint . '/outbox',
+                                          'publicKey' => $endpoint . '/pubkey']))
+
+                   ->whenCalled('open_url')
+                   ->with($endpoint . '/pubkey',
+                          ['headers' => ['Accept' => Class_WebService_ActivityPub::MIME_TYPE]])
+                   ->answers(json_encode(['@context' => 'https://www.w3.org/ns/activitystreams',
+                                          'type' => 'Key',
+                                          'id' => $endpoint . '/pubkey',
+                                          'owner' => $endpoint,
+                                          'publicKeyPem' => 'TheirKey']))
+
+                   ->whenCalled('getResponse')
+                   ->answers($this->mock()
+                             ->whenCalled('isError')->answers(false)
+                             ->whenCalled('getHeader')->with('Signature')->answers('TheirSignature')
+                             ->whenCalled('getHeaders')->answers([])
+                             ->whenCalled('getBody')
+                             ->answers(json_encode(['@context' => 'https://www.w3.org/ns/activitystreams',
+                                                    'type' => 'CollectionPage',
+                                                    'totalItems' => 16,
+                                                    'items' => [['date_avis' => '2019-04-19 17:46:27',
+                                                                 'entete' => 'Au top',
+                                                                 'avis' => 'trop bien !',
+                                                                 'note' => '4',
+                                                                 'abon_ou_bib' => 1,
+                                                                 'source_author' => 'Arcadia']]])));
+
+    Class_WebService_ActivityPub::setWebClient($client);
+  }
+
+
+  public function tearDown() {
+    Class_WebService_ActivityPub::setThrowErrors(false);
+    Class_WebService_ActivityPub::setSigner(null);
+    Class_WebService_ActivityPub::setWebClient(null);
+
+    parent::tearDown();
   }
 }
 
 
 
+
+class NoticeAjaxControllerNoticeWithAvisFederationReviewGuestLoggedDisabledTest
+  extends NoticeAjaxControllerNoticeWithAvisFederationReviewGuestLoggedTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    Class_AdminVar::set('FEDERATION_COMMUNITY_SERVER', '');
+
+    $this->dispatch('/opac/noticeajax/avis/id_notice/34/page/0/onglet/bloc/cherche/federationreview', true);
+  }
+
+
+  /** @test */
+  public function shouldNotHaveCommunityReviews() {
+    $this->assertNotXPathContentContains('//a', 'Avis communautaires');
+  }
+
+
+  /** @test */
+  public function shouldNotCallWebservice() {
+    $this->assertFalse(Class_WebService_ActivityPub::getWebClient()
+                       ->methodHasBeenCalled('open_url'));
+  }
+}
+
+
+
+
+class NoticeAjaxControllerNoticeWithAvisFederationReviewGuestLoggedEnabledTest
+  extends NoticeAjaxControllerNoticeWithAvisFederationReviewGuestLoggedTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    Class_AdminVar::set('FEDERATION_COMMUNITY_SERVER', $this->_endpoint);
+
+    $this->dispatch('/opac/noticeajax/avis/id_notice/34/page/0/onglet/bloc/cherche/federationreview', true);
+  }
+
+
+  /** @test */
+  public function shouldHaveCommunityReviews() {
+    $this->assertXPathContentContains('//a', 'Avis communautaires');
+  }
+
+
+  /** @test */
+  public function shouldHaveAuthorArcadia() {
+    $this->assertXPathContentContains('//span[@class="auteur_critique"]', 'Arcadia');
+  }
+}
+
+
+
+
 class NoticeAjaxControllerNoticeWithAvisEditLinkNotLoggedTest
   extends NoticeAjaxControllerNoticeWithAvisTestCase {
   /**
@@ -1630,11 +1767,12 @@ class NoticeAjaxControllerNoticeWithAvisWithModerationAndAuthorLostNotLoggedTest
 
 
 
-class NoticeAjaxControllerNoticeWithAvisEditLinkGuestLoggedTest extends NoticeAjaxControllerNoticeWithAvisTestCase {
+class NoticeAjaxControllerNoticeWithAvisEditLinkGuestLoggedTest
+  extends NoticeAjaxControllerNoticeWithAvisTestCase {
+
   protected function _loginHook($account) {
     $account->ROLE_LEVEL = ZendAfi_Acl_AdminControllerRoles::INVITE;
     $account->ROLE = 'invite';
-
   }
 
 
@@ -2495,4 +2633,4 @@ class NoticeAjaxControllerWithKiosqueInResumeTest extends AbstractControllerTest
     $this->dispatch('/noticeajax/detail/id/2', true);
     $this->assertXPathContentContains( '//dd', 'Ceci est un logiciel libre.');
   }
-}
\ No newline at end of file
+}
diff --git a/tests/application/modules/opac/controllers/OnSiteConsultationTest.php b/tests/application/modules/opac/controllers/OnSiteConsultationTest.php
index 71827e4f23a02913a0c5ff23415979d65ac4d7ba..b4ec5ece40ae4d6498d667d35870586e213ac280 100644
--- a/tests/application/modules/opac/controllers/OnSiteConsultationTest.php
+++ b/tests/application/modules/opac/controllers/OnSiteConsultationTest.php
@@ -167,7 +167,14 @@ class OnSiteConsultationExemplaireTest extends OnSiteConsultationTestCase {
   /** @test */
   public function secondExemplaireShouldContainsReservationLink() {
     $this->dispatch('noticeajax/exemplaires/id/135', true);
-    $this->assertXPath('//a[contains(@href, "/recherche/reservation-pickup-ajax/id_notice/135/id_int_bib/4/id_bib/99/copy_id/800")]',$this->_response->getBody());
+    $this->assertXPath('//a[contains(@href, "/recherche/reservation-pickup-ajax/id/135/id_notice/135/id_int_bib/4/id_bib/99/copy_id/800")]',$this->_response->getBody());
+  }
+
+
+  /** @test */
+  public function exemplairesWithInspectorGadgetShouldKeepInspectorGadget() {
+    $this->dispatch('noticeajax/exemplaires/id/135/inspector_gadget/1', true);
+    $this->assertXPath('//a[contains(@href, "/recherche/reservation-pickup-ajax/id/135/inspector_gadget/1/id_notice/135/id_int_bib/4/id_bib/99/copy_id/800")]',$this->_response->getBody());
   }
 }
 
diff --git a/tests/application/modules/opac/controllers/ProfilOptionsControllerTest.php b/tests/application/modules/opac/controllers/ProfilOptionsControllerTest.php
index cb6770b4cfd42c2eb91b152b83441958da0e1ee9..28e788296e02caca60bc104c4697293cee9d2e81 100644
--- a/tests/application/modules/opac/controllers/ProfilOptionsControllerTest.php
+++ b/tests/application/modules/opac/controllers/ProfilOptionsControllerTest.php
@@ -249,7 +249,8 @@ abstract class ProfilOptionsControllerWithProfilAdulteTestCase extends AbstractC
                                                    'type_module' => 'RECH_SIMPLE',
                                                    'preferences' => ['recherche_avancee' => "on",
                                                                      'select_doc' => 'on',
-                                                                     'select_annexe' => 'on']],
+                                                                     'select_annexe' => 'on',
+                                                                     'in_files' => 1]],
 
                                            '2' => ['division' => '4',
                                                    'type_module' => 'LOGIN',
@@ -1572,6 +1573,12 @@ class ProfilOptionsControllerViewProfilJeunesseAccueilTest extends ProfilOptions
   }
 
 
+  /** @test */
+  public function hiddenInputShouldContainsInFilesOne() {
+    $this->assertXPath('//form[@class="rechSimpleForm"]//input[@name="in_files"][@value="1"][@type="hidden"]');
+  }
+
+
   /** @test */
   public function comboRechSimpleTypeDocShouldOnlyContainsTypesOneTwoAndFour() {
     foreach([1,2,4] as $id)
diff --git a/tests/application/modules/opac/controllers/RechercheControllerReservationTest.php b/tests/application/modules/opac/controllers/RechercheControllerReservationTest.php
index a30d24dc17324037de2b805c39fb64c56fe3065d..3f85abce911dc5993ebf5b444287c4db4be0cff7 100644
--- a/tests/application/modules/opac/controllers/RechercheControllerReservationTest.php
+++ b/tests/application/modules/opac/controllers/RechercheControllerReservationTest.php
@@ -47,6 +47,7 @@ abstract class RechercheControllerReservationTestCase
 
 
 
+
 abstract class RechercheControllerReservationWithPickupChoiceTestCase
   extends RechercheControllerReservationTestCase {
 
@@ -59,6 +60,7 @@ abstract class RechercheControllerReservationWithPickupChoiceTestCase
 
 
 
+
 class RechercheControllerReservationPickupAjaxActionWithChosenPickupTest
   extends RechercheControllerReservationWithPickupChoiceTestCase {
 
@@ -408,8 +410,10 @@ class RechercheControllerReservationWithMailPostAction
 
 
 
+
 class RechercheControllerReservationWithWebServiceKohaTest
   extends AbstractControllerTestCase {
+  protected $_storm_default_to_volatile = true;
 
   public function setUp() {
     parent::setUp();
@@ -448,6 +452,36 @@ class RechercheControllerReservationWithWebServiceKohaTest
   }
 
 
+
+  /** @test */
+  public function reservationAjaxWithItemRequiringCalendarHoldShouldRedirectToReservationCalendarAjax() {
+    $this->fixture('Class_Exemplaire',
+                   ['id' => 456,
+                    'code_barres' => 123,
+                    'id_int_bib' => 1,
+                    'notice' => $this->fixture('Class_Notice',
+                                               ['id' => 8890,
+                                                'titre_principal' => 'Elementaire mon cher polar',
+                                                'auteur_principal' => 'Conan Doyle'])]);
+
+    $this->jajm->setIdabon(395749);
+
+    $this->koha
+      ->whenCalled('isConnected')
+      ->answers(true)
+
+      ->whenCalled('reserverExemplaire')
+      ->willDo(function()
+               {
+                 throw new Class_WebService_SIGB_RequiresCalendarHoldException();
+               });
+
+    $this->dispatch('/recherche/reservationajax/id_bib/1/copy_id/456/code_annexe/VS');
+
+    $this->assertRedirectTo('/recherche/reservation-calendar-ajax/id_bib/1/copy_id/456/code_annexe/VS');
+  }
+
+
   /** @test */
   public function withMatchingHoldMessageShouldContainsPickupLocation() {
     $item = $this->fixture('Class_Exemplaire',
@@ -544,6 +578,185 @@ class RechercheControllerReservationWithWebServiceKohaTest
 
 
 
+class RechercheControllerReservationCalendarAjaxTest extends AbstractControllerTestCase {
+  protected
+    $_storm_default_to_volatile = true,
+    $_json,
+    $_xpath;
+
+  public function setUp() {
+    parent::setUp();
+
+
+    $item = $this->fixture('Class_Exemplaire',
+                           ['id' => 456,
+                            'id_origine' => 1,
+                            'annexe' => 'VS']);
+
+    $comm_sigb = $this->mock()
+                      ->whenCalled('holdsForItem')
+                      ->with($item)
+                      ->answers(['statut' => true,
+                                 'holds' => [(new Class_WebService_SIGB_Koha_Reservation(3,$item))
+                                             ->setReserveDate('2020-01-15')
+                                             ->setExpirationDate('2020-06-16'),
+                                             (new Class_WebService_SIGB_Koha_Reservation(2,$item))
+                                             ->setReserveDate('2019-07-26')
+                                             ->setExpirationDate('2019-12-31')]]);
+
+    Class_CommSigb::setInstance($comm_sigb);
+
+    $this->dispatch('/recherche/reservation-calendar-ajax/id_bib/1/copy_id/456/code_annexe/VS',true);
+    $this->_json = json_decode($this->_response->getBody());
+    $this->_xpath = new Storm_Test_XPath();
+  }
+
+
+  /** @test */
+  public function popupHoldsListShouldContainsDu26JuilletAu31Decembre() {
+    $this->_xpath->assertXPathContentContains($this->_json->content,
+                                              '//ul/li[1]',
+                                              utf8_encode('Du 26 juillet 2019 au 31 décembre 2019'));
+  }
+
+
+  /** @test */
+  public function popupHoldsListShouldContainsDu15JanvierAu16Juin() {
+    $this->_xpath->assertXPathContentContains($this->_json->content,
+                                              '//ul/li[2]',
+                                              utf8_encode('Du 15 janvier 2020 au 16 juin 2020'));
+  }
+
+
+  /** @test */
+  public function formActionShouldBeRechercheReservationCalendarAjax() {
+    $this->_xpath->assertXPath($this->_json->content,
+                               '//form[@action="/recherche/reservation-calendar-ajax/id_bib/1/copy_id/456/code_annexe/VS"]');
+  }
+
+
+  /** @test */
+  public function formCancelButtonShouldCloseDialog() {
+    $this->_xpath->assertXPath($this->_json->content,
+                               '//button[@value="Annuler"][contains(@onclick, "opacDialogClose()")]');
+  }
+
+
+  /** @test */
+  public function formShouldContainsSubmit() {
+    $this->_xpath->assertXPath($this->_json->content,
+                               '//input[@type="submit"][@value="Valider"]');
+  }
+
+
+  /** @test */
+  public function responseShouldContainsInputDatePickerForResevationDate() {
+    $this->_xpath->assertXPath($this->_json->content,
+                               '//input[@name="reservedate"]');
+  }
+
+
+  /** @test */
+  public function responseShouldContainsInputDatePickerForExpirationDate() {
+    $this->_xpath->assertXPath($this->_json->content,
+                               '//input[@name="expirationdate"]');
+  }
+}
+
+
+
+
+class RechercheControllerReservationCalendarPostTest extends AbstractControllerTestCase {
+  protected
+    $_storm_default_to_volatile = true,
+    $_comm_sigb;
+
+
+
+  public function setUp() {
+    parent::setUp();
+
+    $item = $this->fixture('Class_Exemplaire',
+                           ['id' => 456,
+                            'code_barres' => 123,
+                            'id_int_bib' => 1,
+                            'notice' => $this->fixture('Class_Notice',
+                                                       ['id' => 8890,
+                                                        'titre_principal' => 'Elementaire mon cher polar',
+                                                        'auteur_principal' => 'Conan Doyle'])]);
+
+    $this->_comm_sigb = $this->mock()
+                             ->whenCalled('holdsForItem')
+                             ->with($item)
+                             ->answers(['statut' => true,
+                                        'holds' => []])
+      ->beStrict();
+
+    Class_CommSigb::setInstance($this->_comm_sigb);
+  }
+
+
+  /** @test */
+  public function withReserveDateAndExpirationDateShouldNotifyHoldSuccess() {
+    $this->_comm_sigb
+         ->whenCalled('reserverExemplaire')
+         ->with(1,456,'VS','2020-01-19','2020-05-25')
+         ->answers(['statut' => true, 'erreur' => '']);
+
+    $this->postDispatch('/recherche/reservation-calendar-ajax/id_bib/1/copy_id/456/code_annexe/VS',
+                        ['reservedate' => '19/01/2020' ,
+                         'expirationdate' => '25/05/2020']);
+
+    $this->json = json_decode($this->_response->getBody());
+    $this->assertContains('Votre réservation est enregistrée',
+                          $this->json->content);
+  }
+
+
+  /** @test */
+  public function withInvalidReserveDateShouldDisplayErrorReserveDateInvalide() {
+    $this->postDispatch('/recherche/reservation-calendar-ajax/id_bib/1/copy_id/456/code_annexe/VS',
+                        ['reservedate' => 'pouet' ,
+                         'expirationdate' => '25/05/2020']);
+
+    $this->json = json_decode($this->_response->getBody());
+    $xpath = new Storm_Test_XPath();
+
+    $xpath->assertXPathContentContains($this->json->content,
+                                       '//ul[@class="errors"]/li',
+                                       '\'pouet\' ne correspond pas au format de date attendu',
+                                       $this->json->content);
+  }
+
+
+  /** @test */
+  public function withDurationInferiorToMinimunShouldDisplayErrorDurationTooSmall() {
+    $this->_comm_sigb->whenCalled('reserverExemplaire')
+                     ->with(1,456,'VS','2020-05-10','2020-05-25')
+                     ->answers(['statut' => false,
+                                'erreur' => 'Durée minimale requise: 30 jours']);
+
+
+    $this->postDispatch('/recherche/reservation-calendar-ajax/id_bib/1/copy_id/456/code_annexe/VS',
+                        ['reservedate' => '10/05/2020' ,
+                         'expirationdate' => '25/05/2020']);
+
+    $this->json = json_decode($this->_response->getBody());
+    $xpath = new Storm_Test_XPath();
+
+    $xpath->assertXPathContentContains($this->json->content,
+                                       '//ul[@class="errors"]',
+                                       'Durée minimale requise: 30 jours');
+
+    $xpath->assertXPath($this->json->content,
+                        '//input[@name="reservedate"][@value="10/05/2020"]',
+                        $this->json->content);
+  }
+}
+
+
+
+
 class RechercheControllerReservationWithMailFormTest
   extends AbstractControllerTestCase {
 
@@ -1016,7 +1229,7 @@ class RechercheControllerReservationPickupAjaxAndNotifyByMailEnabledTest
 
   /** @test */
   public function mailSubjectShouldContainsJulianMonsieurSurExemplaire12341() {
-    $this->assertContains("Julian Monsieur sur l'exemplaire 12341",
+    $this->assertContains("Julian Monsieur ? =?utf8?Q?sur l'exemplaire 12341",
                           quoted_printable_decode(current($this->_sent_mails)->getSubject()));
   }
 
diff --git a/tests/application/modules/opac/controllers/RssControllerTest.php b/tests/application/modules/opac/controllers/RssControllerTest.php
index 7ae5b88f23826320166e4819fd1c9d6ed9b81607..8ebc1fdce4b73767657f87cff2adc104e99fe43a 100644
--- a/tests/application/modules/opac/controllers/RssControllerTest.php
+++ b/tests/application/modules/opac/controllers/RssControllerTest.php
@@ -366,24 +366,26 @@ class RssControllerCritiquesTest extends AbstractControllerTestCase {
     ]);
 
     $avis = [
-      $this->fixture('Class_AvisNotice', [
-        'id' => 1,
-        'id_user' => 1,
-        'avis' => 'Testing comment 1',
-        'entete' => 'Testing comment 1',
-        'note' => 4,
-        'clef_oeuvre' => 'testing-comment',
-        'date_avis' => '2012-01-01',
-      ]),
-      $this->fixture('Class_AvisNotice', [
-        'id' => 2,
-        'id_user' => 1,
-        'avis' => 'Testing comment 2',
-        'entete' => 'Testing comment 2',
-        'note' => 4,
-        'clef_oeuvre' => 'testing-comment',
-        'date_avis' => '2012-01-01',
-      ]),
+             $this->fixture('Class_AvisNotice', [
+                                                 'id' => 1,
+                                                 'id_user' => 1,
+                                                 'avis' => 'Testing comment 1',
+                                                 'entete' => 'Testing comment 1',
+                                                 'note' => 4,
+                                                 'clef_oeuvre' => 'testing-comment',
+                                                 'source_author' => null,
+                                                 'date_avis' => '2012-01-01',
+                                                 ]),
+             $this->fixture('Class_AvisNotice', [
+                                                 'id' => 2,
+                                                 'id_user' => 1,
+                                                 'avis' => 'Testing comment 2',
+                                                 'entete' => 'Testing comment 2',
+                                                 'note' => 4,
+                                                 'clef_oeuvre' => 'testing-comment',
+                                                 'date_avis' => '2012-01-01',
+                                                 'source_author' => null,
+                                                 ]),
     ];
 
     $avis[0]->setNotice($notice1);
diff --git a/tests/application/modules/telephone/controllers/BlogControllerTest.php b/tests/application/modules/telephone/controllers/BlogControllerTest.php
index 6f95518457fc5a8536a2465daf65a8c7abf78265..298492f889534b3485f0af998a1914e7c332b02a 100644
--- a/tests/application/modules/telephone/controllers/BlogControllerTest.php
+++ b/tests/application/modules/telephone/controllers/BlogControllerTest.php
@@ -16,7 +16,7 @@
  *
  * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
  * along with BOKEH; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA 
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
 
 require_once 'TelephoneAbstractControllerTestCase.php';
@@ -47,7 +47,8 @@ abstract class Telephone_BlogControllerAvisActionTestCase extends TelephoneAbstr
                              ->setNote(3)
                              ->setEntete('bien')
                              ->setAvis('bla bla')
-                             ->setUser($patouche)));
+                             ->setUser($patouche)
+                             ->setSourceAuthor(null)));
   }
 }
 
@@ -119,7 +120,7 @@ class Telephone_BlogControllerViewCritiquesActionTest extends Telephone_BlogCont
   public function actionShouldBeViewCritiques() {
     $this->assertAction('viewcritiques');
   }
-  
+
 }
 
 ?>
\ No newline at end of file
diff --git a/tests/db/UpgradeDBTest.php b/tests/db/UpgradeDBTest.php
index 6fe50e6c25d2d2df3378674852c57a904e90d833..e573e0015211d005f209659a65c7c5c4cb9dbd29 100644
--- a/tests/db/UpgradeDBTest.php
+++ b/tests/db/UpgradeDBTest.php
@@ -2227,7 +2227,6 @@ class UpgradeDB_352_Test extends UpgradeDBTestCase {
 
 
 
-
 class UpgradeDB_353_Test extends UpgradeDBTestCase {
   public function prepare() {}
 
@@ -2692,14 +2691,12 @@ class UpgradeDB_373_Test extends UpgradeDBTestCase {
                          '1' => 'Bibliothèque + codes-barres'],
                         Class_CosmoVar::getList('unicite_code_barres'));
   }
-
 }
 
 
 
 
 class UpgradeDB_374_Test extends UpgradeDBTestCase {
-
   public function prepare() {
     $this->silentQuery("ALTER TABLE `bib_c_site` DROP COLUMNS notify_on_new_resa, notify_on_new_user;");
   }
@@ -2756,3 +2753,169 @@ class UpgradeDB_376_Test extends UpgradeDBTestCase {
     $this->assertFieldType('codif_auteur', 'thumbnail_url', 'varchar(255)');
   }
 }
+
+
+
+
+class UpgradeDB_377_Test extends UpgradeDBTestCase {
+  public function prepare() {
+    $this
+      ->silentQuery('ALTER TABLE notices DROP COLUMN file_content')
+      ->silentQuery('ALTER TABLE notices DROP KEY file_content');
+  }
+
+
+  /** @test */
+  public function tableNoticesShouldContainsColumnFileContent() {
+    $this->assertFieldType('notices', 'file_content', 'longtext');
+  }
+
+
+  /** @test */
+  public function noticesColumnFileContentShouldBeIndexed() {
+    $this->assertIndex('notices', 'file_content', 'FULLTEXT');
+  }
+}
+
+
+
+
+class UpgradeDB_378_Test extends UpgradeDBTestCase {
+  public function prepare() {
+    $this
+      ->silentQuery('ALTER TABLE exemplaires DROP COLUMN id_data_profile');
+  }
+
+
+  /** @test */
+  public function tableExemplaireShouldHaveColumnIdDataProfileInt() {
+    $this->assertFieldType('exemplaires', 'id_data_profile', 'int(11) unsigned');
+  }
+}
+
+
+
+
+class UpgradeDB_379_Test extends UpgradeDBTestCase {
+  public function prepare() {
+    $this->dropTable('journal');
+    $this->dropTable('journal_detail');
+    $this->dropTable('federation_group_membership');
+    $this->dropIndexedFieldFrom('notices_avis', 'source_actor_id');
+    $this->dropIndexedFieldFrom('notices_avis', 'source_author');
+    $this->dropIndexedFieldFrom('notices_avis', 'source_primary');
+  }
+
+
+  /** @test */
+  public function journalTableShouldExists() {
+    $this->assertTable('journal');
+  }
+
+
+  public function journalFields() {
+    return [['id', 'int(11) unsigned'],
+            ['type', 'varchar(255)'],
+            ['created_at', 'datetime']];
+  }
+
+
+  /**
+   * @test
+   * @dataProvider journalFields
+   */
+  public function journalFieldShouldExists($field, $type) {
+    $this->assertFieldType('journal', $field, $type);
+  }
+
+
+  public function journalIndexes() {
+    return [['type'], ['created_at']];
+  }
+
+
+  /**
+   * @test
+   * @dataProvider journalIndexes
+   */
+  public function journalIndexShouldExists($name) {
+    $this->assertIndex('journal', $name);
+  }
+
+
+  /** @test */
+  public function journalDetailTableShouldExists() {
+    $this->assertTable('journal_detail');
+  }
+
+
+  public function journalDetailFields() {
+    return [['id', 'int(11) unsigned'],
+            ['journal_id', 'int(11) unsigned'],
+            ['type', 'varchar(255)'],
+            ['value', 'text']];
+  }
+
+
+  /**
+   * @test
+   * @dataProvider journalDetailFields
+   */
+  public function journalDetailFieldShouldExists($field, $type) {
+    $this->assertFieldType('journal_detail', $field, $type);
+  }
+
+
+  public function journalDetailIndexes() {
+    return [['journal_id'], ['type']];
+  }
+
+
+  /**
+   * @test
+   * @dataProvider journalDetailIndexes
+   */
+  public function journalDetailIndexShouldExists($name) {
+    $this->assertIndex('journal_detail', $name);
+  }
+
+
+  /** @test */
+  public function federationGroupMembershipTableShouldExists() {
+    $this->assertTable('federation_group_membership');
+  }
+
+
+  public function federationGroupMembershipFields() {
+    return [['id', 'int(11) unsigned'],
+            ['group_name', 'varchar(255)'],
+            ['actor_id', 'varchar(255)'],
+            ['accepted_at', 'datetime']];
+  }
+
+
+  /**
+   * @test
+   * @dataProvider federationGroupMembershipFields
+   */
+  public function federationGroupMembershipFieldShouldExists($field, $type) {
+    $this->assertFieldType('federation_group_membership', $field, $type);
+  }
+
+
+  public function noticesAvisNewFields() {
+    return [['source_actor_id', 'varchar(255)'],
+            ['source_author', 'varchar(255)'],
+            ['source_primary', 'int(11)']];
+  }
+
+
+  /**
+   * @test
+   * @dataProvider noticesAvisNewFields
+   */
+  public function noticesAvisNewFieldShouldExistsAndBeIndexed($field, $type) {
+    $this->assertFieldType('notices_avis', $field, $type);
+    $this->assertIndex('notices_avis', $field);
+  }
+}
diff --git a/tests/fixtures/KohaFixtures.php b/tests/fixtures/KohaFixtures.php
index 738645654e043675ed5f17d7d40d293e07435ff4..a92556b1c6d22b538ff8edd5db1910456641d5b2 100644
--- a/tests/fixtures/KohaFixtures.php
+++ b/tests/fixtures/KohaFixtures.php
@@ -728,6 +728,31 @@ class KohaFixtures {
                 <dateaccessioned>2011-02-18</dateaccessioned>
                 <itype>LIV</itype>
               </item>
+
+              <!-- 11 -->
+              <item>
+                <biblioitemnumber>33270</biblioitemnumber>
+                <wthdrawn>22</wthdrawn>
+                <holdingbranchname>Bibliothèque Départementale de la Meuse</holdingbranchname>
+                <notforloan>0</notforloan>
+                <replacementpricedate>2011-02-18</replacementpricedate>
+                <itemnumber>0239426</itemnumber>
+                <ccode>ROMJEUN</ccode>
+                <itemcallnumber>JR ROW h</itemcallnumber>
+                <date_due></date_due>
+                <barcode>2661660440</barcode>
+                <itemlost>0</itemlost>
+                <datelastseen>2011-02-18</datelastseen>
+                <homebranch>BDM</homebranch>
+                <homebranchname>Bibliothèque Départementale de la Meuse</homebranchname>
+                <biblionumber>33233</biblionumber>
+                <holdingbranch>BDM</holdingbranch>
+                <timestamp>2011-02-18 14:24:01</timestamp>
+                <damaged>0</damaged>
+                <cn_sort>JR_ROW_H</cn_sort>
+                <dateaccessioned>2011-02-18</dateaccessioned>
+                <itype>LIV</itype>
+              </item>
             </items>
           </record>
         </GetRecords>';
@@ -917,6 +942,14 @@ class KohaFixtures {
       }
 
 
+      public static function xmlLookupPatronJamesBond() {
+        return '<?xml version="1.0" encoding="UTF-8" ?>
+        <LookupPatron>
+          <id>007</id>
+        </LookupPatron>';
+      }
+
+
       public static function xmlGetPatronInfoDupont() {
         return '<?xml version="1.0" encoding="UTF-8" ?>
 <GetPatronInfo>
@@ -1070,6 +1103,297 @@ class KohaFixtures {
        </GetPatronInfo>';
       }
 
+
+      public static function xmlGetPatronInfoJamesBond() {
+        return '<?xml version="1.0" encoding="UTF-8" ?>
+        <GetPatronInfo>
+          <borrowernumber>007</borrowernumber>
+          <cardnumber>007</cardnumber>
+          <itype>I_WILL_CRASH_YOU</itype>
+          <holds>
+              <hold>
+                <expirationdate>2019-12-31</expirationdate>
+                <reservedate>2019-07-26</reservedate>
+                <biblionumber>1</biblionumber>
+                <title>Élémentaire mon cher polar</title>
+                <item>
+                  <itemnumber>1</itemnumber>
+                  <withdrawn>0</withdrawn>
+                  <biblionumber>96629</biblionumber>
+                  <barcode>MALLE00010</barcode>
+                  <title>Élémentaire mon cher polar</title>
+                  <notforloan>0</notforloan>
+                  <itemtype>MAL_EXPO</itemtype>
+                  <homebranch>BDY</homebranch>
+                  <holdingbranch>BDY</holdingbranch>
+                  <itype>PRET_MALLE</itype>
+                  <biblioitemnumber>96629</biblioitemnumber>
+                </item>
+                <branchname>BDP</branchname>
+                <reserve_id>1124</reserve_id>
+                <itemnumber>112194</itemnumber>
+                <status>NEW</status>
+                <priority>1</priority>
+             </hold>
+              <hold>
+                <expirationdate>2020-01-15</expirationdate>
+                <reservedate>2019-10-12</reservedate>
+                <biblionumber>2</biblionumber>
+                <title>Il fait chaud !</title>
+                <item>
+                  <itemnumber>2</itemnumber>
+                  <withdrawn>0</withdrawn>
+                  <biblionumber>2</biblionumber>
+                  <barcode>VALISE00010</barcode>
+                  <notforloan>0</notforloan>
+                  <itemtype>MAL_EXPO</itemtype>
+                  <homebranch>BDY</homebranch>
+                  <holdingbranch>BDY</holdingbranch>
+                  <itype>PRET_VALISE</itype>
+                  <biblioitemnumber>2</biblioitemnumber>
+                </item>
+                <branchname>BDP</branchname>
+                <reserve_id>2</reserve_id>
+                <itemnumber>2</itemnumber>
+                <status>1</status>
+                <priority>1</priority>
+            </hold>
+            <hold>
+                <expirationdate>2020-11-15</expirationdate>
+                <reservedate>2019-07-26</reservedate>
+                <biblionumber>3</biblionumber>
+                <title>Vive les glaces</title>
+                <item>
+                  <itemnumber>3</itemnumber>
+                  <withdrawn>0</withdrawn>
+                  <biblionumber>3</biblionumber>
+                  <barcode>GLACIERE00010</barcode>
+                  <notforloan>0</notforloan>
+                  <itemtype>MAL_GLACIERE</itemtype>
+                  <homebranch>BDY</homebranch>
+                  <holdingbranch>BDY</holdingbranch>
+                  <itype>PRET_GLACIERE</itype>
+                  <biblioitemnumber>3</biblioitemnumber>
+                </item>
+                <branchname>BDP</branchname>
+                <reserve_id>3</reserve_id>
+                <itemnumber>3</itemnumber>
+                <status>1</status>
+                <found>W</found>
+                <priority>1</priority>
+            </hold>
+              <hold>
+                <reservedate>2019-05-23</reservedate>
+                <biblionumber>4</biblionumber>
+                <title>Le livre du moment</title>
+                <item>
+                  <itemnumber>4</itemnumber>
+                  <withdrawn>0</withdrawn>
+                  <biblionumber>4</biblionumber>
+                  <barcode>LIVRE00010</barcode>
+                  <notforloan>0</notforloan>
+                  <itemtype>LIVRE</itemtype>
+                  <homebranch>BDY</homebranch>
+                  <holdingbranch>BDY</holdingbranch>
+                  <itype>LIVRE</itype>
+                  <biblioitemnumber>4</biblioitemnumber>
+                </item>
+                <branchname>BDP</branchname>
+                <reserve_id>4</reserve_id>
+                <itemnumber>4</itemnumber>
+                <status>1</status>
+                <priority>1</priority>
+            </hold>
+          </holds>
+          <loans>
+          </loans>
+       </GetPatronInfo>'
+;
+      }
+
+
+      public static function xmlGetRecordsElementaireMonCherPolar () {
+          return
+            '<?xml version="1.0" encoding="ISO-8859-1" ?>
+      <GetRecords>
+        <record>
+          <biblioitemnumber>1</biblioitemnumber>
+          <isbn>9782862749198</isbn>
+          <marcxml>
+            <record
+                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                xsi:schemaLocation="http://www.loc.gov/MARC21/slim http://www.loc.gov/ standards/marcxml/schema/MARC21slim.xsd"
+                xmlns="http://www.loc.gov/MARC21/slim">
+              <leader>00397nac a22001451u 4500</leader>
+              <datafield tag="010" ind1=" " ind2=" ">
+                <subfield code="a">9782862749198</subfield>
+              </datafield>
+              <datafield tag="090" ind1=" " ind2=" ">
+                <subfield code="a">1</subfield>
+              </datafield>
+              <datafield tag="101" ind1=" " ind2=" ">
+                <subfield code="a">fre</subfield>
+              </datafield>
+              <datafield tag="100" ind1=" " ind2=" ">
+                <subfield code="a">20080725              frey50       </subfield>
+              </datafield>
+              <datafield tag="200" ind1=" " ind2=" ">
+                <subfield code="a">Jardins d\'enfance</subfield>
+                <subfield code="b">LITT</subfield>
+                <subfield code="f">Abécassis, Eliette</subfield>
+              </datafield>
+              <datafield tag="210" ind1=" " ind2=" ">
+                <subfield code="c">cherche midi éditeur</subfield>
+                <subfield code="d">11/2001</subfield>
+              </datafield>
+              <datafield tag="215" ind1=" " ind2=" ">
+                <subfield code="a">180</subfield>
+              </datafield>
+              <datafield tag="225" ind1=" " ind2=" ">
+                <subfield code="a">nouvelles</subfield>
+              </datafield>
+              <datafield tag="995" ind1=" " ind2=" ">
+                <subfield code="9">1</subfield>
+                <subfield code="c">BIB</subfield>
+                <subfield code="2">0</subfield>
+                <subfield code="k">R ABE</subfield>
+                <subfield code="o">0</subfield>
+                <subfield code="e">Secteur Adulte</subfield>
+                <subfield code="b">BIB</subfield>
+                <subfield code="j">7786000200</subfield>
+                <subfield code="q">a</subfield>
+                <subfield code="r">2</subfield>
+                <subfield code="s">Achats</subfield>
+              </datafield>
+              <controlfield tag="001">1</controlfield>
+            </record>
+          </marcxml>
+          <publicationyear>2001</publicationyear>
+          <collectiontitle>nouvelles</collectiontitle>
+          <pages>180</pages>
+          <issues>
+          </issues>
+          <itemtype>LITT</itemtype>
+          <biblionumber>1</biblionumber>
+          <timestamp>2008-09-03 18:43:19</timestamp>
+          <cn_sort>_</cn_sort>
+          <publishercode>cherche midi éditeur</publishercode>
+          <reserves>
+          </reserves>
+          <items>
+              <item>
+                  <borrower0>007</borrower0>
+                  <biblioitemnumber>1</biblioitemnumber>
+                  <card0>AGENT007</card0>
+                  <date_due>2020-07-18 23:59:00</date_due>
+                  <notforloan>0</notforloan>
+                  <datelastborrowed>2019-07-19</datelastborrowed>
+                  <borrowernumber>007</borrowernumber>
+                  <damaged>0</damaged>
+                  <cn_sort></cn_sort>
+                  <replacementpricedate>2019-06-21</replacementpricedate>
+                  <homebranch>BDY</homebranch>
+                  <itype>PRET_MALLE</itype>
+                  <withdrawn>0</withdrawn>
+                  <itemlost>0</itemlost>
+                  <timestamp0>2019-07-19 15:36:22</timestamp0>
+                  <holdingbranchname>Bibliothèque départementale de l\'Yonne</holdingbranchname>
+                  <cardnumber>007</cardnumber>
+                  <issues>2</issues>
+                  <timestamp>2019-07-19 15:36:42</timestamp>
+                  <itemnumber>112194</itemnumber>
+                  <holdingbranch>BDY</holdingbranch>
+                  <datelastseen>2019-07-19</datelastseen>
+                  <homebranchname>Bibliothèque départementale de l\'Yonne</homebranchname>
+                  <barcode>MALLE00010</barcode>
+                  <biblionumber>1</biblionumber>
+                  <dateaccessioned>2019-06-21</dateaccessioned>
+                  <onloan>2020-07-18</onloan>
+                </item>
+          </items>
+        </record>
+     </GetRecords>';
+        }
+
+
+      public static function elementaireMonCherPolarHolds() {
+        return
+          '{
+              "holds": [
+                {
+                  "cancellationreason": null,
+                  "itemtype": null,
+                  "waitingdate": null,
+                  "suspend_until": null,
+                  "priority": 1,
+                  "reminderdate": null,
+                  "timestamp": "2019-07-24 15:08:20",
+                  "notificationdate": null,
+                  "found": null,
+                  "status": "NEW",
+                  "reserve_id": 1124,
+                  "borrowernumber": 195,
+                  "itemnumber": 112194,
+                  "lowestPriority": false,
+                  "cancellationdate": null,
+                  "biblionumber": 96629,
+                  "expirationdate": "2019-12-31",
+                  "reservenotes": "",
+                  "reservedate": "2019-07-26",
+                  "suspend": false,
+                  "branchcode": "BDY"
+                },
+                {
+                  "biblionumber": 96629,
+                  "lowestPriority": false,
+                  "cancellationdate": null,
+                  "suspend": false,
+                  "branchcode": "BDY",
+                  "reservenotes": "",
+                  "expirationdate": "2020-06-16",
+                  "reservedate": "2020-01-01",
+                  "itemtype": null,
+                  "reminderdate": null,
+                  "waitingdate": null,
+                  "suspend_until": null,
+                  "priority": 3,
+                  "cancellationreason": null,
+                  "borrowernumber": 42,
+                  "reserve_id": 1125,
+                  "itemnumber": 112194,
+                  "notificationdate": null,
+                  "found": null,
+                  "timestamp": "2019-07-24 15:11:33",
+                  "status": "NEW"
+                },
+                {
+                  "cancellationreason": null,
+                  "suspend_until": null,
+                  "waitingdate": null,
+                  "priority": 2,
+                  "reminderdate": null,
+                  "itemtype": null,
+                  "status": "NEW",
+                  "notificationdate": null,
+                  "timestamp": "2019-07-24 15:11:33",
+                  "found": null,
+                  "itemnumber": 112194,
+                  "reserve_id": 1126,
+                  "borrowernumber": 44,
+                  "cancellationdate": null,
+                  "lowestPriority": false,
+                  "biblionumber": 96629,
+                  "reservedate": "2019-11-13",
+                  "expirationdate": "2020-07-16",
+                  "reservenotes": "",
+                  "suspend": false,
+                  "branchcode": "BDY"
+                }
+              ]
+            }';
+      }
+
+
       public static function xmlGetRecordsWithTransfert () {
         return '<?xml version="1.0" encoding="UTF-8" ?>
     <GetRecords>
diff --git a/tests/fixtures/unimarc_article_rfp.txt b/tests/fixtures/unimarc_article_rfp.txt
new file mode 100644
index 0000000000000000000000000000000000000000..3a9e503449b485391130300a8c496445483d2bb0
--- /dev/null
+++ b/tests/fixtures/unimarc_article_rfp.txt
@@ -0,0 +1 @@
+01031naa2 22002051i 450 00100060000010000350000600900150004120001770005685600460023310100080027921500150028730000290030233002140033131900280054570000350057321000090060889600220061746100630063946301230070221349  a20190509u        u  u0frey50    a2018-08-011 aGARNIER Pascale, BROUGÈRE Gilles, RAYNA Sylvie & RUPIN Pablo. À 2 ans, vivre dans un collectif d’enfants. Crèche, école maternelle, classe passerelle, jardin maternel  uhttps://journals.openedition.org/rfp/53910 afre  ap. 115-119  aTexte intégral en ligne  a Notes critiques  Référence(s) :  GARNIER Pascale, BROUGÈRE Gilles, RAYNA Sylvie & RUPIN Pablo. À 2 ans, vivre dans un collectif d’enfants. Crèche, école maternelle, classe passerelle, jardin maternel.  aAucun droit spécifique 1aPlaisancebÉric40709id:4605  d2018  a./images/vide.png  017165tRevue française de pédagogie9id:171659lnk:perio  d2018-08-01eJanvier-Février-Mars 2017vn° 198tRegards croisés sur le baccalauréat professionnel 9id:359lnk:bull
\ No newline at end of file
diff --git a/tests/library/Class/CommSigbTest.php b/tests/library/Class/CommSigbTest.php
index e582ffabe34bf8638c5054c5d58f93f3200458ef..c838ebf33803141030646189103c74a62fd65484 100644
--- a/tests/library/Class/CommSigbTest.php
+++ b/tests/library/Class/CommSigbTest.php
@@ -462,6 +462,50 @@ class CommSigbMeuseKohaTest extends CommSigbTestCase {
   }
 
 
+
+  /** @test */
+  public function reserverExemplaireWithParamsReserveAndExpirationDataShouldPassParamsToKohaService() {
+    $this->mock_service
+      ->expects($this->once())
+      ->method('reserverExemplaire')
+      ->with($this->userModel,
+             $this->fixture('Class_Exemplaire', ['id' => '123', 'id_notice' => 7888]),
+             'ABC',
+             '2019-12-01',
+             '2020-02-08')
+      ->will($this->returnValue(['statut' => 1,
+                                 'erreur' => '']));
+
+    $this->assertEquals(['statut' => 1,
+                         'erreur' => ''],
+                        $this->comm_sigb->reserverExemplaire(5,
+                                                             '123',
+                                                             'ABC',
+                                                             '2019-12-01',
+                                                             '2020-02-08'));
+
+  }
+
+
+  /** @test */
+  public function holdsForItemShouldCallHoldsForItemOnKohaService() {
+    $item = $this->fixture('Class_Exemplaire',
+                           ['id' => '123',
+                            'id_notice' => 7888,
+                            'int_bib' => $this->bib_koha]);
+    $this->mock_service
+      ->expects($this->once())
+      ->method('holdsForItem')
+      ->with($item)
+      ->will($this->returnValue(['statut' => true,
+                                 'holds' => []]));
+
+    $this->assertEquals(['statut' => true,
+                         'holds' => []],
+                        $this->comm_sigb->holdsForItem($item));
+  }
+
+
   /** @test */
   public function getModeCommShouldReturnAnArrayWithCommParams() {
     $this->assertEquals(array("url_serveur" => 'http://cat-aficg55.biblibre.com/cgi-bin/koha/ilsdi.pl',
diff --git a/tests/library/Class/CriteresRechercheTest.php b/tests/library/Class/CriteresRechercheTest.php
index 5fb75af5c018bc01a7f9dd1742c6e675a3c2b32d..74ff176628f37e20a7830b9d05e67674e09ac74e 100644
--- a/tests/library/Class/CriteresRechercheTest.php
+++ b/tests/library/Class/CriteresRechercheTest.php
@@ -69,12 +69,14 @@ class CriteresRechercheRetourTest extends ModelTestCase {
         'facettes' => 'B1-T1']],
 
       [['expressionRecherche' => 'La nouvelle grille',
+        'in_files' => '1',
         'facettes' => 'B1-zork',
         'facette' => '1345'],
 
        ['controller' => 'recherche',
         'action' => 'simple',
         'expressionRecherche' => 'La nouvelle grille',
+        'in_files' => '1',
         'facettes' => 'B1']],
 
       [['expressionRecherche' => '',
diff --git a/tests/library/Class/HttpSignatureTest.php b/tests/library/Class/HttpSignatureTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..9ea89ca215e9c512c008d82a1fc8c96e4c916770
--- /dev/null
+++ b/tests/library/Class/HttpSignatureTest.php
@@ -0,0 +1,118 @@
+<?php
+/**
+ * Copyright (c) 2012-2019, 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_HttpSignatureTest extends ModelTestCase {
+  protected
+    $_pubkey = '-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmUEi4rcQARpDBIUoQI7E
+D9/WidC0ILPrxhWPcHg6+Mo24Mkj9cKER2E1jZ+nMv0xfKeAt6juXqOxWqD2CUuh
+Dgp3ndJkI+9x8sLUHnGBIprUa8c++CVJ6nsMqzHoCMzRTYvbeaFkYjRGWDQES/0J
+Co/BtrM0csuRkRunJb98SqkGaP0+mhmDljphebqHvtAAsU1N3jc1BY2/HLuzPADd
+2fOEcvsYPXd5YGp/DnfhejyctC4w+NpGZobaZ8jtp4AacXVcox9SJ1C07zqZzxhP
+r1ieSwML9mDKueYe6BjdYFhXEwV2+7fsqNykV3dZDs/5reGyFLMqIMcE4VDMojY8
+yQIDAQAB
+-----END PUBLIC KEY-----',
+
+    $_privkey = '-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEAmUEi4rcQARpDBIUoQI7ED9/WidC0ILPrxhWPcHg6+Mo24Mkj
+9cKER2E1jZ+nMv0xfKeAt6juXqOxWqD2CUuhDgp3ndJkI+9x8sLUHnGBIprUa8c+
++CVJ6nsMqzHoCMzRTYvbeaFkYjRGWDQES/0JCo/BtrM0csuRkRunJb98SqkGaP0+
+mhmDljphebqHvtAAsU1N3jc1BY2/HLuzPADd2fOEcvsYPXd5YGp/DnfhejyctC4w
++NpGZobaZ8jtp4AacXVcox9SJ1C07zqZzxhPr1ieSwML9mDKueYe6BjdYFhXEwV2
++7fsqNykV3dZDs/5reGyFLMqIMcE4VDMojY8yQIDAQABAoIBABR3TmFYcRq0lx6T
+aby1VBmKmuvsoyF65ZGeb3lllPqEhq+eLN81CtU9dhljqMB2b5VmCRp9xNd+pMCl
+njW/k9J8M10wK49g+qagvhMStVwZsSRzh0U8NZLKu/Zgw8vpDkp80uJ7WxyCPqKo
+z6oWMI7og8YSSH7MELSALOItoDuYAgfto0oA/b1KYcXxJ0lYmC08D0nBM1Dug2/W
+UVeiA3pw1J9KZAgjj4lK4ZiN7JqY+FbIogbgR0jKePLTjECETY3X2LZ1Yvth8TP9
+vdgHLUkGQfh/ZMbK44oBEX/2qiacVi1XAzuhlrz3+NyvgCtqMgkMS/uEMN+HgvCR
+w9vIIwECgYEAyucnH7vio1MsVkL9uu5cZEQDWDxV3fPWNMrPZ+sPccqmCI30fUWF
+0w5vJHyt0AaviW1KYAAhkpnj+qtsKiU8Xx6PeOQoJ04plEDGPENtkRlGaEAzABlX
+PAdMo2YjGPI7wuJbTMlzlsY8BhuKKYrgaXTdZNDnTjtu2Xgr9/3JDU0CgYEAwVvw
+ADlZwAUKU77XjrrtP5dJIA0KJHZI9npatZ0hTUZmyKTwCzde8FuBWngQHk5qW1t0
+LD9SuwWuhfUX/lvfKqWTy+1ak6jN1pAQoE1Gb3yx8q+C56VasEBFX2WkCcZgEN1x
+DRpB7RgEmeLCsF25WV75Y/f5XH0zl8CGC/BcX20CgYEAso5SvrlwC7yg8tSHRx6G
+DfJQYzDNe8IeCl1DwjZ4Y/IqxLJvqmIpD3/PTPOvXbbUeQLFhc/3u3RTzP9X84rL
+IwXYylE2CMjfDEkoalYIML1mWU3N09N5Eil2RwEV99kLwEfEgsFxSAjxP4qyvjYp
+oIQoZJT2SMFCnnwDbXxXlq0CgYEAmEBgVozSEtTlMNQQv56IuY3SUp5x4gwRn6Lw
+UhkL4+EPheX57ZsH8pLa4/WuG277aDw22bBy4Di1F13KKssEinweSHD45VQB4HVH
+4jF2yMqTA9kXZndZVXcGKPvLkrbVZfI31m1ag+pplRJs4pqqG6khDopvm1gqi89Y
+vYXh9nECgYEAm9f+N3Mxx/bGRzU5D1IjIwwPCynEx8M/NLmdu3GWstRjl8B9lTKG
+OSm8iswUChadsHDhP1slvjeywDE7pTh8IPunmwFuc7/Q0J0vAmNHl8+oeAqwVJ82
+wQY7fGvKXZZ06i7+GfBDH0wUvUESJYR2strZqfaSB+GelLV9vp0p7iE=
+-----END RSA PRIVATE KEY-----';
+
+
+  /** @test */
+  public function signingRsaShouldReturn344CharsLongSignature() {
+    $sign = (new Class_HttpSignature(''))
+      ->sign(['(request-target)' => 'post /activitypub/reviews',
+              'date' => 'Thu, 21 Dec 2000 16:01:07 +0100',
+              'digest' => '99914b932bd37a50b983c5e7c90ae93b'],
+             Class_HttpSignature::REQUEST_TARGET . ' date digest',
+             $this->_privkey,
+             'rsa-sha256');
+
+    $this->assertEquals(344, strlen($sign));
+  }
+
+
+  /** @test */
+  public function verifyRsaShouldSucceedWithKnownValidSignature() {
+    $sign = new Class_HttpSignature('keyId="rsa-key-1",algorithm="rsa-sha256",headers="(request-target) date digest",signature="dfflJw4lqkpVh9aOwXPLdCqSM8LmD2IX0jDUgVNFZ/frkjGttriewAq2iUAYFWNr9oWvHt6QaW/sspydDyYl752PlSAFFjDewgk6m+lhhMasMwWrST4U+16AXuFpjJJoPWlWuTHgpfcYz2QquPTIcA4TfA5ANORzISfsMIRyb7eJBD9W/d3qSXutn5RuBLLrp0Hx2abGM31rr4gzzZUJ6OhocTI2ZA0XdjuXGF/eIexqV4snlnXUXEpZULW+B6iwPHYj23ADRz3SwF9GpovrEOhYXxmLgAYHY9ErV/82YWr9+NIXPwc/UL4zpZSHqMab0T3+IC/qK+U2g2XW9QKqZA=="');
+
+    $this->assertTrue($sign->verify(['(request-target)' => 'post /activitypub/reviews',
+                                     'date' => 'Thu, 21 Dec 2000 16:01:07 +0100',
+                                     'digest' => '99914b932bd37a50b983c5e7c90ae93b'],
+                                    $this->_pubkey));
+  }
+
+
+  /** @test */
+  public function verifyRsaShouldFailWithBadSignature() {
+    $sign = new Class_HttpSignature('keyId="rsa-key-1",algorithm="rsa-sha256",headers="(request-target) date digest",signature="pHxV6i6cn9ySijY46m+3Tx+NZpCVwEQ0YWNowa4KWAJmI9wNd9hDwlSOG++Hf/vuAM9Pi2bGgaCT7y1I3jNB1fcsi9QlBVK1vW7o7RM5qL55fSHYOvx03KWb0n4ed4D+rcTiO9Pv74+ERMZAgTX4pEzFqJlr1v7UO6Qe5cM6WRT461L8KASwtRJgJ1vmRNLisNZz+YWuQNz9C0Da2y/YWEryxPeZxSZCi0T3bDI/fUNmzLpxNwVtijwH2xivRmcGoUFvB/YEgOSLdJhJf/2AvbXQi4A+C2+OqFPeHx5hkLhoSLLjMwh93wDfIjrfdy7eN4jjVRdn5UYggiWDKxhR+g=="');
+
+    $this->assertFalse($sign->verify(['(request-target)' => 'post /activitypub/reviews',
+                                      'date' => 'Thu, 21 Dec 2000 16:01:07 +0100',
+                                      'digest' => '99914b932bd37a50b983c5e7c90ae93b'],
+                                     $this->_pubkey));
+  }
+
+
+  /** @test */
+  public function verifyRsaShouldFailWithBadPubkey() {
+    $sign = new Class_HttpSignature('keyId="rsa-key-1",algorithm="rsa-sha256",headers="(request-target) date digest",signature="dfflJw4lqkpVh9aOwXPLdCqSM8LmD2IX0jDUgVNFZ/frkjGttriewAq2iUAYFWNr9oWvHt6QaW/sspydDyYl752PlSAFFjDewgk6m+lhhMasMwWrST4U+16AXuFpjJJoPWlWuTHgpfcYz2QquPTIcA4TfA5ANORzISfsMIRyb7eJBD9W/d3qSXutn5RuBLLrp0Hx2abGM31rr4gzzZUJ6OhocTI2ZA0XdjuXGF/eIexqV4snlnXUXEpZULW+B6iwPHYj23ADRz3SwF9GpovrEOhYXxmLgAYHY9ErV/82YWr9+NIXPwc/UL4zpZSHqMab0T3+IC/qK+U2g2XW9QKqZA=="');
+
+    $bad_key = '-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA9AYHqfFq69j/nCKxOI5T
+NaJgXVYVTXqUZCrwMTrTViUf4CdhenW/akMKhPR7+Z6okFAwnY6DWRZUanDp9jYV
+WLS4TtMq4svS8E0i94vWWg3x+6vBuuScZaYksM6nOjaOVvd1SIqvZzrVO2QrYfd9
+O/gV5k6ySPhcWYuPfcT+EFUkcwKQ/4enXiSZO9lKi6uKgaabVHS28JgYv1991cau
+eZgVoRGTvjeHg0oZmIygKeV03xqqB87Hry4HlBAgWTL9TsEjdMNiVpVNxtXdHYi7
+eRzZBqZNEl5LphJoTKhb+A/wPIskqHXGUvvCqTgY7ofNtOTsZdQALthWrBNvHMga
+nQIDAQAB
+-----END PUBLIC KEY-----';
+
+    $this->assertFalse($sign->verify(['(request-target)' => 'post /activitypub/reviews',
+                                      'date' => 'Thu, 21 Dec 2000 16:01:07 +0100',
+                                      'digest' => '99914b932bd37a50b983c5e7c90ae93b'],
+                                     $bad_key));
+  }
+}
diff --git a/tests/library/Class/MoteurRechercheTest.php b/tests/library/Class/MoteurRechercheTest.php
index 243f2e09136d6e109fe85db794a67320a4037050..357037f7e01f3dfcf0e3573e849a23f4ec6d7bc1 100644
--- a/tests/library/Class/MoteurRechercheTest.php
+++ b/tests/library/Class/MoteurRechercheTest.php
@@ -256,6 +256,13 @@ class MoteurRechercheSimpleTest extends MoteurRechercheTestCase {
              'req_liste' => $this->listSqlWith($match_axes ." AGAINST('+(BAKOUNINE BAKOUNINES BAKOUNIN)' IN BOOLEAN MODE)",
                                                "(MATCH(titres) AGAINST(' BAKOUNINE') * 1.5) + (MATCH(auteurs) AGAINST(' BAKOUNINE')) desc")],
 
+            [['expressionRecherche' => 'Bakounine',
+              'in_files' => 1],
+             'nb_mots' => 1,
+             'req_liste' => $this->listSqlWith('(' . $match_axes ." AGAINST('+(BAKOUNINE BAKOUNINES BAKOUNIN)' IN BOOLEAN MODE) OR MATCH(file_content) AGAINST('Bakounine' IN BOOLEAN MODE))",
+                                               "(MATCH(titres) AGAINST(' BAKOUNINE') * 1.5) + (MATCH(auteurs) AGAINST(' BAKOUNINE')) + (MATCH(file_content) AGAINST('Bakounine') * 0.5) desc")],
+
+
             [['expressionRecherche' => 'Slavoj Zizek',
               'tri' => 'alpha_titre'] ,
              'nb_mots' => 2,
diff --git a/tests/library/Class/NoticeTest.php b/tests/library/Class/NoticeTest.php
index 4c21255d917c6d6babbd2a543d104d698fe139de..41f1089c824584d5d2fbc8219e73bd57b0821b24 100644
--- a/tests/library/Class/NoticeTest.php
+++ b/tests/library/Class/NoticeTest.php
@@ -446,8 +446,11 @@ class NoticeTestTypeDoc extends ModelTestCase {
 class NoticeTestGetAvis extends ModelTestCase {
   public function setUp() {
     parent::setUp();
+
     $base_properties = ['avis' => 'Testing comment',
-                        'entete' => 'Testing comment'];
+                        'entete' => 'Testing comment',
+                        'clef_oeuvre' => 'TESTING-RECORD--',
+                        'source_actor_id' => null];
 
     $user_bib = $this->fixture('Class_Users',
                                ['id' => 1,
@@ -478,12 +481,6 @@ class NoticeTestGetAvis extends ModelTestCase {
                                                    ['id' => 4,
                                                     'abon_ou_bib' => 0,
                                                     'note' => 3]));
-    $this->onLoaderOfModel('Class_AvisNotice')
-         ->whenCalled('findAllBy')
-         ->with(['clef_oeuvre' => 'TESTING-RECORD--'])
-         ->answers([$this->avis_bib1, $this->avis_bib2,
-                    $this->avis_abon1, $this->avis_abon2])
-         ->beStrict();
 
     $this->notice = $this->fixture('Class_Notice',
                                    ['id' => 12,
diff --git a/tests/library/Class/WebService/LastfmTest.php b/tests/library/Class/WebService/LastfmTest.php
index c0bcc098dc335daf94e516db324c68b44ea86f2a..8ea9c61f26947360e792fef0c38007042a19c1c0 100644
--- a/tests/library/Class/WebService/LastfmTest.php
+++ b/tests/library/Class/WebService/LastfmTest.php
@@ -149,12 +149,11 @@ class LastfmGetPhotosRageAgainstTheMachineTest extends ModelTestCase {
 
 
 
-
 class LastfmGetMorceauxNoTracksIntegrationTest extends ModelTestCase {
   public function setUp() {
     parent::setUp();
     $this->_last_fm = new Class_WebService_Lastfm();
-    $this->_album = $this->_last_fm->getMorceaux('No track', 'Franz Liszt');
+    $this->_album = $this->_last_fm->getMorceaux('No track', 'Not found');
   }
 
 
diff --git a/tests/library/Class/WebService/SIGB/KohaRestfulTest.php b/tests/library/Class/WebService/SIGB/KohaRestfulTest.php
index 39339c1d134bfd146ba08764c3972ae165b7e51e..b72b4fd9045ec107b9d57e84129039688a1c90c8 100644
--- a/tests/library/Class/WebService/SIGB/KohaRestfulTest.php
+++ b/tests/library/Class/WebService/SIGB/KohaRestfulTest.php
@@ -19,6 +19,7 @@
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
 
+require_once 'tests/fixtures/KohaFixtures.php';
 
 abstract class KohaRestfulTestCase extends ModelTestCase {
   const BASE_URL = 'http://cat-aficg55.biblibre.com/cgi-bin/koha/';
@@ -218,6 +219,101 @@ class KohaRestfulSuggestionsOfUserTest extends KohaRestfulTestCase {
 
 
 
+
+class KohaRestfullHoldsErrorTest extends KohaRestfulTestCase {
+  /** @test */
+  public function withRestfulDisabledShouldAnswersStatusFalseWithMessageRestfulDisabled() {
+    $this->service->setRestful(false);
+    $this->assertEquals(['statut' => false,
+                         'erreur' => 'Koha Restful désactivé',
+                         'holds' => []],
+                        $this->service->holdsForItem(null));
+  }
+
+
+  /** @test */
+  public function forbiddenIpResponseShouldAnswersStatusFalseWithMessage() {
+    $this->mock_web_client
+      ->whenCalled('open_url')
+      ->with(static::BASE_URL . 'rest.pl/biblio/96629/holds')
+      ->answers('[
+   "Forbidden. 178.16.171.35 is not allowed to use this service. Are you sure configuration variable \'authorizedips\' is correctly configured?"
+]');
+
+
+    $item = $this->fixture('Class_Exemplaire',
+                           ['id' => 2,
+                            'id_origine' => 96629]);
+    $this->assertEquals(['statut' => false,
+                         'erreur' => 'Échec de la connexion au webservice, le SIGB a répondu "[
+   "Forbidden. 178.16.171.35 is not allowed to use this service. Are you sure configuration variable \'authorizedips\' is correctly configured?"
+]"',
+                         'holds' => []],
+                        $this->service->holdsForItem($item));
+  }
+
+
+  public function tearDown() {
+    parent::tearDown();
+    $this->service->setRestful(true);
+  }
+}
+
+
+
+
+class KohaRestfullHoldsTest extends KohaRestfulTestCase {
+  protected
+    $_response,
+    $_holds;
+
+  public function setUp() {
+    parent::setUp();
+    $this->mock_web_client->whenCalled('open_url')
+                          ->with(static::BASE_URL . 'rest.pl/biblio/96629/holds')
+                          ->answers(KohaFixtures::elementaireMonCherPolarHolds());
+
+    $item = $this->fixture('Class_Exemplaire',
+                           ['id' => 2,
+                            'id_origine' => 96629]);
+    $this->_response = $this->service->holdsForItem($item);
+    $this->_holds = $this->_response['holds'];
+  }
+
+
+  /** @test */
+  public function responseStatusShouldBeTrue() {
+    $this->assertTrue($this->_response['statut']);
+  }
+
+
+  /** @test */
+  public function firstHoldReserveDateShouldBe2019_07_26() {
+    $this->assertEquals('2019-07-26',$this->_holds[0]->getReserveDate());
+  }
+
+
+  /** @test */
+  public function firstHoldExpirationDateShouldBe2019_12_31() {
+    $this->assertEquals('2019-12-31', $this->_holds[0]->getExpirationDate());
+  }
+
+
+  /** @test */
+  public function firstHoldIdShouldBe1124() {
+    $this->assertEquals(1124, $this->_holds[0]->getId());
+  }
+
+
+  /** @test */
+  public function holdsCountShouldBeThree() {
+    $this->assertCount(3, $this->_holds);
+  }
+}
+
+
+
+
 abstract class KohaRestfulSuggestTestCase extends KohaRestfulTestCase {
   public function setUp() {
     parent::setUp();
diff --git a/tests/library/Class/WebService/SIGB/KohaTest.php b/tests/library/Class/WebService/SIGB/KohaTest.php
index 7386c5aa26e3927bc742829b98281516c68e2c93..833d2458b0dbd5f56e614e39a7e886e2ec688206 100644
--- a/tests/library/Class/WebService/SIGB/KohaTest.php
+++ b/tests/library/Class/WebService/SIGB/KohaTest.php
@@ -156,6 +156,13 @@ class KohaServiceGetNoticeJardinEnfantTest extends KohaTestCase {
     $this->jardins_enfant = $this->service->getNotice('1');
   }
 
+
+  /** @test */
+  public function firstItemRequiresCalendarHoldShouldAnswerFalse() {
+    $this->assertFalse($this->jardins_enfant->getExemplaires()[0]->requiresCalendarHold());
+  }
+
+
   /** @test */
   public function shouldAnswerOneNotice() {
     $this->assertInstanceOf('Class_WebService_SIGB_Notice', $this->jardins_enfant);
@@ -206,6 +213,7 @@ class KohaServiceGetNoticeHarryPotterTest extends KohaServiceGetNoticeHarryPotte
     parent::setUp();
 
     $this->service->setCodificationDisponibilites(['-3' => 'Document en cours d\'équipement']);
+    $this->service->setWithdrawnMapping(['22' => 'Fait partie d\'une malle']);
 
     $this->potter = $this->service->getNotice('33233');
   }
@@ -225,8 +233,8 @@ class KohaServiceGetNoticeHarryPotterTest extends KohaServiceGetNoticeHarryPotte
 
 
   /** @test */
-  public function getExemplairesShouldReturnAnArrayWithSizeTen() {
-    $this->assertEquals(10, count($this->potter->getExemplaires()));
+  public function getExemplairesShouldReturnAnArrayWithSizeEleven() {
+    $this->assertEquals(11, count($this->potter->getExemplaires()));
   }
 
   /** @test */
@@ -295,6 +303,13 @@ class KohaServiceGetNoticeHarryPotterTest extends KohaServiceGetNoticeHarryPotte
     $this->assertEquals(Class_WebService_SIGB_Exemplaire::newInstance()->message('DISPO_PILONNE'), $this->potter->exemplaireAt(3)->getDisponibilite());
   }
 
+
+  /** @test */
+  public function fourthExemplaireShouldBePilonne() {
+    $this->assertTrue($this->potter->exemplaireAt(3)->isPilonne());
+  }
+
+
   /** @test */
   public function fifthExemplaireShouldNotBeReservable()  {
     $this->assertFalse($this->potter->exemplaireAt(4)->isReservable());
@@ -354,6 +369,19 @@ class KohaServiceGetNoticeHarryPotterTest extends KohaServiceGetNoticeHarryPotte
     $this->assertEquals('Document en cours d\'équipement',
                         $this->potter->exemplaireAt(9)->getDisponibilite());
   }
+
+
+  /** @test */
+  public function eleventhExemplaireDisponibiliteShouldBeFaitPartieDuneMalle() {
+   $this->assertEquals('Fait partie d\'une malle',
+                        $this->potter->exemplaireAt(10)->getDisponibilite());
+  }
+
+
+  /** @test */
+  public function eleventhExemplaireShouldNotBePilonne() {
+    $this->assertFalse($this->potter->exemplaireAt(10)->isPilonne());
+  }
 }
 
 
@@ -364,7 +392,8 @@ class KohaServiceGetNoticeHarryPotterAltenativeCodifDisponibiliteTest extends Ko
     parent::setUp();
 
     $this->service = Class_WebService_SIGB_Koha::getService(['url_serveur' => 'http://cat-aficg55.biblibre.com/cgi-bin/koha/ilsdi.pl',
-                                                             'Codification_disponibilites' => "1:Disponible\n4:En transit"]);
+                                                             'Codification_disponibilites' => "1:Disponible\n4:En transit",
+                                                             'withdrawn_mapping' => "1:poubelle\n22:malle"]);
     $this->service->setWebClient($this->mock_web_client);
     $this->potter = $this->service->getNotice('33233');
   }
@@ -380,6 +409,12 @@ class KohaServiceGetNoticeHarryPotterAltenativeCodifDisponibiliteTest extends Ko
   public function seventhExemplaireDisponibiliteShouldBeEnTraitement()  {
     $this->assertEquals("En traitement", $this->potter->exemplaireAt(6)->getDisponibilite());
   }
+
+
+  /** @test */
+  public function fourthItemAvailabilityShouldBePoubelle() {
+    $this->assertEquals('poubelle', $this->potter->exemplaireAt(3)->getDisponibilite());
+  }
 }
 
 
@@ -641,6 +676,9 @@ class KohaGetEmprunteurLaureAfondTest extends KohaTestCase {
 
 }
 
+
+
+
 class KohaGetEmprunteurLisianneWithIdSIGBTest extends KohaTestCase {
   public function setUp() {
     parent::setUp();
@@ -856,6 +894,185 @@ class KohaGetEmprunteurJeanAndreWithIdSIGBTest extends KohaTestCase {
 }
 
 
+
+
+class KohaGetRecordElementaireMonCherPolarTest extends KohaTestCase {
+  protected
+    $_user,
+    $_item,
+    $_item_elementaire;
+
+  public function setUp() {
+    parent::setUp();
+
+    $this->service = Class_WebService_SIGB_Koha::getService(['url_serveur' => 'http://cat-aficg55.biblibre.com/cgi-bin/koha/ilsdi.pl',
+                                                             'grouped_holds_itypes' => "PRET_MALLE",
+                                                             'bundled_holds_minimal_duration' => 30,
+                                                             'bundled_holds_maximal_duration' => 60]);
+
+    $this->service->setWebClient($this->mock_web_client);
+
+    $this->mock_web_client
+      ->whenCalled('open_url')
+      ->with('http://cat-aficg55.biblibre.com/cgi-bin/koha/ilsdi.pl?service=GetRecords&id=1')
+      ->answers(KohaFixtures::xmlGetRecordsElementaireMonCherPolar());
+
+    $this->_item_elementaire = $this->service->getNotice('1')->getExemplaires()[0];
+
+    $this->_user = $this->fixture('Class_Users',
+                                  ['id' => 7,
+                                   'login' => 'james.bond',
+                                   'password' => 'agent007',
+                                   'id_sigb' => '007'
+                                  ]);
+
+    $this->_item = $this->fixture('Class_Exemplaire',
+                                  ['id' => '7',
+                                   'sigb_exemplaire' => $this->_item_elementaire]);
+  }
+
+
+  /** @test */
+  public function firstItemRequiresCalendarHoldShouldAnswerTrue() {
+    $this->assertTrue($this->_item_elementaire->requiresCalendarHold());
+  }
+
+
+  /** @test */
+  public function reserverExemplaireShouldThrowRequiresCalendarHoldException() {
+    try {
+      $this->service->reserverExemplaire($this->_user,
+                                         $this->_item,
+                                         '007');
+
+    } catch (Class_WebService_SIGB_RequiresCalendarHoldException $e) {
+      return true;
+    }
+
+    $this->fail('Expecting Class_WebService_SIGB_RequiresCalendarHoldException');
+  }
+
+
+  /** @test */
+  public function reserverExemplaireWithReserveAndExpirationDateShouldRequestHoldTitle() {
+    $this->fixture('Class_CodifAnnexe',
+                   ['id' => 8,
+                    'code' => '007',
+                    'id_origine' => '007']);
+
+    $exemplaire = $this->fixture('Class_Exemplaire',
+                                 ['id' => '7',
+                                  'id_origine' => 1,
+                                  'zone995' => serialize([['clef' => '9', 'valeur' => '123']]),
+                                  'sigb_exemplaire' => $this->_item_elementaire]);
+
+    $this->mock_web_client
+      ->whenCalled('open_url')
+        ->with('http://cat-aficg55.biblibre.com/cgi-bin/koha/ilsdi.pl?service=HoldItem&patron_id=007&bib_id=1&item_id=123&needed_before_date=2020-01-19&pickup_expiry_date=2020-02-25&pickup_location=007')
+        ->answers("<HoldTitle>
+                      <title>Elementaire mon cher Polar</title>
+                      <pickup_location>007</pickup_location>
+                   </HoldTitle>");
+
+      $ret = $this->service->reserverExemplaire($this->_user, $exemplaire, '007', '2020-01-19', '2020-02-25');
+
+      $this->assertEquals(['statut' => true, 'erreur' => ''], $ret);
+  }
+
+
+  /** @test */
+  public function reserverExemplaireWithDurationInferiorToMinimumShouldReturnErrorMessage() {
+    $ret = $this->service->reserverExemplaire($this->_user,
+                                              $this->_item,
+                                              '007',
+                                              '2020-05-19',
+                                              '2020-05-25');
+
+    $this->assertEquals(['statut' => false, 'erreur' => 'Durée minimale requise: 30 jours'],
+                        $ret);
+  }
+
+
+  /** @test */
+  public function reserverExemplaireWithDurationSuperiorToMaximalShouldReturnErrorMessage() {
+    $ret = $this->service->reserverExemplaire($this->_user,
+                                              $this->_item,
+                                              '007',
+                                              '2020-05-19',
+                                              '2020-08-25');
+
+    $this->assertEquals(['statut' => false, 'erreur' => 'Durée maximale autorisée: 60 jours'],
+                        $ret);
+  }
+}
+
+
+
+
+class KohaGetEmprunteurJamesBondWithGroupedHoldsTest extends KohaTestCase {
+  public function setUp() {
+    parent::setUp();
+
+    $this->service = Class_WebService_SIGB_Koha::getService(['url_serveur' => 'http://cat-aficg55.biblibre.com/cgi-bin/koha/ilsdi.pl',
+                                                             'grouped_holds_itypes' => "PRET_MALLE\nPRET_GLACIERE\nPRET_VALISE"]);
+    $this->service->setWebClient($this->mock_web_client);
+
+    $this->mock_web_client
+      ->whenCalled('postData')
+      ->with('http://cat-aficg55.biblibre.com/cgi-bin/koha/ilsdi.pl',
+             ['service' => 'AuthenticatePatron',
+              'username' => 'james.bond',
+              'password' => 'agent007'])
+      ->answers(KohaFixtures::xmlLookupPatronJamesBond())
+
+      ->whenCalled('open_url')
+      ->with('http://cat-aficg55.biblibre.com/cgi-bin/koha/ilsdi.pl?service=GetPatronInfo&patron_id=007&show_contact=1&show_loans=1&show_holds=1')
+      ->answers(KohaFixtures::xmlGetPatronInfoJamesBond());
+
+    $this->jamesbond = $this->service->getEmprunteur(Class_Users::newInstance()
+                                                     ->setLogin('james.bond')
+                                                     ->setPassword('agent007')
+                                                     ->setIdabon('007'));
+  }
+
+
+  /** @test */
+  public function holdsCountShouldBeFour() {
+    $this->assertEquals(4,$this->jamesbond->getNbReservations());
+  }
+
+
+  /** @test */
+  public function holdOnElementaireMonCherPolarStatusShouldBeEnAttenteDeValidationDuAu() {
+    $this->assertEquals('En attente de validation. Du 26 juillet 2019 au 31 décembre 2019',
+                        $this->jamesbond->getReservationAt(0)->getEtat());
+  }
+
+
+  /** @test */
+  public function holdIlFaitChaudStatusShouldBeEnAttenteDu12Octobre2019Au15Janvier2020() {
+    $this->assertEquals('En attente. Du 12 octobre 2019 au 15 janvier 2020',
+                        $this->jamesbond->getReservationAt(1)->getEtat());
+  }
+
+
+  /** @test */
+  public function holdIlViveLesGlacesStatusShouldBeExemplaireMisDeCote() {
+    $this->assertEquals('Exemplaire mis de côté',
+                        $this->jamesbond->getReservationAt(2)->getEtat());
+  }
+
+
+  /** @test */
+  public function holdLeLivreDuMomentStatusShouldBeEnAttente() {
+    $this->assertEquals('En attente',
+                        $this->jamesbond->getReservationAt(3)->getEtat());
+  }
+}
+
+
+
+
 class KohaOperationsTest extends KohaTestCase {
   public function setUp() {
     parent::setUp();
diff --git a/tests/library/Class/WebService/SIGB/OrpheeFixtures.php b/tests/library/Class/WebService/SIGB/OrpheeFixtures.php
index abaffd96125425ddd25d1f19588ce3c3735f9fee..3cadcc7ff088fd0434dd2bdfd995f34a652a15d8 100644
--- a/tests/library/Class/WebService/SIGB/OrpheeFixtures.php
+++ b/tests/library/Class/WebService/SIGB/OrpheeFixtures.php
@@ -623,6 +623,7 @@ renouvellement -->
       <cb></cb>
       <no_ntc><![CDATA[975467778]]></no_ntc>
       <no_dmt><![CDATA[790]]></no_dmt>
+      <date_fin><![CDATA[13/04/2019]]></date_fin>
       <sup><![CDATA[Livre]]></sup>
       <tit><![CDATA[Le singe bleu]]></tit>
       <auteur><![CDATA[Steinbeck, John (1902-1968)]]></auteur>
@@ -630,6 +631,17 @@ renouvellement -->
       <lib_sit><![CDATA[affectée]]></lib_sit>
       <sit><![CDATA[2]]></sit>
     </document>
+    <document>
+      <cb></cb>
+      <no_ntc><![CDATA[975467779]]></no_ntc>
+      <no_dmt><![CDATA[791]]></no_dmt>
+      <sup><![CDATA[Livre]]></sup>
+      <tit><![CDATA[A l\'Est d\'Eden]]></tit>
+      <auteur><![CDATA[Steinbeck, John (1902-1968)]]></auteur>
+      <editeur><![CDATA[Gallimard]]></editeur>
+      <lib_sit><![CDATA[affectée]]></lib_sit>
+      <sit><![CDATA[2]]></sit>
+    </document>
   </documents>
 </datas> ';
   }
@@ -706,6 +718,18 @@ renouvellement -->
       <auteur><![CDATA[Steinbeck, John (1902-1968)]]></auteur>
       <editeur><![CDATA[Gallimard]]></editeur>
       <lib_sit><![CDATA[affectée]]></lib_sit>
+      <date_fin><![CDATA[13/04/2019]]></date_fin>
+      <sit><![CDATA[2]]></sit>
+    </document>
+    <document>
+      <cb></cb>
+      <no_ntc><![CDATA[975467779]]></no_ntc>
+      <no_dmt><![CDATA[791]]></no_dmt>
+      <sup><![CDATA[Livre]]></sup>
+      <tit><![CDATA[A l\'Est d\'Eden]]></tit>
+      <auteur><![CDATA[Steinbeck, John (1902-1968)]]></auteur>
+      <editeur><![CDATA[Gallimard]]></editeur>
+      <lib_sit><![CDATA[affectée]]></lib_sit>
       <sit><![CDATA[2]]></sit>
     </document>
   </documents>
diff --git a/tests/library/Class/WebService/SIGB/OrpheeServiceTest.php b/tests/library/Class/WebService/SIGB/OrpheeServiceTest.php
index 253eafa78867fa72f3d6bcbb4a8818d307b82971..abe98d5289229a07ccfbd152b838cc5fe9478cff 100644
--- a/tests/library/Class/WebService/SIGB/OrpheeServiceTest.php
+++ b/tests/library/Class/WebService/SIGB/OrpheeServiceTest.php
@@ -24,6 +24,7 @@ include_once('Class/WebService/SIGB/Orphee/SessionStrategy.php');
 
 include_once('OrpheeFixtures.php');
 
+
 class Class_WebService_SIGB_Orphee_ServiceForTesting extends Class_WebService_SIGB_Orphee_Service {
   public function __construct($search_client) {
     $this->_provided_search_client = $search_client;
@@ -43,9 +44,7 @@ class Class_WebService_SIGB_Orphee_ServiceForTesting extends Class_WebService_SI
 
 
 class OrpheeServiceGetServiceTest extends ModelTestCase {
-  protected
-    $_storm_default_to_volatile = true,
-    $_orphee;
+  protected $_search_client;
 
   public function setUp() {
     parent::setUp();
@@ -55,23 +54,51 @@ class OrpheeServiceGetServiceTest extends ModelTestCase {
       ->whenCalled('GetId')->answers(GetIdResponse::withIdResult('1234'))
       ->whenCalled('__setCookie')->answers(true)
       ->whenCalled('EndSession')->answers(true);
-
-    $this->_orphee = Class_WebService_SIGB_Orphee_Service::getService('tests/fixtures/orphee.wsdl');
-    $this->_orphee->setSearchClient($this->_search_client);
-    $this->_orphee->isConnected();
   }
 
 
   /** @test */
   public function shouldCallEndSessionOnServiceDestruction() {
-    unset($this->_orphee);
+    $orphee = Class_WebService_SIGB_Orphee_Service::getService('tests/fixtures/orphee.wsdl');
+    $orphee->setSearchClient($this->_search_client);
+    $orphee->isConnected();
+
+    unset($orphee);
     gc_collect_cycles();
     $this->assertTrue($this->_search_client->methodHasBeenCalled('EndSession'));
   }
+
+
+  /** @test */
+  public function serviceHoldModeShouldDefaultToHoldTitle() {
+    $orphee = Class_WebService_SIGB_Orphee::getService(['url_serveur' => 'tests/fixtures/orphee.wsdl',
+                                                        'allow_hold_available_items' => 0]);
+    $orphee->setSearchClient($this->_search_client);
+
+    $this->assertFalse($orphee->isHoldModeItem());
+  }
+
+
+  /** @test */
+  public function serviceWithParamHoldModeOneShouldActivateHoldModeItem() {
+    $orphee = Class_WebService_SIGB_Orphee::getService(['url_serveur' => 'tests/fixtures/orphee.wsdl',
+                                                        'allow_hold_available_items' => 0,
+                                                        'hold_mode' => '1']);
+    $orphee->setSearchClient($this->_search_client);
+
+    $this->assertTrue($orphee->isHoldModeItem());
+  }
+
+
+  public function tearDown() {
+    Class_WebService_SIGB_Orphee::reset();
+    parent::tearDown();
+  }
 }
 
 
 
+
 abstract class OrpheeServiceTestCase extends ModelTestCase {
   protected
     $_search_client,
@@ -294,6 +321,7 @@ class OrpheeServiceTestAutoConnectError extends OrpheeServiceTestCase {
   protected $_search_client;
   protected $_orphee;
 
+
   public function _beforeOrpheeServiceCreate(){
     $this->_search_client
       ->whenCalled('GetId')
@@ -921,8 +949,8 @@ class OrpheeServiceGetInfoUserCarteHenryDupontTest extends OrpheeServiceTestCase
 
 
   /** @test */
-  public function nbOfHoldsWaitingToBePulledShouldBeOne() {
-    $this->assertCount(1, $this->emprunteur->getHoldsWaitingToBePulled());
+  public function nbOfHoldsWaitingToBePulledShouldBeTwo() {
+    $this->assertCount(2, $this->emprunteur->getHoldsWaitingToBePulled());
   }
 
 
@@ -951,8 +979,8 @@ class OrpheeServiceGetInfoUserCarteHenryDupontTest extends OrpheeServiceTestCase
 
 
   /** @test */
-  public function getNbReservationsShouldAnswerFive() {
-    $this->assertEquals(5, $this->emprunteur->getNbReservations());
+  public function getNbReservationsShouldAnswerSix() {
+    $this->assertEquals(6, $this->emprunteur->getNbReservations());
   }
 
 
@@ -1175,8 +1203,20 @@ class OrpheeServiceGetInfoUserCarteHenryDupontTest extends OrpheeServiceTestCase
 
 
   /** @test */
-  public function fifthHoldGetEtatShouldReturnAffectee() {
-    $this->assertEquals('affectée', $this->emprunteur->getReservations()[4]->getEtat());
+  public function fifthHoldGetEtatShouldReturnAffecteeUntil13_04_2019() {
+    $this->assertEquals('affectée jusqu\'au 13/04/2019', $this->_reservations[4]->getEtat());
+  }
+
+
+  /** @test */
+  public function fifthHoldTitleShouldBeLeSingeBleu() {
+    $this->assertEquals('Le singe bleu', $this->_reservations[4]->getTitre());
+  }
+
+
+  /** @test */
+  public function sixthHoldGetEtatShouldReturnAffectee() {
+    $this->assertEquals('affectée', $this->_reservations[5]->getEtat());
   }
 
 
@@ -1349,6 +1389,7 @@ class OrpheeServiceGetInfoUserCarteHenryDupontVersion092015Test extends OrpheeSe
 
 
 
+
 class OrpheeServiceReservationTest extends OrpheeServiceTestCase {
   public function setUp() {
     parent::setUp();
@@ -1361,7 +1402,7 @@ class OrpheeServiceReservationTest extends OrpheeServiceTestCase {
 
 
   /** @test */
-  public function testReservationSuccessful() {
+  public function withHoldModeTitleShouldCallRsvNtcAdh() {
     $this->_search_client
       ->whenCalled('RsvNtcAdh')
       ->with(RsvNtcAdh::withNoticeUserNo('1301700727', 100753, 0))
@@ -1381,6 +1422,35 @@ class OrpheeServiceReservationTest extends OrpheeServiceTestCase {
   }
 
 
+  /** @test */
+  public function withHoldModeItemShouldCallRsvDmtAdh() {
+    $this->_search_client
+      ->whenCalled('GetLstDmt')
+      ->with(GetLstDmt::withNtcAndFas('0030008850', 0))
+      ->answers(GetLstDmtResponse::withResult(OrpheeFixtures::xmlGetLstDmtLivreEspagnol()))
+
+      ->whenCalled('RsvDmtAdh')
+      ->with(RsvDmtAdh::withNoticeItemUser(30008850, 30007086, 100753))
+      ->answers(RsvDmtAdhResponse::withResult('<datas><msg><code><![CDATA[1]]></code><libelle><![CDATA[Réservation mise en attente]]></libelle></msg></datas>'));
+
+
+    $result = $this->_orphee
+      ->beHoldModeItem()
+      ->reserverExemplaire($this->_henry_dupont,
+                           $this->fixture('Class_Exemplaire',
+                                          ['id' => 234,
+                                           'id_origine' => '30008850',
+                                           'code_barres' => 'Ancien-07086',
+                                           'int_bib' => $this->_henry_dupont->getIntBib(),
+                                           'notice' => $this->fixture('Class_Notice',
+                                                                      ['id' => 988,
+                                                                       'tome_alpha' => 2,
+                                                                       'type_doc' => Class_TypeDoc::LIVRE])]),
+                           '');
+    $this->assertEquals(['statut' => true, 'erreur' => ''], $result);
+  }
+
+
 
   /** @test */
   public function testReservationPeriodiqueSuccessful() {
diff --git a/tests/library/ZendAfi/MailTest.php b/tests/library/ZendAfi/MailTest.php
index c40c6d923758855dd2c63e57eb3d2970b3a05250..55ad4dd4a22743040b07ae234359c35b76ff568b 100644
--- a/tests/library/ZendAfi/MailTest.php
+++ b/tests/library/ZendAfi/MailTest.php
@@ -50,4 +50,23 @@ class ZendAfi_MailTest extends ModelTestCase {
 
     $this->assertNotEmpty($mail->getRecipients());
   }
+
+
+  /** @test */
+  public function sujectWithUnicodeCharsShouldBeEncoded() {
+    $mock_transport = new MockMailTransport();
+    Zend_Mail::setDefaultTransport($mock_transport);
+
+    $mail = (new ZendAfi_Mail())
+      ->setFrom('tester@bokeh.afibre.fr')
+      ->addTo('patron@here.fr')
+      ->setSubject('Réseau des médiathèques Médi@Val : l\'actu de la rentrée')
+      ->setBodyHtml('<h1><a href="https://www.reseaumediaval.fr/index/index/id_profil/1" target="_top">&agrave; la m&eacute;diath&egrave;que de Marcy l&#39;Etoile</a></h1>');
+    $mail->send();
+
+    $sent_mail = $mock_transport->getSentMails()[0];
+    $this->assertEquals('=?utf8?Q?R=C3=A9seau=20des=20m=C3=A9diath=C3=A8ques=20M=C3=A9di@Val=20?=
+ =?utf8?Q?:=20l\'actu=20de=20la=20rentr=C3=A9e?=', $sent_mail->getSubject());
+
+  }
 }
\ No newline at end of file
diff --git a/tests/library/ZendAfi/View/Helper/Abonne/AbonnementTest.php b/tests/library/ZendAfi/View/Helper/Abonne/AbonnementTest.php
index 2bb4de77a7f48c21ef8f79f61fb5b28c4b48f0e9..991d0de2fc2b2b413fdd7dbd8082e44d49288bbd 100644
--- a/tests/library/ZendAfi/View/Helper/Abonne/AbonnementTest.php
+++ b/tests/library/ZendAfi/View/Helper/Abonne/AbonnementTest.php
@@ -18,6 +18,7 @@
  * along with BOKEH; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
+
 require_once 'library/ZendAfi/View/Helper/ViewHelperTestCase.php';
 
 class View_Helper_Abonne_AbonnementTest extends ViewHelperTestCase {
@@ -28,8 +29,17 @@ class View_Helper_Abonne_AbonnementTest extends ViewHelperTestCase {
 
   public function setUp() {
     parent::setUp();
+
+    Class_User_ILSSubscription::setTimeSource(new TimeSourceForTest('2018-10-03'));
+
     $this->_helper = new ZendAfi_View_Helper_Abonne_Abonnement();
-    $this->_helper->setView(new ZendAfi_Controller_Action_Helper_View());
+    $this->_helper->setView($this->view);
+  }
+
+
+  public function tearDown() {
+    Class_User_ILSSubscription::setTimeSource(null);
+    parent::tearDown();
   }
 
 
@@ -49,4 +59,3 @@ class View_Helper_Abonne_AbonnementTest extends ViewHelperTestCase {
                                       'Votre abonnement est valide');
   }
 }
-?>
\ No newline at end of file
diff --git a/tests/library/ZendAfi/View/Helper/Accueil/CritiquesTest.php b/tests/library/ZendAfi/View/Helper/Accueil/CritiquesTest.php
index 431f986365bd281025e58d9e79711337667f1702..d88f4113ceff7729c4f7b52949c8bceb03b7f97a 100644
--- a/tests/library/ZendAfi/View/Helper/Accueil/CritiquesTest.php
+++ b/tests/library/ZendAfi/View/Helper/Accueil/CritiquesTest.php
@@ -95,6 +95,7 @@ abstract class CritiquesAvisTestCase extends ViewHelperTestCase {
                                       'note' => 5,
                                       'date_avis' => '2010-03-18 13:00:00',
                                       'user' => $lolo,
+                                      'source_author' => null,
                                       'statut' => 1,
                                       'notices' => [$millenium]]);
 
@@ -106,6 +107,7 @@ abstract class CritiquesAvisTestCase extends ViewHelperTestCase {
                                                  'date_avis' => '2010-03-18 13:00:00',
                                                  'user' => $super_lolo,
                                                  'statut' => 1,
+                                                 'source_author' => null,
                                                  'notices' => [$millenium]]);
 
     $avis_millenium_from_suplo_with_html = $this->fixture('Class_AvisNotice',
@@ -116,6 +118,7 @@ abstract class CritiquesAvisTestCase extends ViewHelperTestCase {
                                                            'date_avis' => '2010-03-18 13:00:00',
                                                            'user' => $super_lolo,
                                                            'statut' => 1,
+                                                           'source_author' => null,
                                                            'notices' => [$millenium]]);
 
     $avis_orphan = $this->fixture('Class_AvisNotice',
@@ -126,6 +129,7 @@ abstract class CritiquesAvisTestCase extends ViewHelperTestCase {
                                    'date_avis' => '2010-03-18 13:00:00',
                                    'user' => $lolo,
                                    'abon_ou_bib' => 0,
+                                   'source_author' => null,
                                    'statut' => 1,
                                    'notices' => []]);
 
@@ -142,6 +146,7 @@ abstract class CritiquesAvisTestCase extends ViewHelperTestCase {
                                    'date_avis' => '2010-03-18 13:00:00',
                                    'user' => $lolo,
                                    'abon_ou_bib' => 0,
+                                   'source_author' => null,
                                    'statut' => 1,
                                    'notices' => [$potter]]);
 
diff --git a/tests/library/ZendAfi/View/Helper/AvisTest.php b/tests/library/ZendAfi/View/Helper/AvisTest.php
index be52f9d785600e08bdd51c433be0152258407dc8..fec5ed23b50b81a1755cde60d32797e6bee8e5a7 100644
--- a/tests/library/ZendAfi/View/Helper/AvisTest.php
+++ b/tests/library/ZendAfi/View/Helper/AvisTest.php
@@ -57,6 +57,7 @@ class ViewHelperAvisTestWithAvisNotice extends ViewHelperTestCase {
       ->setUser($lolo)
       ->setAbonOuBib(0)
       ->setStatut(1)
+      ->setSourceAuthor(null)
       ->setNotices(array($millenium,
                          $millenium_with_vignette));
 
@@ -199,6 +200,7 @@ class ViewHelperAvisTestAsBib extends ViewHelperTestCase {
       ->setUser($tintin)
       ->setStatut(0)
       ->setAbonOuBib(1)
+      ->setSourceAuthor(null)
       ->setNotices(array());
 
     $helper = new ZendAfi_View_Helper_Avis();
@@ -209,9 +211,10 @@ class ViewHelperAvisTestAsBib extends ViewHelperTestCase {
   public function testBrInAvis() {
     $this->assertTrue(strpos($this->html, "Pas<br />\nterrible") !== false, $this->html);
   }
+}
+
 
 
-}
 
 class ViewHelperAvisTestWithoutAvisNoticeAndModeration extends ViewHelperTestCase {
   public function setUp() {
@@ -234,6 +237,7 @@ class ViewHelperAvisTestWithoutAvisNoticeAndModeration extends ViewHelperTestCas
       ->setUser($tintin)
       ->setStatut(0)
       ->setAbonOuBib(0)
+      ->setSourceAuthor(null)
       ->setNotices(array());
 
 
@@ -339,6 +343,7 @@ class ViewHelperAvisTestHtmlForCritiquesModule extends ViewHelperTestCase {
                                       'user' => $tintin,
                                       'abon_ou_bib' => 0,
                                       'statut' => 0,
+                                      'source_author' => null,
                                       'notices' => [$millenium]]);
 
     $helper = new ZendAfi_View_Helper_Avis();
@@ -384,6 +389,7 @@ class ViewHelperAvisAmazonTestContenuAvisHtml extends ViewHelperTestCase {
       ->setDateAvis('2010-03-18 13:00:00')
       ->setAbonOuBib(0)
       ->setStatut(0)
+      ->setSourceAuthor(null)
       ->setUser(null);
 
     $helper = new ZendAfi_View_Helper_Avis();
diff --git a/tests/library/ZendAfi/View/Helper/Notice/AvisTest.php b/tests/library/ZendAfi/View/Helper/Notice/AvisTest.php
index c6db98c423c3653c2967745199266430c8778de9..ca030a2c7c3851a2f34b2aac07b7925de7009316 100644
--- a/tests/library/ZendAfi/View/Helper/Notice/AvisTest.php
+++ b/tests/library/ZendAfi/View/Helper/Notice/AvisTest.php
@@ -21,80 +21,62 @@
 
 
 class ZendAfi_View_Helper_Notice_AvisTest extends ViewHelperTestCase {
-  protected $_html;
+  protected
+    $_storm_default_to_volatile = true,
+    $_millenium,
+    $_avis;
 
   public function setUp() {
     parent::setUp();
-    $this->_helper = new ZendAfi_View_Helper_Notice_Avis();
-    $this->_helper->setView(new ZendAfi_Controller_Action_Helper_View());
-
-    $this->_view = new ZendAfi_Controller_Action_Helper_View();
-
-    Class_Profil::setCurrentProfil(new Class_Profil());
-    Zend_Registry::get('locale')->setLocale('fr');
-    Zend_Registry::get('translate')->setLocale('fr');
 
-    $this->millenium = new Class_Notice();
-    $this->millenium
-      ->setId(25);
+    $this->_helper = new ZendAfi_View_Helper_Notice_Avis();
+    $this->_helper->setView($this->view);
 
-    $this->avis = array("bib" => array("nombre" => 0),
-                        "abonne" => array("nombre" => 0));
+    $this->_millenium = $this->fixture('Class_Notice', ['id' => 25]);
 
-    $this->avis_bib_seulement = Class_AdminVar::newInstanceWithId('AVIS_BIB_SEULEMENT');
+    $this->_avis = ["bib" => new Class_Notice_ReviewsSet('Bibliothécaires', [], 0, 0),
+                    "abonne" => new Class_Notice_ReviewsSet('Lecteurs du portail', [], 0, 0)];
 
-    $account = new stdClass();
-    $account->username     = 'AutoTest' . time();
-    $account->password     = md5( 'password' );
-    $account->ID_USER      = 0;
-    $account->ROLE_LEVEL   = 4;
-    $account->confirmed    = true;
-    $account->enabled      = true;
-    ZendAfi_Auth::getInstance()->getStorage()->write($account);
+    $this->login(ZendAfi_Acl_AdminControllerRoles::ADMIN_BIB);
   }
 
 
-  public function testVisibleWithAdminAndAvisReaderAllowed() {
-    ZendAfi_Auth::getInstance()->getIdentity()->ROLE_LEVEL = 5;
-    $this->avis_bib_seulement->setValeur('0');
+  /** @test */
+  public function modoPortailWithAvisReaderAllowedShouldBeAbleToAddReview() {
+    Class_Users::getIdentity()->beModoPortail();
+    Class_AdminVar::set('AVIS_BIB_SEULEMENT', '0');
 
-    $html = $this->_helper->Notice_Avis($this->millenium, $this->avis, ['onglet' => 'bloc']);
-    $this->assertTrue(strpos($html, 'Donnez ou modifiez votre avis') != false, $html);
+    $html = $this->_helper->notice_Avis($this->_millenium, $this->_avis, ['onglet' => 'bloc']);
+    $this->assertContains('Donnez ou modifiez votre avis', $html);
   }
 
 
-  public function testVisibleWithReaderAndAvisReaderAllowed() {
-    ZendAfi_Auth::getInstance()->getIdentity()->ROLE_LEVEL = 1;
-    $this->avis_bib_seulement->setValeur('0');
+  /** @test */
+  public function inviteWithAvisReaderAllowedShouldBeAbleToAddReview() {
+    Class_Users::getIdentity()->beInvite();
+    Class_AdminVar::set('AVIS_BIB_SEULEMENT', '0');
 
-    $html = $this->_helper->Notice_Avis($this->millenium, $this->avis, ['onglet' => 'bloc']);
-    $this->assertTrue(strpos($html, 'Donnez ou modifiez votre avis') != false);
+    $html = $this->_helper->notice_Avis($this->_millenium, $this->_avis, ['onglet' => 'bloc']);
+    $this->assertContains('Donnez ou modifiez votre avis', $html);
   }
 
 
-  public function testVisibleWithAdminAndAvisReaderForbidden() {
-    ZendAfi_Auth::getInstance()->getIdentity()->ROLE_LEVEL = 5;
-    $this->avis_bib_seulement->setValeur('1');
+  /** @test */
+  public function modoPortailAndAvisReaderForbiddenShouldBeAbleToAddReview() {
+    Class_Users::getIdentity()->beModoPortail();
+    Class_AdminVar::set('AVIS_BIB_SEULEMENT', '1');
 
-    $html = $this->_helper->Notice_Avis($this->millenium, $this->avis, ['onglet' => 'bloc']);
-    $this->assertTrue(strpos($html, 'Donnez ou modifiez votre avis') != false);
+    $html = $this->_helper->Notice_Avis($this->_millenium, $this->_avis, ['onglet' => 'bloc']);
+    $this->assertContains('Donnez ou modifiez votre avis', $html);
   }
 
 
+  /** @test */
   public function testInvisibleWithReaderAndAvisReaderForbidden() {
-    $user = $this->fixture('Class_Users',
-                           ['id' => 1,
-                            'login' => 'pom',
-                            'password' => '123',
-                           ]);
-    $user->setRoleLevel(ZendAfi_Acl_AdminControllerRoles::INVITE)->save();
-
-    ZendAfi_Auth::getInstance()->logUser($user);
-
-    $this->avis_bib_seulement->setValeur('1');
+    Class_Users::getIdentity()->beInvite();
+    Class_AdminVar::set('AVIS_BIB_SEULEMENT', '1');
 
-    $html = $this->_helper->Notice_Avis($this->millenium, $this->avis, ['onglet' => 'bloc']);
-    $this->assertFalse(strpos($html, 'Donnez ou modifiez votre avis'));
+    $html = $this->_helper->Notice_Avis($this->_millenium, $this->_avis, ['onglet' => 'bloc']);
+    $this->assertNotContains('Donnez ou modifiez votre avis', $html);
   }
 }
-?>
\ No newline at end of file
diff --git a/tests/library/ZendAfi/View/Helper/TagRechercheSimpleTest.php b/tests/library/ZendAfi/View/Helper/TagRechercheSimpleTest.php
index c5292c70bd17ad3e2c069c11c4099d0c243dd9cd..07055a6c466b740010455ee040b1823d86e475bd 100644
--- a/tests/library/ZendAfi/View/Helper/TagRechercheSimpleTest.php
+++ b/tests/library/ZendAfi/View/Helper/TagRechercheSimpleTest.php
@@ -32,9 +32,8 @@ abstract class ZendAfi_View_Helper_TagRechercheSimpleTestCase extends ViewHelper
          ->whenCalled('saveThesaurus')
          ->answers(null);
 
-    $view = new ZendAfi_Controller_Action_Helper_View();
     $this->_helper = new ZendAfi_View_Helper_TagRechercheSimple();
-    $this->_helper->setView($view);
+    $this->_helper->setView($this->view);
 
     $this->_preferences = (new Class_Systeme_ModulesAccueil_RechercheSimple())->getDefaultValues();
   }
@@ -358,7 +357,8 @@ class ZendAfi_View_Helper_TagRechercheSimpleAdvancedSearchTest
 
 
 
-class ZendAfi_View_Helper_TagRechercheSimpleDocTypeTest extends ZendAfi_View_Helper_TagRechercheSimpleTestCase {
+class ZendAfi_View_Helper_TagRechercheSimpleDocTypeTest
+  extends ZendAfi_View_Helper_TagRechercheSimpleTestCase {
 
 
   public function setUp() {
@@ -380,23 +380,18 @@ class ZendAfi_View_Helper_TagRechercheSimpleDocTypeTest extends ZendAfi_View_Hel
          ->whenCalled('getTable')
          ->answers($table);
 
-    $this->preferences['select_doc'] = '1';
-    $this->preferences['tri'] = '*';
-    $this->preferences['message'] = '';
-    $this->preferences['placeholder'] = '';
-    $this->preferences['largeur'] = '';
-    $this->preferences['exemple'] = '';
-    $this->preferences['select_annexe'] = '';
-    $this->preferences['domain_ids'] = '';
-    $this->preferences['select_bib'] = '';
-    $this->preferences['recherche_avancee'] = '';
+    $this->_preferences['select_doc'] = '1';
+    $this->_preferences['largeur'] = '';
+    $this->_preferences['select_annexe'] = '';
+    $this->_preferences['select_bib'] = '';
+    $this->_preferences['recherche_avancee'] = '';
   }
 
 
   /** @test */
   public function tousShouldBeSelected() {
-    $this->preferences['type_doc'] = '';
-    $this->_html = $this->_helper->tagRechercheSimple($this->preferences, 1);
+    $this->_preferences['type_doc'] = '';
+    $this->_html = $this->_helper->tagRechercheSimple($this->_preferences, 1);
     $this->assertXPathContentContains($this->_html, '//option[@value=""][@selected]', 'tous');
     $this->assertXPathContentContains($this->_html, '//option[@value="0"][not(@selected)]', 'Non identifi');
   }
@@ -404,16 +399,16 @@ class ZendAfi_View_Helper_TagRechercheSimpleDocTypeTest extends ZendAfi_View_Hel
 
   /** @test */
   public function livresShouldBeSelected() {
-    $this->preferences['type_doc'] = '1';
-    $this->_html = $this->_helper->tagRechercheSimple($this->preferences, 1);
+    $this->_preferences['type_doc'] = '1';
+    $this->_html = $this->_helper->tagRechercheSimple($this->_preferences, 1);
     $this->assertXPathContentContains($this->_html, '//option[@value="1"][@selected]', 'Livres');
   }
 
 
   /** @test */
   public function nonIdentifieShouldBeSelected() {
-    $this->preferences['type_doc'] = '0';
-    $this->_html = $this->_helper->tagRechercheSimple($this->preferences, 1);
+    $this->_preferences['type_doc'] = '0';
+    $this->_html = $this->_helper->tagRechercheSimple($this->_preferences, 1);
     $this->assertXPathContentContains($this->_html, '//option[@value="0"][@selected]', 'Non identifi');
     $this->assertXPathContentContains($this->_html, '//option[@value=""][not(@selected)]', 'tous');
   }
diff --git a/tests/scenarios/Activitypub/ActivitypubAdminTest.php b/tests/scenarios/Activitypub/ActivitypubAdminTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..0b267c6967f56512926623e13437a717da8cc672
--- /dev/null
+++ b/tests/scenarios/Activitypub/ActivitypubAdminTest.php
@@ -0,0 +1,383 @@
+<?php
+/**
+ * Copyright (c) 2012-2017, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+require_once __DIR__ . '/../../../library/activitystreams/autoload.php';
+
+use Patbator\ActivityStreams\Model\Service;
+use Patbator\ActivityStreams\Stream;
+
+
+
+class ActivitypubAdminMenuDisabledTest extends Admin_AbstractControllerTestCase {
+  protected $_storm_default_to_volatile = true;
+
+  public function setUp() {
+    parent::setUp();
+    Class_AdminVar::set('FEDERATION_COMMUNITY_SERVER', '');
+
+    $this->dispatch('/admin', true);
+  }
+
+
+  /** @test */
+  public function federationMenuShouldNotBePresent() {
+    $this->assertNotXPathContentContains('//td', 'Fédération');
+  }
+
+
+  /** @test */
+  public function reviewsLinkShouldNotBePresent() {
+    $this->assertNotXPathContentContains('//a[contains(@href, "/federation-reviews")]', 'Avis');
+  }
+}
+
+
+
+
+abstract class ActivitypubAdminEnabledTestCase extends Admin_AbstractControllerTestCase {
+  protected $_storm_default_to_volatile = true;
+
+  public function setUp() {
+    parent::setUp();
+    Class_AdminVar::set('FEDERATION_COMMUNITY_SERVER',
+                        'https://reviews.my-server.io/activitypub/reviews');
+  }
+}
+
+
+
+
+class ActivitypubAdminMenuEnabledTest extends ActivitypubAdminEnabledTestCase {
+  public function setUp() {
+    parent::setUp();
+    $this->dispatch('/admin', true);
+  }
+
+
+  /** @test */
+  public function federationMenuShouldBePresent() {
+    $this->assertXPathContentContains('//td', 'Fédération');
+  }
+
+
+  /** @test */
+  public function reviewsLinkShouldBePresent() {
+    $this->assertXPathContentContains('//a[contains(@href, "/federation-reviews")]', 'Avis');
+  }
+
+
+  /** @test */
+  public function journalMenuShouldBePresent() {
+    $this->assertXPathContentContains('//td', 'Journal');
+  }
+}
+
+
+
+
+class ActivitypubAdminFederationReviewsControllerIndexTest extends ActivitypubAdminEnabledTestCase {
+  public function setUp() {
+    parent::setUp();
+    $this->dispatch('/admin/federation-reviews');
+  }
+
+
+  /** @test */
+  public function pageShouldContainsEnableDisplay() {
+    $this->assertXPath('//button[contains(@data-url, "/federation-reviews/enable-display")]');
+  }
+
+
+  /** @test */
+  public function pageShouldContainsEnableShare() {
+    $this->assertXPath('//button[contains(@data-url, "/federation-reviews/enable-share")]');
+  }
+}
+
+
+
+
+class ActivitypubAdminFederationReviewsControllerEnableDisplayWithoutCommunityServerTest
+  extends Admin_AbstractControllerTestCase {
+  protected $_storm_default_to_volatile = true;
+
+
+  public function setUp() {
+    parent::setUp();
+    $this->dispatch('/admin/federation-reviews/enable-display');
+  }
+
+
+  /** @test */
+  public function shouldRedirect() {
+    $this->assertRedirect();
+  }
+
+
+  /** @test */
+  public function reviewDisplayShouldNotBeEnabled() {
+    $this->assertFalse(Class_FederationReview::getInstance()->isDisplayEnabled());
+  }
+}
+
+
+
+abstract class ActivitypubAdminFederationReviewsControllerWithCommunityServerTestCase
+  extends ActivitypubAdminEnabledTestCase {
+
+  protected $_signer;
+
+
+  public function setUp() {
+    parent::setUp();
+
+    $endpoint = Class_AdminVar::get('FEDERATION_COMMUNITY_SERVER');
+    Class_WebService_ActivityPub::setThrowErrors(true);
+
+    $this->_signer = $this
+      ->mock()
+      ->whenCalled('signRequest')->answers('MySignature')
+      ->whenCalled('getKeyId')->answers($endpoint . '/pubkey')
+
+      ->whenCalled('verify')
+      ->with(['(request-target)' => 'post /activitypub/reviews/inbox'], 'TheirKey')
+      ->answers(true);
+
+    Class_WebService_ActivityPub::setSigner($this->_signer);
+
+    $client = $this->mock()
+                   ->whenCalled('open_url')
+                   ->with($endpoint,
+                          ['headers' => ['Accept' => Class_WebService_ActivityPub::MIME_TYPE]])
+                   ->answers(json_encode(['@context' => 'https://www.w3.org/ns/activitystreams',
+                                          'type' => 'Service',
+                                          'id' => $endpoint,
+                                          'inbox' => $endpoint . '/inbox',
+                                          'publicKey' => $endpoint . '/pubkey']))
+
+                   ->whenCalled('open_url')
+                   ->with($endpoint . '/pubkey',
+                          ['headers' => ['Accept' => Class_WebService_ActivityPub::MIME_TYPE]])
+                   ->answers(json_encode(['@context' => 'https://www.w3.org/ns/activitystreams',
+                                          'type' => 'Key',
+                                          'id' => $endpoint . '/pubkey',
+                                          'owner' => $endpoint,
+                                          'publicKeyPem' => 'TheirKey']))
+
+                   ->whenCalled('postRawDataResponse')
+                   ->answers($this->mock()
+                             ->whenCalled('isError')->answers(false)
+                             ->whenCalled('getHeader')->with('Signature')->answers('TheirSignature')
+                             ->whenCalled('getHeaders')->answers([])
+                             ->whenCalled('getBody')
+                             ->answers(json_encode(['@context' => 'https://www.w3.org/ns/activitystreams',
+                                                                  'type' => 'Accept'])))
+      ;
+
+    Class_WebService_ActivityPub::setWebClient($client);
+  }
+
+
+  public function tearDown() {
+    Class_WebService_ActivityPub::setThrowErrors(false);
+    Class_WebService_ActivityPub::setSigner(null);
+    parent::tearDown();
+  }
+}
+
+
+
+class ActivitypubAdminFederationReviewsControllerEnableDisplayTest
+  extends ActivitypubAdminFederationReviewsControllerWithCommunityServerTestCase {
+
+  protected $_json;
+
+  public function setUp() {
+    parent::setUp();
+    Class_AdminVar::set('FEDERATION_ACTOR_NAME', 'Arcadia');
+
+    $this->dispatch('/admin/federation-reviews/enable-display');
+    $params = Class_WebService_ActivityPub::getWebClient()
+      ->getAttributesForLastCallOn('postRawDataResponse');
+
+    $this->_json = json_decode($params[1], true);
+  }
+
+
+  /** @test */
+  public function sentNameShouldBeArcadia() {
+    $this->assertEquals('Arcadia', $this->_json['actor']['name']);
+  }
+
+
+  /** @test */
+  public function shouldRedirect() {
+    $this->assertRedirectTo('/admin/federation-reviews/index');
+  }
+
+
+  /** @test */
+  public function reviewDisplayShouldBeEnabled() {
+    $this->assertTrue(Class_FederationReview::getInstance()->isDisplayEnabled());
+  }
+
+
+  /** @test */
+  public function federationPubkeyShouldNotBeEmpty() {
+    $pubkey = Class_AdminVar::get('FEDERATION_PUBKEY');
+    $this->assertContains('-----BEGIN PUBLIC KEY-----', $pubkey);
+    $this->assertContains('-----END PUBLIC KEY-----', $pubkey);
+  }
+
+
+  /** @test */
+  public function federationPrivkeyShouldNotBeEmpty() {
+    $privkey = Class_AdminVar::get('FEDERATION_PRIVKEY');
+    $this->assertContains('-----BEGIN RSA PRIVATE KEY-----', $privkey);
+    $this->assertContains('-----END RSA PRIVATE KEY-----', $privkey);
+  }
+}
+
+
+
+class ActivitypubAdminFederationReviewsControllerEnableShareWithoutCommunityServerTest
+  extends Admin_AbstractControllerTestCase {
+  protected $_storm_default_to_volatile = true;
+
+
+  public function setUp() {
+    parent::setUp();
+    $this->dispatch('/admin/federation-reviews/enable-share');
+  }
+
+
+  /** @test */
+  public function shouldRedirect() {
+    $this->assertRedirect();
+  }
+
+
+  /** @test */
+  public function reviewShareShouldNotBeEnabled() {
+    $this->assertFalse(Class_FederationReview::getInstance()->isShareEnabled());
+  }
+}
+
+
+
+class ActivitypubAdminFederationReviewsControllerEnableShareTest
+  extends ActivitypubAdminFederationReviewsControllerWithCommunityServerTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    $this->dispatch('/admin/federation-reviews/enable-share');
+  }
+
+
+  /** @test */
+  public function shouldRedirect() {
+    $this->assertRedirectTo('/admin/federation-reviews/index');
+  }
+
+
+  /** @test */
+  public function reviewShareShouldBeEnabled() {
+    $this->assertTrue(Class_FederationReview::getInstance()->isShareEnabled(),
+                      json_encode($this->_getFlashMessengerNotifications(), JSON_PRETTY_PRINT));
+  }
+}
+
+
+
+class ActivitypubAdminFederationReviewsControllerIndexAllEnabledTest
+  extends ActivitypubAdminEnabledTestCase {
+  protected $_storm_default_to_volatile = true;
+
+
+  public function setUp() {
+    parent::setUp();
+    $this->fixture('Class_Journal',
+                   ['id' => 150,
+                    'type' => 'AP_REVIEW_DISPLAY_JOIN']);
+    $this->fixture('Class_Journal',
+                   ['id' => 151,
+                    'type' => 'AP_REVIEW_SHARE_JOIN']);
+
+    $this->dispatch('/admin/federation-reviews');
+  }
+
+
+  /** @test */
+  public function pageShouldContainsDisableDisplay() {
+    $this->assertXPath('//button[contains(@data-url, "/federation-reviews/disable-display")]');
+  }
+
+
+  /** @test */
+  public function pageShouldContainsDisableShare() {
+    $this->assertXPath('//button[contains(@data-url, "/federation-reviews/disable-share")]');
+  }
+}
+
+
+
+class ActivitypubAdminFederationReviewsControllerDisableDisplayTest
+  extends ActivitypubAdminFederationReviewsControllerWithCommunityServerTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    $this->dispatch('/admin/federation-reviews/disable-display');
+  }
+
+
+  /** @test */
+  public function shouldRedirect() {
+    $this->assertRedirectTo('/admin/federation-reviews/index');
+  }
+
+
+  /** @test */
+  public function reviewDisplayShouldBeDisabled() {
+    $this->assertFalse(Class_FederationReview::getInstance()->isDisplayEnabled());
+  }
+}
+
+
+
+class ActivitypubAdminFederationReviewsControllerDisableShareTest
+  extends ActivitypubAdminFederationReviewsControllerWithCommunityServerTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    $this->dispatch('/admin/federation-reviews/disable-share');
+  }
+
+
+  /** @test */
+  public function shouldRedirect() {
+    $this->assertRedirectTo('/admin/federation-reviews/index');
+  }
+
+
+  /** @test */
+  public function reviewShareShouldBeDisabled() {
+    $this->assertFalse(Class_FederationReview::getInstance()->isShareEnabled());
+  }
+}
diff --git a/tests/scenarios/Activitypub/ActivitypubReviewTest.php b/tests/scenarios/Activitypub/ActivitypubReviewTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..4ae91337733672b8c5ad0b43194e4e038a55690a
--- /dev/null
+++ b/tests/scenarios/Activitypub/ActivitypubReviewTest.php
@@ -0,0 +1,883 @@
+<?php
+/**
+ * Copyright (c) 2012-2017, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+require_once __DIR__ . '/../../../library/activitystreams/autoload.php';
+require_once __DIR__ . '/../../../application/modules/activitypub/controllers/ReviewController.php';
+
+use Patbator\ActivityStreams\Model\Follow;
+use Patbator\ActivityStreams\Model\Accept;
+use Patbator\ActivityStreams\Model\Reject;
+use Patbator\ActivityStreams\Model\Service;
+use Patbator\ActivityStreams\Model\Join;
+use Patbator\ActivityStreams\Model\Leave;
+use Patbator\ActivityStreams\Model\Group;
+use Patbator\ActivityStreams\Stream;
+
+
+abstract class ActivitypubReviewTestCase extends AbstractControllerTestCase {
+  protected
+    $_storm_default_to_volatile = true,
+    $_json;
+}
+
+
+
+abstract class ActivitypubReviewClientTestCase extends ActivitypubReviewTestCase {
+  public function setUp() {
+    parent::setUp();
+    Class_AdminVar::set('FEDERATION_COMMUNITY_SERVER',
+                        'https://my.communityserver.la/activitypub/review');
+  }
+}
+
+
+
+class ActivitypubReviewIndexDisabledTest extends ActivitypubReviewTestCase {
+  public function setUp() {
+    parent::setUp();
+    Class_AdminVar::set('FEDERATION_ACTOR_NAME', 'Arcadia');
+
+    $this->dispatch('/activitypub/review', true,
+                    ['Accept' => Class_WebService_ActivityPub::MIME_TYPE]);
+    $this->_json = json_decode($this->_response->getBody(), true);
+  }
+
+
+  /** @test */
+  public function responseShouldBeServiceUnavailable() {
+    $this->assertResponseCode(503);
+  }
+}
+
+
+
+class ActivitypubReviewIndexEnabledTest extends ActivitypubReviewClientTestCase {
+  public function setUp() {
+    parent::setUp();
+    Class_AdminVar::set('FEDERATION_ACTOR_NAME', 'Arcadia');
+
+    $this->dispatch('/activitypub/review', true,
+                    ['Accept' => Class_WebService_ActivityPub::MIME_TYPE]);
+    $this->_json = json_decode($this->_response->getBody(), true);
+  }
+
+
+  /** @test */
+  public function typeShouldBeService() {
+    $this->assertEquals('Service', $this->_json['type']);
+  }
+
+
+  /** @test */
+  public function nameShouldBeArcadia() {
+    $this->assertEquals('Arcadia', $this->_json['name']);
+  }
+
+
+  /** @test */
+  public function outboxShouldBePresent() {
+    $this->assertContains('outbox', array_keys($this->_json));
+    $this->assertContains('/activitypub/review/outbox', $this->_json['outbox']);
+  }
+
+
+  /** @test */
+  public function inboxShouldBePresent() {
+    $this->assertContains('inbox', array_keys($this->_json));
+    $this->assertContains('/activitypub/review/outbox', $this->_json['outbox']);
+  }
+
+
+  /** @test */
+  public function publicKeyShouldBePresent() {
+    $this->assertContains('publicKey', array_keys($this->_json));
+    $this->assertContains('/activitypub/review/pubkey', $this->_json['publicKey']);
+  }
+}
+
+
+
+
+class ActivitypubReviewPubkeyDisabledTest extends ActivitypubReviewTestCase {
+  public function setUp() {
+    parent::setUp();
+    $this->dispatch('/activitypub/review/pubkey', true,
+                    ['Accept' => Class_WebService_ActivityPub::MIME_TYPE]);
+    $this->_json = json_decode($this->_response->getBody(), true);
+  }
+
+
+  /** @test */
+  public function responseShouldBeServiceUnavailable() {
+    $this->assertResponseCode(503);
+  }
+}
+
+
+
+
+class ActivitypubReviewPubkeyTest extends ActivitypubReviewClientTestCase {
+  public function setUp() {
+    parent::setUp();
+    $this->dispatch('/activitypub/review/pubkey', true,
+                    ['Accept' => Class_WebService_ActivityPub::MIME_TYPE]);
+    $this->_json = json_decode($this->_response->getBody(), true);
+  }
+
+
+  /** @test */
+  public function typeShouldBeKey() {
+    $this->assertEquals('Key', $this->_json['type']);
+  }
+
+
+  /** @test */
+  public function idShouldBePresent() {
+    $this->assertTrue(array_key_exists('id', $this->_json));
+    $this->assertContains('/activitypub/review/pubkey', $this->_json['id']);
+  }
+
+
+  /** @test */
+  public function ownerShouldBePresent() {
+    $this->assertTrue(array_key_exists('owner', $this->_json));
+    $this->assertContains('/activitypub/review', $this->_json['owner']);
+  }
+
+
+  /** @test */
+  public function publicKeyPemShouldBePresent() {
+    $this->assertTrue(array_key_exists('publicKeyPem', $this->_json));
+    $this->assertContains('-----BEGIN PUBLIC KEY-----',
+                          $this->_json['publicKeyPem']);
+  }
+
+
+  /** @test */
+  public function federationPubkeyShouldNotBeEmpty() {
+    $pubkey = Class_AdminVar::get('FEDERATION_PUBKEY');
+    $this->assertContains('-----BEGIN PUBLIC KEY-----', $pubkey);
+    $this->assertContains('-----END PUBLIC KEY-----', $pubkey);
+  }
+
+
+  /** @test */
+  public function federationPrivkeyShouldNotBeEmpty() {
+    $privkey = Class_AdminVar::get('FEDERATION_PRIVKEY');
+    $this->assertContains('-----BEGIN RSA PRIVATE KEY-----', $privkey);
+    $this->assertContains('-----END RSA PRIVATE KEY-----', $privkey);
+  }
+}
+
+
+
+class ActivitypubReviewOutboxTest extends ActivitypubReviewClientTestCase {
+  public function setUp() {
+    parent::setUp();
+
+    $client_endpoint = Class_AdminVar::get('FEDERATION_COMMUNITY_SERVER');
+
+    $this->dispatch('/activitypub/review/outbox',
+                    true,
+                    ['Accept' => Class_WebService_ActivityPub::MIME_TYPE,
+                     'Authorization' => 'Bearer ' . $client_endpoint]);
+    $this->_json = json_decode($this->_response->getBody(), true);
+  }
+
+
+  /** @test */
+  public function responseTypeShouldBeCollectionPage() {
+    $this->assertEquals('CollectionPage', $this->_json['type']);
+  }
+}
+
+
+
+class ActivitypubReviewOutboxPageOneTest extends ActivitypubReviewClientTestCase {
+  public function setUp() {
+    parent::setUp();
+    $this->fixture('Class_AvisNotice',
+                   ['id' => 42,
+                    'entete' => 'Not so bad',
+                    'user' => Class_Users::getIdentity(),
+                    'date_mod' => null,
+                    'source_author' => null]);
+
+    $client_endpoint = Class_AdminVar::get('FEDERATION_COMMUNITY_SERVER');
+
+    $this->dispatch('/activitypub/review/outbox/page/1',
+                    true,
+                    ['Accept' => Class_WebService_ActivityPub::MIME_TYPE,
+                     'Authorization' => 'Bearer ' . $client_endpoint]);
+
+    $this->_json = json_decode($this->_response->getBody(), true);
+  }
+
+
+  /** @test */
+  public function typeShouldBeCollectionPage() {
+    $this->assertEquals('CollectionPage', $this->_json['type']);
+  }
+
+
+  /** @test */
+  public function firstReviewShouldHaveEnteteNotSoBad() {
+    $this->assertEquals('Not so bad', $this->_json['items'][0]['entete']);
+  }
+}
+
+
+
+class ActivitypubReviewOutboxBadFromTest extends ActivitypubReviewClientTestCase {
+  public function setUp() {
+    parent::setUp();
+
+    $client_endpoint = Class_AdminVar::get('FEDERATION_COMMUNITY_SERVER');
+
+    $this->dispatch('/activitypub/review/outbox?from=no%20valid',
+                    true,
+                    ['Accept' => Class_WebService_ActivityPub::MIME_TYPE,
+                     'Authorization' => 'Bearer ' . $client_endpoint]);
+    $this->_json = json_decode($this->_response->getBody(), true);
+  }
+
+
+  /** @test */
+  public function responseShouldBeError400() {
+    $this->assertResponseCode(400);
+  }
+}
+
+
+
+class ActivitypubReviewOutboxValidFromTest extends ActivitypubReviewClientTestCase {
+  public function setUp() {
+    parent::setUp();
+
+    $client_endpoint = Class_AdminVar::get('FEDERATION_COMMUNITY_SERVER');
+
+    $this->dispatch('/activitypub/review/outbox?from=2019-05-17',
+                    true,
+                    ['Accept' => Class_WebService_ActivityPub::MIME_TYPE,
+                     'Authorization' => 'Bearer ' . $client_endpoint]);
+    $this->_json = json_decode($this->_response->getBody(), true);
+  }
+
+
+  /** @test */
+  public function responseShouldBeSuccess200() {
+    $this->assertResponseCode(200);
+  }
+
+
+  /** @test */
+  public function typeShouldBeCollectionPage() {
+    $this->assertEquals('CollectionPage', $this->_json['type']);
+  }
+}
+
+
+
+abstract class ActivitypubReviewServerTestCase extends ActivitypubReviewTestCase {
+  public function setUp() {
+    parent::setUp();
+    Class_AdminVar::set('FEDERATION_IS_COMMUNITY_SERVER', '1');
+  }
+}
+
+
+
+abstract class ActivitypubReviewInboxJoinTestCase extends ActivitypubReviewServerTestCase {
+  protected
+    $_web_client,
+    $_endpoint,
+    $_activity;
+
+  public function setUp() {
+    parent::setUp();
+
+    $this->_web_client = $this
+      ->mock()
+      ->whenCalled('open_url')
+      ->with('https://my-library-portal.fr/activitypub/review',
+             ['headers' => ['Accept' => Class_WebService_ActivityPub::MIME_TYPE]])
+      ->answers('{
+  "@context": "https://www.w3.org/ns/activitystreams",
+  "type": "Service",
+  "id": "https://my-library-portal.fr/activitypub/review",
+  "inbox": "https://my-library-portal.fr/activitypub/review/inbox",
+  "outbox": "https://my-library-portal.fr/activitypub/review/outbox",
+  "publicKey": "https://my-library-portal.fr/activitypub/review/pubkey"
+}')
+
+      ->whenCalled('open_url')
+      ->with('https://my-library-portal.fr/activitypub/review/pubkey',
+             ['headers' => ['Accept' => Class_WebService_ActivityPub::MIME_TYPE]])
+      ->answers(json_encode(['@context' => 'https://www.w3.org/ns/activitystreams',
+                             'type' => 'Key',
+                             'id' => 'https://my-library-portal.fr/activitypub/review/pubkey',
+                             'owner' => 'https://my-library-portal.fr/activitypub/review',
+                             'publicKeyPem' => 'TheirKey']))
+
+      ->beStrict()
+      ;
+
+    $signer = Storm_Test_ObjectWrapper::on(new Class_HttpSignature('keyId="https://my-library-portal.fr/activitypub/review/pubkey", algorithm="rsa-sha256", headers="(request-target) date digest", signature="TheirSignature"'))
+      ->whenCalled('sign')->answers('MySignature')
+
+      ->whenCalled('verify')
+      ->with(['date' => 'Thu, 12 Mar 2015 15:54:35 +0100',
+              'digest' => 'MD5=TheirDigest',
+              '(request-target)' => 'post /activitypub/review/inbox'],
+             'TheirKey')
+      ->answers(true);
+
+    Class_WebService_ActivityPub::setSigner($signer);
+    Class_WebService_ActivityPub::setTimeSource(new TimeSourceForTest('2019-02-08 16:41:33'));
+    Class_WebService_ActivityPub::setWebClient($this->_web_client);
+    Class_WebService_ActivityPub::setThrowErrors(true);
+
+    $actor = (new Service())->name('My library')
+                            ->id('https://my-library-portal.fr/activitypub/review');
+
+    $this->_endpoint = Class_Url::absolute(['module' => 'activitypub',
+                                            'controller' => 'review'], null, true);
+
+    $object = (new Group())->name($this->_groupName());
+
+    $join = (new Join)
+      ->summary('My library followed your reviews')
+      ->id('https://my-library-portal.fr/activitypub/review/follow/id/42')
+      ->actor($actor)
+      ->object($object);
+
+    $this->postDispatchRaw('/activitypub/review/inbox',
+                           (new Stream($join))->render(),
+                           ['Content-Type' => Class_WebService_ActivityPub::MIME_TYPE,
+                            'Date' => 'Thu, 12 Mar 2015 15:54:35 +0100',
+                            'Digest' => 'MD5=TheirDigest',
+                            'Signature' => 'keyId="https://my-library-portal.fr/activitypub/review/pubkey", algorithm="rsa-sha256", headers="(request-target) date digest", signature="TheirSignature"']);
+
+    if ($stream = Stream::fromJson($this->_response->getBody()))
+      $this->_activity = $stream->getRoot();
+  }
+
+
+  public function tearDown() {
+    Class_WebService_ActivityPub::setWebClient(null);
+    Class_WebService_ActivityPub::setSigner(null);
+    parent::tearDown();
+  }
+}
+
+
+
+
+class ActivitypubReviewInboxJoinDisplayGroupTest extends ActivitypubReviewInboxJoinTestCase {
+  protected function _groupName() {
+    return 'REVIEW_DISPLAY';
+  }
+
+
+  /** @test */
+  public function responseShouldBeSuccess() {
+    $this->assertEquals(200, $this->_response->getHttpResponseCode());
+  }
+
+
+  /** @test */
+  public function shouldBeAccepted() {
+    $this->assertTrue($this->_activity instanceof Accept);
+  }
+
+
+  /** @test */
+  public function actorShouldBeMemberOfReviewDisplayGroup() {
+    $this->assertNotNull(Class_Federation_GroupMembership::findFirstBy(['actor_id' => 'https://my-library-portal.fr/activitypub/review',
+                                                                        'group_name' => 'REVIEW_DISPLAY']));
+  }
+}
+
+
+
+class ActivitypubReviewInboxJoinDisplayGroupTwiceTest extends ActivitypubReviewInboxJoinTestCase {
+  protected function _groupName() {
+    $this->fixture('Class_Federation_GroupMembership',
+                   ['id' => 44,
+                    'actor_id' => 'https://my-library-portal.fr/activitypub/review',
+                    'group_name' => 'REVIEW_DISPLAY']);
+
+    return 'REVIEW_DISPLAY';
+  }
+
+
+  /** @test */
+  public function responseShouldBeSuccess() {
+    $this->assertEquals(200, $this->_response->getHttpResponseCode());
+  }
+
+
+  /** @test */
+  public function shouldBeAccepted() {
+    $this->assertTrue($this->_activity instanceof Accept);
+  }
+
+
+  /** @test */
+  public function actorShouldBeMemberOfReviewDisplayGroupOnce() {
+    $this->assertEquals(1, Class_Federation_GroupMembership::countBy(['actor_id' => 'https://my-library-portal.fr/activitypub/review',
+                                                                      'group_name' => 'REVIEW_DISPLAY']));
+  }
+}
+
+
+
+class ActivitypubReviewInboxJoinUnknownGroupTest extends ActivitypubReviewInboxJoinTestCase {
+  protected function _groupName() {
+    return 'ANY_UNKNOWN_GROUP';
+  }
+
+
+  /** @test */
+  public function responseShouldBeSuccess() {
+    $this->assertEquals(200, $this->_response->getHttpResponseCode());
+  }
+
+
+  /** @test */
+  public function shouldBeRejected() {
+    $this->assertTrue($this->_activity instanceof Reject);
+  }
+
+
+  /** @test */
+  public function actorShouldNotBeMemberOfAnyGroup() {
+    $this->assertEquals(0, Class_Federation_GroupMembership::countBy(['actor_id' => 'https://my-library-portal.fr/activitypub/review']));
+  }
+}
+
+
+
+class ActivitypubReviewInboxJoinShareGroupTest extends ActivitypubReviewInboxJoinTestCase {
+  protected function _groupName() {
+    return 'REVIEW_SHARE';
+  }
+
+
+  /** @test */
+  public function responseShouldBeSuccess() {
+    $this->assertEquals(200, $this->_response->getHttpResponseCode());
+  }
+
+
+  /** @test */
+  public function shouldBeAccepted() {
+    $this->assertTrue($this->_activity instanceof Accept);
+  }
+
+
+  /** @test */
+  public function actorShouldBeMemberOfReviewDisplayGroup() {
+    $this->assertNotNull(Class_Federation_GroupMembership::findFirstBy(['actor_id' => 'https://my-library-portal.fr/activitypub/review',
+                                                                        'group_name' => 'REVIEW_SHARE']));
+  }
+}
+
+
+
+abstract class ActivitypubReviewInboxLeaveTestCase extends ActivitypubReviewServerTestCase {
+  protected
+    $_web_client,
+    $_endpoint,
+    $_activity;
+
+  public function setUp() {
+    parent::setUp();
+
+    $this->fixture('Class_Federation_GroupMembership',
+                   ['id' => 345,
+                    'actor_id' => 'https://my-library-portal.fr/activitypub/review',
+                    'group_name' => 'REVIEW_DISPLAY']);
+
+
+    $this->fixture('Class_Federation_GroupMembership',
+                   ['id' => 346,
+                    'actor_id' => 'https://my-library-portal.fr/activitypub/review',
+                    'group_name' => 'REVIEW_SHARE']);
+
+    $this->fixture('Class_AvisNotice',
+                   ['id' => 55,
+                    'user' => Class_Users::getIdentity(),
+                    'CLEF_OEUVRE' => 'UNAMOURDELAPIN44--DAVISJ-',
+                    'ID_NOTICE' => '113132',
+                    'DATE_AVIS' => '2019-04-19 17:46:27',
+                    'DATE_MOD' => 'NULL',
+                    'NOTE' => '3',
+                    'ENTETE' => 'Au top',
+                    'AVIS' => 'comme d\'habitude trop bien !',
+                    'STATUT' => '0',
+                    'abon_ou_bib' => '1',
+                    'USER_KEY' => '0--0--sysadm',
+                    'flags' => '0',
+                    'type_doc' => '1',
+                    'source_actor_id' => 'https://my-library-portal.fr/activitypub/review',
+                    'source_author' => 'Arcadia',
+                    'source_primary' => 4839
+                   ]);
+
+    $this->fixture('Class_Journal',
+                   ['id' => 344,
+                    'type' => 'AP_REVIEW_HARVEST_346']);
+
+    $this->_web_client = $this
+      ->mock()
+      ->whenCalled('open_url')
+      ->with('https://my-library-portal.fr/activitypub/review',
+             ['headers' => ['Accept' => Class_WebService_ActivityPub::MIME_TYPE]])
+      ->answers('{
+  "@context": "https://www.w3.org/ns/activitystreams",
+  "type": "Service",
+  "id": "https://my-library-portal.fr/activitypub/review",
+  "inbox": "https://my-library-portal.fr/activitypub/review/inbox",
+  "outbox": "https://my-library-portal.fr/activitypub/review/outbox",
+  "publicKey": "https://my-library-portal.fr/activitypub/review/pubkey"
+}')
+
+      ->whenCalled('open_url')
+      ->with('https://my-library-portal.fr/activitypub/review/pubkey',
+             ['headers' => ['Accept' => Class_WebService_ActivityPub::MIME_TYPE]])
+      ->answers(json_encode(['@context' => 'https://www.w3.org/ns/activitystreams',
+                             'type' => 'Key',
+                             'id' => 'https://my-library-portal.fr/activitypub/review/pubkey',
+                             'owner' => 'https://my-library-portal.fr/activitypub/review',
+                             'publicKeyPem' => 'TheirKey']))
+
+      ->beStrict()
+      ;
+
+    $signer = Storm_Test_ObjectWrapper::on(new Class_HttpSignature('keyId="https://my-library-portal.fr/activitypub/review/pubkey", algorithm="rsa-sha256", headers="(request-target) date digest", signature="TheirSignature"'))
+      ->whenCalled('sign')->answers('MySignature')
+
+      ->whenCalled('verify')
+      ->with(['date' => 'Thu, 12 Mar 2015 15:54:35 +0100',
+              'digest' => 'MD5=TheirDigest',
+              '(request-target)' => 'post /activitypub/review/inbox'],
+             'TheirKey')
+      ->answers(true);
+
+    Class_WebService_ActivityPub::setSigner($signer);
+    Class_WebService_ActivityPub::setTimeSource(new TimeSourceForTest('2019-02-08 16:41:33'));
+    Class_WebService_ActivityPub::setWebClient($this->_web_client);
+    Class_WebService_ActivityPub::setThrowErrors(true);
+
+    $actor = (new Service())->name('My library')
+                            ->id('https://my-library-portal.fr/activitypub/review');
+
+    $this->_endpoint = Class_Url::absolute(['module' => 'activitypub',
+                                            'controller' => 'review'], null, true);
+
+    $object = (new Group())->name($this->_groupName());
+
+    $leave = (new Leave)
+      ->summary('My library followed your reviews')
+      ->id('https://my-library-portal.fr/activitypub/review/follow/id/42')
+      ->actor($actor)
+      ->object($object);
+
+    $this->postDispatchRaw('/activitypub/review/inbox',
+                           (new Stream($leave))->render(),
+                           ['Content-Type' => Class_WebService_ActivityPub::MIME_TYPE,
+                            'Date' => 'Thu, 12 Mar 2015 15:54:35 +0100',
+                            'Digest' => 'MD5=TheirDigest',
+                            'Signature' => 'keyId="https://my-library-portal.fr/activitypub/review/pubkey", algorithm="rsa-sha256", headers="(request-target) date digest", signature="TheirSignature"']);
+
+    if ($stream = Stream::fromJson($this->_response->getBody()))
+      $this->_activity = $stream->getRoot();
+  }
+
+
+  public function tearDown() {
+    Class_WebService_ActivityPub::setWebClient(null);
+    Class_WebService_ActivityPub::setSigner(null);
+    parent::tearDown();
+  }
+}
+
+
+
+
+class ActivitypubReviewInboxLeaveDisplayGroupTest extends ActivitypubReviewInboxLeaveTestCase {
+  protected function _groupName() {
+    return 'REVIEW_DISPLAY';
+  }
+
+
+  /** @test */
+  public function responseShouldBeSuccess() {
+    $this->assertEquals(200, $this->_response->getHttpResponseCode());
+  }
+
+
+  /** @test */
+  public function shouldBeAccepted() {
+    $this->assertTrue($this->_activity instanceof Accept);
+  }
+
+
+  /** @test */
+  public function actorShouldBeNoLongerMemberOfReviewDisplayGroup() {
+    $this->assertNull(Class_Federation_GroupMembership::findFirstBy(['actor_id' => 'https://my-library-portal.fr/activitypub/review',
+                                                                     'group_name' => 'REVIEW_DISPLAY']));
+  }
+}
+
+
+
+
+class ActivitypubReviewInboxLeaveShareGroupTest extends ActivitypubReviewInboxLeaveTestCase {
+  protected function _groupName() {
+    return 'REVIEW_SHARE';
+  }
+
+
+  /** @test */
+  public function responseShouldBeSuccess() {
+    $this->assertEquals(200, $this->_response->getHttpResponseCode());
+  }
+
+
+  /** @test */
+  public function shouldBeAccepted() {
+    $this->assertTrue($this->_activity instanceof Accept);
+  }
+
+
+  /** @test */
+  public function actorShouldBeNoLongerMemberOfReviewShareGroup() {
+    $this->assertNull(Class_Federation_GroupMembership::findFirstBy(['actor_id' => 'https://my-library-portal.fr/activitypub/review',
+                                                                     'group_name' => 'REVIEW_SHARE']));
+  }
+
+
+  /** @test */
+  public function sharedReviewsShouldBeRemoved() {
+    $this->assertNull(Class_AvisNotice::findFirstBy(['source_actor_id' => 'https://my-library-portal.fr/activitypub/review']));
+  }
+
+
+  /** @test */
+  public function harvestJournalShouldBeRemoved() {
+    $this->assertNull(Class_Journal::findFirstBy(['type' => 'AP_REVIEW_HARVEST_346']));
+  }
+}
+
+
+
+
+class ActivitypubReviewInboxLeaveUnkownGroupTest extends ActivitypubReviewInboxLeaveTestCase {
+  protected function _groupName() {
+    return 'I_M_UNKNOWN';
+  }
+
+
+  /** @test */
+  public function responseShouldBeSuccess() {
+    $this->assertEquals(200, $this->_response->getHttpResponseCode());
+  }
+
+
+  /** @test */
+  public function shouldBeRejected() {
+    $this->assertTrue($this->_activity instanceof Reject);
+  }
+
+
+  /** @test */
+  public function actorShouldStillBeMemberOfReviewDisplayGroup() {
+    $this->assertNotNull(Class_Federation_GroupMembership::findFirstBy(['actor_id' => 'https://my-library-portal.fr/activitypub/review',
+                                                                     'group_name' => 'REVIEW_DISPLAY']));
+  }
+}
+
+
+
+class ActivitypubReviewOutboxForRecordTest extends ActivitypubReviewServerTestCase {
+  public function setUp() {
+    parent::setUp();
+
+    $this->fixture('Class_AvisNotice',
+                   ['id' => 55,
+                    'user' => Class_Users::getIdentity(),
+                    'CLEF_OEUVRE' => 'UNAMOURDELAPIN44--DAVISJ-',
+                    'ID_NOTICE' => '113132',
+                    'DATE_AVIS' => '2019-04-19 17:46:27',
+                    'DATE_MOD' => 'NULL',
+                    'NOTE' => '3',
+                    'ENTETE' => 'Au top',
+                    'AVIS' => 'comme d\'habitude trop bien !',
+                    'STATUT' => '0',
+                    'abon_ou_bib' => '1',
+                    'USER_KEY' => '0--0--sysadm',
+                    'flags' => '0',
+                    'type_doc' => '1',
+                    'source_actor_id' => 'https://other.bokeh.es/activitypub/review',
+                    'source_author' => 'Arcadia',
+                    'source_primary' => 4839
+                   ]);
+
+    $client_endpoint = 'https://my.super_bokeh.nl/activitypub/review';
+
+    $this->fixture('Class_Federation_GroupMembership',
+                   ['id' => 1,
+                    'actor_id' => $client_endpoint,
+                    'group_name' => 'REVIEW_DISPLAY']);
+
+    $this->dispatch('/activitypub/review/outbox?key=UNAMOURDELAPIN44--DAVISJ-&page=1',
+                    true,
+                    ['Accept' => Class_WebService_ActivityPub::MIME_TYPE,
+                     'Authorization' => 'Bearer ' . $client_endpoint]);
+
+    $this->_json = json_decode($this->_response->getBody(), true);
+  }
+
+
+  /** @test */
+  public function responseShouldBeACollectionPage() {
+    $this->assertEquals('CollectionPage', $this->_json['type']);
+  }
+
+
+  /** @test */
+  public function firstReviewTitleShouldBeAuTop() {
+    $this->assertEquals('Au top', $this->_json['items'][0]['entete']);
+  }
+
+
+  /** @test */
+  public function firstReviewAuthorShouldBeArcadia() {
+    $this->assertEquals('Arcadia', $this->_json['items'][0]['source_author']);
+  }
+}
+
+
+
+class ActivitypubReviewOutboxHarvestingTest extends ActivitypubReviewClientTestCase {
+  protected $_review;
+
+  public function setUp() {
+    parent::setUp();
+
+    $this->fixture('Class_AvisNotice',
+                   ['id' => 55,
+                    'user' => Class_Users::getIdentity(),
+                    'CLEF_OEUVRE' => 'UNAMOURDELAPIN44--DAVISJ-',
+                    'ID_NOTICE' => '113132',
+                    'DATE_AVIS' => '2019-04-19 17:46:27',
+                    'DATE_MOD' => 'NULL',
+                    'NOTE' => '3',
+                    'ENTETE' => 'Au top',
+                    'AVIS' => 'comme d\'habitude trop bien !',
+                    'STATUT' => '0',
+                    'USER_KEY' => '0--0--sysadm',
+                    'flags' => '0',
+                    'type_doc' => '1',
+                    'source_author' => null,
+                   ]);
+
+    $client_endpoint = Class_AdminVar::get('FEDERATION_COMMUNITY_SERVER');
+
+    $this->dispatch('/activitypub/review/outbox?from=&page=1',
+                    true,
+                    ['Accept' => Class_WebService_ActivityPub::MIME_TYPE,
+                     'Authorization' => 'Bearer ' . $client_endpoint]);
+
+    $this->_json = json_decode($this->_response->getBody(), true);
+    $this->_review = $this->_json['items'][0];
+  }
+
+
+  /** @test */
+  public function responseTypeShouldBeCollectionPage() {
+    $this->assertEquals('CollectionPage', $this->_json['type']);
+  }
+
+
+  /** @test */
+  public function collectionShouldHaveOneItemInTotal() {
+    $this->assertEquals(1, $this->_json['totalItems']);
+  }
+
+
+  /** @test */
+  public function reviewIdShouldBe55() {
+    $this->assertEquals(55, $this->_review['id']);
+  }
+
+
+  /** @test */
+  public function reviewClefOeuvreShouldBeUnAmourDeLapin() {
+    $this->assertEquals('UNAMOURDELAPIN44--DAVISJ-', $this->_review['clef_oeuvre']);
+  }
+
+
+  /** @test */
+  public function reviewDateShouldBe2019_04_19() {
+    $this->assertEquals('2019-04-19 17:46:27', $this->_review['date_avis']);
+  }
+
+
+  /** @test */
+  public function reviewShouldHaveBeenRated3() {
+    $this->assertEquals(3, $this->_review['note']);
+  }
+
+
+  /** @test */
+  public function reviewEnteteShouldBeAuTop() {
+    $this->assertEquals('Au top', $this->_review['entete']);
+  }
+
+
+  /** @test */
+  public function reviewContentShouldBeCommeDHabitude() {
+    $this->assertEquals('comme d\'habitude trop bien !', $this->_review['avis']);
+  }
+
+
+  public function filteredFields() {
+    return array_map(function($item) { return [$item]; },
+                     ['id_user',
+                      'id_notice',
+                      'statut',
+                      'user_key',
+                      'flags',
+                      'type_doc',
+                      'source_actor_id',
+                      'source_primary']);
+  }
+
+
+  /** @test @dataProvider filteredFields */
+  public function reviewShouldNotHaveFilteredField($field) {
+    $this->assertNotContains($field, array_keys($this->_review));
+    $this->assertNotContains(strtoupper($field), array_keys($this->_review));
+  }
+}
diff --git a/tests/scenarios/Activitypub/FederationReviewBatchTest.php b/tests/scenarios/Activitypub/FederationReviewBatchTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..cffa410cdc927af4a1ba312a11ff9ad8df4130b0
--- /dev/null
+++ b/tests/scenarios/Activitypub/FederationReviewBatchTest.php
@@ -0,0 +1,139 @@
+<?php
+/**
+ * Copyright (c) 2012-2019, 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 ActivitypubAdminBatchControllerTest extends Admin_AbstractControllerTestCase {
+  protected $_storm_default_to_volatile = true;
+
+  /** @test */
+  public function withServerDisabledFederationBatchShouldNotBeAvailable() {
+    Class_AdminVar::set('FEDERATION_IS_COMMUNITY_SERVER', '0');
+    $this->dispatch('/admin/batch');
+    $this->assertNotXPath('//a[contains(@href, "FEDERATION_REVIEW_HARVEST")]');
+  }
+
+
+  /** @test */
+  public function withServerEnabledFederationBatchShouldBeAvailable() {
+    Class_AdminVar::set('FEDERATION_IS_COMMUNITY_SERVER', '1');
+    $this->dispatch('/admin/batch');
+    $this->assertXPath('//a[contains(@href, "FEDERATION_REVIEW_HARVEST")]');
+  }
+}
+
+
+
+class ActivitypubFederationReviewBatchTest extends ModelTestCase {
+  protected
+    $_actor_id,
+    $_get_response_called = false,
+    $_web_client;
+
+  public function setUp() {
+    parent::setUp();
+    $this->_actor_id = 'https://review-share.member.com/activitypub/review';
+
+    $this->fixture('Class_Federation_GroupMembership',
+                   ['id' => 1,
+                    'group_name' => 'REVIEW_SHARE',
+                    'actor_id' => $this->_actor_id,
+                    'accepted_at' => '2019-05-03 14:15:54']);
+
+    $this->_web_client = $this
+      ->mock()
+      ->whenCalled('open_url')
+      ->with($this->_actor_id, ['headers' => ['Accept' => Class_WebService_ActivityPub::MIME_TYPE]])
+      ->answers(str_replace('{actor}',
+                            $this->_actor_id,
+                            '{
+  "@context": "https://www.w3.org/ns/activitystreams",
+  "type": "Service",
+  "id": "{actor}",
+  "name": "Arcadia",
+  "inbox": "{actor}/inbox",
+  "outbox": "{actor}/outbox",
+  "publicKey": "{actor}/pubkey"
+}'))
+
+      ->whenCalled('open_url')
+      ->with($this->_actor_id . '/pubkey',
+             ['headers' => ['Accept' => Class_WebService_ActivityPub::MIME_TYPE]])
+      ->answers(str_replace('{actor}',
+                            $this->_actor_id,
+                            '{
+  "@context": "https://www.w3.org/ns/activitystreams",
+  "type": "Key",
+  "id": "{actor}/pubkey",
+  "owner": "{actor}",
+  "publicKeyPem": "TheirKey"
+}'))
+
+      ->whenCalled('getResponse')
+      ->willDo(function()
+               {
+                 if ($this->_get_response_called)
+                   return $this->_responseWithItems([]);
+
+                 $this->_get_response_called = true;
+                 return $this->_responseWithItems([['id' => '3773',
+                                                    'clef_oeuvre' => 'UNAMOURDELAPIN44--DAVISJ-',
+                                                    'date_avis' => '2019-04-19 17:46:27',
+                                                    'date_mod' => null,
+                                                    'note' => '3',
+                                                    'entete' => 'Au top',
+                                                    'avis' => 'Comme d\'habitude, trop bien !',
+                                                    'source_author' => 'SuperBibliothécaire']]);
+               })
+      ;
+
+    $signer = Storm_Test_ObjectWrapper::on(new Class_HttpSignature('keyId="'. $this->_actor_id .'/pubkey", algorithm="rsa-sha256", headers="(request-target) date digest", signature="TheirSignature"'))
+      ->whenCalled('sign')->answers('MySignature')
+      ->whenCalled('verify')->answers(true);
+
+    Class_WebService_ActivityPub::setSigner($signer);
+    Class_WebService_ActivityPub::setTimeSource(new TimeSourceForTest('2019-05-03 16:10:35'));
+    Class_WebService_ActivityPub::setWebClient($this->_web_client);
+    Class_WebService_ActivityPub::setThrowErrors(true);
+
+    (new Class_Batch_FederationReviewHarvest)->run();
+  }
+
+
+  protected function _responseWithItems($items) {
+    return $this->mock()
+                ->whenCalled('isError')->answers(false)
+                ->whenCalled('getHeader')->with('Signature')->answers('TheirSignature')
+                ->whenCalled('getHeaders')->answers([])
+                ->whenCalled('getBody')
+                ->answers(json_encode(['@context' => 'https://www.w3.org/ns/activitystreams',
+                                       'type' => 'CollectionPage',
+                                       'totalItems' => count($items),
+                                       'items' => $items]));
+  }
+
+
+  /** @test */
+  public function shouldHaveAReviewWithActorIdAndAuthor() {
+    $review = Class_AvisNotice::findFirstBy(['source_actor_id' => $this->_actor_id,
+                                             'source_author' => 'Arcadia']);
+    $this->assertNotNull($review);
+  }
+}
diff --git a/tests/scenarios/Authorities/AuthoritiesTest.php b/tests/scenarios/Authorities/AuthoritiesTest.php
index c8b6ce93a9eaef90b2d1aa01e4ca2e258e0558c5..12e313c9e1c436eff1af851c1ab9216f13d80ebc 100644
--- a/tests/scenarios/Authorities/AuthoritiesTest.php
+++ b/tests/scenarios/Authorities/AuthoritiesTest.php
@@ -41,6 +41,25 @@ abstract class AuthoritiesTestCase extends AbstractControllerTestCase {
                     'type' => Class_Notice::TYPE_AUTHORITY,
                     'id_origine' => '185611',
                     'id_notice' => 1]);
+
+
+    $unimarc = (new Class_Notice_AuthorityPartial)
+      ->newWith('j', '190254', 'Schéma de services', ['b' => 'TESS'])
+      ->render();
+
+    $this->fixture('Class_Notice',
+                   ['id' => 2,
+                    'type_doc' => 'j',
+                    'annee' => '2017',
+                    'type' => Class_Notice::TYPE_AUTHORITY,
+                    'facettes' => 'HMOTS HMOTS0002',
+                    'unimarc' => $unimarc]);
+
+    $this->fixture('Class_Exemplaire',
+                   ['id' => 1022,
+                    'type' => Class_Notice::TYPE_AUTHORITY,
+                    'id_origine' => '190254',
+                    'id_notice' => 2]);
   }
 }
 
@@ -73,6 +92,297 @@ class AuthoritiesRechercheControllerViewNoticeWithAuthorityIdAndThesaurusIdTest
 
 
 
+
+class AuthoritiesAuthoritySearchControllerWithoutCritereTest extends AuthoritiesTestCase {
+  public function setUp() {
+    parent::setUp();
+    Class_MoteurRecherche::setInstance($this->mock()
+                                       ->whenCalled('lancerRecherche')
+                                       ->answers(null));
+    $this->dispatch('/opac/authority-search');
+  }
+
+
+  public function tearDown() {
+    Class_MoteurRecherche::setInstance(null);
+    parent::tearDown();
+  }
+
+
+  /** @test */
+  public function titleShouldBeParcoursDesAutorites() {
+    $this->assertXPathContentContains('//h1', 'Parcours des autorités');
+  }
+
+
+  /** @test */
+  public function shouldDisplaySearchInput() {
+    $this->assertXPath('//form[contains(@action, "/authority-search")]//input[@name="expressionRecherche"]');
+  }
+
+
+  /** @test */
+  public function shouldNotLaunchSearch() {
+    $this->assertFalse(Class_MoteurRecherche::getInstance()->methodHasBeenCalled('lancerRecherche'));
+  }
+}
+
+
+
+
+class AuthoritiesAuthoritySearchControllerWithTreeRootsTest extends AuthoritiesTestCase {
+  public function setUp() {
+    parent::setUp();
+    Class_MoteurRecherche::setInstance($this->mock()
+                                       ->whenCalled('lancerRecherche')
+                                       ->answers(null));
+    $this->dispatch('/opac/authority-search/index/tree_roots/185611');
+  }
+
+
+  public function tearDown() {
+    Class_MoteurRecherche::setInstance(null);
+    parent::tearDown();
+  }
+
+
+  /** @test */
+  public function shouldDisplayDeveloppementlocalInTree() {
+    $this->assertXPathContentContains('//div[@class="filtre_recherche"]//a[contains(@href, "record_id/1")]',
+                                      'Développement local');
+  }
+}
+
+
+
+
+class AuthoritiesAuthoritySearchControllerWithTreeRootsSelectedTest extends AuthoritiesTestCase {
+  public function setUp() {
+    parent::setUp();
+    Class_MoteurRecherche::setInstance($this->mock()
+                                       ->whenCalled('lancerRecherche')
+                                       ->answers(null));
+    $this->dispatch('/opac/authority-search/index/tree_roots/185611/record_id/1');
+  }
+
+
+  public function tearDown() {
+    Class_MoteurRecherche::setInstance(null);
+    parent::tearDown();
+  }
+
+
+  /** @test */
+  public function shouldDisplayDeveloppementlocalSelectedInTree() {
+    $this->assertXPathContentContains('//div[@class="filtre_recherche"]//a[contains(@href, "record_id/1")][@class="bold"]',
+                                      'Développement local');
+  }
+
+
+  /** @test */
+  public function shouldDisplayDeveloppementlocalSpecificsInTree() {
+    $this->assertXPathContentContains('//div[@class="filtre_recherche"]//a[contains(@href, "record_id/2")]',
+                                      'Schéma de services');
+  }
+}
+
+
+
+
+class AuthoritiesAuthoritySearchControllerPostTest extends AuthoritiesTestCase {
+  public function setUp() {
+    parent::setUp();
+    $this->postDispatch('/opac/authority-search', ['expressionRecherche' => 'local']);
+  }
+
+
+  /** @test */
+  public function shouldRedirectToUrlWithExpressionRecherche() {
+    $this->assertRedirectRegex('#/authority-search/index/expressionRecherche/local$#');
+  }
+}
+
+
+
+
+class AuthoritiesAuthoritySearchControllerWithExpressionLocalAndAResultTest
+  extends AuthoritiesTestCase {
+
+  public function setUp() {
+    parent::setUp();
+
+    Zend_Registry::set('sql',
+                       $this->mock()
+                       ->whenCalled('fetchAll')
+                       ->with("select id_notice, facettes from notices Where (MATCH(titres, auteurs, editeur, collection, matieres, dewey) AGAINST('+(LOCAL LOCAUX LOKAL)' IN BOOLEAN MODE)) and type=2 order by (MATCH(titres) AGAINST(' LOCAL') * 1.5) + (MATCH(auteurs) AGAINST(' LOCAL')) desc", true, false)
+                       ->answers([[1, '']]));
+
+    $this->dispatch('/opac/authority-search/index/expressionRecherche/local');
+  }
+
+
+  /** @test */
+  public function titleShouldBeParcoursDesAutorites() {
+    $this->assertXPathContentContains('//h1', 'Parcours des autorités');
+  }
+
+
+  /** @test */
+  public function shouldDisplaySearchInputWithValue() {
+    $this->assertXPath('//form[contains(@action, "/authority-search")]//input[@name="expressionRecherche"][@value="local"]');
+  }
+
+
+  /** @test */
+  public function shouldContainsIlYAUnResultat() {
+    $this->assertContains('Il y a <span class="nombre-recherche"> 1 </span> résultat',
+                          $this->_response->getBody());
+  }
+
+
+  /** @test */
+  public function shouldContainsDeveloppementLocalRecordLink() {
+    $this->assertXPathContentContains('//a[contains(@href, "/authority-search/index/expressionRecherche/local/record_id/1")]',
+                                      'Développement local');
+  }
+
+
+  /** @test */
+  public function shouldContainsTypeMatiereNomCommun() {
+    $this->assertXPathContentContains('//span', '(matière nom commun, TESS)');
+  }
+}
+
+
+
+
+class AuthoritiesAuthoritySearchControllerWithExpressionLocalLimitedToFacetHMOTSAndAResultTest
+  extends AuthoritiesTestCase {
+
+  public function setUp() {
+    parent::setUp();
+
+    Zend_Registry::set('sql',
+                       $this->mock()
+                       ->whenCalled('fetchAll')
+                       ->with("select id_notice, facettes from notices Where (MATCH(titres, auteurs, editeur, collection, matieres, dewey) AGAINST('+(LOCAL LOCAUX LOKAL)' IN BOOLEAN MODE) and MATCH(facettes) AGAINST('+HMOTS' IN BOOLEAN MODE)) and type=2 order by (MATCH(titres) AGAINST(' LOCAL') * 1.5) + (MATCH(auteurs) AGAINST(' LOCAL')) desc", true, false)
+                       ->answers([[1, '']]));
+
+    $this->dispatch('/authority-search/index/facettes/HMOTS/expressionRecherche/local');
+  }
+
+
+  /** @test */
+  public function titleShouldBeParcoursDesAutorites() {
+    $this->assertXPathContentContains('//h1', 'Parcours des autorités');
+  }
+
+
+  /** @test */
+  public function shouldDisplaySearchInputWithValue() {
+    $this->assertXPath('//form[contains(@action, "/authority-search/index/facettes/HMOTS")][not(contains(@action, "/expressionRecherche"))]//input[@name="expressionRecherche"][@value="local"]');
+  }
+
+
+  /** @test */
+  public function shouldContainsIlYAUnResultat() {
+    $this->assertContains('Il y a <span class="nombre-recherche"> 1 </span> résultat',
+                          $this->_response->getBody());
+  }
+
+
+  /** @test */
+  public function shouldContainsDeveloppementLocalRecordLink() {
+    $this->assertXPathContentContains('//a[contains(@href, "/authority-search/index/facettes/HMOTS/expressionRecherche/local/record_id/1")]',
+                                      'Développement local');
+  }
+
+
+  /** @test */
+  public function shouldContainsTypeMatiereNomCommun() {
+    $this->assertXPathContentContains('//span', '(matière nom commun, TESS)');
+  }
+}
+
+
+
+
+class AuthoritiesAuthoritySearchControllerWithRecordIdTest
+  extends AuthoritiesTestCase {
+
+  protected function _urlToRecord($id) {
+    return '/authority-search/index/expressionRecherche/local/record_id/' . $id;
+  }
+
+
+  public function setUp() {
+    parent::setUp();
+
+    $this->fixture('Class_Exemplaire',
+                   ['id' => 234,
+                    'type' => Class_Notice::TYPE_AUTHORITY,
+                    'id_notice' => 4,
+                    'id_origine' => '185349']);
+
+    $this->fixture('Class_Exemplaire',
+                   ['id' => 238,
+                    'type' => Class_Notice::TYPE_AUTHORITY,
+                    'id_notice' => 5,
+                    'id_origine' => '189260']);
+
+    $this->dispatch($this->_urlToRecord(1));
+  }
+
+
+  public function sectionsWithLink() {
+    return [
+            ['Terme générique', 'Aménagement du territoire', 4],
+            ['Terme spécifique', 'Contrat de pays', 5],
+    ];
+  }
+
+
+  /**
+   * @test
+   * @dataProvider sectionsWithLink
+   */
+  public function shouldContainsSectionWithLinkTo($section_label, $relation_label, $record_id) {
+    $this->assertXPathContentContains('//h3', $section_label);
+    $this->assertXPathContentContains('//a[contains(@href, "' . $this->_urlToRecord($record_id) . '")]',
+                                      $relation_label);
+  }
+
+
+  /** @test */
+  public function shouldContainsRejectedTermDL() {
+    $this->assertXPathContentContains('//h3', 'Terme rejeté');
+    $this->assertXPathContentContains('//li', 'DL');
+  }
+
+
+  /** @test */
+  public function shouldContainsLinkedTermActeurLocalEncore() {
+    $this->assertXPathContentContains('//h3', 'Terme associé');
+    $this->assertXPathContentContains('//li', 'Acteur local encore');
+  }
+
+
+  /** @test */
+  public function shouldContainsNotes() {
+    $this->assertXPathContentContains('//h3', 'Note d\'application');
+    $this->assertXPathContentContains('//p', 'lorsque le besoin');
+  }
+
+
+  /** @test */
+  public function shouldContainsUsages() {
+    $this->assertXPathContentContains('//p', 'Utilisé dans aucune notice');
+  }
+}
+
+
+
+
 class AuthoritiesNoticeAjaxControllerTest extends AuthoritiesTestCase {
   public function setUp() {
     parent::setUp();
diff --git a/tests/scenarios/SearchResult/SearchResultTest.php b/tests/scenarios/SearchResult/SearchResultTest.php
index a089ced7d727c63a046101b55253bde82a2762b3..2e4c525cbd2698f5c18f85073a222221d19db314 100644
--- a/tests/scenarios/SearchResult/SearchResultTest.php
+++ b/tests/scenarios/SearchResult/SearchResultTest.php
@@ -32,7 +32,7 @@ class SearchResultHeaderTest extends AbstractControllerTestCase {
                                                     ->setController('recherche')
                                                     ->setAction('resultat')
                                                     ->setSubAction('simple'),
-                                                    ['header_composition' => 'Advanced;History;Display;Order;PageSize;']);
+                                                    ['header_composition' => 'Advanced;History;Display;Order;PageSize;SearchMode']);
 
     $this->dispatch('/opac/recherche/pomme', true);
   }
@@ -84,6 +84,14 @@ class SearchResultHeaderTest extends AbstractControllerTestCase {
   public function domainSelectorShouldBeAnAutoCompleteSelector() {
     $this->assertXPath('//form//input[@name="rech_collection"]');
   }
+
+
+  /** @test */
+  public function searchModeWidgetShouldSelect() {
+    $this->assertXPathContentContains('//form//select[@name="in_files"]//option[@selected][@value="0"]',
+                                      'Index seulement',
+                                      $this->_response->getBody());
+  }
 }
 
 
diff --git a/tests/scenarios/Security/SearchTest.php b/tests/scenarios/Security/SearchTest.php
index 8e747c73728aaece7e12b84aac2d4f5d008efd42..871731f719c8c0006aee056b1bd0c693032ff99d 100644
--- a/tests/scenarios/Security/SearchTest.php
+++ b/tests/scenarios/Security/SearchTest.php
@@ -48,7 +48,8 @@ class Security_SearchTest extends AbstractControllerTestCase {
                               'type_doc ' => '1',
                               'annexe' => '1',
                               'section' => '1',
-                              'genre' => ''];
+                              'genre' => '',
+                              'in_files' => 1];
 
     $this->onLoaderOfModel('Class_TypeDoc')
          ->whenCalled('findUsedTypeDocIds')