From 8cf70968b4d6b558f414756e58e9fd527f2ad8b6 Mon Sep 17 00:00:00 2001
From: gloas <gloas@afi-sa.fr>
Date: Tue, 28 May 2019 12:13:12 +0200
Subject: [PATCH] dev #64573 wip on bootstrap

---
 .../admin/controllers/TemplateController.php  |   9 +-
 .../opac/controllers/AbonneController.php     |  22 ++++
 .../abonne/ajouter-a-la-selection.phtml       |   2 +
 .../opac/views/scripts/abonne/selection.phtml |   2 +
 library/Class/Newsletter.php                  |   3 +-
 library/Class/Notice/Field.php                |   2 +-
 library/Class/PanierNotice.php                |  13 +++
 library/Class/Template.php                    |  16 +++
 library/Class/Template/Settings.php           |   5 +
 .../Controller/Plugin/Manager/Template.php    |  13 ++-
 .../View/Helper/Abonne/NamesOrLogin.php       |  21 ++--
 library/ZendAfi/View/Helper/Accueil/Base.php  |   6 +-
 .../Intonation/Assets/css/intonation.css      |  33 +++++-
 .../templates/Intonation/Library/Settings.php |  44 +++++++-
 .../Intonation/Library/UserPatcher.php        |  63 +++++++++++
 .../Intonation/Library/View/Wrapper/Loan.php  |   3 +
 .../Library/View/Wrapper/Newsletter.php       |   9 ++
 .../Library/View/Wrapper/Record.php           |   5 +-
 .../View/Wrapper/RecordInSelection.php        |  46 ++++++++
 .../Library/View/Wrapper/RecordToSelect.php   |  47 ++++++++
 .../View/Wrapper/RichContent/Section.php      |   5 +
 .../Library/View/Wrapper/Search.php           |   6 +-
 .../Library/View/Wrapper/Selection.php        | 103 ++++++++++++++++-
 .../Wrapper/User/RichContent/ChangeImage.php  |  36 +++++-
 .../Wrapper/User/RichContent/Selection.php    |  82 ++++++++++++++
 .../Wrapper/User/RichContent/Selections.php   |   2 +-
 .../Library/Widget/Accessibility/View.php     |  16 ++-
 .../Library/Widget/AdminTools/View.php        |   5 +
 .../Library/Widget/Breadcrumb/View.php        |   5 +
 .../Intonation/Library/Widget/Login/View.php  |  13 ++-
 .../Intonation/Library/Widget/Notify/View.php |   5 +
 .../Intonation/Library/Widget/Scroll/View.php |  19 ++--
 .../Intonation/Library/Widget/Search/View.php |   5 +
 library/templates/Intonation/Template.php     |   9 ++
 .../View/Abonne/AddRecordsToSelection.php     |  78 +++++++++++++
 .../Intonation/View/Abonne/ChangeImage.php    |  40 -------
 .../Intonation/View/Abonne/LoansList.php      |  39 +++----
 .../Intonation/View/Abonne/SelectionBoard.php |  48 ++++++++
 .../Intonation/View/Abonne/Selections.php     |  45 ++++++++
 .../Intonation/View/BookmarkedSearches.php    |  10 +-
 library/templates/Intonation/View/Cardify.php |   6 +-
 .../Intonation/View/CardifyHorizontal.php     |  20 ++--
 .../Intonation/View/CardifyWithOverlay.php    |  25 +++--
 .../templates/Intonation/View/Jumbotron.php   |   4 +-
 library/templates/Intonation/View/Opac.php    |  10 ++
 library/templates/Intonation/View/Paniers.php |  53 ---------
 .../Selections.php => RenderActions.php}      |  22 ++--
 .../Intonation/View/RenderCollection.php      |  37 +++++++
 .../Intonation/View/RenderInlineForm.php      |   7 +-
 .../Intonation/View/RenderRecordBadges.php    |   2 +-
 .../Intonation/View/RenderTruncateList.php    |   7 +-
 .../templates/Intonation/View/TagAction.php   |  19 +++-
 .../templates/Intonation/View/TagMedia.php    |  26 ++++-
 .../controllers/NoticeAjaxControllerTest.php  |   2 +-
 tests/scenarios/Templates/TemplatesTest.php   | 104 +++++++++++++++++-
 55 files changed, 1044 insertions(+), 235 deletions(-)
 create mode 100644 application/modules/opac/views/scripts/abonne/ajouter-a-la-selection.phtml
 create mode 100644 application/modules/opac/views/scripts/abonne/selection.phtml
 create mode 100644 library/templates/Intonation/Library/UserPatcher.php
 create mode 100644 library/templates/Intonation/Library/View/Wrapper/RecordInSelection.php
 create mode 100644 library/templates/Intonation/Library/View/Wrapper/RecordToSelect.php
 create mode 100644 library/templates/Intonation/Library/View/Wrapper/User/RichContent/Selection.php
 create mode 100644 library/templates/Intonation/View/Abonne/AddRecordsToSelection.php
 create mode 100644 library/templates/Intonation/View/Abonne/SelectionBoard.php
 create mode 100644 library/templates/Intonation/View/Abonne/Selections.php
 delete mode 100644 library/templates/Intonation/View/Paniers.php
 rename library/templates/Intonation/View/{User/Selections.php => RenderActions.php} (58%)
 create mode 100644 library/templates/Intonation/View/RenderCollection.php

diff --git a/application/modules/admin/controllers/TemplateController.php b/application/modules/admin/controllers/TemplateController.php
index 9ff2780caf7..eda524395f4 100644
--- a/application/modules/admin/controllers/TemplateController.php
+++ b/application/modules/admin/controllers/TemplateController.php
@@ -67,7 +67,14 @@ class Admin_TemplateController extends ZendAfi_Controller_Action {
 
   public function resetAction() {
     $template = Class_Template::current()->resetSettings();
-    $this->_helper->notify($this->_('Modèle "%s" réinitialisé', $template->getId()));
+    $this->_helper->notify($this->_('Thème %s réinitialisé', $template->getId()));
+    return $this->_redirectClose($this->_getReferer());
+  }
+
+
+  public function updateAction() {
+    $template = Class_Template::current()->updateSettings();
+    $this->_helper->notify($this->_('Thème %s mis à jour', $template->getId()));
     return $this->_redirectClose($this->_getReferer());
   }
 }
\ No newline at end of file
diff --git a/application/modules/opac/controllers/AbonneController.php b/application/modules/opac/controllers/AbonneController.php
index 8fba91dc393..dd1feffa263 100644
--- a/application/modules/opac/controllers/AbonneController.php
+++ b/application/modules/opac/controllers/AbonneController.php
@@ -35,6 +35,7 @@ class AbonneController extends ZendAfi_Controller_Action {
       return;
 
     $this->_user = $this->view->user = Class_Users::getIdentity();
+    Class_Template::current()->upgradeUser($this->_user);
 
     $this->clearEmprunteurCache();
 
@@ -1463,6 +1464,27 @@ class AbonneController extends ZendAfi_Controller_Action {
   }
 
 
+  public function selectionAction() {
+    if (!$this->view->selection = Class_PanierNotice::find($this->_getParam('id'))) {
+      $this->_helper->notify($this->_('Une erreur c\'est produite.'), ['status' => 'error']);
+      return $this->_redirectClose($this->_getReferer());
+    }
+  }
+
+
+  public function ajouterALaSelectionAction() {
+    if (!$this->view->selection = Class_PanierNotice::find($this->_getParam('id'))) {
+      $this->_helper->notify($this->_('Une erreur c\'est produite.'), ['status' => 'error']);
+      return $this->_redirectClose($this->_getReferer());
+    }
+
+    $this->view->criteria =
+      (new Class_CriteresRecherche())
+      ->setParams(array_merge($this->_request->getParams(),
+                              ['tri' => Class_CriteresRecherche::SORT_PUBLICATION]));
+  }
+
+
   public function mesAvisAction() {
   }
 
diff --git a/application/modules/opac/views/scripts/abonne/ajouter-a-la-selection.phtml b/application/modules/opac/views/scripts/abonne/ajouter-a-la-selection.phtml
new file mode 100644
index 00000000000..3d77d83cbab
--- /dev/null
+++ b/application/modules/opac/views/scripts/abonne/ajouter-a-la-selection.phtml
@@ -0,0 +1,2 @@
+<?php
+echo $this->abonne_AddRecordsToSelection($this->user, $this->selection, $this->criteria);
diff --git a/application/modules/opac/views/scripts/abonne/selection.phtml b/application/modules/opac/views/scripts/abonne/selection.phtml
new file mode 100644
index 00000000000..c35853c891b
--- /dev/null
+++ b/application/modules/opac/views/scripts/abonne/selection.phtml
@@ -0,0 +1,2 @@
+<?php
+echo $this->abonne_SelectionBoard($this->selection);
diff --git a/library/Class/Newsletter.php b/library/Class/Newsletter.php
index 45485061ed3..5a822ce1faf 100644
--- a/library/Class/Newsletter.php
+++ b/library/Class/Newsletter.php
@@ -55,7 +55,8 @@
 
 class NewsletterLoader extends Storm_Model_Loader {
   public function getPublishedNewsletters() {
-    return Class_Newsletter::findAllBy(['draft' => 0]);
+    return Class_Newsletter::findAllBy(['draft' => 0,
+                                        'order' => 'titre']);
   }
 }
 
diff --git a/library/Class/Notice/Field.php b/library/Class/Notice/Field.php
index c56025d09ad..efdc51457fc 100644
--- a/library/Class/Notice/Field.php
+++ b/library/Class/Notice/Field.php
@@ -39,7 +39,7 @@ class Class_Notice_Field extends Class_Entity {
 
   public function getUrlForLink() {
     return ($params = $this->getUrlParams())
-      ? Class_Url::relative($params)
+      ? Class_Url::relative($params, null, false)
       : $this->getUrl();
   }
 
diff --git a/library/Class/PanierNotice.php b/library/Class/PanierNotice.php
index 45ad3b6ef1d..0d39212dfb7 100644
--- a/library/Class/PanierNotice.php
+++ b/library/Class/PanierNotice.php
@@ -498,6 +498,11 @@ class Class_PanierNotice extends Storm_Model_Abstract {
   }
 
 
+  public function contains($record) {
+    return $this->isNoticeInPanier($record);
+  }
+
+
   public function fixLostUserId() {
     if ( !$this->getIdabon())
       return;
@@ -511,4 +516,12 @@ class Class_PanierNotice extends Storm_Model_Abstract {
   public function asWhere() {
     return sprintf('clef_alpha in("%s")', implode('","', $this->getClesNotices()));
   }
+
+
+  public function isMine() {
+    if (!$user = Class_Users::getIdentity())
+      return false;
+
+    return  $user == $this->getUser();
+  }
 }
\ No newline at end of file
diff --git a/library/Class/Template.php b/library/Class/Template.php
index b30773c0b0e..a17a580d650 100644
--- a/library/Class/Template.php
+++ b/library/Class/Template.php
@@ -98,6 +98,11 @@ class Class_Template {
   }
 
 
+  public function getUpdateUrl() {
+    return Class_Url::relative(sprintf('/admin/template/update/template/%s', $this->getId()));
+  }
+
+
   public function getControlKey() {
     if ($this->_control_key)
       return $this->_control_key;
@@ -308,6 +313,12 @@ class Class_Template {
   }
 
 
+  public function updateSettings() {
+    $this->getSettings()->updateSettings();
+    return $this;
+  }
+
+
   public function addHelperPath($view) {
     return $this;
   }
@@ -368,4 +379,9 @@ class Class_Template {
 
     return $this->_icons_cache[$cache_key] = $mapping[$key];
   }
+
+
+  public function upgradeUser($user) {
+    return $this;
+  }
 }
\ No newline at end of file
diff --git a/library/Class/Template/Settings.php b/library/Class/Template/Settings.php
index 95e83a9719d..4c00600af41 100644
--- a/library/Class/Template/Settings.php
+++ b/library/Class/Template/Settings.php
@@ -103,4 +103,9 @@ class Class_Template_Settings extends Storm_Model_Abstract {
     $settings = $this->getSettingsInstance();
     return $this->_hydrating_mapping = $settings->getIntonationHydratingMapping();
   }
+
+
+  public function updateSettings() {
+    return $this;
+  }
 }
\ No newline at end of file
diff --git a/library/ZendAfi/Controller/Plugin/Manager/Template.php b/library/ZendAfi/Controller/Plugin/Manager/Template.php
index 3f4cdcee2b4..7121943f8d6 100644
--- a/library/ZendAfi/Controller/Plugin/Manager/Template.php
+++ b/library/ZendAfi/Controller/Plugin/Manager/Template.php
@@ -25,18 +25,23 @@ class ZendAfi_Controller_Plugin_Manager_Template extends ZendAfi_Controller_Plug
     return [
             ['url' => $model->getTryUrl(),
              'icon' => 'view',
-             'label' => $this->_('Essayer le modèle "%s"', $model->getTitle()),
+             'label' => $this->_('Essayer le thème %s', $model->getTitle()),
              'anchorOptions' => ['target' => '_blank']],
 
             ['url' => $model->getEditUrl(),
              'icon' => 'edit',
-             'label' => $this->_('Paramétrer le modèle "%s"', $model->getTitle()),
+             'label' => $this->_('Paramétrer le thème %s', $model->getTitle()),
              'anchorOptions' => ['data-popup' => true]],
 
             ['url' => $model->getResetUrl(),
              'icon' => 'reload',
-             'label' => $this->_('Réinitialiser le modèle "%s"', $model->getTitle()),
-             'anchorOptions' => $this->_confirm($this->_('Etes-vous sur de vouloir réinitialiser le modèle "%s" ?', $model->getTitle()))]
+             'label' => $this->_('Réinitialiser le thème %s', $model->getTitle()),
+             'anchorOptions' => $this->_confirm($this->_('Etes-vous sur de vouloir réinitialiser le thème %s ?', $model->getTitle()))],
+
+            ['url' => $model->getUpdateUrl(),
+             'icon' => 'batch',
+             'label' => $this->_('Mettre à jour le thème %s', $model->getTitle()),
+             'anchorOptions' => $this->_confirm($this->_('Etes-vous sur de vouloir mettre à jour le thème %s ?', $model->getTitle()))]
     ];
   }
 
