diff --git a/VERSIONS b/VERSIONS
index 2533a0bf2f3d0c17b33172addd13cca39acd69d1..3e8b1d96f2ca922c6f36590c8ac5927a28051f9d 100644
--- a/VERSIONS
+++ b/VERSIONS
@@ -1,3 +1,20 @@
+11/03/2021 - v8.0.108
+
+ - ticket #125740 : Magasin de thèmes : Ajout des réservations PNB dans l'onglet des réservations.
+
+ - ticket #129753 : Magasin de thèmes : Correction d'une mise à jour non nécessaire des configurations de boites.
+
+ - ticket #128947 : Magasin de thèmes :
+     Correction de l'affichage des formulaires de recherche pour les nouveaux thèmes.
+     Pour les thèmes existants, si besoin, il est nécessaire de supprimer les hydratations de span class search_axe_label et span class search_axe_operator_prefix puis de mettre à jour le thème.
+
+ - ticket #128992 : Amélioration des performances.
+
+ - ticket #123732 : Explorateur de fichiers : Amélioration des icones des fichiers.
+
+ - ticket #128703 : PNB Dilicom : Amélioration du comportement des liens d'emprunt et de consultation.
+
+
 08/03/2021 - v8.0.107
 
  - ticket #127690 : PNB Dilicom : Correction d'une erreur dans le calcul de validité quand les licences PNB ont un type de durée exprimée en mois.
diff --git a/application/modules/opac/views/scripts/bib-numerique/loan-book.phtml b/application/modules/opac/views/scripts/bib-numerique/loan-book.phtml
index 002edf7c13e2538da6fc8c7ca98b9d610394464f..a94ff7751ae3a809ec0253c0ca1f1d47377080cc 100644
--- a/application/modules/opac/views/scripts/bib-numerique/loan-book.phtml
+++ b/application/modules/opac/views/scripts/bib-numerique/loan-book.phtml
@@ -1,5 +1,8 @@
 <?php
 
+Class_ScriptLoader::getInstance()
+->addOPACScript('subModal')
+->addJQueryReady('initializeAjaxFormSubmit($("#pnb_devices"));');
 
 $html =  [$this->renderForm($this->form)];
 
diff --git a/cosmogramme/sql/patch/patch_403.php b/cosmogramme/sql/patch/patch_403.php
index ad16f277f96e4772cf990c91df768db9517e185c..83051efb59664836937d283057a7b6b72d69d834 100644
--- a/cosmogramme/sql/patch/patch_403.php
+++ b/cosmogramme/sql/patch/patch_403.php
@@ -1,12 +1,2 @@
 <?php
-$adapter = Zend_Db_Table_Abstract::getDefaultAdapter();
-
-try {
-  $adapter->query(
-                  'ALTER TABLE `session_activity` '
-                  . 'ADD COLUMN `everybody_can_subscribe` boolean  default false, '
-                  . 'ADD COLUMN `age_child_min` int(11) unsigned not null default 0, '
-                  . 'CHANGE `date_limite_inscription` `date_limite_fin` date NOT NULL, '
-                  . 'ADD `date_limite_debut` date NOT NULL'
-  );
-} catch(Exception $e) {}
+(new Class_Migration_CleanProfileModules)->run();
diff --git a/cosmogramme/sql/patch/patch_404.php b/cosmogramme/sql/patch/patch_404.php
new file mode 100644
index 0000000000000000000000000000000000000000..ad16f277f96e4772cf990c91df768db9517e185c
--- /dev/null
+++ b/cosmogramme/sql/patch/patch_404.php
@@ -0,0 +1,12 @@
+<?php
+$adapter = Zend_Db_Table_Abstract::getDefaultAdapter();
+
+try {
+  $adapter->query(
+                  'ALTER TABLE `session_activity` '
+                  . 'ADD COLUMN `everybody_can_subscribe` boolean  default false, '
+                  . 'ADD COLUMN `age_child_min` int(11) unsigned not null default 0, '
+                  . 'CHANGE `date_limite_inscription` `date_limite_fin` date NOT NULL, '
+                  . 'ADD `date_limite_debut` date NOT NULL'
+  );
+} catch(Exception $e) {}
diff --git a/library/Class/File/Mime.php b/library/Class/File/Mime.php
index 9c1039082528163871605ea153bb874846766808..e8a3d4c6630f75f539c515d3ac5e4b039370d8c2 100644
--- a/library/Class/File/Mime.php
+++ b/library/Class/File/Mime.php
@@ -225,4 +225,26 @@ class Class_File_Mime {
 
     return static::getType($ext);
   }
+
+
+  public static function isWebImage($ext) {
+    return in_array($ext,
+                    ['gif', 'jpeg', 'jpe', 'jpg', 'png', 'bmp']);
+  }
+
+
+  public static function isCss($ext) {
+    return 'css' == $ext;
+  }
+
+
+  public static function isPdf($ext) {
+    return 'pdf' == $ext;
+  }
+
+
+  public static function isCode($ext) {
+    return in_array($ext,
+                    ['xml', 'php', 'html', 'htm']);
+  }
 }