diff --git a/library/ZendAfi/View/Helper/Abonne/NamesOrLogin.php b/library/ZendAfi/View/Helper/Abonne/NamesOrLogin.php
index 45724137eea..8ffde4fee61 100644
--- a/library/ZendAfi/View/Helper/Abonne/NamesOrLogin.php
+++ b/library/ZendAfi/View/Helper/Abonne/NamesOrLogin.php
@@ -25,11 +25,14 @@ class ZendAfi_View_Helper_Abonne_NamesOrLogin extends Zend_View_Helper_HtmlEleme
     if(!$user)
       return '';
 
-    $html = $html ? $this->view->tag('span', $html): '';
+    $html = $html
+      ? $this->view->tag('span', $html)
+      : '';
+
     return ($user->hasPseudo()
-    ? $this->userPseudo($user)
-    : $this->userNames($user))
-      .$html;
+            ? $this->userPseudo($user)
+            : $this->userNames($user))
+      . $html;
   }
 
   protected function userPseudo($user) {
@@ -38,8 +41,10 @@ class ZendAfi_View_Helper_Abonne_NamesOrLogin extends Zend_View_Helper_HtmlEleme
 
 
   protected function userNames($user) {
-    return $this->view->escape($user->getPrenom()). $this->view->tag('span', $this->view->escape($user->getNom()), ['data-name' => 'last-name']);
-
+    return sprintf('%s %s',
+                   $this->view->escape($user->getPrenom()),
+                   $this->view->tag('span',
+                                    $this->view->escape($user->getNom()),
+                                    ['data-name' => 'last-name']));
   }
-}
-?>
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/library/ZendAfi/View/Helper/Accueil/Base.php b/library/ZendAfi/View/Helper/Accueil/Base.php
index d770fac5441..26a27cf4dbe 100644
--- a/library/ZendAfi/View/Helper/Accueil/Base.php
+++ b/library/ZendAfi/View/Helper/Accueil/Base.php
@@ -367,11 +367,7 @@ class ZendAfi_View_Helper_Accueil_Base extends ZendAfi_View_Helper_ModuleAbstrac
 
 
   public static function getWidget($id, $cfg, $view) {
-    if(!$instance = static::getModuleHelperFromParams($id, $cfg, $view))
-      return null;
-
-    $instance->getHtml();
-    return $instance;
+    return static::getModuleHelperFromParams($id, $cfg, $view);
   }
 
 
diff --git a/library/templates/Intonation/Assets/css/intonation.css b/library/templates/Intonation/Assets/css/intonation.css
index c3c70e14d8b..9a260609cb1 100644
--- a/library/templates/Intonation/Assets/css/intonation.css
+++ b/library/templates/Intonation/Assets/css/intonation.css
@@ -414,7 +414,7 @@ label[data-name=note] ~ div label.multi-element-label + br {
 }
 
 .badge {
-    white-space: normal;
+    text-transform: lowercase;
 }
 
 main.col,
@@ -446,6 +446,35 @@ header.col {
     display: none;
 }
 
-.card .card-columns {
+.img-thumbnail .card-columns {
     column-gap: 0.05rem;
+    column-count: 3;
+}
+
+.card .card-columns .card {
+    margin-bottom: 0.05rem;
+}
+
+.fs_1em {
+    font-size: 1em;
+}
+
+.card .card-columns .card-img-overlay h3,
+.card .card-columns .card-img-overlay h4,
+.card .card-columns .card-img-overlay {
+    font-size: 0.875em;
+    padding: 0;
+}
+
+.no_wrap {
+    white-space: nowrap;
+}
+
+dl.row {
+    margin: 0;
+}
+
+.card-img-overlay .card-link > * {
+    display: inline-block;
+    white-space: normal;
 }
\ No newline at end of file
diff --git a/library/templates/Intonation/Library/Settings.php b/library/templates/Intonation/Library/Settings.php
index e1adff9f0cf..bb10ad8b07a 100644
--- a/library/templates/Intonation/Library/Settings.php
+++ b/library/templates/Intonation/Library/Settings.php
@@ -61,6 +61,8 @@ class Intonation_Library_Settings extends Intonation_System_Abstract {
                                                  'mb-n1',
                                                  'mb-n2',
                                                  'align-items-center',
+                                                 'align-items-start',
+                                                 'align-items-end',
                           ],
 
                           'hydrating_mapping' => ['div id site_web_wrapper' => 'container align-self-center',
@@ -74,12 +76,12 @@ class Intonation_Library_Settings extends Intonation_System_Abstract {
                                                   'div class boutons' => 'row justify-content-around my-3',
                                                   'label' => 'col-12 col-sm-5 col-form-label col-form-label-sm pr-1',
                                                   'label class multi-element-label' => 'col-form-label col-form-label-sm',
-                                                  'input' => 'mb-2 form-control',
+                                                  'input' => 'form-control form-control-sm',
                                                   'input data-spambots true' => '',
                                                   'input type checkbox' => 'form-check-input form-check-input-sm mr-1',
                                                   'input type submit' => 'btn btn-sm btn-primary',
-                                                  'textarea' => 'form-control',
-                                                  'select' => 'form-control form-control custom-select',
+                                                  'textarea' => 'form-control form-control-sm',
+                                                  'select' => 'form-control form-control-sm custom-select custom-select-sm',
                                                   'button' => 'btn btn-secondary',
                                                   'button class btn' => '',
                                                   'button class accessibility' => 'btn btn-light',
@@ -102,7 +104,7 @@ class Intonation_Library_Settings extends Intonation_System_Abstract {
                                                   'a' => 'btn-link',
                                                   'a class active' => '',
                                                   'a class nav-link' => '',
-                                                  'a class card-link' => '',
+                                                  'a class card-link' => 'no_wrap',
                                                   'a class list-group-item' => '',
                                                   'a class account-loans' => 'btn btn-sm list-group-item-info',
                                                   'ul' => 'list-unstyled',
@@ -138,6 +140,7 @@ class Intonation_Library_Settings extends Intonation_System_Abstract {
                                                   'hold' => 'class fas fa-thumbtack',
                                                   'loan' => 'class fas fa-book',
                                                   'late-loan' => 'class fas fa-book',
+                                                  'extend-loan' => 'class fas fa-calendar-plus',
                                                   'home' => 'class fas fa-home',
                                                   'related' => 'class fas fa-clone',
                                                   'description' => 'class fas fa-info',
@@ -165,11 +168,14 @@ class Intonation_Library_Settings extends Intonation_System_Abstract {
 
                                                 'clean' => 'class fas fa-eraser',
                                                 'open' => 'class far fa-caret-square-down',
+                                                'add' => 'class fas fa-plus-square',
                                                 'delete' => 'class fas fa-minus',
                                                 'edit' => 'class fas fa-edit',
+                                                'rename' => 'class fas fa-edit',
 
                                                 'rss' => 'class fas fa-rss',
                                                 'embed' => 'class fas fa-code',
+                                                'export' => 'class fas fa-file-export',
 
                                                 'tag' => 'class fas fa-tag',
                                                 'star' => 'class fas fa-star',
@@ -228,4 +234,34 @@ class Intonation_Library_Settings extends Intonation_System_Abstract {
                                    }, array_keys($this->_default_settings)),
                          $this->_default_settings);
   }
+
+
+  public function updateSettings() {
+    $local_settings = $this->_settings->getSettingsInstance()->toArray();
+    $defaults_settings = $this->_getDefaultSettings();
+
+    foreach ($defaults_settings as $key => $default) {
+      $data = isset($local_settings[$key])
+        ? $local_settings[$key]
+        : $default;
+
+      $local_settings[$key] = is_array($default)
+        ? $this->_mergeData($default, $data)
+        : $data;
+    }
+
+
+    $this->_settings->setSettings(serialize($local_settings));
+    $this->_settings->save();
+    return $this;
+  }
+
+
+  protected function _mergeData($default, $data) {
+    $merged = array_merge($default, $data);
+    if (count(array_filter(array_keys($default), 'is_string')))
+      return $merged;
+
+    return array_values(array_unique($merged));
+  }
 }
\ No newline at end of file
diff --git a/library/templates/Intonation/Library/UserPatcher.php b/library/templates/Intonation/Library/UserPatcher.php
new file mode 100644
index 00000000000..08952f31c2b
--- /dev/null
+++ b/library/templates/Intonation/Library/UserPatcher.php
@@ -0,0 +1,63 @@
+<?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_Library_UserPatcher {
+
+  use Trait_Translator;
+
+  protected $_user;
+
+
+  public function setUser($user) {
+    $this->_user = $user;
+    return $this;
+  }
+
+
+  public function patch() {
+    foreach($this->_getDefaultSelections() as $label)
+      if (!Class_PanierNotice::findFirstBy(['libelle' => $label,
+                                            'id_user' => $this->_user->getId()]))
+        $this->_addSelectionToUser($label);
+
+    return $this;
+  }
+
+
+  protected function _addSelectionToUser($label) {
+    Class_PanierNotice::newInstance(['libelle' => $label,
+                                     'id_user' => $this->_user->getId()])
+      ->save();
+  }
+
+
+  protected function _getDefaultSelections() {
+    return [$this->_('Mes préférés'),
+            $this->_('Déjà lu'),
+            $this->_('À lire')];
+  }
+
+
+  public function isSelectionDefault($selection) {
+    return in_array($selection->getLibelle(), $this->_getDefaultSelections());
+  }
+}
diff --git a/library/templates/Intonation/Library/View/Wrapper/Loan.php b/library/templates/Intonation/Library/View/Wrapper/Loan.php
index cf4e1e48dd8..25cb764c2d1 100644
--- a/library/templates/Intonation/Library/View/Wrapper/Loan.php
+++ b/library/templates/Intonation/Library/View/Wrapper/Loan.php
@@ -198,6 +198,9 @@ class Intonation_Library_View_Wrapper_Loan extends Intonation_Library_View_Wrapp
                                                                      'action' => 'prolongerPret',
                                                                      'id_pret' => $this->_model->getId()]),
                                          'Text' => $this->_('Prolonger'),