\ No newline at end of file
diff --git a/library/Class/FileManager.php b/library/Class/FileManager.php
index 1ce30d4a008171f2772837493d875b353d2c739d..1bf1fd4b3d037d774f74370701da66f41148e2a2 100644
--- a/library/Class/FileManager.php
+++ b/library/Class/FileManager.php
@@ -24,7 +24,8 @@ class Class_FileManager extends Class_Entity {
 
   protected static
     $_file_system,
-    $_open_bar = false;
+    $_open_bar = false,
+    $_icon;
 
   const REGEX_NAME = '/^[a-z0-9][a-z0-9_\-\.]+$/i';
   protected $_attribs = ['Id' => '',
@@ -365,6 +366,14 @@ class Class_FileManager extends Class_Entity {
   }
 
 
+  protected static function _icon() {
+    if (isset(static::$_icon))
+      return static::$_icon;
+
+    return static::$_icon = new Class_FileManager_Icon;
+  }
+
+
   public function callGetterByAttributeName($name) {
     return parent::get($name);
   }
@@ -427,9 +436,7 @@ class Class_FileManager extends Class_Entity {
 
 
   public function getFontAwesome() {
-    return $this->isDir()
-      ? $this->_getFolderIcon()
-      : 'fa fa-file-o';
+    return static::_icon()->iconFor($this);
   }
 
 
@@ -478,7 +485,7 @@ class Class_FileManager extends Class_Entity {
 
 
   public function isImage() {
-    return $this->isResizable() || $this->isSelectable();
+    return $this->isFile() && Class_File_Mime::isWebImage($this->getExtension());
   }
 
 
@@ -493,13 +500,6 @@ class Class_FileManager extends Class_Entity {
   }
 
 
-  protected function _getFolderIcon() {
-    return $this->isOpen()
-      ? 'fa fa-folder-open-o'
-      : 'fa fa-folder-o';
-  }
-
-
   public function getDir() {
     return $this->_attribs['Dir'];
   }
diff --git a/library/Class/FileManager/Icon.php b/library/Class/FileManager/Icon.php
new file mode 100644
index 0000000000000000000000000000000000000000..1e487fd94075dc33a366db6786dabdaa4fe74da7
--- /dev/null
+++ b/library/Class/FileManager/Icon.php
@@ -0,0 +1,51 @@
+<?php
+/**
+ * Copyright (c) 2012-2021, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class Class_FileManager_Icon {
+  public function iconFor($item) {
+    return 'fa fa-'
+      . ($item->isDir()
+         ? $this->_folderIconFor($item)
+         : $this->_fileIconFor($item));
+  }
+
+
+  protected function _folderIconFor($item) {
+    return 'folder' . ($item->isOpen() ? '-open' : '') . '-o';
+  }
+
+
+  protected function _fileIconFor($item) {
+    $ext = $item->getExtension();
+
+    if (Class_File_Mime::isCss($ext))
+      return 'css3';
+
+    if (Class_File_Mime::isPdf($ext))
+      return 'file-pdf-o';
+
+    if (Class_File_Mime::isCode($ext))
+      return 'file-code-o';
+
+    return 'file-o';
+  }
+}
diff --git a/library/Class/Hold/Pnb.php b/library/Class/Hold/Pnb.php
index a1dbe400007e885e55383c64c09035a31852acbd..706e868428d78497e345fe59b0c765ab79f6168f 100644
--- a/library/Class/Hold/Pnb.php
+++ b/library/Class/Hold/Pnb.php
@@ -20,16 +20,26 @@
  */
 
 class Class_Hold_PnbLoader extends Storm_Model_Loader {
+
+  protected $_holds_cache;
+
+
   public function findAllOngoingOfUser($user) {
     if (!$user)
       return [];
 
-    $by_user_id = Class_Hold_Pnb::findAllOngoingBy(['user_id' => $user->getId()]);
-    if (!$user->isAbonne())
-      return $by_user_id;
+    $id = $user->getId();
+
+    if (isset($this->_holds_cache[$id]))
+      return $this->_holds_cache[$id];
 
-    return array_unique(array_merge($by_user_id,
-                                    Class_Hold_Pnb::findAllOngoingBy(['subscriber_id' => $user->getIdabon()])));
+    $by_user_id = Class_Hold_Pnb::findAllOngoingBy(['user_id' => $id]);
+
+    return $this->_holds_cache[$id] =
+      ($user->isAbonne()
+       ? array_unique(array_merge($by_user_id,
+                                  Class_Hold_Pnb::findAllOngoingBy(['subscriber_id' => $user->getIdabon()])))
+       : $by_user_id);
   }
 
 
@@ -59,11 +69,13 @@ class Class_Hold_PnbLoader extends Storm_Model_Loader {
 
 
   public function findAllByAlbum($album) {
-    return new Storm_Model_Collection(Class_Hold_Pnb::findAllBy(['record_origin_id' => $album->getIdOrigine(),
-                                                                 'order' => 'hold_date']));
-
+    return $album
+      ? new Storm_Model_Collection(Class_Hold_Pnb::findAllBy(['record_origin_id' => $album->getIdOrigine(),
+                                                              'order' => 'hold_date']))
+      : new Storm_Model_Collection();
   }
 
+
   public function countPendingByAlbum($album){
     return Class_Hold_Pnb::findAllByAlbum($album)->select('isPending')->count();
   }
@@ -148,9 +160,12 @@ class Class_Hold_Pnb extends Storm_Model_Abstract {
 
     $mail = new Class_MailHtml();
 
+    if ( ! $user_mail = $this->getUserMail())
+      return $this;
+
     $status = $mail->sendMail($subject,
                               Class_AdminVar::getValueOrDefault('DILICOM_PNB_HOLD_AVAILABLE_MAIL'),
-                              $this->getUser()->getMail(),
+                              $user_mail,
                               [
                                'user_name' => $this->getUserFullName(),
                                'record_url' => $this->getRecordAbsoluteUrl(),
@@ -161,7 +176,7 @@ class Class_Hold_Pnb extends Storm_Model_Abstract {
     $this->getLogger()
          ->log(sprintf('[MAIL]%s %s : %s',
                        $status ? ('[' . $status . ']') : '',
-                       $this->getUser()->getMail(),
+                       $user_mail,
                        $subject),
                'debug');
 
@@ -169,6 +184,13 @@ class Class_Hold_Pnb extends Storm_Model_Abstract {
   }
 
 
+  public function getUserMail() {
+    return ($user = $this->getUser())
+      ? $user->getMail()
+      : '';
+  }
+
+
   public function setUser($user) {
     parent::_set('user', $user);
     return $this->setSubscriberId($user->getIdabon());
@@ -211,6 +233,11 @@ class Class_Hold_Pnb extends Storm_Model_Abstract {
   }
 
 
+  public function getTitre() {
+    return $this->getTitle();
+  }
+
+
   public function getMainAuthor() {
     return ($album = $this->getAlbum())
       ? $album->getMainAuthorName()
@@ -218,6 +245,11 @@ class Class_Hold_Pnb extends Storm_Model_Abstract {
   }
 
 
+  public function getAuteur() {
+    return $this->getMainAuthor();
+  }
+
+
   public function getRecord() {
     return ($album = $this->getAlbum())
       ? $album->getNotice()
@@ -225,6 +257,11 @@ class Class_Hold_Pnb extends Storm_Model_Abstract {
   }
 
 
+  public function getNoticeOPAC() {
+    return $this->getRecord();
+  }
+
+
   public function getRecordAbsoluteUrl() {
     return ($record = $this->getRecord())
       ? $record->getAbsoluteUrl()
@@ -262,4 +299,26 @@ class Class_Hold_Pnb extends Storm_Model_Abstract {
     if ($this->isNew())
       $this->setHoldDate($this->getCurrentDateTime());
   }
+
+
+  public function humanHoldDate() {
+    return strftime('%x', strtotime($this->getHoldDate()));
+  }
+
+
+  public function humanHoldStatus() {
+    if ($this->isPending())
+      return $this->_('En attente');
+
+    $expiration_date = strftime('%x', strtotime($this->getExpirationDate()));
+
+    if ($this->isAllocated())
+      return $this->_('Disponible jusqu\'au %s',
+                      $expiration_date);
+
+    return $this->isExpired()
+      ? $this->_('Expirée depuis le %s',
+                 $expiration_date)
+      : '';
+  }
 }
diff --git a/library/Class/Migration/CleanProfileModules.php b/library/Class/Migration/CleanProfileModules.php
new file mode 100644
index 0000000000000000000000000000000000000000..7dbac79b6b967460e1405209b5fa710078336551
--- /dev/null
+++ b/library/Class/Migration/CleanProfileModules.php
@@ -0,0 +1,49 @@
+<?php
+/**
+ * Copyright (c) 2012-2021, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class Class_Migration_CleanProfileModules {
+  public function run() {
+    foreach(Class_Profil::findAll() as $profile)
+      $this->_runOne($profile);
+  }
+
+
+  protected function _runOne($profile) {
+    $cfg_accueil = $profile->getCfgAccueilAsArray();
+    if (!isset($cfg_accueil['modules']))
+      return;
+
+    $modules = array_filter($cfg_accueil['modules'],
+                            function($key)
+                            {
+                              return 'no_' !== substr($key, 0, 3);
+                            },
+                            ARRAY_FILTER_USE_KEY);
+
+    if (count($modules) == count($cfg_accueil['modules']))
+      return;
+
+    $cfg_accueil['modules'] = $modules;
+    $profile->setCfgAccueil($cfg_accueil)
+            ->save();
+  }
+}
diff --git a/library/Class/Systeme/Widget/Abstract.php b/library/Class/Systeme/Widget/Abstract.php
index a29da821b763d22a3c20f85110ac585a47a9422d..50e203509e069aceeb7c706d5f956487a4e8053b 100644
--- a/library/Class/Systeme/Widget/Abstract.php
+++ b/library/Class/Systeme/Widget/Abstract.php
@@ -90,9 +90,7 @@ abstract class Class_Systeme_Widget_Abstract extends Class_Entity {
 
   public function loadFromSettings($settings) {
     return $this
-      ->setProfileId(Class_Profil::getCurrentProfil()->getId())
       ->setId('no_' . uniqid())
-      ->init()
       ->_transmute($settings)
       ->setTitle($this->_getTitle())
       ->setForm($this->_getForm())
@@ -121,7 +119,9 @@ abstract class Class_Systeme_Widget_Abstract extends Class_Entity {
 
 
   public function updateProfile() {
-    return $this->_update();
+    return $this->getProfileId()
+      ? $this->_update()
+      : false;
   }
 
 
diff --git a/library/Class/User/Cards.php b/library/Class/User/Cards.php
index fd9c4cc1fd82f2473884ec7672dccc520a70d2db..da392153c36f90c382d7ededf2156ff2aea192c0 100644
--- a/library/Class/User/Cards.php
+++ b/library/Class/User/Cards.php
@@ -127,6 +127,11 @@ class Class_User_Cards extends Storm_Model_Collection {
   }
 
 
+  public function getPNBHolds() {
+    return $this->_decorateOperationFrom(function($card) { return new Storm_Collection($card->getPNBHolds()); });
+  }
+
+
   public function getHoldTitle($id) {
     $hold = $this->getHolds()
                  ->detect(function($hold) use($id) { return $hold->getId() == $id; });
diff --git a/library/Class/Users.php b/library/Class/Users.php
index 0aef2b02b4e5f8b28b148a0dc1746cc05499c2b0..35b7caaa737937409ad0840bcb8dfb486f690e90 100644
--- a/library/Class/Users.php
+++ b/library/Class/Users.php
@@ -1329,6 +1329,13 @@ class Class_Users extends Storm_Model_Abstract {
   }
 
 
+  public function getPNBHolds() {
+    return Class_AdminVar::isModuleEnabled('DILICOM_PNB_ENABLE_HOLDS')
+      ? Class_Hold_Pnb::findAllOngoingOfUser($this)
+      : [];
+  }
+
+
   protected function _getOngoingPnbHolds() {
     if ($this->_ongoing_pnb_holds)
       return $this->_ongoing_pnb_holds;
diff --git a/library/ZendAfi/Controller/Plugin/XHProfile.php b/library/ZendAfi/Controller/Plugin/XHProfile.php
index 57e9d65049d81834882d34fe6237bcbb57a98a9c..8b8ee018b749ab05f07c8ee71c61defa0a643d67 100644
--- a/library/ZendAfi/Controller/Plugin/XHProfile.php
+++ b/library/ZendAfi/Controller/Plugin/XHProfile.php
@@ -20,30 +20,29 @@
  */
 
 class ZendAfi_Controller_Plugin_XHProfile extends Zend_Controller_Plugin_Abstract {
+  const WITHOUT_AUTH_KEY = 'xhprof_without_authentication';
+
   protected
+    $_without_authentication = false,
     $_enabled = false;
 
-  public function setRootUrl($url) {
-    $this->_root_url = $url;
-    return $this;
+
+  public function __construct($config) {
+    if ($config && null !== $config->get(static::WITHOUT_AUTH_KEY))
+      $this->_without_authentication = true;
   }
 
 
-  public function preDispatch(Zend_Controller_Request_Abstract  $request) {
-    if (! (Class_Users::isCurrentUserSuperAdmin() || ZendAfi_Auth_Others::getInstance()->isSuperAdminLogged()))
+  public function preDispatch(Zend_Controller_Request_Abstract $request) {
+    if (!$this->_canProfile($request))
       return;
 
-    if (function_exists('xhprof_enable')) {
-      $view=new ZendAfi_Controller_Action_Helper_View();
+    $view = new ZendAfi_Controller_Action_Helper_View();
 
-      Class_ScriptLoader::getInstance()
-        ->addJQueryReady('if($("a.hx_prof").length) return false; $("<li><a class=\"hx_prof\" href=\"#\">Profile XHProf</a></li>")
+    Class_ScriptLoader::getInstance()
+      ->addJQueryReady('if($("a.hx_prof").length) return false; $("<li><a class=\"hx_prof\" href=\"#\">Profile XHProf</a></li>")
                             .attr("onclick", "window.open(\''.$view->url(["xhprof" => 1]).'\', \'_blank\')")
                             .appendTo(\'body .menu_admin_front .dev_tools\')');
-    }
-
-    if (!$request->getParam('xhprof', false))
-      return;
 
     $this->_enabled = true;
     xhprof_enable();
@@ -54,9 +53,6 @@ class ZendAfi_Controller_Plugin_XHProfile extends Zend_Controller_Plugin_Abstrac
     if (!$this->_enabled)
       return;
 
-    $view=new ZendAfi_Controller_Action_Helper_View();
-
-
     $xhprof_data = xhprof_disable();
     require_once "xhprof/xhprof_lib/utils/xhprof_lib.php";
     require_once "xhprof/xhprof_lib/utils/xhprof_runs.php";
@@ -67,6 +63,12 @@ class ZendAfi_Controller_Plugin_XHProfile extends Zend_Controller_Plugin_Abstrac
       ->setRedirect(BASE_URL."/xhprof/xhprof_html/index.php?run={$run_id}&source=xhprof_testing\n")
       ->clearBody();
   }
-}
 
-?>
\ No newline at end of file
+
+  protected function _canProfile($request) {
+    return ($this->_without_authentication
+            || ZendAfi_Auth_Others::getInstance()->isSuperAdminLogged())
+      && function_exists('xhprof_enable')
+      && $request->getParam('xhprof', false);
+  }
+}
diff --git a/library/ZendAfi/Form/Decorator/SearchAxeLabel.php b/library/ZendAfi/Form/Decorator/SearchAxeLabel.php
index 34a0a21acc44964405b29ce99396188bfef8ba1c..ef23ee10d6bdf617b4efea166fe7f9a5374effbf 100644
--- a/library/ZendAfi/Form/Decorator/SearchAxeLabel.php
+++ b/library/ZendAfi/Form/Decorator/SearchAxeLabel.php
@@ -53,7 +53,7 @@ class ZendAfi_Form_Decorator_SearchAxeLabel extends Zend_Form_Decorator_Label {
     }
 
     if (!empty($label)) {
-      $options['class'] = $class;
+      $options['class'] = trim($class . ' search_axe_label');
       $label = $view->formLabel($element->getFullyQualifiedName(), trim($label), $options);
     } else {
       $label = '&nbsp;';
@@ -62,7 +62,7 @@ class ZendAfi_Form_Decorator_SearchAxeLabel extends Zend_Form_Decorator_Label {
     if (null !== $tag) {
       require_once 'Zend/Form/Decorator/HtmlTag.php';
       $decorator = new Zend_Form_Decorator_HtmlTag();
-      $decorator->setOptions(array('tag' => $tag));
+      $decorator->setOptions(['tag' => $tag]);
       $label = $decorator->render($label);
     }
 
@@ -76,7 +76,7 @@ class ZendAfi_Form_Decorator_SearchAxeLabel extends Zend_Form_Decorator_Label {
                                                  'and not' => $this->_('sauf')]),
                               ['class' => 'search_axe_operator_prefix']);
 
-    $label = $view->tag($wrap['tag'], $form_select . $label);
+    $label = $view->tag($wrap['tag'], $form_select . $label, ['class' => 'search_axe_label_and_operator']);
 
     switch ($placement) {
       case self::APPEND:
diff --git a/library/ZendAfi/View/Helper/Abonne/HoldsPNB.php b/library/ZendAfi/View/Helper/Abonne/HoldsPNB.php
index ff6089c49bcc3f8b550a31f03ee161fd8a659cce..13d3a32ae0a45ded7d1db31fb86e1d5942871b74 100644
--- a/library/ZendAfi/View/Helper/Abonne/HoldsPNB.php
+++ b/library/ZendAfi/View/Helper/Abonne/HoldsPNB.php
@@ -43,7 +43,7 @@ class ZendAfi_View_Helper_Abonne_HoldsPNB extends ZendAfi_View_Helper_Abonne_Ope
 
 
   public function renderHoldDate($hold) {
-    return date('d/m/Y', strtotime($hold->getHoldDate()));
+    return $hold->humanHoldDate();
   }
 
 
@@ -53,17 +53,7 @@ class ZendAfi_View_Helper_Abonne_HoldsPNB extends ZendAfi_View_Helper_Abonne_Ope
 
 
   public function renderHoldStatus($hold) {
-    if ($hold->isPending())
-      return $this->_('En attente');
-
-    $expiration_date = date('d/m/Y', strtotime($hold->getExpirationDate()));
-    if ($hold->isAllocated())
-      return $this->_('Disponible jusqu\'au %s',
-                      $expiration_date);
-
-    if ($hold->isExpired())
-      return $this->_('Expirée depuis le %s',
-                      $expiration_date);
+    return $hold->humanHoldStatus();
   }
 
 
diff --git a/library/ZendAfi/View/Helper/TagDilicomWidget.php b/library/ZendAfi/View/Helper/TagDilicomWidget.php
index 100677644732de95553bdd9db7ce3cdf775f2fc3..0ddd75efe01465f47a0ca076eb19368b1eb9317b 100644
--- a/library/ZendAfi/View/Helper/TagDilicomWidget.php
+++ b/library/ZendAfi/View/Helper/TagDilicomWidget.php
@@ -78,7 +78,8 @@ class ZendAfi_View_Helper_TagDilicomWidget extends ZendAfi_View_Helper_BaseHelpe
                            'action' => $this->_getConsultBookAction(),
                            'id' => $this->_album->getId()],
                           $this->_('Consulter le livre en ligne (depuis la médiathèque)'),
-                          ['data-popup' => 'true']);
+                          ['data-popup' => 'true',
+                           'data-disabled' => 1]);
   }
 
 
@@ -126,7 +127,8 @@ class ZendAfi_View_Helper_TagDilicomWidget extends ZendAfi_View_Helper_BaseHelpe
                                       'action' => $this->_getLoanBookAction(),
                                       'id' => $this->_album->getId()],
                                      $this->_('Emprunter le livre au format EPUB'),
-                                     ['data-popup' => 'true']);
+                                     ['data-popup' => 'true',
+                                      'data-disabled' => true]);
 
     if (!$this->_user)
       return $loanBookAnchor;
diff --git a/library/ZendAfi/View/Helper/Thumbnail.php b/library/ZendAfi/View/Helper/Thumbnail.php
index 23c9bf0fab5412645595cd8834fda5510e831f35..8c0f8a4d769a00dcf0be30ba5e8eb5d2f25a3ce4 100644
--- a/library/ZendAfi/View/Helper/Thumbnail.php
+++ b/library/ZendAfi/View/Helper/Thumbnail.php
@@ -23,6 +23,9 @@
 class ZendAfi_View_Helper_Thumbnail extends ZendAfi_View_Helper_BaseHelper {
 
   public function thumbnail($instance, $width = 160, $height = 150) {
+    if ($instance && !$instance->isImage())
+      return $this->_tag('i', '', ['class' => $instance->getFontAwesome()]);
+
     $resized_image = (new Class_Notice_Thumbnail_ResizeImage($instance))
       ->thumbnail($width, $height);
 
diff --git a/library/startup.php b/library/startup.php
index 78680404effaf3b6f4d4baa39cef03fe878c9cba..6d5c1e0efe90ae1c6dcd6fa6988fb4e77931f44a 100644
--- a/library/startup.php
+++ b/library/startup.php
@@ -84,7 +84,7 @@ class Bokeh_Engine {
 
   function setupConstants() {
     defineConstant('BOKEH_MAJOR_VERSION','8.0');
-    defineConstant('BOKEH_RELEASE_NUMBER', BOKEH_MAJOR_VERSION . '.107');
+    defineConstant('BOKEH_RELEASE_NUMBER', BOKEH_MAJOR_VERSION . '.108');
 
     defineConstant('BOKEH_REMOTE_FILES', 'https://git.afi-sa.net/afi/opacce/');
 
@@ -334,6 +334,7 @@ class Bokeh_Engine {
       ->registerPlugin(new ZendAfi_Controller_Plugin_PhpParser())
       ->registerPlugin(new ZendAfi_Controller_Plugin_FeaturesTracking())
       ->registerPlugin(new ZendAfi_Controller_Plugin_LastSearch())
+      ->registerPlugin(new ZendAfi_Controller_Plugin_XHProfile($this->_config))
       ->setParam('useDefaultControllerAlways', false);
   }
 
@@ -344,11 +345,6 @@ class Bokeh_Engine {
       ->setBaseURL(BASE_URL);
 
     $this->setupRoutes($this->_front_controller, $this->_config);
-
-    if (! (Class_Users::isCurrentUserSuperAdmin() || ZendAfi_Auth_Others::getInstance()->isSuperAdminLogged()))
-      return $this;
-
-    $this->_front_controller->registerPlugin(new ZendAfi_Controller_Plugin_XHProfile());
     return $this;
   }
 
diff --git a/library/storm b/library/storm
index aad764a8e1f92d102da4e194ea564c398d20d7be..de6da8ef5032cf1c9dc2876a955c7ec91c894328 160000
--- a/library/storm
+++ b/library/storm
@@ -1 +1 @@
-Subproject commit aad764a8e1f92d102da4e194ea564c398d20d7be
+Subproject commit de6da8ef5032cf1c9dc2876a955c7ec91c894328
diff --git a/library/templates/Intonation/Library/Settings.php b/library/templates/Intonation/Library/Settings.php
index 3827cc7819073a8eae5679c51c088dc56d8ef03d..1bd2842491d5fce2b0dde7e8f118401b67cbee50 100644
--- a/library/templates/Intonation/Library/Settings.php
+++ b/library/templates/Intonation/Library/Settings.php
@@ -161,9 +161,10 @@ class Intonation_Library_Settings extends Intonation_System_Abstract {
                                                   'div class search_result' => 'px-3 px-sm-0',
                                                   'h1 class content_title' => 'py-2 px-0 my-2 mx-0 border-bottom',
                                                   'h2 class jumbotron_section_title' => 'text-dark',
-                                                  'span class search_axe_operator_prefix' => 'col-4 col-sm-2',
-                                                  'span class search_axe_label' => 'col-6 col-sm-3 pl-3',
-                                                  'span class search_axe_input' => 'col-12 col-sm-7',
+                                                  'span class search_axe_operator_prefix' => 'col-5',
+                                                  'label class search_axe_label' => 'ml-3 col-6',
+                                                  'span class search_axe_input' => '',
+                                                  'div class search_axe_label_and_operator' => 'col-12 col-sm-5 row no-gutters',
                                                   'p class opened' => 'text-white bg-success p-1 rounded d-inline-block',
                                                   'p class closed' => 'text-white bg-danger p-1 rounded d-inline-block',
                                                   'p class empty_list_message' => 'text-warning px-4',
@@ -268,6 +269,9 @@ class Intonation_Library_Settings extends Intonation_System_Abstract {
                                                   'span class hold_state' => 'badge-secondary',
                                                   'span class hold_rank' => 'badge-secondary',
                                                   'span class hold_expiration' => 'badge-warning',
+                                                  'span class pnb_hold_date' => 'badge-info',
+                                                  'span class pnb_hold_status' => 'badge-secondary',
+                                                  'span class pnb_hold_rank' => 'badge-secondary',
                                                   'span class by_idabon' => 'd-none',
                                                   'span class loan_late' => 'badge-danger',
                                                   'span class loan_issue_date' => 'badge-info',
diff --git a/library/templates/Intonation/Library/View/Wrapper/Hold.php b/library/templates/Intonation/Library/View/Wrapper/Hold.php
index df209f1be28caa980b8b14de4f5a7bfdc32c1095..b7ee29c554f98e2aa00900808df8696b4039ed80 100644
--- a/library/templates/Intonation/Library/View/Wrapper/Hold.php
+++ b/library/templates/Intonation/Library/View/Wrapper/Hold.php
@@ -131,16 +131,6 @@ class Intonation_Library_View_Wrapper_Hold extends Intonation_Library_View_Wrapp
     $wrapper = $this->_getWrapper();
 
     return $wrapper->getMainLink();
-
-    if (!$this->_model->isRenewable())
-      return null;
-
-    return new Intonation_Library_Link(['Url' => $this->_view->url(['controller' => 'abonne',
-                                                                    'action' => 'prolongerPret',
-                                                                    'id_pret' => $this->_model->getId()]),
-                                        'Text' => $this->_('Prolonger'),
-                                        'Title' => $this->_('Prolonger l\'emprunt %s',
-                                                            $this->_model->getTitre())]);
   }
 
 
diff --git a/library/templates/Intonation/Library/View/Wrapper/PNBHold.php b/library/templates/Intonation/Library/View/Wrapper/PNBHold.php
new file mode 100644
index 0000000000000000000000000000000000000000..b646d06f7aa29ba4dd34dd4f7850b4e27fbed42d
--- /dev/null
+++ b/library/templates/Intonation/Library/View/Wrapper/PNBHold.php
@@ -0,0 +1,66 @@
+<?php
+/**
+ * Copyright (c) 2012-2018, 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 Intonation_Library_View_Wrapper_PNBHold extends Intonation_Library_View_Wrapper_Hold {
+
+  protected function _addParagraphe($html) {
+    return $html;
+  }
+
+
+  public function getBadges() {
+    $badges = [(new Intonation_Library_Badge)
+               ->setClass('pnb_hold_date')
+               ->setImage($this->getIco('library',
+                                        'agenda'))
+               ->setText($this->_model->humanHoldDate()),
+
+               (new Intonation_Library_Badge)
+               ->setClass('pnb_hold_rank')
+               ->setText($this->_('Rang %d', $this->_model->getOrder()))
+               ->setTitle($this->_plural($this->_model->getOrder() - 1,
+                                         'Le prochain document disponible est pour vous.',
+                                         'Il y a 1 réservation avant la votre.',
+                                         'Il y a %d réservations avant la votre.')),
+
+               (new Intonation_Library_Badge)
+               ->setClass('pnb_hold_status')
+               ->setText($this->_model->humanHoldStatus())
+    ];
+
+    return $this->_view->renderBadges($badges, $this);
+  }
+
+
+  public function getActions() {
+    return
+      [ new Intonation_Library_Link(['Url' => $this->_view->url(['controller' => 'abonne',
+                                                                 'action' => 'delete-pnb-hold',
+                                                                 'id' => $this->_model->getId()]),
+                                     'Text' => $this->_('Supprimer'),
+                                     'Image' => $this->getIco('delete',
+                                                              'utils'),
+                                     'Title' => $this->_('Supprimer la réservation du document %s',
+                                                         $this->_model->getTitre()),
+                                     'Confirm' => $this->_('Confirmez-vous la suppression de la réservation du document %s ? ', $this->_model->getTitre())])];
+  }
+}
\ No newline at end of file
diff --git a/library/templates/Intonation/Library/View/Wrapper/PNBLoan.php b/library/templates/Intonation/Library/View/Wrapper/PNBLoan.php
index d39b356468886695a0ecdc79b28ee709c73dcac6..3b1458baf69f2e379201228b34faefa78f9f92da 100644
--- a/library/templates/Intonation/Library/View/Wrapper/PNBLoan.php
+++ b/library/templates/Intonation/Library/View/Wrapper/PNBLoan.php
@@ -23,19 +23,6 @@
 class Intonation_Library_View_Wrapper_PNBLoan extends Intonation_Library_View_Wrapper_Loan {
 
 
-  public function getMainTitle() {
-    $desc = $this->_('Emprunté par %s %s',
-                     $this->_view->tag('span',
-                                       $this->_model->getUser()->getIdabon(),
-                                       ['class' => 'by_idabon']),
-                     $this->_model->getUser()->getNomComplet());
-
-    return ($record = $this->_getRecord())
-      ? $desc . BR . $record->getMainTitle()
-      : $desc . BR . $this->_model->getTitre();
-  }
-
-
   public function getSecondaryTitle() {
     return ($record = $this->_getRecord())
       ? $record->getSecondaryTitle()
diff --git a/library/templates/Intonation/Library/View/Wrapper/User/RichContent/Holds.php b/library/templates/Intonation/Library/View/Wrapper/User/RichContent/Holds.php
index a86afb38b090dbf27e3a259ec97d30fa0cffcb2a..059b24e05060216b60ed52102731bca762fa7ebb 100644
--- a/library/templates/Intonation/Library/View/Wrapper/User/RichContent/Holds.php
+++ b/library/templates/Intonation/Library/View/Wrapper/User/RichContent/Holds.php
@@ -30,11 +30,18 @@ class Intonation_Library_View_Wrapper_User_RichContent_Holds extends Intonation_
   public function getContent() {
     $cards = new Class_User_Cards($this->_model);
     $holds = $cards->getHolds();
+    $pnb_holds = $cards->getPNBHolds();
 
-    if ($holds->isEmpty())
-      return '';
+    $html = [];
+    if ( ! $holds->isEmpty())
+      $html [] = $this->_view->div(['class' => 'col-12'],
+                                   $this->_view->abonne_Holds($holds));
 
-    return $this->_view->abonne_Holds($holds);
+    if ( ! $pnb_holds->isEmpty())
+      $html [] = $this->_view->div(['class' => 'col-12'],
+                                   $this->_view->abonne_PNBHolds($pnb_holds));
+
+    return $this->_view->grid($html);
   }
 
 
diff --git a/library/templates/Intonation/System/Abstract.php b/library/templates/Intonation/System/Abstract.php
index 028ce832f99f0dc9fc8d282ff689817a018bfc5b..bb9570b3d1d0e36673c717f0b76d393387245672 100644
--- a/library/templates/Intonation/System/Abstract.php
+++ b/library/templates/Intonation/System/Abstract.php
@@ -101,9 +101,8 @@ abstract class Intonation_System_Abstract {
     $settings = array_merge($defaults_settings, $local);
 
     $this->_settings->setNewDatas($settings);
-
-    $this->_settings->updateProfile();
-    $this->_settings = $this->_settings->load();
+    if ($this->_settings->updateProfile())
+      $this->_settings = $this->_settings->load();
 
     return $this;
   }
diff --git a/library/templates/Intonation/View/Abonne/PNBHolds.php b/library/templates/Intonation/View/Abonne/PNBHolds.php
new file mode 100644
index 0000000000000000000000000000000000000000..4c19c79141ca8a850c970f46b84699f7d676975b
--- /dev/null
+++ b/library/templates/Intonation/View/Abonne/PNBHolds.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
+ */
+
+
+class Intonation_View_Abonne_PNBHolds extends ZendAfi_View_Helper_BaseHelper {
+  public function abonne_PNBHolds($holds) {
+    if (empty($holds))
+      return $this->_('Aucune réservation en cours');
+
+    $holds = array_map(function($hold)
+                       {
+                         return (new Intonation_Library_View_Wrapper_PNBHold)
+                           ->setModel($hold)
+                           ->setView($this->view);
+                       }, $holds->getArrayCopy());
+
+    $callback = function($wrapped) {
+      return $this->view->cardifyHorizontal($wrapped);
+    };
+
+    $html = [$this->_div(['class' => 'col-12'],
+                         $this->_tag('h2', $this->_('Mes réservations de documents numériques'))),
+             $this->_div(['class' => 'col-12'],
+                         $this->view->renderCollection(new Storm_Collection($holds)))
+    ];
+    return $this->view->grid($html);
+  }
+}
\ No newline at end of file
diff --git a/library/templates/Intonation/View/RenderActions.php b/library/templates/Intonation/View/RenderActions.php
index 40aa0047ed6bb154e8ee2e0ffab1c976e2232b75..67323c156a36b4c8214335a581788018a3bb3602 100644
--- a/library/templates/Intonation/View/RenderActions.php
+++ b/library/templates/Intonation/View/RenderActions.php
@@ -27,7 +27,6 @@ class Intonation_View_RenderActions extends ZendAfi_View_Helper_BaseHelper {
       if (!$html = $this->view->tagAction($action))
         continue;
 
-
       $action_attribs = $items_attribs;
 
       if ($tag_class = (is_scalar($action) ? '' :  $action->getIdentifier()))
diff --git a/library/templates/Intonation/View/TagAction.php b/library/templates/Intonation/View/TagAction.php
index 1df12ba4486e075c2c6fbb095acc11a97d7d4b32..09b9c4a6e97f123f964ee1b4fa4dc32389b378f4 100644
--- a/library/templates/Intonation/View/TagAction.php
+++ b/library/templates/Intonation/View/TagAction.php
@@ -35,6 +35,9 @@ class Intonation_View_TagAction extends ZendAfi_View_Helper_BaseHelper {
       ? $attribs
       : [];
 
+    if ( $confirm = $action->getConfirm())
+      $attribs ['onclick'] = $this->view->confirm($confirm);
+
     $classes = 'card-link';
 
     if (isset($attribs['class'])) {
@@ -58,7 +61,7 @@ class Intonation_View_TagAction extends ZendAfi_View_Helper_BaseHelper {
 
     $text = $this->_getText($action, $img);
 
-     if (!$content = $img . $text)
+     if ( ! $content = $img . $text)
       return '';
 
     $attribs = array_merge(['title' => $action->getTitle(),
@@ -71,7 +74,7 @@ class Intonation_View_TagAction extends ZendAfi_View_Helper_BaseHelper {
                                $content,
                                $attribs)
       : $this->_tag('a',
-                    (string)$content,
+                    (string) $content,
                     $attribs);
   }
 
diff --git a/public/opac/css/core.css b/public/opac/css/core.css
index 965ae8cfe89c5e0df85cd5917931a181a2b8e284..d78457c6c02f27aad3bcf7ebaa017fb34c9d1b83 100644
--- a/public/opac/css/core.css
+++ b/public/opac/css/core.css
@@ -190,3 +190,7 @@ label.required:after {
 .dropdown-menu .dropdown-menu {
     position: unset !important;
 }
+
+[data-disabled] {
+    pointer-events: none !important;
+}
diff --git a/public/opac/js/subModal.js b/public/opac/js/subModal.js
index 4c0bde278d923b1313f46b0a24965c372e390bf7..057ed2c22acc96837789c19b0ebedebd8ba0f1b2 100644
--- a/public/opac/js/subModal.js
+++ b/public/opac/js/subModal.js
@@ -6,6 +6,9 @@
   var on_open_listeners = [];
 
   window.initializePopups = function() {
+    $('[data-popup="true"][data-disabled], [data-popup="1"][data-disabled]')
+      .removeAttr('data-disabled');
+
     $('[data-popup="true"], [data-popup="1"]')
       .prop('onclick', null)
       .off('click')
diff --git a/tests/application/modules/admin/controllers/FileManagerControllerTest.php b/tests/application/modules/admin/controllers/FileManagerControllerTest.php
index 04bbb1cec03e4c61c87054c6e84a336d22d6a158..db2e613ac62f91e56fc441c14bf30734b7dc434c 100644
--- a/tests/application/modules/admin/controllers/FileManagerControllerTest.php
+++ b/tests/application/modules/admin/controllers/FileManagerControllerTest.php
@@ -424,7 +424,7 @@ class FileManagerControllerBrowseTest extends FileManagerControllerTestCase {
       ->setParentPath('userfiles/css/colors/test/white')
       ->setName('colors.css')
       ->setDir(false)
-      ->setExtension('.css');
+      ->setExtension('css');
 
     Class_FileManager::getFileSystem()
       ->whenCalled('directoriesAt')
@@ -471,7 +471,7 @@ class FileManagerControllerBrowseTest extends FileManagerControllerTestCase {
       ->with('userfiles/css/colors/test/white')
       ->answers([$colors_css]);
 
-    $this->dispatch('/admin/file-manager?browser=userfiles%2Fcss%2Fcolors%2Ftest%2Fwhite', true);
+    $this->dispatch('/admin/file-manager?browser=userfiles%2Fcss%2Fcolors%2Ftest%2Fwhite');
   }
 
 
@@ -499,6 +499,12 @@ class FileManagerControllerBrowseTest extends FileManagerControllerTestCase {
   }
 
 
+  /** @test */
+  public function colorsCssFileIconShouldBeFaCss3() {
+    $this->assertXPath('//table[contains(@id,"file-manager")]//td//i[@class="fa fa-css3"]');
+  }
+
+
   /** @test */
   public function linkToUserfilesFolderShouldBePresent() {
     $this->assertXPath('//ul//div[@class="actions"]//a[contains(@href, "/admin/file-manager/index?browser=userfiles")]');
diff --git a/tests/db/UpgradeDBTest.php b/tests/db/UpgradeDBTest.php
index 79645272b510634388b16fae9d3ce9c95553b8a6..c87944fd38b6ed6803dfeddc58daeefdd386afdd 100644
--- a/tests/db/UpgradeDBTest.php
+++ b/tests/db/UpgradeDBTest.php
@@ -3668,6 +3668,16 @@ class UpgradeDB_402_Test extends UpgradeDBTestCase {
 
 
 class UpgradeDB_403_Test extends UpgradeDBTestCase {
+  public function prepare() {}
+
+  /** @test */
+  public function placeholderForCleanProfileModules() {}
+}
+
+
+
+
+class UpgradeDB_404_Test extends UpgradeDBTestCase {
   public function prepare() {
     $this->dropFieldFrom('session_activity', 'everybody_can_subscribe');
     $this->dropFieldFrom('session_activity', 'date_limite_debut');
@@ -3697,4 +3707,4 @@ class UpgradeDB_403_Test extends UpgradeDBTestCase {
   public function sessionActivityShouldContainsColumnDateLimiteFinAsDate() {
     $this->assertFieldType('session_activity', 'date_limite_fin', 'date');
   }
-}
+}
\ No newline at end of file
diff --git a/tests/library/Class/CatalogueTest.php b/tests/library/Class/CatalogueTest.php
index fef18689bc6eaa258f8eec881da1f67fba02ec7d..7f927b4b09fac53fb04a19a76adae3b60467315c 100644
--- a/tests/library/Class/CatalogueTest.php
+++ b/tests/library/Class/CatalogueTest.php
@@ -544,7 +544,7 @@ class CatalogueTestOAISpec extends ModelTestCase {
 
 
 class CatalogueGetNoticesByPreferencesNotRandomTest extends ModelTestCase {
-  protected $_cache_key = '872ab9cb2f42608b8678ef562af208ae';
+  protected $_cache_key = '5be5b882d00792a8bcf7299b575bb175';
 
   public function setUp() {
     parent::setUp();
diff --git a/tests/library/Class/Migration/CleanProfileModulesTest.php b/tests/library/Class/Migration/CleanProfileModulesTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..8e3942563c908859d63890cb74da208da10fdb66
--- /dev/null
+++ b/tests/library/Class/Migration/CleanProfileModulesTest.php
@@ -0,0 +1,54 @@
+<?php
+/**
+ * Copyright (c) 2012-2021, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class Class_Migration_CleanProfileModulesTest extends ModelTestCase {
+  public function setUp() {
+    parent::setUp();
+    $modules = ['no_60414995adf26' => ['type_module' => 'KIOSQUE'],
+                'no_83849798729e0' => ['type_module' => 'KIOSQUE'],
+                4 => ['type_module' => 'CALENDAR']];
+
+    $this->fixture('Class_Profil',
+                   ['id' => 1,
+                    'cfg_accueil' => ['modules' => $modules]]);
+
+    $this->fixture('Class_Profil',
+                   ['id' => 2,
+                    'cfg_accueil' => ['modules' => $modules]]);
+
+    (new Class_Migration_CleanProfileModules)->run();
+  }
+
+
+  /** @test */
+  public function profilOneCfgAccueilShouldContainsCalendarOnly() {
+    $this->assertEquals([4],
+                        array_keys(Class_Profil::find(1)->getCfgAccueilAsArray()['modules']));
+  }
+
+
+  /** @test */
+  public function profilTwoCfgAccueilShouldContainsCalendarOnly() {
+    $this->assertEquals([4],
+                        array_keys(Class_Profil::find(2)->getCfgAccueilAsArray()['modules']));
+  }
+}
diff --git a/tests/library/ZendAfi/View/Helper/Accueil/SitoTest.php b/tests/library/ZendAfi/View/Helper/Accueil/SitoTest.php
index fea01be34af261c16d53687b264106d55807f680..c240c9aa8c0e6309aad49f616a990502677a2e06 100644
--- a/tests/library/ZendAfi/View/Helper/Accueil/SitoTest.php
+++ b/tests/library/ZendAfi/View/Helper/Accueil/SitoTest.php
@@ -254,7 +254,7 @@ class SitoViewHelperCachedTest extends SitoViewHelperTestCase {
 
   /** @test */
   public function cacheShouldBeUse() {
-    $value = (new Storm_Cache())->getCache()->load('faf677dfb74eb163c47d535bd09a43f3');
+    $value = (new Storm_Cache())->getCache()->load('2834982d0d27025a7755ea93669e9537');
     $this->assertNotEquals(false, $value);
   }
 }
diff --git a/tests/scenarios/Templates/TemplateDigitalResourcesTest.php b/tests/scenarios/Templates/TemplateDigitalResourcesTest.php
index 2c311125ff82c2b19dab4abc59009dda82619c7c..ae31c828753fbdc278c7c2f850016af65271dcf5 100644
--- a/tests/scenarios/Templates/TemplateDigitalResourcesTest.php
+++ b/tests/scenarios/Templates/TemplateDigitalResourcesTest.php
@@ -20,6 +20,8 @@
  */
 
 
+require_once 'tests/fixtures/DilicomFixtures.php';
+
 class TemplateDigitalResourcesDispatchTest extends AbstractControllerTestCase {
   protected $_storm_default_to_volatile = true;
 
@@ -132,3 +134,106 @@ class TemplateDigitalResourcesMediaAndTrailerDispatchTest extends AbstractContro
     $this->assertXPathContentContains('//script', 'initializePopups();');
   }
 }
+
+
+
+
+abstract class TemplateDigitalResourcesDilicomTestCase extends AbstractControllerTestCase {
+
+  protected $_storm_default_to_volatile = true;
+
+
+  public function setUp() {
+    parent::setUp();
+
+    $this->_buildTemplateProfil(['id' => 9,
+                                 'template' => 'MUSCLE']);
+
+    $this->fixture('Class_Bib',
+                   ['id' => 1,
+                    'libelle' => 'annecy',
+                    'gln' => '2222'
+                   ]);
+
+    $group = $this->fixture('Class_UserGroup',
+                            ['id' => '20',
+                             'libelle' => 'Multimedia',
+                             'rights' => [Class_UserGroup::RIGHT_ACCES_PNB_DILICOM]]);
+
+    $logged_user = $this->fixture('Class_Users',
+                                  ['id' => 6,
+                                   'nom'=>'Pito',
+                                   'login'=>'Chat',
+                                   'password'=>'123456',
+                                   'id_site' => 1,
+                                   'idabon' => '12345',
+                                   'user_groups' => [$group]]);
+
+    $logged_user->beAbonneSIGB()->assertSave();
+    ZendAfi_Auth::getInstance()->logUser($logged_user);
+
+    $this->fixture('Class_Album',
+                   ['id' => 34,
+                    'titre' => 'La Planète des chats',
+                    'type_doc_id' => Class_TypeDoc::LIVRE_NUM]);
+  }
+}
+
+
+
+
+class TemplateDigitalResourcesLoanBookAjaxTest extends TemplateDigitalResourcesDilicomTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    $this->dispatch('bib-numerique/loan-book-ajax/id/34');
+  }
+
+
+  /** @test */
+  public function subModalJSShouldBeLoaded() {
+    $this->assertXPath('//script[contains(@src, "public/opac/js/subModal.js")]');
+  }
+
+
+  /** @test */
+  public function initializeAjaxFormSubmitShouldBePresent() {
+    $this->assertXPathContentContains('//script', 'initializeAjaxFormSubmit($("#pnb_devices"));');
+  }
+}
+
+
+
+
+class TemplateDigitalResourcesDilicomItemTest extends TemplateDigitalResourcesDilicomTestCase {
+
+  public function setUp() {
+    parent::setUp();
+
+    $this->fixture('Class_Notice',
+                   ['id' => 123,
+                    'type_doc' => Class_TypeDoc::LIVRE_NUM]);
+
+    $this->fixture('Class_Exemplaire',
+                   ['id' => 8,
+                    'id_origine' => 3,
+                    'id_notice' => 123]);
+
+    (new DilicomFixtures())->albumTotemThora();
+    RessourcesNumeriquesFixtures::activateDilicom();
+
+    $this->dispatch('noticeajax/digital-resources/id_notice/123');
+  }
+
+
+  /** @test */
+  public function loanLinkShouldBeDisabled() {
+    $this->assertXPathContentContains('//a[@data-disabled]', 'Emprunter le livre au format EPUB');
+  }
+
+
+  /** @test */
+  public function consultLinkShouldBeDisabled() {
+    $this->assertXPathContentContains('//a[@data-disabled]', 'Consulter le livre en ligne');
+  }
+}
\ No newline at end of file
diff --git a/tests/scenarios/Templates/TemplatesAbonneTest.php b/tests/scenarios/Templates/TemplatesAbonneTest.php
index c347489c36520e5f11f95b49433c5304a71f4333..b0ba8d2f5886a4df6bfd1d50f0bdd621ee7afe4a 100644
--- a/tests/scenarios/Templates/TemplatesAbonneTest.php
+++ b/tests/scenarios/Templates/TemplatesAbonneTest.php
@@ -1215,4 +1215,87 @@ class TemplatesDispatchAbonnePagesTest extends AbstractControllerTestCase {
     $this->dispatch('/opac/abonne/donner-des-avis/');
     $this->assertXPath('//li/a[@title="josh donne ton avis"]');
   }
+}
+
+
+
+class TemplatesAbonneWithPNBHoldsTest extends AbstractControllerTestCase {
+  protected $_storm_default_to_volatile = true;
+
+
+  public function setUp() {
+    parent::setUp();
+
+    $this->_buildTemplateProfil(['id' => 90,
+                                 'template' => 'CHILI']);
+
+    $group = $this->fixture('Class_UserGroup',
+                            ['id' => '20',
+                             'libelle' => 'Multimedia',
+                             'rights' => [Class_UserGroup::RIGHT_ACCES_PNB_DILICOM]]);
+
+    $logged_user =
+      $this->fixture('Class_Users',
+                     ['id' => 6,
+                      'nom'=>'Pito',
+                      'login'=>'Chat',
+                      'password'=>'123456',
+                      'id_site' => 1,
+                      'idabon' => '12345',
+                      'user_groups' => [$group]]);
+
+    $logged_user
+      ->beAbonneSIGB()
+      ->assertSave();
+
+    ZendAfi_Auth::getInstance()->logUser($logged_user);
+    RessourcesNumeriquesFixtures::activateDilicom();
+
+    $this->fixture('Class_Album',
+                   ['id' => 37,
+                    'id_origine' => 'Dilicom-88817216',
+                    'titre' => 'Les jardins'
+                   ]);
+
+    foreach(range(1, 3) as $index)
+      $this->fixture('Class_Hold_Pnb',
+                     ['id' => $index,
+                      'user_id' => 6,
+                      'record_origin_id' => 'Dilicom-88817216',
+                      'hold_date' => '2018-10-06 22:10:09']);
+
+    Class_AdminVar::set('DILICOM_PNB_ENABLE_HOLDS', 1);
+
+    $this->dispatch('abonne/reservations');
+  }
+
+
+  /** @test */
+  public function h2MesReservationsNumeriquesShouldBePresent() {
+    $this->assertXPathContentContains('//div/h2', 'Mes réservations de documents');
+  }
+
+
+  /** @test */
+  public function holdDateShouldBeInBadgePNBHoldDate() {
+    $this->assertXPathContentContains('//div//span[@class = "badge_tag pnb_hold_date text-left badge badge-info"]', '06/10/2018');
+  }
+
+
+  /** @test */
+  public function holdStatusShouldBeInBadgePNBHoldStatus() {
+    $this->assertXPathContentContains('//div//span[@class = "badge_tag pnb_hold_status text-left badge badge-secondary"]', 'En attente');
+  }
+
+
+  /** @test */
+  public function holdRankShouldBeInBadgePNBRank() {
+    $this->assertXPathContentContains('//div//span[@class = "badge_tag pnb_hold_rank text-left badge badge-secondary"]', 'Rang 1');
+  }
+
+
+  /** @test */
+  public function actionDeleteHoldShouldPresent() {
+    $this->assertXPathContentContains('//div//a[@href="/abonne/delete-pnb-hold/id/6_1"][@class="card-link"]', 'Supprimer');
+  }
 }
\ No newline at end of file
diff --git a/tests/scenarios/Templates/TemplatesAdvancedSearchTest.php b/tests/scenarios/Templates/TemplatesAdvancedSearchTest.php
index 6bfd18266bd78c16c70fcf0cb09699bf3cb96d09..138a675abd7428db44719bbbf5bc8db4eb4fe35e 100644
--- a/tests/scenarios/Templates/TemplatesAdvancedSearchTest.php
+++ b/tests/scenarios/Templates/TemplatesAdvancedSearchTest.php
@@ -148,4 +148,22 @@ class TemplatesDispatchIntonationAdvancedSearchTest extends TemplatesIntonationT
   public function inputTitleFieldShouldBePresent() {
     $this->assertXPath('//input[@name="rech_titres"]');
   }
+
+
+  /** @test */
+  public function searchAxeLabelShouldBePresent() {
+    $this->assertXPath('//label[@class="optional search_axe_label ml-3 col-6"]');
+  }
+
+
+  /** @test */
+  public function divSearchAxeLabelAndOperatorShouldBePresent() {
+    $this->assertXPath('//div[@class="search_axe_label_and_operator col-12 col-sm-5 row no-gutters"]');
+  }
+
+
+  /** @test */
+  public function searchAxeInputShouldBePresent() {
+    $this->assertXPath('//span[@class="search_axe_input"]');
+  }
 }
\ No newline at end of file
diff --git a/tests/scenarios/Templates/TemplatesArticlesTest.php b/tests/scenarios/Templates/TemplatesArticlesTest.php
index 188c635a115ac66f4021cafb467e6a9a70279bb8..34745cd023e7993b9d478b2ceb1fc64626309d29 100644
--- a/tests/scenarios/Templates/TemplatesArticlesTest.php
+++ b/tests/scenarios/Templates/TemplatesArticlesTest.php
@@ -817,6 +817,15 @@ class TemplatesArticlesWithWidgetRenderWallNotLoggedTest
   public function wallJSShouldBeCallWithId() {
     $this->assertXPathContentContains('//script[contains(text(), \'$(function(){$("#no_\')]', '.parent().masonry();});');
   }
+
+
+  /**
+   * @test
+   * @see 129753
+   */
+  public function profilShouldNotBeSavedWithNewModule() {
+    $this->assertEmpty(Class_Profil::getCurrentProfil()->getCfgAccueilAsArray()['modules']);
+  }
 }