+                                         'Image' => Class_Template::current()->getIco($this->_view,
+                                                                                      'extend-loan',
+                                                                                      'library'),
                                          'Title' => $this->_('Prolonger l\'emprunt %s',
                                                              $this->_model->getTitre())])];
   }
diff --git a/library/templates/Intonation/Library/View/Wrapper/Newsletter.php b/library/templates/Intonation/Library/View/Wrapper/Newsletter.php
index a33135456dc..e316bc46fb4 100644
--- a/library/templates/Intonation/Library/View/Wrapper/Newsletter.php
+++ b/library/templates/Intonation/Library/View/Wrapper/Newsletter.php
@@ -26,6 +26,15 @@ class Intonation_Library_View_Wrapper_Newsletter extends Intonation_Library_View
   }
 
 
+  public function getContentForJSSearch() {
+    $content = parent::getContentForJSSearch();
+    if ($this->_model->hasRecipient(Class_Users::getIdentity()))
+      return sprintf($content, $this->_('inscrit'));
+
+    return $content;
+  }
+
+
   public function getMainLink() {
     $params = $this->_model->hasRecipient(Class_Users::getIdentity())
       ? ['Url' => $this->_view->url(['controller' => 'abonne',
diff --git a/library/templates/Intonation/Library/View/Wrapper/Record.php b/library/templates/Intonation/Library/View/Wrapper/Record.php
index ea5c5e69cd0..7ac69fd2b53 100644
--- a/library/templates/Intonation/Library/View/Wrapper/Record.php
+++ b/library/templates/Intonation/Library/View/Wrapper/Record.php
@@ -47,7 +47,10 @@ class Intonation_Library_View_Wrapper_Record extends Intonation_Library_View_Wra
                                                              ['controller' => 'recherche',
                                                               'action' => 'viewnotice',
                                                               'id' => $this->_model->getId()]),
-                                        'Text' => $this->_('Voir le document'),
+                                        'Image' => Class_Template::current()->getIco($this->_view,
+                                                                                     'read-document',
+                                                                                     'library'),
+                                        'Text' => $this->_('Voir'),
                                         'Title' => $this->_('Voir le document "%s" de "%s" de type "%s"',
                                                             $this->getMainTitle(),
                                                             $this->getSecondaryTitle(),
diff --git a/library/templates/Intonation/Library/View/Wrapper/RecordInSelection.php b/library/templates/Intonation/Library/View/Wrapper/RecordInSelection.php
new file mode 100644
index 00000000000..9c3a9250466
--- /dev/null
+++ b/library/templates/Intonation/Library/View/Wrapper/RecordInSelection.php
@@ -0,0 +1,46 @@
+<?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_RecordInSelection extends Intonation_Library_View_Wrapper_Record {
+
+  protected $_selection;
+
+
+  public function setSelection($selection) {
+    $this->_selection = $selection;
+    return $this;
+  }
+
+  public function getActions() {
+    return [new Intonation_Library_Link(['Url' => $this->_view->url(['controller' => 'abonne',
+                                                                     'action' => 'supprimer-de-selection',
+                                                                     'selection' => $this->_selection->getId(),
+                                                                     'id' => $this->_model->getId()]),
+                                         'Text' => $this->_('Supprimer'),
+                                         'Title' => $this->_('Supprimer le document %s de la sélection %s',
+                                                             $this->_model->getTitrePrincipal(' '),
+                                                             $this->_selection->getLibelle()),
+                                         'Image' => Class_Template::current()->getIco($this->_view,
+                                                                                      'delete',
+                                                                                      'utils')])];
+  }
+}
\ No newline at end of file
diff --git a/library/templates/Intonation/Library/View/Wrapper/RecordToSelect.php b/library/templates/Intonation/Library/View/Wrapper/RecordToSelect.php
new file mode 100644
index 00000000000..65eaced3a90
--- /dev/null
+++ b/library/templates/Intonation/Library/View/Wrapper/RecordToSelect.php
@@ -0,0 +1,47 @@
+<?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_RecordToSelect extends Intonation_Library_View_Wrapper_Record {
+
+  protected $_selection;
+
+
+  public function setSelection($selection) {
+    $this->_selection = $selection;
+    return $this;
+  }
+
+
+  public function getDescription() {
+    return '';
+  }
+
+
+  public function getMainLink() {
+    return new Intonation_Library_Link(['Text' => $this->_('Ajouter'),
+                                        'Attribs' => ['onclick' => '$(this).closest(\'.card\').slideUp();return true;',
+                                                      'style' => 'cursor: pointer;'],
+                                        'Title' => $this->_('Ajouter le document %s à la sélection %s',
+                                                            $this->getMainTitle(),
+                                                            $this->_selection->getLibelle())]);
+  }
+}
\ No newline at end of file
diff --git a/library/templates/Intonation/Library/View/Wrapper/RichContent/Section.php b/library/templates/Intonation/Library/View/Wrapper/RichContent/Section.php
index 5edd5c748f2..d143a95c031 100644
--- a/library/templates/Intonation/Library/View/Wrapper/RichContent/Section.php
+++ b/library/templates/Intonation/Library/View/Wrapper/RichContent/Section.php
@@ -45,6 +45,11 @@ abstract class Intonation_Library_View_Wrapper_RichContent_Section {
   }
 
 
+  public function getModel() {
+    return $this->_model;
+  }
+
+
   public function isVisible() {
     return $this->_visible && $this->getContent();
   }
diff --git a/library/templates/Intonation/Library/View/Wrapper/Search.php b/library/templates/Intonation/Library/View/Wrapper/Search.php
index fab468cd188..8819927f71a 100644
--- a/library/templates/Intonation/Library/View/Wrapper/Search.php
+++ b/library/templates/Intonation/Library/View/Wrapper/Search.php
@@ -130,11 +130,11 @@ class Intonation_Library_View_Wrapper_Search extends Intonation_Library_View_Wra
 
     $badges = [['tag' => 'span',
                 'class' => 'info',
-                'text' => $this->_('Nombre de résultats : %s',
+                'text' => $this->_('Nombre de documents : %s',
                                    $this->_view->tag('span',
                                                      $result->getRecordsCount(),
-                                                     ['class' => 'badge badge-light'])),
-                'title' => $this->_('Le nombre de résultats pour la recherche %s', $this->getMainTitle())]];
+                                                     ['class' => 'badge badge-light fs_1em'])),
+                'title' => $this->_('Le nombre de documents pour la recherche %s', $this->_model->getLabel())]];
     return $this->_view->renderBadges($badges);
   }
 
diff --git a/library/templates/Intonation/Library/View/Wrapper/Selection.php b/library/templates/Intonation/Library/View/Wrapper/Selection.php
index de2b2b86926..ac904aa0844 100644
--- a/library/templates/Intonation/Library/View/Wrapper/Selection.php
+++ b/library/templates/Intonation/Library/View/Wrapper/Selection.php
@@ -28,7 +28,11 @@ class Intonation_Library_View_Wrapper_Selection extends Intonation_Library_View_
 
 
   public function getSecondaryTitle() {
-    return '';
+    return
+      $this->_('Créée par %s',
+               ($this->_model->isMine()
+                ? $this->_('vous')
+                : $this->_model->getUserNomComplet()));
   }
 
 
@@ -43,7 +47,16 @@ class Intonation_Library_View_Wrapper_Selection extends Intonation_Library_View_
 
 
   public function getDescription() {
-    return '';
+    $records = $this->_model->getNoticesAsArray();
+
+    $records_title = [];
+    foreach ($records as $record)
+      $records_title [] = $record->getTitrePrincipal(' ');
+
+    return $this->getBadges()
+      . BR
+      . $this->_('Liste des documents : %s',
+                 $this->_view->truncate(implode(', ', $records_title)));
   }
 
 
@@ -53,6 +66,14 @@ class Intonation_Library_View_Wrapper_Selection extends Intonation_Library_View_
 
 
   public function getMainLink() {
+    return new Intonation_Library_Link(['Url' => $this->_view->url(['controller' => 'abonne',
+                                                                    'action' => 'selection',
+                                                                    'id' => $this->_model->getId()]),
+                                        'Text' => $this->_('Voir'),
+                                        'Title' => $this->_('Voir la sélection'),
+                                        'Image' => Class_Template::current()->getIco($this->_view,
+                                                                                     'search_more',
+                                                                                     'library')]);
   }
 
 
@@ -77,12 +98,69 @@ class Intonation_Library_View_Wrapper_Selection extends Intonation_Library_View_
 
 
   public function getBadges() {
-    return '';
+    $badges = [['tag' => 'span',
+                'class' => 'info',
+                'text' => $this->_('Nombre de documents : %s',
+                                   $this->_view->tag('span',
+                                                     $this->_model->numberOfNotices(),
+                                                     ['class' => 'badge badge-light fs_1em'])),
+                'title' => $this->_('Le nombre de documents dans la sélection %s', $this->_model->getLibelle())],
+
+               ['tag' => 'span',
+                'class' => 'warning',
+                'text' => $this->_('Mise à jour le : %s',
+                                   $this->_view->tag('span',
+                                                     Class_Date::getHumanDate($this->_model->getCurrentDate(), 'dd/MM/yyyy'),
+                                                     ['class' => 'badge badge-light fs_1em'])),
+                'title' => $this->_('La date de dernière mise à jour de la sélection %s', $this->_model->getLibelle())]];
+    return $this->_view->renderBadges($badges);
   }
 
 
   public function getActions() {
-    return [];
+    $actions = [new Intonation_Library_Link(['Url' => $this->_view->url(['controller' => 'abonne',
+                                                                         'action' => 'exporter-selection',
+                                                                         'id' => $this->_model->getId()]),
+                                             'Text' => $this->_('Exporter'),
+                                             'Title' => $this->_('Exporter la sélection %s',
+                                                                 $this->_model->getLibelle()),
+                                             'Image' => Class_Template::current()->getIco($this->_view,
+                                                                                          'export',
+                                                                                          'utils')]),
+
+                new Intonation_Library_Link(['Url' => $this->_view->url(['controller' => 'abonne',
+                                                                         'action' => 'ajouter-a-la-selection',
+                                                                         'id' => $this->_model->getId()]),
+                                             'Text' => $this->_('Ajouter'),
+                                             'Title' => $this->_('Ajouter un document à la sélection %s',
+                                                                 $this->_model->getLibelle()),
+                                             'Image' => Class_Template::current()->getIco($this->_view,
+                                                                                          'add',
+                                                                                          'utils')])];
+
+    if ((new Intonation_Library_UserPatcher)->isSelectionDefault($this->_model))
+      return $actions;
+
+    return array_merge($actions,
+                       [new Intonation_Library_Link(['Url' => $this->_view->url(['controller' => 'abonne',
+                                                                                 'action' => 'renommer-selection',
+                                                                                 'id' => $this->_model->getId()]),
+                                                     'Text' => $this->_('Renommer'),
+                                                     'Title' => $this->_('Renommer la sélection %s',
+                                                                         $this->_model->getLibelle()),
+                                                     'Image' => Class_Template::current()->getIco($this->_view,
+                                                                                                  'rename',
+                                                                                                  'utils')]),
+
+                        new Intonation_Library_Link(['Url' => $this->_view->url(['controller' => 'abonne',
+                                                                                 'action' => 'supprimer-selection',
+                                                                                 'id' => $this->_model->getId()]),
+                                                     'Text' => $this->_('Supprimer'),
+                                                     'Title' => $this->_('Supprimer la sélection %s',
+                                                                         $this->_model->getLibelle()),
+                                                     'Image' => Class_Template::current()->getIco($this->_view,
+                                                                                                  'delete',
+                                                                                                  'utils')])]);
   }
 
 
@@ -92,6 +170,21 @@ class Intonation_Library_View_Wrapper_Selection extends Intonation_Library_View_
 
 
   public function getHtmlPicture() {
-    return '';
+    $records = $this->_model->getNoticesOnlyVignettes(true);
+
+    if (!$records = array_slice($records, 0, 9))
+      return '';
+
+    return
+      $this->_view->renderWall(new Storm_Collection($records),
+                               function($record)
+                               {
+                                 return
+                                   $this->_view
+                                   ->cardifyWithOverlay(
+                                                        (new Intonation_Library_View_Wrapper_Record)
+                                                        ->setModel($record)
+                                                        ->setView($this->_view));
+                               });
   }
 }
diff --git a/library/templates/Intonation/Library/View/Wrapper/User/RichContent/ChangeImage.php b/library/templates/Intonation/Library/View/Wrapper/User/RichContent/ChangeImage.php
index f6ff76dbe5b..ed5b239c05d 100644
--- a/library/templates/Intonation/Library/View/Wrapper/User/RichContent/ChangeImage.php
+++ b/library/templates/Intonation/Library/View/Wrapper/User/RichContent/ChangeImage.php
@@ -28,7 +28,41 @@ class Intonation_Library_View_Wrapper_User_RichContent_ChangeImage extends Inton
 
 
   public function getContent() {
-    return $this->_content;
+    if (!$paths = explode(';', Class_AdminVar::getValueOrDefault('USER_PROFILE_IMAGES')))
+      return '';
+
+    Class_FileManager::beOpenBar();
+
+    $images = [];
+    foreach ($paths as $path)
+      if ($image = Class_FileManager::find($path))
+        $images [] = $image;
+
+    $current_image = (new Intonation_Library_View_Wrapper_User)
+      ->setModel($this->_model)
+      ->getPicture();
+
+    $html = [];
+    foreach ($images as $image)
+      $html [] = $this->_cardify($image, $current_image);
+
+    return $this->_view->div(['class' => 'card-columns'],
+                            implode($html));
+  }
+
+
+  protected function _cardify($image, $current) {
+    $url = $image->getUrl();
+    $selected = ($current == $url) ? ' border border-primary disabled shadow-lg selected' : '';
+
+    return $this->_view->div(['class' => 'card shadow-sm' . $selected],
+                            $this->_view->tagAnchor($this->_view->url(['controller' => 'abonne',
+                                                                     'action' => 'pick-image',],
+                                                                    null, true)
+                                                   . '?' . http_build_query(['id' => $image->getId()]),
+                                                   $this->_view->tagImg($url),
+                                                   ['class' => 'card-img',
+                                                    'data-popup' => 'true']));
   }
 
 
diff --git a/library/templates/Intonation/Library/View/Wrapper/User/RichContent/Selection.php b/library/templates/Intonation/Library/View/Wrapper/User/RichContent/Selection.php
new file mode 100644
index 00000000000..2ca9552f089
--- /dev/null
+++ b/library/templates/Intonation/Library/View/Wrapper/User/RichContent/Selection.php
@@ -0,0 +1,82 @@
+<?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_Library_View_Wrapper_User_RichContent_Selection extends Intonation_Library_View_Wrapper_User_RichContent_Section {
+
+  public function getTitle() {
+    return $this->_('La sélection %s',
+                    $this->_model->getLibelle());
+  }
+
+
+  public function getContent() {
+    if (!$records = $this->_model->getNoticesAsArray())
+      return '';
+
+    $wrapper = (new Intonation_Library_View_Wrapper_Selection)
+      ->setModel($this->_model)
+      ->setView($this->_view);
+
+    $html = [$this->_view->div(['class' => 'col-12'],
+                               $wrapper->getBadges()),
+
+             $this->_view->div(['class' => 'col-12 col-sm-3 pt-3 pl-3 order-sm-3'],
+                               $this->_view->renderActions($wrapper->getActions())),
+
+             $this->_view->div(['class' => 'col-12 col-sm-9'],
+                                $this->_view->renderList(new Storm_Collection($records),
+                                 function($record)
+                                 {
+                                   return
+                                     $this->_view
+                                     ->cardifyHorizontal(
+                                                         (new Intonation_Library_View_Wrapper_RecordInSelection)
+                                                         ->setSelection($this->_model)
+                                                         ->setModel($record)
+                                                         ->setView($this->_view));
+                                 }))];
+
+    return $this->_view->grid(implode($html));
+  }
+
+
+  public function getClass() {
+    return 'user_selections';
+  }
+
+
+  public function getNavUrl() {
+    return ['controller' => 'abonne',
+            'action' => 'selection'];
+  }
+
+
+  public function getNavIco() {
+    return 'selection';
+  }
+
+
+  public function getNavTitle() {
+    return $this->_('La sélection sélection %s',
+                    $this->_model->getLibelle());
+  }
+}
diff --git a/library/templates/Intonation/Library/View/Wrapper/User/RichContent/Selections.php b/library/templates/Intonation/Library/View/Wrapper/User/RichContent/Selections.php
index 78fab35e775..43999484617 100644
--- a/library/templates/Intonation/Library/View/Wrapper/User/RichContent/Selections.php
+++ b/library/templates/Intonation/Library/View/Wrapper/User/RichContent/Selections.php
@@ -29,7 +29,7 @@ class Intonation_Library_View_Wrapper_User_RichContent_Selections extends Intona
 
   public function getContent() {
     return
-      $this->_view->paniers($this->_model->getPaniers())
+      $this->_view->abonne_Selections($this->_model->getPaniers())
       . $this->_view->bookmarkedSearches(Class_User_BookmarkedSearch::findAllBy(['id_user' => $this->_model->getId(),
                                                                                  'order' => 'creation_date desc']))
       . $this->_view->user_Newsletters($this->_model);
diff --git a/library/templates/Intonation/Library/Widget/Accessibility/View.php b/library/templates/Intonation/Library/Widget/Accessibility/View.php
index 6b316804d77..9bba3e5fe81 100644
--- a/library/templates/Intonation/Library/Widget/Accessibility/View.php
+++ b/library/templates/Intonation/Library/Widget/Accessibility/View.php
@@ -29,14 +29,21 @@ class Intonation_Library_Widget_Accessibility_View extends Zendafi_View_Helper_A
   }
 
 
-  protected function _getHTML() {
+  protected function _renderHeadScriptsOn($script_loader) {
     $id = 'accessibility_widget_tools_' . $this->getId();
 
-    Class_ScriptLoader::getInstance()
+    $script_loader
       ->addAdminScript('jquery.cookie')
       ->addOPACPluginScript('accessibility/accessibility.js')
       ->addJQueryReady(sprintf('$("#%s").accessibility_tools()',
-                               $id));
+                               $id))
+      ->addJqueryReady('$(".dropdown-menu").on("click", function (e) {e.stopPropagation();});');
+;
+  }
+
+
+  protected function _getHTML() {
+    $id = 'accessibility_widget_tools_' . $this->getId();
 
     $html = [$this->_cssPicker(),
              $this->_fontSizePicker()];
@@ -51,9 +58,6 @@ class Intonation_Library_Widget_Accessibility_View extends Zendafi_View_Helper_A
 
 
   protected function _renderToggle($html, $wrapper_id) {
-    Class_ScriptLoader::getInstance()
-      ->addJqueryReady('$(".dropdown-menu").on("click", function (e) {e.stopPropagation();});');
-
     $id = 'dropdown_button_' . $this->getId();
 
     return $this->view->tag('div',
diff --git a/library/templates/Intonation/Library/Widget/AdminTools/View.php b/library/templates/Intonation/Library/Widget/AdminTools/View.php
index 0159882d1da..8d34e7cb73b 100644
--- a/library/templates/Intonation/Library/Widget/AdminTools/View.php
+++ b/library/templates/Intonation/Library/Widget/AdminTools/View.php
@@ -32,6 +32,11 @@ class Intonation_Library_Widget_Admintools_View extends Zendafi_View_Helper_Accu
   }
 
 
+  public function shouldCacheContent() {
+    return false;
+  }
+
+
   protected function _getHTML() {
     if (!$entries = $this->_getEntries())
       return '';
diff --git a/library/templates/Intonation/Library/Widget/Breadcrumb/View.php b/library/templates/Intonation/Library/Widget/Breadcrumb/View.php
index 0841eddb621..5af1875a926 100644
--- a/library/templates/Intonation/Library/Widget/Breadcrumb/View.php
+++ b/library/templates/Intonation/Library/Widget/Breadcrumb/View.php
@@ -35,6 +35,11 @@ class Intonation_Library_Widget_Breadcrumb_View extends Zendafi_View_Helper_Accu
   }
 
 
+  public function shouldCacheContent() {
+    return false;
+  }
+
+
   protected function _breadcrumb() {
     $links = new Storm_Collection();
 
diff --git a/library/templates/Intonation/Library/Widget/Login/View.php b/library/templates/Intonation/Library/Widget/Login/View.php
index 5df8559ab88..2243741b644 100644
--- a/library/templates/Intonation/Library/Widget/Login/View.php
+++ b/library/templates/Intonation/Library/Widget/Login/View.php
@@ -33,6 +33,11 @@ class Intonation_Library_Widget_Login_View extends Zendafi_View_Helper_Accueil_B
   }
 
 
+  public function shouldCacheContent() {
+    return false;
+  }
+
+
   protected function _getHtml() {
     $this->_user = Class_Users::getIdentity();
     $this->_render_strategy = $this->_getRenderStrategy();
@@ -69,7 +74,7 @@ class Intonation_Library_Widget_Login_View extends Zendafi_View_Helper_Accueil_B
 
 
 
-class IntonationLoginRenderAbstract {
+abstract class IntonationLoginRenderAbstract {
   use Trait_Translator;
 
 
@@ -129,9 +134,7 @@ class IntonationLoginRenderAbstract {
   }
 
 
-  public function renderLogged() {
-    return $this->renderLogin();
-  }
+  abstract function renderLogged();
 
 
   public function renderLogin() {
@@ -240,7 +243,7 @@ class IntonationLoginRenderInline extends IntonationLoginRenderAbstract {
                                                  'class' => 'btn btn-sm btn-primary'])),
               $this->_view->button((new Class_Entity())
                                    ->setText($this->_settings->getLienDeconnection())
-                                   ->setAttribs(['class' => 'btn btn-sm btn-link',
+                                   ->setAttribs(['class' => 'btn btn-sm btn-danger',
                                                  'title' => $this->_('Se déconnecter de la session %s', $user->getNomComplet())])
                                    ->setUrl($this->_view->url(['controller'=>'auth',
                                                                'action'=>'logout'],
diff --git a/library/templates/Intonation/Library/Widget/Notify/View.php b/library/templates/Intonation/Library/Widget/Notify/View.php
index c1e962a5a1a..71b89c838af 100644
--- a/library/templates/Intonation/Library/Widget/Notify/View.php
+++ b/library/templates/Intonation/Library/Widget/Notify/View.php
@@ -32,6 +32,11 @@ class Intonation_Library_Widget_Notify_View extends Zendafi_View_Helper_Accueil_
   }
 
 
+  public function shouldCacheContent() {
+    return false;
+  }
+
+
   protected function _getContent() {
     if(!$alerts = $this->_getNotifications())
       return '';
diff --git a/library/templates/Intonation/Library/Widget/Scroll/View.php b/library/templates/Intonation/Library/Widget/Scroll/View.php
index 53fcf10dff4..fc03f1b739a 100644
--- a/library/templates/Intonation/Library/Widget/Scroll/View.php
+++ b/library/templates/Intonation/Library/Widget/Scroll/View.php
@@ -29,18 +29,23 @@ class Intonation_Library_Widget_Scroll_View extends Zendafi_View_Helper_Accueil_
   }
 
 
-  protected function _getHTML() {
-    $scripts = Class_ScriptLoader::getInstance()
+  protected function _renderHeadScriptsOn($script_loader) {
+    $id = 'scroll_widget_' . $this->getId();
+    $direction = $this->_settings->getDirection();
+
+    $script_loader
       ->addOPACPluginScript('scroll/scroll.js')
-      ->addOPACPluginStyleSheet('scroll/scroll.css');
+      ->addOPACPluginStyleSheet('scroll/scroll.css')
+      ->addJQueryReady(sprintf('$("#%s").scroll_button({direction: "%s"})',
+                               $id,
+                               $direction));
+  }
+
 
+  protected function _getHTML() {
     $id = 'scroll_widget_' . $this->getId();
     $direction = $this->_settings->getDirection();
 
-    $scripts->addJQueryReady(sprintf('$("#%s").scroll_button({direction: "%s"})',
-                                     $id,
-                                     $direction));
-
     $ico = 'fa fa-arrow-circle-down';
     $title = $this->_('Défiler jusqu\'au bas de la page');
 
diff --git a/library/templates/Intonation/Library/Widget/Search/View.php b/library/templates/Intonation/Library/Widget/Search/View.php
index 18a08368513..eb478333dd8 100644
--- a/library/templates/Intonation/Library/Widget/Search/View.php
+++ b/library/templates/Intonation/Library/Widget/Search/View.php
@@ -29,6 +29,11 @@ class Intonation_Library_Widget_Search_View extends ZendAfi_View_Helper_Accueil_
   }
 
 
+  public function shouldCacheContent() {
+    return false;
+  }
+
+
   protected function _getHTML() {
     $this->_user = Class_Users::getIdentity();
     $this->_render_strategy = $this->_getRenderStrategy();
diff --git a/library/templates/Intonation/Template.php b/library/templates/Intonation/Template.php
index 7b85dd0e1af..386eff937f3 100644
--- a/library/templates/Intonation/Template.php
+++ b/library/templates/Intonation/Template.php
@@ -261,4 +261,13 @@ class Intonation_Template extends Class_Template {
 
     return $mapping;
   }
+
+
+  public function upgradeUser($user) {
+    (new Intonation_Library_UserPatcher)
+      ->setUser($user)
+      ->patch();
+
+    return $this;
+  }
 }
\ No newline at end of file
diff --git a/library/templates/Intonation/View/Abonne/AddRecordsToSelection.php b/library/templates/Intonation/View/Abonne/AddRecordsToSelection.php
new file mode 100644
index 00000000000..b737ff1905f
--- /dev/null
+++ b/library/templates/Intonation/View/Abonne/AddRecordsToSelection.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 Intonation_View_Abonne_AddRecordsToSelection extends ZendAfi_View_Helper_BaseHelper {
+  public function abonne_AddRecordsToSelection($user, $selection, $criteria) {
+    xdebug_break();
+    $this->view->titre = $this->_('Ajouter des documents à la sélection %s',
+                                  $selection->getLibelle());
+
+    $engine = Class_MoteurRecherche::getInstance();
+
+    $result = $engine->lancerRecherche($criteria);
+
+    $all_records = $result->fetchRecords();
+
+    $records = [];
+    foreach ($all_records as $record)
+      if (!$selection->contains($record))
+        $records [] = $record;
+
+    $records = array_map(function($record) use ($selection)
+                         {
+                           return (new Intonation_Library_View_Wrapper_RecordToSelect)
+                             ->setSelection($selection)
+                             ->setModel($record)
+                             ->setView($this->view);
+                         }, $records);
+
+    $callback = function($wrapped) {
+      return $this->view->cardifyWithOverlay($wrapped);
+    };
+
+    $form = (new ZendAfi_Form)
+      ->setMethod('GET')
+      ->setAction($this->view->url())
+      ->addElement('search',
+                   'expressionRecherche',
+                   ['label' => $this->_('Rechercher'),
+                    'value' => $criteria->getExpressionRecherche(),
+                    'id' => 'expressionRecherche' . uniqid(),
+                    'class' => 'expressionRecherche',
+                    'placeholder' => $this->_('Un titre, un auteur')])
+
+      ->addElement('submit',
+                   'search_submit',
+                   ['label' => $this->_('Ok'),
+                    'onclick' => '$(this).parents(\'form\').submit()',
+                    'order' => 10])
+      ->addUniqDisplayGroup('group');
+
+    return $this->view->grid(
+                             $this->view->div(['class' => 'col-12'],
+                                              $this->_tag('h1', $this->view->titre))
+                             . $this->view->div(['class' => 'col-12 col-sm-6 py-3'],
+                                                $this->view->renderInlineForm($form))
+                             . $this->view->div(['class' => 'col-12'],
+                                                $this->view->renderWall(new Storm_Collection($records), $callback)));
+  }
+}
diff --git a/library/templates/Intonation/View/Abonne/ChangeImage.php b/library/templates/Intonation/View/Abonne/ChangeImage.php
index 631aae5d20e..756bdbbdc87 100644
--- a/library/templates/Intonation/View/Abonne/ChangeImage.php
+++ b/library/templates/Intonation/View/Abonne/ChangeImage.php
@@ -35,7 +35,6 @@ class Intonation_View_Abonne_ChangeImage extends Intonation_View_Abonne {
     $sections [6] = (new Intonation_Library_View_Wrapper_User_RichContent_ChangeImage)
       ->setModel($this->view->user)
       ->setView($this->view)
-      ->setContent($this->_getContent($this->view->user))
       ->beVisible();
 
     $rich_content->setSections($sections);
@@ -44,43 +43,4 @@ class Intonation_View_Abonne_ChangeImage extends Intonation_View_Abonne {
 
   protected function _showSections($sections) {
   }
-
-
-  protected function _getContent($user) {
-    if (!$paths = explode(';', Class_AdminVar::getValueOrDefault('USER_PROFILE_IMAGES')))
-      return '';
-
-    Class_FileManager::beOpenBar();
-
-    $images = [];
-    foreach ($paths as $path)
-      if ($image = Class_FileManager::find($path))
-        $images [] = $image;
-
-    $current_image = (new Intonation_Library_View_Wrapper_User)
-      ->setModel($user)
-      ->getPicture();
-
-    $html = [];
-    foreach ($images as $image)
-      $html [] = $this->_cardify($image, $current_image);
-
-    return $this->view->div(['class' => 'card-columns'],
-                            implode($html));
-  }
-
-
-  protected function _cardify($image, $current) {
-    $url = $image->getUrl();
-    $selected = ($current == $url) ? ' border border-primary disabled shadow-lg selected' : '';
-
-    return $this->view->div(['class' => 'card shadow-sm' . $selected],
-                            $this->view->tagAnchor($this->view->url(['controller' => 'abonne',
-                                                                     'action' => 'pick-image',],
-                                                                    null, true)
-                                                   . '?' . http_build_query(['id' => $image->getId()]),
-                                                   $this->view->tagImg($url),
-                                                   ['class' => 'card-img',
-                                                    'data-popup' => 'true']));
-  }
 }
diff --git a/library/templates/Intonation/View/Abonne/LoansList.php b/library/templates/Intonation/View/Abonne/LoansList.php
index 930a5aef60d..e00d1c0adf0 100644
--- a/library/templates/Intonation/View/Abonne/LoansList.php
+++ b/library/templates/Intonation/View/Abonne/LoansList.php
@@ -29,25 +29,18 @@ class Intonation_View_Abonne_LoansList extends ZendAfi_View_Helper_BaseHelper {
 
 
   public function abonne_LoansList($config) {
+    if ($error = $config->getError())
+      return '';
+
     $this->_config = $config;
     $loans = $config->getLoans();
-    $renewable_loans = $config->getRenewableLoansIds();
     $user = $config->getUser();
     $this->_profile = $profile = $config->getProfile();
-    $error = $config->getError();
-    $request_params = $config->getRequestParams();
     $this->_preferences = $preferences = new Class_Profil_Preferences_Loans();
 
     if($error)
       return '';
 
-    $extend_all = $renewable_loans
-      ? ($this->view->tagAnchor(['action' => 'prolongerPret',
-                                 'id_pret' => $renewable_loans],
-                                $this->_('Tout prolonger'),
-                                ['class' => 'btn']))
-      : '';
-
     $pnb = ($user->hasPNB())
       ? ($this->_tag('h2', $this->_('Prêts numériques en cours'))
          . $this->view->abonne_LoansPNB($user->getPNBLoans()))
@@ -57,7 +50,7 @@ class Intonation_View_Abonne_LoansList extends ZendAfi_View_Helper_BaseHelper {
                 $this->render($loans),
                 $pnb];
 
-    return implode(array_filter($content));
+    return implode($content);
   }
 
 
@@ -82,16 +75,7 @@ class Intonation_View_Abonne_LoansList extends ZendAfi_View_Helper_BaseHelper {
 
 
   protected function _renderTools() {
-    $tools = [];
-    $composition = $this->_preferences->getToolsCompositionOf($this->_profile);
-    $selected_tools = $composition->getSelected();
-
-    foreach($selected_tools as $tool)
-      $tools [] = $tool->renderWith($this);
-
-    return $this->_tag('div',
-                       implode(array_filter($tools)),
-                       ['class' => 'tools-bar']);
+    return '';
   }
 
 
@@ -103,10 +87,15 @@ class Intonation_View_Abonne_LoansList extends ZendAfi_View_Helper_BaseHelper {
                            ->setView($this->view);
                        }, $loans->getArrayCopy());
 
-    $callback = function($wrapped) {
-      return $this->view->cardifyHorizontal($wrapped);
-    };
+    $actions = [new Intonation_Library_Link(['Url' => ['controller' => 'abonne',
+                                                       'action' => 'prolongerPret',
+                                                       'id_pret' => $this->getRenewableLoans()],
+                                             'Text' => $this->_('Tout prolonger'),
+                                             'Title' => $this->_('Prolonger tous les prêts en cours'),
+                                             'Image' => Class_Template::current()->getIco($this->view,
+                                                                                          'extend-loan',
+                                                                                          'library')])];
 
-    return $this->view->renderTruncateList(new Storm_Collection($loans), $callback);
+    return $this->view->renderCollection(new Storm_Collection($loans), $actions);
   }
 }
diff --git a/library/templates/Intonation/View/Abonne/SelectionBoard.php b/library/templates/Intonation/View/Abonne/SelectionBoard.php
new file mode 100644
index 00000000000..5d6b3e01b4e
--- /dev/null
+++ b/library/templates/Intonation/View/Abonne/SelectionBoard.php
@@ -0,0 +1,48 @@
+<?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 Intonation_View_Abonne_SelectionBoard extends Intonation_View_Abonne {
+  public function abonne_SelectionBoard($selection) {
+    $this->view->selection = $selection;
+    $html = $this->abonne(Class_Users::getIdentity());
+    $this->view->titre = $this->_('%s : sélection %s',
+                                  $this->view->titre,
+                                  $selection->getLibelle());
+    return $html;
+  }
+
+
+  protected function _showSections($sections) {
+  }
+
+
+  protected function _hookOn($rich_content) {
+    $sections = $rich_content->getSections();
+
+    $sections [4] = (new Intonation_Library_View_Wrapper_User_RichContent_Selection)
+      ->setModel($this->view->selection)
+      ->setView($this->view)
+      ->beVisible();
+
+    $rich_content->setSections($sections);
+  }
+}
\ No newline at end of file
diff --git a/library/templates/Intonation/View/Abonne/Selections.php b/library/templates/Intonation/View/Abonne/Selections.php
new file mode 100644
index 00000000000..e930080de9d
--- /dev/null
+++ b/library/templates/Intonation/View/Abonne/Selections.php
@@ -0,0 +1,45 @@
+<?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_Selections extends ZendAfi_View_Helper_BaseHelper {
+
+  public function abonne_Selections($selections) {
+    $selections = array_map(function($selection)
+                            {
+                              return (new Intonation_Library_View_Wrapper_Selection)
+                                ->setModel($selection)
+                                ->setView($this->view);
+                            }, $selections);
+
+    $collection = new Storm_Collection($selections);
+
+    $actions = [new Intonation_Library_Link(['Url' => $this->view->url(['controller' => 'abonne',
+                                                                        'action' => 'creer-selection']),
+                                             'Text' => $this->_('Créer'),
+                                             'Title' => $this->_('Créer une nouvelle sélection'),
+                                             'Image' => Class_Template::current()->getIco($this->view,
+                                                                                          'add',
+                                                                                          'utils')])];
+
+    return $this->view->renderCollection($collection, $actions);
+  }
+}
\ No newline at end of file
diff --git a/library/templates/Intonation/View/BookmarkedSearches.php b/library/templates/Intonation/View/BookmarkedSearches.php
index f31e666392e..a0fc34fe67a 100644
--- a/library/templates/Intonation/View/BookmarkedSearches.php
+++ b/library/templates/Intonation/View/BookmarkedSearches.php
@@ -46,6 +46,14 @@ class Intonation_View_BookmarkedSearches extends ZendAfi_View_Helper_BaseHelper
 
     return
       $html
-      . $this->view->renderTruncateList(new Storm_Collection($searches), $callback);
+      . $this->view->renderCollection(new Storm_Collection($searches),
+                                      [new Intonation_Library_Link(['Url' => ['controller' => 'abonne',
+                                                                              'action' => 'suivre-une-recherche'],
+                                                                    'Text' => $this->_('Suivre'),
+                                                                    'Title' => $this->_('Suivre une recherche'),
+                                                                    'Image' =>
+                                                                    (Class_Template::current()->getIco($this->view,
+                                                                                                       'no-selection',
+                                                                                                       'library'))])]);
   }
 }
\ No newline at end of file
diff --git a/library/templates/Intonation/View/Cardify.php b/library/templates/Intonation/View/Cardify.php
index 3f8aabca2e6..24c898041ee 100644
--- a/library/templates/Intonation/View/Cardify.php
+++ b/library/templates/Intonation/View/Cardify.php
@@ -22,14 +22,16 @@
 
 class Intonation_View_Cardify extends ZendAfi_View_Helper_BaseHelper {
   public function cardify($element) {
-    $img = $this->view->tagMedia($element, ['class' => 'card-img-top']);
+    $img = $this->view->tagMedia($element,
+                                 ['class' => 'card-img-top text-center'],
+                                 ['class' => 'card-img']);
 
     if ($link = $element->getMainLink())
       $img = $this->view->tagAnchor($link->getUrl(),
                                     $img,
                                     ['title' => $link->getTitle(),
                                      'data-popup' => $link->getPopup(),
-                                     'class' => 'card-img-top']);
+                                     'class' => 'card-img-top text-center']);
 
     return $this->view->cardifyOnlyDescription($element, $img);
   }
diff --git a/library/templates/Intonation/View/CardifyHorizontal.php b/library/templates/Intonation/View/CardifyHorizontal.php
index 0ee78a75254..f806f67f51e 100644
--- a/library/templates/Intonation/View/CardifyHorizontal.php
+++ b/library/templates/Intonation/View/CardifyHorizontal.php
@@ -24,9 +24,6 @@ class Intonation_View_CardifyHorizontal extends ZendAfi_View_Helper_BaseHelper {
   public function cardifyHorizontal($element) {
     $img = $this->view->tagMedia($element, ['class' => 'img-thumbnail']);
 
-    if ($img_link = $element->getPictureAction())
-      $img = $this->view->tagAction($img_link->setText($img));
-
     $content = [$element->getContentForJSSearch()];
 
     if ($title = $element->getMainTitle())
@@ -42,21 +39,17 @@ class Intonation_View_CardifyHorizontal extends ZendAfi_View_Helper_BaseHelper {
                                                         'class' => 'card-link']),
                                 ['class' => 'card-subtitle']);
 
+    if (!$second_link && $secondary_title = $element->getSecondaryTitle())
+      $content [] = $this->_tag('h6',
+                                $secondary_title,
+                                ['class' => 'text-muted card-subtitle']);
+
     if ($summary = $element->getDescription())
       $content [] = $this->_tag('div',
                                 $element->getDescription(),
                                 ['title' => strip_tags($element->getDescriptionTitle()),
                                  'class' => 'card-text']);
 
-    $links = [];
-    if ($link = $element->getMainLink())
-      $links [] = $this->view->tagAction($link);
-
-    foreach($element->getActions() as $action)
-      $links [] = is_string($action)
-        ? $action
-      : $this->view->tagAction($action);
-
     return $this->_tag('div',
                        $this->view->grid($this->view->div(['class' => 'col-3 p-3'],
                                                           $img)
@@ -65,7 +58,8 @@ class Intonation_View_CardifyHorizontal extends ZendAfi_View_Helper_BaseHelper {
                                                             implode($content))
 
                                          . $this->view->div(['class' => 'col-12 col-sm-3 p-3'],
-                                                            implode(BR.BR, $links))),
+                                                            $this->view->renderActions(array_merge([$element->getMainLink()],
+                                                                                                   $element->getActions())))),
                        ['class' => 'card no_overflow']);
   }
 }
\ No newline at end of file
diff --git a/library/templates/Intonation/View/CardifyWithOverlay.php b/library/templates/Intonation/View/CardifyWithOverlay.php
index 4677eaad29a..189d34392f2 100644
--- a/library/templates/Intonation/View/CardifyWithOverlay.php
+++ b/library/templates/Intonation/View/CardifyWithOverlay.php
@@ -37,14 +37,13 @@ class Intonation_View_CardifyWithOverlay extends ZendAfi_View_Helper_BaseHelper
                          $title,
                          ['class' => 'card-title']);
 
-    $content = [$element->getContentForJSSearch(),
-                $title];
+    $content = [$title];
 
     if ($author_name = $element->getSecondaryTitle())
       $content [] = $this->_tag('h4',
                                 $element->getSecondaryIco()
                                 . $author_name,
-                                ['class' => 'card-subtitle text-muted']);
+                                ['class' => 'card-subtitle']);
 
     $doc_type = Class_Template::current()
       ->getIco($this->view,
@@ -54,17 +53,21 @@ class Intonation_View_CardifyWithOverlay extends ZendAfi_View_Helper_BaseHelper
 
     $content = implode($content) . $doc_type;
 
-    $main_link = $element->getMainLink();
-    $link = $this->view->tagAnchor($main_link->getUrl(),
-                                   $content,
-                                   ['class' => 'card-link',
-                                    'title' => $main_link->getTitle()]);
+    $main_link = $element
+      ->getMainLink()
+      ->setText($content)
+      ->setImage('');
+
+    $link = $this->view->tagAction($main_link);
 
     $overlay = $this->_tag('div',
-                           $link,
-                           ['class' => 'card-block card-img-overlay rounded text-center']);
+                           $element->getContentForJSSearch()
+                           . $link,
+                           ['class' => 'card-block card-img-overlay rounded text-center text-white']);
 
-    $img = $this->view->tagMedia($element, ['class' => 'text-center img_as_background text-muted']);
+    $img = $this->view->tagMedia($element,
+                                 ['class' => 'text-center img_as_background'],
+                                 ['class' => 'card-img']);
 
     return $img . $overlay;
   }
diff --git a/library/templates/Intonation/View/Jumbotron.php b/library/templates/Intonation/View/Jumbotron.php
index 77adae937d5..307a98708f3 100644
--- a/library/templates/Intonation/View/Jumbotron.php
+++ b/library/templates/Intonation/View/Jumbotron.php
@@ -52,7 +52,7 @@ class Intonation_View_Jumbotron extends ZendAfi_View_Helper_BaseHelper {
     $html [] = $this->_div(['class' => 'order-5 order-xl-4 mh-100 no-gutters col-10 col-xl-3'], $this->_actions());
     $html [] = $this->_div(['class' => 'order-6 col-10 col-xl-9 mt-2'], $this->_nav());
 
-    return $this->_div(['class' => 'jumbotron jumbotron-fluid w-100 no_overflow'],
+    return $this->_div(['class' => 'jumbotron jumbotron-fluid w-100 no_overflow py-3 mb-3'],
                        $this->view->grid(implode($html),
                                          [],
                                          ['class' => 'justify-content-center']));
@@ -136,7 +136,7 @@ class Intonation_View_Jumbotron extends ZendAfi_View_Helper_BaseHelper {
 
 
   protected function _renderNavItem($item) {
-    $id = $this->_element->getModel()->getId();
+    $id = $item->getModel()->getId();
 
     $params = ['title' => $item->getNavTitle(),
                'class' => 'nav-link' . ((false !== strpos($this->_current_url, implode('/', $item->getNavUrl()))) ? ' active' : '') . ' ' . $item->getClass()];
diff --git a/library/templates/Intonation/View/Opac.php b/library/templates/Intonation/View/Opac.php
index 8241d553864..cc157ae7c56 100644
--- a/library/templates/Intonation/View/Opac.php
+++ b/library/templates/Intonation/View/Opac.php
@@ -151,6 +151,16 @@ class Intonation_View_Opac extends ZendAfi_View_Helper_BaseHelper {
     if($custom_js_urls = $this->_template->getCustomJsUrl())
       $head_scripts->addScripts($custom_js_urls);
 
+    if ($profile_css = $this->_profile->getHeaderCss())
+      $head_scripts->addStyleSheet($profile_css . '?cache='.md5(time()),
+                                   ['id' => 'profil_css',
+                                    'data-url' => $profile_css,
+                                    'media' => 'all']);
+
+    if (Class_Users::isCurrentUserAdmin())
+      $head_scripts->loadAmber(true)
+                   ->addAmberPackage('AFI-OPAC');
+
     $content = [$this->_tag('meta', null, ['http-equiv' => 'Content-Type',
                                            'content' => 'text/html;charset=UTF-8']),
 
diff --git a/library/templates/Intonation/View/Paniers.php b/library/templates/Intonation/View/Paniers.php
deleted file mode 100644
index d9cd3b80335..00000000000
--- a/library/templates/Intonation/View/Paniers.php
+++ /dev/null
@@ -1,53 +0,0 @@
-<?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_Paniers extends ZendAfi_View_Helper_BaseHelper {
-
-  public function paniers($paniers, $panier_courant = null) {
-    $paniers_pro = Class_Users::getIdentity()->hasRightAccessDomaines()
-      ? $this->view->tagAnchor($this->view->url(['controller' => 'panier',
-                                                 'action' => 'domain'], null ,true),
-                               $this->_('Voir les sélections rangées dans les domaines'),
-                               ['class' => 'mr-3',
-                                'title' => $this->_('Voir les sélections rangés dans les domaines')]) .
-      $this->view->tagAnchor($this->view->url(['controller' => 'panier',
-                                               'action' => 'pro'], null ,true),
-                             $this->_('Voir les sélections des professionnels'),
-                             ['title' => $this->_('Voir les sélections des professionnels')])
-      : '';
-
-    $html [] = $this->view->div([],
-                                $paniers_pro) . BR;
-
-
-    $html [] = $this->view->tagAnchor($this->view->url(['controller' => 'panier',
-                                                        'action' => 'add'], null ,true),
-                                      $this->_('Créer une nouvelle sélection'),
-                                      ['data-popup' => 'true',
-                                       'class' => 'my-3',
-                                       'title' => $this->_('Créer une nouvelle sélection')]);
-
-    $html [] = $this->view->user_Selections($paniers);
-
-    return implode($html);
-  }
-}
\ No newline at end of file
diff --git a/library/templates/Intonation/View/User/Selections.php b/library/templates/Intonation/View/RenderActions.php
similarity index 58%
rename from library/templates/Intonation/View/User/Selections.php
rename to library/templates/Intonation/View/RenderActions.php
index 426b432958b..3fa775d7147 100644
--- a/library/templates/Intonation/View/User/Selections.php
+++ b/library/templates/Intonation/View/RenderActions.php
@@ -20,19 +20,15 @@
  */
 
 
-class Intonation_View_User_Selections extends ZendAfi_View_Helper_BaseHelper {
-  public function user_Selections($selections) {
-    $selections = array_map(function($selection)
-                       {
-                         return (new Intonation_Library_View_Wrapper_Selection)
-                           ->setModel($selection)
-                           ->setView($this->view);
-                       }, $selections);
+class Intonation_View_RenderActions extends ZendAfi_View_Helper_BaseHelper {
+  public function renderActions($actions) {
+    $links = [];
+    foreach($actions as $action)
+      $links [] = $this->view->div(['class' => 'col col-sm-12 p-1 m-1'],
+                                   (is_string($action)
+                                    ? $action
+                                    : $this->view->tagAction($action)));
 
-    $callback = function($wrapped) {
-      return $this->view->cardifyOnlyDescription($wrapped);
-    };
-
-    return $this->view->renderTruncateList(new Storm_Collection($selections), $callback);
+    return $this->view->grid(implode($links));
   }
 }
diff --git a/library/templates/Intonation/View/RenderCollection.php b/library/templates/Intonation/View/RenderCollection.php
new file mode 100644
index 00000000000..25cc0218d1c
--- /dev/null
+++ b/library/templates/Intonation/View/RenderCollection.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 Intonation_View_RenderCollection extends ZendAfi_View_Helper_BaseHelper {
+  public function renderCollection($collection, $actions = []) {
+    $html = [$this->view->div(['class' => 'col-12 col-sm-3 pt-sm-5 pl-3 order-sm-3'],
+                               $this->view->renderActions($actions)),
+
+             $this->view->div(['class' => 'col-12 col-sm-9'],
+                              $this->view->renderTruncateList($collection,
+                                                              (function($item)
+                                                              {
+                                                                return $this->view->cardifyHorizontal($item);
+                                                              })))];
+
+    return $this->view->grid(implode($html));
+  }
+}
diff --git a/library/templates/Intonation/View/RenderInlineForm.php b/library/templates/Intonation/View/RenderInlineForm.php
index 613cc813bc4..e4a228fd929 100644
--- a/library/templates/Intonation/View/RenderInlineForm.php
+++ b/library/templates/Intonation/View/RenderInlineForm.php
@@ -53,7 +53,12 @@ class Intonation_View_RenderInlineForm extends Intonation_View_RenderForm {
     $decorator->setOptions([]);
     $newDecorators[$name] = $decorator;
 
-    $newDecorators[] = ['HtmlTag', ['tag' => 'div', 'class' => 'col']];
+    $newDecorators[] = ['HtmlTag', ['tag' => 'div', 'class' => 'col-12 col-sm']];
+
+    $newDecorators[] = ['HtmlTagWrapper',
+                        ['tag' => 'div',
+                         'class' => 'col row form-group']];
+
 
     return $newDecorators;
   }
diff --git a/library/templates/Intonation/View/RenderRecordBadges.php b/library/templates/Intonation/View/RenderRecordBadges.php
index f6bbf85e6c4..e1938fcaaea 100644
--- a/library/templates/Intonation/View/RenderRecordBadges.php
+++ b/library/templates/Intonation/View/RenderRecordBadges.php
@@ -24,7 +24,7 @@ class Intonation_View_RenderRecordBadges extends ZendAfi_View_Helper_BaseHelper
 
   public function renderRecordBadges($record) {
     $badges = [['tag' => 'span',
-                'class' => 'secondary',
+                'class' => 'warning fs_1em',
                 'text' => (Class_Template::current()
                            ->getIco($this->view,
                                     $record->getTypeDoc(),
diff --git a/library/templates/Intonation/View/RenderTruncateList.php b/library/templates/Intonation/View/RenderTruncateList.php
index 6d78ae3bfd1..23964017216 100644
--- a/library/templates/Intonation/View/RenderTruncateList.php
+++ b/library/templates/Intonation/View/RenderTruncateList.php
@@ -75,14 +75,14 @@ class Intonation_View_RenderTruncateList extends ZendAfi_View_Helper_BaseHelper
     $form
       ->addElement('select',
                    $id,
-                   ['label' => '',
+                   ['label' => $this->_('Afficher'),
                     'title' => $this->_('Limiter le nombre d\'éléments à afficher'),
                     'onchange' => $onchange,
                     'multiOptions' => $multi_options])
 
       ->addElement('text',
                    $input_id,
-                   ['label' => '',
+                   ['label' => $this->_('Filtrer'),
                     'placeholder' => $this->_('Filtrer avec un nom, un mot, une date'),
                     'title' => $this->_('Filtrer la liste avec des noms, des mots, des dates'),
                     'onkeyup' => $onchange])
@@ -100,7 +100,8 @@ class Intonation_View_RenderTruncateList extends ZendAfi_View_Helper_BaseHelper
                              . $this->_tag('div',
                                            $this->view->renderInlineForm($form),
                                            ['class' => 'col-11 col-sm-8']),
-                             ['class' => 'container']);
+                             ['class' => 'container'],
+                             ['class' => 'justify-content-center']);
   }
 
 
diff --git a/library/templates/Intonation/View/TagAction.php b/library/templates/Intonation/View/TagAction.php
index abaf619b9ab..8b756bf5018 100644
--- a/library/templates/Intonation/View/TagAction.php
+++ b/library/templates/Intonation/View/TagAction.php
@@ -33,11 +33,18 @@ class Intonation_View_TagAction extends ZendAfi_View_Helper_BaseHelper {
     if ('button' == $action->getTag())
       return $this->view->button($action);
 
-    return $this->view->tagAnchor($action->getUrl(),
-                                  $img . $action->getText(),
-                                  array_merge(['title' => $action->getTitle(),
-                                              'data-popup' => $action->getPopup(),
-                                              'class' => 'card-link ' . $action->getClasses()],
-                                              $attribs));
+    $content = $img . $action->getText();
+    $attribs = array_merge(['title' => $action->getTitle(),
+                            'data-popup' => $action->getPopup(),
+                            'class' => 'card-link ' . $action->getClasses()],
+                           $attribs);
+
+    return ($url = $action->getUrl())
+      ? $this->view->tagAnchor($url,
+                               $content,
+                               $attribs)
+      : $this->_tag('a',
+                    $content,
+                    $attribs);
   }
 }
diff --git a/library/templates/Intonation/View/TagMedia.php b/library/templates/Intonation/View/TagMedia.php
index 779d5416279..4203a5a9025 100644
--- a/library/templates/Intonation/View/TagMedia.php
+++ b/library/templates/Intonation/View/TagMedia.php
@@ -21,17 +21,33 @@
 
 
 class Intonation_View_TagMedia extends ZendAfi_View_Helper_BaseHelper {
-  public function tagMedia($instance, $attribs = null) {
+  public function tagMedia($instance, $attribs = [], $img_attribs = []) {
+    if ($html = $instance->getHtmlPicture())
+      return $this->_div($attribs,
+                         $html);
+
+    if (!$media = $this->_getMedia($instance, $attribs, $img_attribs))
+      return '';
+
+    if ($img_link = $instance->getMainLink())
+      return  $this->view->tagAction($img_link
+                                    ->setText($media)
+                                     ->setImage(''));
+
+    return '';
+  }
+
+
+  protected function _getMedia($instance, $attribs, $img_attribs) {
     if ($embed = $instance->getEmbedMedia())
       return $this->_div($attribs,
                          $this->view->renderEmbed($embed->getSource(),
                                                   $embed->getUrl()));
 
-    if ($html = $instance->getHtmlPicture())
+    if ($picture = $instance->getPicture())
       return $this->_div($attribs,
-                         $html);
+                         $this->view->tagImg($picture, $img_attribs));
 
-    return $this->_div($attribs,
-                       $this->view->tagImg($instance->getPicture()));
+    return '';
   }
 }
diff --git a/tests/application/modules/opac/controllers/NoticeAjaxControllerTest.php b/tests/application/modules/opac/controllers/NoticeAjaxControllerTest.php
index 70891b3613a..6ab0e3f5722 100644
--- a/tests/application/modules/opac/controllers/NoticeAjaxControllerTest.php
+++ b/tests/application/modules/opac/controllers/NoticeAjaxControllerTest.php
@@ -1220,7 +1220,7 @@ class NoticeAjaxControllerNoticeActionTest extends AbstractControllerTestCase {
 
 
 
-class NoticeAjaxControllerVideoMorceauTest extends AbstractControllerTestCase {
+abstract class NoticeAjaxControllerVideoMorceauTest extends AbstractControllerTestCase {
   public function setUp() {
     parent::setUp();
     $this->dispatch('/opac/noticeajax/videomorceau?auteur=The%20Beatles&titre=Come%20together&width=300', true);
diff --git a/tests/scenarios/Templates/TemplatesTest.php b/tests/scenarios/Templates/TemplatesTest.php
index 5ca7e1f77b4..16b6bb21661 100644
--- a/tests/scenarios/Templates/TemplatesTest.php
+++ b/tests/scenarios/Templates/TemplatesTest.php
@@ -61,6 +61,12 @@ class TemplatesControllerIndexDispatchTest extends TemplatesEnabledTestCase {
   public function resetIntonationShouldBePresent() {
     $this->assertXPath('//table//a[contains(@href,"/admin/template/reset/template/INTONATION")]');
   }
+
+
+  /** @test */
+  public function updateIntonationShouldBePresent() {
+    $this->assertXPath('//table//a[contains(@href,"/admin/template/update/template/INTONATION")]');
+  }
 }
 
 
@@ -125,6 +131,60 @@ class TemplatesControllerResetIntonationTest extends TemplatesEnabledTestCase {
 
 
 
+class TemplatesControllerUpdateIntonationTest extends TemplatesEnabledTestCase {
+  protected $_storm_default_to_volatile = true;
+
+  public function setUp() {
+    parent::setUp();
+    $settings = $this->fixture('Class_Template_Settings',
+                               ['id' => 1,
+                                'template' => 'INTONATION']);
+
+    $settings->updateAttributes(['IntonationCoreJs' => 0,
+                                 'IntonationCustomCssUrl' => ['https://my-server.org/assets/css/top.css'],
+                                 'IntonationCustomCssClass' => ['bg-dark',
+                                                                'align-items-center']])->save();
+
+    $this->dispatch('/admin/template/update/template/INTONATION', true);
+  }
+
+
+  /** @test */
+  public function shouldRedirectToReferer() {
+    $this->assertRedirect();
+  }
+
+
+  /** @test */
+  public function bgDarkShouldBePresentInCustomCssClass() {
+    $settings = Class_Template::current()->getSettings();
+    $custom_css_class = $settings->getIntonationCustomCssClass();
+    $this->assertContains('bg-dark',
+                          $custom_css_class);
+  }
+
+
+  /** @test */
+  public function alignItemsCenterBePresentInCustomCssClass() {
+    $settings = Class_Template::current()->getSettings();
+    $settings->updateSettings();
+    $custom_css_class = $settings->getIntonationCustomCssClass();
+    $this->assertContains('align-items-center',
+                          $custom_css_class);
+  }
+
+
+  /** @test */
+  public function alignItemsCenterShouldBePresentInCustomCssClassOnce() {
+    $settings = Class_Template::current()->getSettings();
+    $settings->updateSettings();
+    $custom_css_class = $settings->getIntonationCustomCssClass();
+    $this->assertEquals(1, array_count_values($custom_css_class)['align-items-center']);
+  }
+}
+
+
+
 
 class TemplatesControllerTryHistoricTest extends TemplatesEnabledTestCase {
   protected $_storm_default_to_volatile = true;
@@ -270,6 +330,7 @@ abstract class TemplatesIntonationTestCase extends TemplatesEnabledTestCase {
 
     $profile = $this->fixture('Class_Profil',
                               ['id' => 71,
+                               'header_css' => 'profile_css.css',
                                'parent_id' => null])
                     ->setMenuHautOn(false)
                     ->setCfgMenus($nav)
@@ -314,6 +375,12 @@ class TemplatesDispatchIntonationTest extends TemplatesIntonationTestCase {
   }
 
 
+  /** @test */
+  public function profileCssUrlShouldBeLoaded() {
+    $this->assertXPath('//head/link[contains(@href, "profile_css.css")]');
+  }
+
+
   /** @test */
   public function customJsUrlShouldBeLoaded() {
     $this->assertXPath('//head/script[contains(@src, "https://my-server.org/assets/js/bottom.js")]');
@@ -1028,7 +1095,7 @@ class TemplatesIntonationHydratingtest extends ModelTestCase {
 
             [['element' => 'select',
               'attribs' => []],
-             'form-control form-control custom-select'],
+             'form-control form-control-sm custom-select custom-select-sm'],
 
             [['element' => 'button',
               'attribs' => ['class' => 'btn search-button']],
@@ -1806,7 +1873,7 @@ class TemplatesViewRecordTest extends TemplatesIntonationTestCase {
 
   /** @test */
   public function bagesShouldContainsTopicRoman() {
-    $this->assertXPathContentContains('//a[contains(@class, "badge")][contains(@href, "/recherche/simple/code_rebond/G13")]', 'Roman');
+    $this->assertXPathContentContains('//a[contains(@class, "badge")][contains(@href, "/recherche/simple")][contains(@href, "code_rebond/G13")]', 'Roman');
   }
 
 
@@ -2693,6 +2760,16 @@ abstract class TemplatesIntonationAccountTestCase extends TemplatesIntonationTes
       ->setPseudo('Paul')
       ->beAbonneSIGB();
 
+    $this->fixture('Class_Notice',
+                   ['id' => 10,
+                    'titre_principal' => 'Le combat ordinaire',
+                    'clef_alpha' => 'COMBAT ORDINAIRE']);
+
+    $this->fixture('Class_Notice',
+                   ['id' => 12,
+                    'titre_principal' => 'Blacksad',
+                    'clef_alpha' => 'BLACKSAD']);
+
     $this->fixture('Class_PanierNotice',
                    ['id' => 2,
                     'id_panier' => 1,
@@ -2812,7 +2889,7 @@ class TemplatesIntonationDispatchAccountTest extends TemplatesIntonationAccountT
 
   /** @test */
   public function paulShouldHaveBadgeNumberOfSelection1() {
-    $this->assertXPathContentContains('//div//a[contains(@class, "badge")]', '1 sélection(s)');
+    $this->assertXPathContentContains('//div//a[contains(@class, "badge")]', '4 sélection(s)');
   }
 
 
@@ -2961,4 +3038,25 @@ class TemplatesIntonationDispatchAccountEditTest extends TemplatesIntonationAcco
     $this->postDispatch('/opac/abonne/modifier/id_profil/72', ['nom' => 'Dev']);
     $this->assertEquals('Dev', Class_Users::getIdentity()->getNom());
   }
+}
+
+
+
+
+class TemplatesIntonationDispatchAbonneSelectionTest extends TemplatesIntonationAccountTestCase {
+  /** @test */
+  public function exporterSelection2LinkShouldBePresent() {
+    $this->dispatch('/opac/abonne/selection/id/2/id_profil/72');
+    $this->assertXPathContentContains('//a[contains(@href, "/abonne/exporter-selection/id/2")]', 'Exporter');
+  }
+}
+
+
+
+class TemplatesIntonationDispatchAbonneAjouterASelectionTest extends TemplatesIntonationAccountTestCase {
+  /** @test */
+  public function exporterSelection2LinkShouldBePresent() {
+    $this->dispatch('/opac/abonne/ajouter-a-la-selection/id/2/id_profil/72');
+    $this->assertXPathContentContains('//h1', 'Ajouter des documents à la sélection');
+  }
 }
\ No newline at end of file
-- 
GitLab