From 61fc1bc0ca3ebc6cf7fbfd476e0009a0ee24f745 Mon Sep 17 00:00:00 2001
From: gloas <gloas@afi-sa.fr>
Date: Tue, 18 Feb 2020 11:51:52 +0100
Subject: [PATCH] wip on theme store

---
 .../opac/controllers/AbonneController.php     |  33 ++-
 .../opac/controllers/RechercheController.php  |   6 +
 .../opac/controllers/RssController.php        |   8 +
 .../scripts/abonne/suggestion-achat-add.phtml |  38 +--
 .../views/scripts/abonne/suggestions.phtml    |   2 +
 .../opac/views/scripts/rss/render-items.phtml |   2 +
 library/Class/Rss.php                         |  18 +-
 library/Class/RssItem.php                     |   7 +-
 library/Class/SuggestionAchat.php             |  18 +-
 library/Class/Systeme/ModulesAccueil/Rss.php  |   6 +-
 library/Class/User/Cards.php                  |   5 +
 .../WebService/SIGB/Koha/RestfulService.php   |   1 +
 .../SIGB/Koha/SuggestionsReader.php           |  10 +
 .../SIGB/Nanook/PatronInfoReader.php          |  10 +-
 .../Class/WebService/SIGB/Nanook/Service.php  |   7 +-
 .../WebService/SIGB/Nanook/Suggestion.php     |   1 +
 library/Class/WebService/SIGB/Suggestion.php  |  20 ++
 .../View/Helper/Abonne/NewSuggestion.php      |  65 +++++
 .../Intonation/Assets/css/intonation.css      |   7 +-
 .../Intonation/Library/View/Wrapper/Hold.php  |  59 +++-
 .../Library/View/Wrapper/Library.php          |   1 +
 .../Intonation/Library/View/Wrapper/Loan.php  |   8 +-
 .../Intonation/Library/View/Wrapper/Rss.php   | 125 ++++++++
 .../Library/View/Wrapper/RssItem.php          | 114 ++++++++
 .../Library/View/Wrapper/Search.php           |  48 +--
 .../Library/View/Wrapper/Suggestion.php       | 186 ++++++++++++
 .../Library/View/Wrapper/User/RichContent.php |   1 +
 .../Wrapper/User/RichContent/ChangeImage.php  |   2 +-
 .../User/RichContent/NewSuggestion.php        | 130 +++++++++
 .../Wrapper/User/RichContent/Settings.php     |   4 +-
 .../Wrapper/User/RichContent/Suggestions.php  |  68 +++++
 .../Library/Widget/Carousel/Author/View.php   |  29 +-
 .../Widget/Carousel/Rss/Definition.php        |  41 +++
 .../Library/Widget/Carousel/Rss/Form.php      |  54 ++++
 .../Library/Widget/Carousel/Rss/View.php      |  72 +++++
 .../Library/Widget/Carousel/View.php          |  11 +-
 .../Intonation/Library/Widget/Search/View.php |   2 +-
 library/templates/Intonation/Template.php     |   4 +-
 .../Intonation/View/Abonne/ChangeImage.php    |   2 +-
 .../Intonation/View/Abonne/NewSuggestion.php  |  61 ++++
 .../Intonation/View/Abonne/Suggestions.php    |  53 ++++
 .../View/Abonne/SuggestionsBoard.php          |  31 ++
 .../Intonation/View/CardifyHorizontal.php     |   2 +-
 .../View/CardifyOnlyDescription.php           |   2 +-
 .../Intonation/View/CardifyWithOverlay.php    |   2 +-
 .../templates/Intonation/View/Jumbotron.php   |   2 +-
 .../Intonation/View/RenderCollection.php      |  18 +-
 .../Intonation/View/RenderTruncateList.php    |  46 +--
 .../Intonation/View/Rss/RenderItems.php       |  41 +++
 .../Intonation/View/Search/History.php        |   2 +
 .../Intonation/View/Search/HtmlCriteria.php   |   2 +-
 .../Intonation/View/Search/Result.php         |   4 +-
 .../templates/Intonation/View/TagAction.php   |   2 +-
 .../Intonation/View/User/Informations.php     |  25 +-
 .../opac/java/search_input/search_input.css   |   2 +-
 public/opac/java/search_input/search_input.js |  74 ++---
 public/opac/js/calendrier.js                  |   3 +
 scripts/emacs/phafi-mode.el                   |   4 +-
 .../Templates/PolygoneTemplateTest.php        | 276 +++++++++++++++++-
 tests/scenarios/Templates/TemplatesTest.php   |  59 +++-
 60 files changed, 1742 insertions(+), 194 deletions(-)
 create mode 100644 application/modules/opac/views/scripts/abonne/suggestions.phtml
 create mode 100644 application/modules/opac/views/scripts/rss/render-items.phtml
 create mode 100644 library/ZendAfi/View/Helper/Abonne/NewSuggestion.php
 create mode 100644 library/templates/Intonation/Library/View/Wrapper/Rss.php
 create mode 100644 library/templates/Intonation/Library/View/Wrapper/RssItem.php
 create mode 100644 library/templates/Intonation/Library/View/Wrapper/Suggestion.php
 create mode 100644 library/templates/Intonation/Library/View/Wrapper/User/RichContent/NewSuggestion.php
 create mode 100644 library/templates/Intonation/Library/View/Wrapper/User/RichContent/Suggestions.php
 create mode 100644 library/templates/Intonation/Library/Widget/Carousel/Rss/Definition.php
 create mode 100644 library/templates/Intonation/Library/Widget/Carousel/Rss/Form.php
 create mode 100644 library/templates/Intonation/Library/Widget/Carousel/Rss/View.php
 create mode 100644 library/templates/Intonation/View/Abonne/NewSuggestion.php
 create mode 100644 library/templates/Intonation/View/Abonne/Suggestions.php
 create mode 100644 library/templates/Intonation/View/Abonne/SuggestionsBoard.php
 create mode 100644 library/templates/Intonation/View/Rss/RenderItems.php

diff --git a/application/modules/opac/controllers/AbonneController.php b/application/modules/opac/controllers/AbonneController.php
index 38804abbd35..491a5f3e478 100644
--- a/application/modules/opac/controllers/AbonneController.php
+++ b/application/modules/opac/controllers/AbonneController.php
@@ -995,7 +995,12 @@ class AbonneController extends ZendAfi_Controller_Action {
     }
 
     $this->_helper->notify($this->_('Suggestion d\'achat enregistrée'));
-    return $this->_redirect(Class_Url::absolute($this->view->url(['action' => 'suggestion-achat'])));
+
+    $action = Class_Template::current()->isLegacy()
+      ? 'suggestion-achat'
+      : 'suggestions';
+
+    return $this->_redirect(Class_Url::absolute($this->view->url(['action' => $action])));
   }
 
 
@@ -1508,15 +1513,23 @@ class AbonneController extends ZendAfi_Controller_Action {
     }
 
     $allowed_paths = explode(';', Class_AdminVar::getValueOrDefault('USER_PROFILE_IMAGES'));
-    if (!in_array($image->getPath(), $allowed_paths)) {
-      $this->_helper->notify($this->_('Une erreur c\'est produite. Votre image de profil n\'a pas été modifiée.'));
-      return $this->_redirectClose($this->_getReferer());
-    }
+    $allowed_images = [];
 
-    (new Class_User_Settings($this->_user))->setProfileImage($image->getPath());
-    $this->_user->save();
+    foreach ($allowed_paths as $path)
+      if ($image_var = Class_FileManager::find($path))
+        $allowed_images [] = $image_var;
+
+    foreach ($allowed_images as $allowed_image) {
+      if ($image->getId() == $allowed_image->getId()) {
+        (new Class_User_Settings($this->_user))->setProfileImage($image->getPath());
+        $this->_user->save();
 
-    $this->_helper->notify($this->_('Votre image de profil a bien été modifiée.'));
+        $this->_helper->notify($this->_('Votre image de profil a bien été modifiée.'));
+        return $this->_redirectClose($this->_getReferer());
+      }
+    }
+
+    $this->_helper->notify($this->_('Une erreur c\'est produite. Votre image de profil n\'a pas été modifiée.'));
     return $this->_redirectClose($this->_getReferer());
   }
 
@@ -1529,6 +1542,10 @@ class AbonneController extends ZendAfi_Controller_Action {
   }
 
 
+  public function suggestionsAction() {
+  }
+
+
   public function selectionAction() {
     if (!$this->view->selection = Class_PanierNotice::find($this->_getParam('selection_id'))) {
       return $this->_redirectClose($this->_getReferer());
diff --git a/application/modules/opac/controllers/RechercheController.php b/application/modules/opac/controllers/RechercheController.php
index 9891a3b75ad..3889c63f74e 100644
--- a/application/modules/opac/controllers/RechercheController.php
+++ b/application/modules/opac/controllers/RechercheController.php
@@ -1020,6 +1020,12 @@ class RechercheController extends ZendAfi_Controller_Action {
   }
 
 
+  public function clearLastSearchSessionAction() {
+    $this->_helper->getHelper('viewRenderer')->setNoRender();
+    Zend_Registry::get('session')->last_search = null;
+  }
+
+
   protected function newCriteresRecherches($params) {
     $criteres = (new Class_CriteresRecherche())
       ->setParams($params);
diff --git a/application/modules/opac/controllers/RssController.php b/application/modules/opac/controllers/RssController.php
index a6d55907e0b..0142edbdd06 100644
--- a/application/modules/opac/controllers/RssController.php
+++ b/application/modules/opac/controllers/RssController.php
@@ -284,4 +284,12 @@ class RssController extends ZendAfi_Controller_Action {
                    $notice->getAnnee(),
                    $this->view->absoluteUrl($notice->fetchUrlLocalVignette()));
   }
+
+
+  public function renderItemsAction() {
+    if (!$this->view->rss = Class_Rss::find($this->_getParam('id', null)))
+      return $this->getHelper('ViewRenderer')->setNoRender();
+
+    $this->getHelper('ViewRenderer')->setLayoutScript('subModal.phtml');
+  }
 }
diff --git a/application/modules/opac/views/scripts/abonne/suggestion-achat-add.phtml b/application/modules/opac/views/scripts/abonne/suggestion-achat-add.phtml
index c0c5de91765..675ed4f19cf 100644
--- a/application/modules/opac/views/scripts/abonne/suggestion-achat-add.phtml
+++ b/application/modules/opac/views/scripts/abonne/suggestion-achat-add.phtml
@@ -1,38 +1,2 @@
 <?php
-$this->openBoite($this->_('Suggérer un achat'));
-
-$send_form_button = '';
-
-if ($this->records) {
-  echo $this->tag('p', $this->_('Les documents suivants sont dans le catalogue.'));
-  echo $this->tag('p', $this->_('Votre suggestion en fait-elle partie ?'));
-
-  echo $this->button(new Class_Entity(['Text' => $this->_('Oui'),
-                                       'Url' => $this->url(['action' => 'suggestion-achat']),
-                                       'Attribs' => ['title' => $this->_('Annuler et revenir à la liste des suggestions.')]]));
-  $send_form_button = new Class_Entity(['Text' => $this->_('Non'),
-                                        'Attribs' => ['title' => $this->_('Envoyer la suggestion'),
-                                                     'onclick' => "var form = $('#suggestion'); form.attr('action', form.attr('action') + '?validate_suggestion=1'); $('#submit').click();"]]);
-
-  echo $this->button($send_form_button);
-
-  $send_form_button->setText($this->_('Envoyer'));
-
-  echo $this->ListeNotices_Mur($this->records);
-  $submit = $this->form->getElement('submit');
-  $submit
-    ->setLabel($this->_('Relancer la recherche'))
-    ->setAttribs(['title' => $this->_('Relance la recherche ou envoie la suggestion en cas de recherche infructueuse')]);
-}
-
-if (trim($this->preferences['help-text']))
-  echo $this->tag('div',
-                  $this->preferences['help-text'],
-                  ['class' => 'help-text']);
-echo $this->renderForm($this->form);
-
-if ($send_form_button)
-  echo $this->button($send_form_button);
-
-$this->closeBoite();
-?>
+echo $this->abonne_NewSuggestion($this->form, $this->preferences, $this->records);
diff --git a/application/modules/opac/views/scripts/abonne/suggestions.phtml b/application/modules/opac/views/scripts/abonne/suggestions.phtml
new file mode 100644
index 00000000000..b77d7ad8112
--- /dev/null
+++ b/application/modules/opac/views/scripts/abonne/suggestions.phtml
@@ -0,0 +1,2 @@
+<?php
+echo $this->abonne_SuggestionsBoard($this->user);
diff --git a/application/modules/opac/views/scripts/rss/render-items.phtml b/application/modules/opac/views/scripts/rss/render-items.phtml
new file mode 100644
index 00000000000..46529203a70
--- /dev/null
+++ b/application/modules/opac/views/scripts/rss/render-items.phtml
@@ -0,0 +1,2 @@
+<?php
+echo $this->rss_RenderItems($this->rss);
diff --git a/library/Class/Rss.php b/library/Class/Rss.php
index 36a850454aa..6265212af06 100644
--- a/library/Class/Rss.php
+++ b/library/Class/Rss.php
@@ -41,13 +41,29 @@ class Class_RssModelFlux extends BaseItem {
 }
 
 class RssLoader extends Storm_Model_Loader {
+
+
+  public function getRssFrom($categories, $ids) {
+    $rss = [];
+
+    foreach($categories as $category)
+      $rss = array_merge($rss, Class_Rss::findAllBy(['id_cat' => $category,
+                                                     'order' => 'titre']));
+
+    foreach($ids as $id)
+      $rss [] = Class_Rss::find($id);
+
+    return array_filter($rss);
+  }
+
+
   /**
    * @param array $id_feeds
    * @param array $id_categories
    * @return array
    */
   public function getFluxFromIdsAndCategories($id_feeds, $id_categories) {
-    $feeds = array();
+    $feeds = [];
 
     foreach ($id_feeds as $id_feed)   {
       if ($id_feed)
diff --git a/library/Class/RssItem.php b/library/Class/RssItem.php
index 12445fdd61d..50783a499a1 100644
--- a/library/Class/RssItem.php
+++ b/library/Class/RssItem.php
@@ -16,7 +16,7 @@
  *
  * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
  * along with BOKEH; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA 
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
 
 /**
@@ -45,6 +45,11 @@ class Class_RssItem {
   }
 
 
+  public function getPubDate() {
+    return trim($this->_wrapped_item->pubDate());
+  }
+
+
   public function getDate() {
     $date = '';
     if ($item_date = trim($this->_wrapped_item->pubDate())) {
diff --git a/library/Class/SuggestionAchat.php b/library/Class/SuggestionAchat.php
index 266f3fd63cf..6e19134ff3b 100644
--- a/library/Class/SuggestionAchat.php
+++ b/library/Class/SuggestionAchat.php
@@ -52,6 +52,23 @@ class Class_SuggestionAchat extends Storm_Model_Abstract {
   }
 
 
+  public function getStatus() {
+    return '';
+  }
+
+
+  public function getResponse() {
+    return '';
+  }
+
+
+  public function getLibrary() {
+    return ($library = Class_Bib::findFirstBy(['id_site' => $this->getBibId()]))
+      ? $library->getLibelle()
+      : '';
+  }
+
+
   public function validate() {
     $this
       ->validateTitleOrComment()
@@ -136,7 +153,6 @@ class Class_SuggestionAchat extends Storm_Model_Abstract {
   }
 
 
-
   public function getIdabon() {
     if ($this->hasUser() &&  $this->getUser()->isAbonne())
       return $this->getUser()->getIdabon();
diff --git a/library/Class/Systeme/ModulesAccueil/Rss.php b/library/Class/Systeme/ModulesAccueil/Rss.php
index aca2bdc785a..68152d2f4c1 100644
--- a/library/Class/Systeme/ModulesAccueil/Rss.php
+++ b/library/Class/Systeme/ModulesAccueil/Rss.php
@@ -19,7 +19,11 @@
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
 class Class_Systeme_ModulesAccueil_Rss extends Class_Systeme_ModulesAccueil_Null {
-  const CODE = 'RSS';
+  const
+    CODE = 'RSS',
+    ORDER_SELECTION = 'selection',
+    ORDER_RANDOM = 'random',
+    ORDER_ALPHA = 'titre';
 
   protected
     $_group = Class_Systeme_ModulesAccueil::GROUP_INFO,
diff --git a/library/Class/User/Cards.php b/library/Class/User/Cards.php
index 6f4504e78bd..45d92510572 100644
--- a/library/Class/User/Cards.php
+++ b/library/Class/User/Cards.php
@@ -49,6 +49,11 @@ class Class_User_Cards extends Storm_Model_Collection {
   }
 
 
+  public function getSuggestions() {
+    return $this->_decorateOperationFrom(function($card) { return new Storm_Collection($card->getSuggestionAchat()); });
+  }
+
+
   public function countWaitingToBePulled() {
     return $this->injectInto(0,
                              function($count, $card)
diff --git a/library/Class/WebService/SIGB/Koha/RestfulService.php b/library/Class/WebService/SIGB/Koha/RestfulService.php
index 3b35cc3fbe2..31f23e32c5f 100644
--- a/library/Class/WebService/SIGB/Koha/RestfulService.php
+++ b/library/Class/WebService/SIGB/Koha/RestfulService.php
@@ -64,6 +64,7 @@ class Class_WebService_SIGB_Koha_RestfulService
                                     trim($json)));
 
     return (new Class_WebService_SIGB_Koha_SuggestionsReader())
+      ->setUser($user)
       ->parse($json);
   }
 
diff --git a/library/Class/WebService/SIGB/Koha/SuggestionsReader.php b/library/Class/WebService/SIGB/Koha/SuggestionsReader.php
index 93fd0447e6c..d2cab928693 100644
--- a/library/Class/WebService/SIGB/Koha/SuggestionsReader.php
+++ b/library/Class/WebService/SIGB/Koha/SuggestionsReader.php
@@ -23,6 +23,15 @@
 class Class_WebService_SIGB_Koha_SuggestionsReader {
   use Trait_Translator;
 
+  protected $_user;
+
+
+  public function setUser($user) {
+    $this->_user = $user;
+    return $this;
+  }
+
+
   public function parse($json) {
     return ($datas = json_decode($json)) ?
       array_map([$this, '_parseOne'], $datas) : [];
@@ -31,6 +40,7 @@ class Class_WebService_SIGB_Koha_SuggestionsReader {
 
   protected function _parseOne($data) {
     return (new Class_WebService_SIGB_Suggestion())
+      ->setUser($this->_user)
       ->setTitle(trim($data->title))
       ->setAuthor(trim($data->author))
       ->setPublicationYear($this->_publicationYearFor($data))
diff --git a/library/Class/WebService/SIGB/Nanook/PatronInfoReader.php b/library/Class/WebService/SIGB/Nanook/PatronInfoReader.php
index 546621cdd82..93f09e9e715 100644
--- a/library/Class/WebService/SIGB/Nanook/PatronInfoReader.php
+++ b/library/Class/WebService/SIGB/Nanook/PatronInfoReader.php
@@ -20,6 +20,7 @@
  */
 class Class_WebService_SIGB_Nanook_PatronInfoReader extends Class_WebService_SIGB_AbstractILSDIPatronInfoReader{
   protected
+    $_user,
     $_suggests = [],
     $_item_priorities = [],
     $_current_suggest;
@@ -32,6 +33,12 @@ class Class_WebService_SIGB_Nanook_PatronInfoReader extends Class_WebService_SIG
   }
 
 
+  public function setUser($user) {
+    $this->_user = $user;
+    return $this;
+  }
+
+
   public function startHold($attributes) {
     parent::startHold($attributes);
     $this->_item_priorities = [];
@@ -195,7 +202,8 @@ class Class_WebService_SIGB_Nanook_PatronInfoReader extends Class_WebService_SIG
 
 
   public function startSuggest() {
-    $this->_current_suggest = new Class_WebService_SIGB_Nanook_Suggestion();
+    $this->_current_suggest = (new Class_WebService_SIGB_Nanook_Suggestion)
+      ->setUser($this->_user);
   }
 
 
diff --git a/library/Class/WebService/SIGB/Nanook/Service.php b/library/Class/WebService/SIGB/Nanook/Service.php
index b7afef243bd..b9dae0a4c92 100644
--- a/library/Class/WebService/SIGB/Nanook/Service.php
+++ b/library/Class/WebService/SIGB/Nanook/Service.php
@@ -333,9 +333,10 @@ class Class_Webservice_SIGB_Nanook_Service extends Class_WebService_SIGB_Abstrac
 
 
   public function suggestionsOf($user) {
-    return $this->getSuggestionsFromilsdiPatronInfo(['patronId' => $user->getIdSigb()],
-                                                    Class_WebService_SIGB_Nanook_PatronInfoReader::newInstance());
-
+    return
+      $this->getSuggestionsFromilsdiPatronInfo(['patronId' => $user->getIdSigb()],
+                                               Class_WebService_SIGB_Nanook_PatronInfoReader::newInstance()
+                                               ->setUser($user));
   }
 
 
diff --git a/library/Class/WebService/SIGB/Nanook/Suggestion.php b/library/Class/WebService/SIGB/Nanook/Suggestion.php
index 30d734ece03..5048294755a 100644
--- a/library/Class/WebService/SIGB/Nanook/Suggestion.php
+++ b/library/Class/WebService/SIGB/Nanook/Suggestion.php
@@ -28,6 +28,7 @@ class Class_WebService_SIGB_Nanook_Suggestion extends Class_WebService_SIGB_Sugg
     $this->_attribs = array_merge($this->_attribs, ['url' => '']);
   }
 
+
   public function acceptVisitor($visitor) {
     $visitor
       ->visitField($this->_('Isbn/Ean'), $this->getIsbn())
diff --git a/library/Class/WebService/SIGB/Suggestion.php b/library/Class/WebService/SIGB/Suggestion.php
index e3fa8794191..019634b98d5 100644
--- a/library/Class/WebService/SIGB/Suggestion.php
+++ b/library/Class/WebService/SIGB/Suggestion.php
@@ -86,4 +86,24 @@ class Class_WebService_SIGB_Suggestion extends Class_Entity {
   public function getAuteur() {
     return $this->getAuthor();
   }
+
+
+  public function getTypeDoc() {
+    return null;
+  }
+
+
+  public function getDateCreation() {
+    return $this->getDate();
+  }
+
+
+  public function getCommentaire() {
+    return $this->getNote();
+  }
+
+
+  public function getDescriptionUrl() {
+    return $this->getUrl();
+  }
 }
\ No newline at end of file
diff --git a/library/ZendAfi/View/Helper/Abonne/NewSuggestion.php b/library/ZendAfi/View/Helper/Abonne/NewSuggestion.php
new file mode 100644
index 00000000000..113d940d22e
--- /dev/null
+++ b/library/ZendAfi/View/Helper/Abonne/NewSuggestion.php
@@ -0,0 +1,65 @@
+<?php
+/**
+ * Copyright (c) 2012-2020, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class ZendAfi_View_Helper_Abonne_NewSuggestion extends ZendAfi_View_Helper_BaseHelper {
+  public function abonne_NewSuggestion($form, $preferences, $records = []) {
+    $html = [$this->view->openBoiteContent($this->_('Suggérer un achat'))];
+
+    $send_form_button = '';
+
+    if ($records) {
+      $html [] = $this->view->tag('p', $this->_('Les documents suivants sont dans le catalogue.'));
+      $html [] = $this->view->tag('p', $this->_('Votre suggestion en fait-elle partie ?'));
+
+      $html [] = $this->view->button(new Class_Entity(['Text' => $this->_('Oui'),
+                                                 'Url' => $this->view->url(['action' => 'suggestion-achat']),
+                                                 'Attribs' => ['title' => $this->_('Annuler et revenir à la liste des suggestions.')]]));
+      $send_form_button = new Class_Entity(['Text' => $this->_('Non'),
+                                            'Attribs' => ['title' => $this->_('Envoyer la suggestion'),
+                                                          'onclick' => "var form = $('#suggestion'); form.attr('action', form.attr('action') + '?validate_suggestion=1'); $('#submit').click();"]]);
+
+      $html [] = $this->view->button($send_form_button);
+
+      $send_form_button->setText($this->_('Envoyer'));
+
+      $html [] = $this->view->ListeNotices_Mur($records);
+      $submit = $form->getElement('submit');
+      $submit
+        ->setLabel($this->_('Relancer la recherche'))
+        ->setAttribs(['title' => $this->_('Relance la recherche ou envoie la suggestion en cas de recherche infructueuse')]);
+    }
+
+    if (trim($preferences['help-text']))
+      $html [] = $this->view->tag('div',
+                  $preferences['help-text'],
+                  ['class' => 'help-text']);
+
+    $html [] = $this->view->renderForm($form);
+
+    if ($send_form_button)
+      $html [] = $this->view->button($send_form_button);
+
+    $html [] = $this->view->closeBoiteContent();
+
+    return implode($html);
+  }
+}
\ No newline at end of file
diff --git a/library/templates/Intonation/Assets/css/intonation.css b/library/templates/Intonation/Assets/css/intonation.css
index d5aaf6fb519..eaad168b604 100644
--- a/library/templates/Intonation/Assets/css/intonation.css
+++ b/library/templates/Intonation/Assets/css/intonation.css
@@ -418,8 +418,7 @@ label[data-name=note] ~ div label.multi-element-label + br {
 }
 
 .jumbotron .nav-link div {
-    font-size: 11px;
-    max-width: 100px;
+    font-size: 10px;
 }
 
 .modal_image {
@@ -738,4 +737,8 @@ dl.row {
 
 .menu_admin_front a[class*="deactivate"][style*="inline"] {
     display: block !important;
+}
+
+.description dd a + a {
+    display: block;
 }
\ No newline at end of file
diff --git a/library/templates/Intonation/Library/View/Wrapper/Hold.php b/library/templates/Intonation/Library/View/Wrapper/Hold.php
index 05a0e4301f5..ab7b14b0b0f 100644
--- a/library/templates/Intonation/Library/View/Wrapper/Hold.php
+++ b/library/templates/Intonation/Library/View/Wrapper/Hold.php
@@ -68,26 +68,34 @@ class Intonation_Library_View_Wrapper_Hold extends Intonation_Library_View_Wrapp
 
 
   public function getDescription() {
+    $html = [$this->getBadges()];
+
     if (!$this->_model->getNoticeOPAC())
-      return '';
+      return implode($html);
 
     $wrapper = (new Intonation_Library_View_Wrapper_Record)
       ->setView($this->_view)
       ->setModel($this->_model->getNoticeOPAC());
 
-    return $wrapper->getDescription();
+    $html [] = $wrapper->getDescription();
+
+    return implode($html);
   }
 
 
   public function getFullDescription() {
+        $html = [$this->getBadges()];
+
     if (!$this->_model->getNoticeOPAC())
-      return '';
+      return implode($html);
 
     $wrapper = (new Intonation_Library_View_Wrapper_Record)
       ->setView($this->_view)
       ->setModel($this->_model->getNoticeOPAC());
 
-    return $wrapper->getFullDescription();
+    $html [] = $wrapper->getFullDescription();
+
+    return implode($html);
   }
 
 
@@ -167,30 +175,49 @@ class Intonation_Library_View_Wrapper_Hold extends Intonation_Library_View_Wrapp
 
 
   public function getBadges() {
-    if (!$this->_model->getNoticeOPAC())
-      return null;
-
     $badges = [
                ((new Intonation_Library_Badge)
                 ->setTag('span')
-                ->setClass('secondary')
+                ->setClass('success')
+                ->setText($this->_model->isWaitingToBePulled() ? $this->_('Prêt !') : '')
+                ->setTitle($this->_('Votre réservation %s est prête', $this->_model->getTitre()))),
+
+               ((new Intonation_Library_Badge)
+                ->setTag('span')
+                ->setClass($this->_model->isWaitingToBePulled() ? 'success' : 'secondary')
                 ->setImage(Class_Template::current()->getIco($this->_view,
                                                              'library',
                                                              'library'))
-                ->setText($this->_model->getBibliotheque())
-                ->setTitle($this->_('Bibliothèque de l\'emprunt: %s', $this->_model->getBibliotheque())))
-    ];
+                ->setText($this->_model->getPickupLocationLabel())
+                ->setTitle($this->_('Récupérer la réservation à %s', $this->_model->getPickupLocationLabel()))),
 
-    $wrapper = (new Intonation_Library_View_Wrapper_Record)
-      ->setView($this->_view)
-      ->setModel($this->_model->getNoticeOPAC());
+               ((new Intonation_Library_Badge)
+                ->setTag('span')
+                ->setClass('secondary')
+                ->setText($this->_model->getEtat())
+                ->setTitle($this->_('État de la réservation: %s', $this->_model->getEtat()))),
+
+               ((new Intonation_Library_Badge)
+                ->setTag('span')
+                ->setClass('secondary')
+                ->setText($this->_model->getRang())
+                ->setTitle($this->_('Rang de la réservation: %s', $this->_model->getRang())))
+    ];
 
-    return $this->_view->renderBadges($badges) . $wrapper->getBadges();
+    return $this->_view->renderBadges($badges);
   }
 
 
   public function getActions() {
-    return [];
+    return [ new Intonation_Library_Link(['Url' => $this->_view->url(['controller' => 'abonne',
+                                                                      'action' => 'reservations',
+                                                                      'id_delete' => $this->_model->getId()]),
+                                          'Text' => $this->_('Supprimer'),
+                                          'Image' => Class_Template::current()->getIco($this->_view,
+                                                                                       'delete',
+                                                                                       'utils'),
+                                          'Title' => $this->_('Supprimer la réservation %s',
+                                                              $this->_model->getTitre())])];
   }
 
 
diff --git a/library/templates/Intonation/Library/View/Wrapper/Library.php b/library/templates/Intonation/Library/View/Wrapper/Library.php
index 6ea269e4165..697c24fa971 100644
--- a/library/templates/Intonation/Library/View/Wrapper/Library.php
+++ b/library/templates/Intonation/Library/View/Wrapper/Library.php
@@ -133,6 +133,7 @@ class Intonation_Library_View_Wrapper_Library extends Intonation_Library_View_Wr
                 ((new Intonation_Library_Badge)
                  ->setTag('a')
                  ->setClass('primary text-light')
+                 ->setText(str_replace([' ', '.', ','], ' ', $this->_model->getTelephone()))
                  ->setUrl(sprintf('tel:%s',
                                   str_replace([' ', '.', ',',], '', $this->_model->getTelephone())))
                  ->setImage(Class_Template::current()->getIco($this->_view,
diff --git a/library/templates/Intonation/Library/View/Wrapper/Loan.php b/library/templates/Intonation/Library/View/Wrapper/Loan.php
index 613240c5af7..0e60c66c4cf 100644
--- a/library/templates/Intonation/Library/View/Wrapper/Loan.php
+++ b/library/templates/Intonation/Library/View/Wrapper/Loan.php
@@ -145,14 +145,14 @@ class Intonation_Library_View_Wrapper_Loan extends Intonation_Library_View_Wrapp
 
                ((new Intonation_Library_Badge)
                 ->setTag('span')
-                ->setClass('info')
+                ->setClass('warning')
                 ->setImage(Class_Template::current()->getIco($this->_view,
                                                              'thumbtack',
                                                              'library'))
-                ->setText(($this->_model->getOnHold()
-                           ? $this->_('Réservé')
+                ->setText(($this->_model->getBookedByOthers()
+                           ? $this->_('Déjà réservé')
                            : ''))
-                ->setTitle($this->_('Exemplaire réservé'))),
+                ->setTitle($this->_('Exemplaire déjà réservé par d\'autres'))),
 
                ((new Intonation_Library_Badge)
                 ->setTag('span')
diff --git a/library/templates/Intonation/Library/View/Wrapper/Rss.php b/library/templates/Intonation/Library/View/Wrapper/Rss.php
new file mode 100644
index 00000000000..7407cff1cd2
--- /dev/null
+++ b/library/templates/Intonation/Library/View/Wrapper/Rss.php
@@ -0,0 +1,125 @@
+<?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_Rss extends Intonation_Library_View_Wrapper_Abstract {
+
+  public function getMainTitle() {
+    return $this->_model->getTitre();
+  }
+
+
+  public function getMainLink() {
+    return new Intonation_Library_Link(['Url' => $this->_model->getUrl(),
+                                        'Text' => $this->_view->_('Voir le flux'),
+                                        'Title' => $this->_view->_('Voir le flux %s', $this->_model->getTitre()),
+                                        'Image' => Class_Template::current()->getIco($this->_view,
+                                                                                     'read-document',
+                                                                                     'library')]);
+  }
+
+
+  public function getPicture() {
+    return '';
+  }
+
+
+  public function getPictureAction() {
+  }
+
+
+  public function getSecondaryTitle() {
+  }
+
+
+  public function getSecondaryLink() {
+  }
+
+
+  public function getSecondaryIco() {
+  }
+
+
+  public function getActions() {
+    return [];
+  }
+
+
+  public function getDescription() {
+    return
+      $this->getBadges()
+      . $this->_model->getDescription()
+      . $this->loadRssItems();
+  }
+
+
+  public function getFullDescription() {
+    return $this->getDescription();
+  }
+
+
+  public function getDescriptionTitle() {
+  }
+
+
+  public function getBadges() {
+    $badges = [];
+
+    $tags = explode(';', $this->_model->getTags());
+
+    foreach ($tags as $tag)
+      $badges [] = (new Intonation_Library_Badge)
+      ->setTag('span')
+      ->setClass('secondary')
+      ->setText($tag)
+      ->setTitle($this->_('Tag attribué au flux %s', $this->_model->getTitre()));
+
+    return $this->_view->renderBadges($badges);
+  }
+
+
+  public function getDocType() {
+  }
+
+
+  public function getDocTypeLabel() {
+  }
+
+
+  public function getEmbedMedia() {
+    return '';
+  }
+
+
+  public function getHtmlPicture() {
+    return '';
+  }
+
+
+  public function getOsmData() {
+    return null;
+  }
+
+
+  protected function loadRssItems() {
+    return $this->_view->renderAjax('rss', 'render-items', ['id' => $this->_model->getId()]);
+  }
+}
diff --git a/library/templates/Intonation/Library/View/Wrapper/RssItem.php b/library/templates/Intonation/Library/View/Wrapper/RssItem.php
new file mode 100644
index 00000000000..ac669864ef4
--- /dev/null
+++ b/library/templates/Intonation/Library/View/Wrapper/RssItem.php
@@ -0,0 +1,114 @@
+<?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_RssItem extends Intonation_Library_View_Wrapper_Abstract {
+
+  public function getMainTitle() {
+    return $this->_model->getTitle();
+  }
+
+
+  public function getMainLink() {
+    return new Intonation_Library_Link(['Url' => $this->_model->getLink(),
+                                        'Text' => $this->_view->_('Accéder au contenu'),
+                                        'Title' => $this->_view->_('Accéder au contenu de %s', $this->getMainTitle()),
+                                        'Image' => Class_Template::current()->getIco($this->_view,
+                                                                                     'read-document',
+                                                                                     'library')]);
+  }
+
+
+  public function getPicture() {
+    return '';
+  }
+
+
+  public function getPictureAction() {
+  }
+
+
+  public function getSecondaryTitle() {
+  }
+
+
+  public function getSecondaryLink() {
+  }
+
+
+  public function getSecondaryIco() {
+  }
+
+
+  public function getActions() {
+    return [];
+  }
+
+
+  public function getDescription() {
+    return
+      $this->getBadges()
+      . $this->_model->getDescription();
+  }
+
+
+  public function getFullDescription() {
+    return $this->getDescription();
+  }
+
+
+  public function getDescriptionTitle() {
+  }
+
+
+  public function getBadges() {
+    $badges = [(new Intonation_Library_Badge)
+               ->setTag('span')
+               ->setClass('info')
+               ->setText($this->_model->getPubDate())
+               ->setTitle($this->_('Date de diffusion de %s', $this->getMainTitle()))];
+
+    return $this->_view->renderBadges($badges);
+  }
+
+
+  public function getDocType() {
+  }
+
+
+  public function getDocTypeLabel() {
+  }
+
+
+  public function getEmbedMedia() {
+    return '';
+  }
+
+
+  public function getHtmlPicture() {
+    return '';
+  }
+
+
+  public function getOsmData() {
+    return null;
+  }
+}
diff --git a/library/templates/Intonation/Library/View/Wrapper/Search.php b/library/templates/Intonation/Library/View/Wrapper/Search.php
index 8c462d81054..27cbe3400c0 100644
--- a/library/templates/Intonation/Library/View/Wrapper/Search.php
+++ b/library/templates/Intonation/Library/View/Wrapper/Search.php
@@ -43,26 +43,12 @@ class Intonation_Library_View_Wrapper_Search extends Intonation_Library_View_Wra
 
 
   public function getMainLink() {
-    $id = $this->_model->getExistingIdFor(Class_Users::getIdentity());
-    $can_follow = !$id;
-    return new Intonation_Library_Link(['Url' => $this->_view->url(array_merge($this->_model->getCriteriasUrl(),
-                                                                               ['controller' => 'bookmarked-searches',
-                                                                                'action' => $can_follow ? 'add' : 'delete',
-                                                                                'label' => $this->getMainTitle(),
-                                                                                'id' => $id])),
+    return new Intonation_Library_Link(['Url' => $this->_view->url($this->_model->getCriteriasUrl()),
+                                        'Text' => $this->_('Voir'),
+                                        'Title' => $this->_('Voir le résultat de recherche'),
                                         'Image' => Class_Template::current()->getIco($this->_view,
-                                                                                     ($can_follow
-                                                                                      ? 'no-selection'
-                                                                                      : 'selection'),
-                                                                                     'library'),
-                                        'Text' => ($can_follow
-                                                   ? $this->_('Suivre')
-                                                   : $this->_('Ne plus suivre')),
-                                        'Title' => ($can_follow
-                                                    ? $this->_('Suivre cette recherche')
-                                                    : $this->_('Ne plus suivre cette recherche.')),
-                                        'NoButtonText' => 1,
-                                        'Popup' => true]);
+                                                                                     'search_more',
+                                                                                     'library')]);
   }
 
 
@@ -146,12 +132,26 @@ class Intonation_Library_View_Wrapper_Search extends Intonation_Library_View_Wra
 
 
   public function getActions() {
-    return [new Intonation_Library_Link(['Url' => $this->_view->url($this->_model->getCriteriasUrl()),
-                                         'Text' => $this->_('Voir'),
-                                         'Title' => $this->_('Voir le résultat de recherche'),
+    $id = $this->_model->getExistingIdFor(Class_Users::getIdentity());
+    $can_follow = !$id;
+    return [new Intonation_Library_Link(['Url' => $this->_view->url(array_merge($this->_model->getCriteriasUrl(),
+                                                                                ['controller' => 'bookmarked-searches',
+                                                                                 'action' => $can_follow ? 'add' : 'delete',
+                                                                                 'label' => $this->getMainTitle(),
+                                                                                 'id' => $id])),
                                          'Image' => Class_Template::current()->getIco($this->_view,
-                                                                                      'search_more',
-                                                                                      'library')])];
+                                                                                      ($can_follow
+                                                                                       ? 'no-selection'
+                                                                                       : 'selection'),
+                                                                                      'library'),
+                                         'Text' => ($can_follow
+                                                    ? $this->_('Suivre')
+                                                    : $this->_('Ne plus suivre')),
+                                         'Title' => ($can_follow
+                                                     ? $this->_('Suivre cette recherche')
+                                                     : $this->_('Ne plus suivre cette recherche.')),
+                                         'InlineText' => 1,
+                                         'Popup' => true])];
   }
 
 
diff --git a/library/templates/Intonation/Library/View/Wrapper/Suggestion.php b/library/templates/Intonation/Library/View/Wrapper/Suggestion.php
new file mode 100644
index 00000000000..f883c5ea745
--- /dev/null
+++ b/library/templates/Intonation/Library/View/Wrapper/Suggestion.php
@@ -0,0 +1,186 @@
+<?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_Suggestion extends Intonation_Library_View_Wrapper_Abstract {
+
+  public function getMainTitle() {
+    return $this->_('%s suggéré par %s %s',
+                    $this->_model->getTitre(),
+                    $this->_model->getUser()->getIdabon(),
+                    $this->_model->getUser()->getNomComplet());
+  }
+
+
+  public function getSecondaryTitle() {
+    return $this->_model->getAuteur();
+  }
+
+
+  public function getPicture() {
+    return '';
+  }
+
+
+  public function getPictureAction() {
+    return '';
+  }
+
+
+  public function getSecondaryIco() {
+    return Class_Template::current()
+      ->getIco($this->_view,
+               'author',
+               'library');
+  }
+
+
+  public function getDescription() {
+    $html = [$this->getBadges(),
+             $this->_model->getCommentaire()];
+
+    if ($url = $this->_model->getDescriptionUrl())
+      $html [] = $this->_view->tagAnchor($url, $url);
+
+    if ($response = $this->_model->getResponse()) {
+      $html [] = $this->_view->tag('h4', $this->_('Réponse du %s', $this->_model->getResponseDate()));
+      $html [] = $response;
+    }
+
+    $html = array_filter($html);
+    return implode(BR, $html);
+  }
+
+
+  public function getFullDescription() {
+    return $this->getDescription();
+  }
+
+
+  public function getDescriptionTitle() {
+    return '';
+  }
+
+
+  public function getMainLink() {
+    return null;
+  }
+
+
+  public function getSecondaryLink() {
+    if(!$author = $this->getSecondaryTitle())
+      return null;
+
+    if (!$facet = Class_CodifAuteur::findWithFullName($author))
+      return null;
+
+    $author_field = new Class_Notice_FieldAuthor($facet);
+
+    return new Intonation_Library_Link(['Url' => $author_field->getUrlForLink(),
+                                        'Image' => $this->getSecondaryIco(),
+                                        'Text' =>  $author,
+                                        'Title' => $author_field->getTitle()]);
+  }
+
+
+  public function getDocType() {
+    return $this->_model->getTypeDoc();
+  }
+
+
+  public function getDocTypeLabel() {
+    return ($doc_type = $this->getDocType())
+      ? $doc_type->getLabel()
+      : '';
+  }
+
+
+  public function getBadges() {
+    $badges = [
+               ((new Intonation_Library_Badge)
+                ->setTag('a')
+                ->setClass('warning fs_1em')
+                ->setImage((Class_Template::current()
+                            ->getIco($this->_view,
+                                     $this->getDocTypeLabel(),
+                                     'doc_types')))
+                ->setText($this->getDocTypeLabel())
+                ->setTitle($this->_('Type de document %s de la suggestion %s',
+                                    $this->getDocTypeLabel(),
+                                    $this->_model->getTitre()))),
+
+               ((new Intonation_Library_Badge)
+                ->setTag('span')
+                ->setClass('primary fs_1em')
+                ->setImage((Class_Template::current()
+                            ->getIco($this->_view,
+                                     $this->getDocTypeLabel(),
+                                     'doc_types')))
+                ->setText($this->_model->getStatus())
+                ->setTitle($this->_('Statut de la suggestion %s',
+                                    $this->getDocTypeLabel(),
+                                    $this->_model->getTitre())))];
+
+    if ($lib = $this->_model->getLibrary())
+      $badges [] = ((new Intonation_Library_Badge)
+                    ->setTag('span')
+                    ->setClass('info')
+                    ->setImage(Class_Template::current()->getIco($this->_view,
+                                                                 'library',
+                                                                 'library'))
+                    ->setText($lib)
+                    ->setTitle($this->_('Site de la suggestion %s', $lib)));
+
+    $badges [] = ((new Intonation_Library_Badge)
+                  ->setTag('span')
+                  ->setClass('secondary')
+                  ->setText($this->_model->getDateCreation())
+                  ->setTitle($this->_('Date de la suggestion %s', $this->_model->getDateCreation())));
+
+    $badges [] = ((new Intonation_Library_Badge)
+                  ->setTag('span')
+                  ->setClass('secondary')
+                  ->setText($this->_model->getIsbn())
+                  ->setTitle($this->_('ISBN %s', $this->_model->getIsbn())));
+
+    return $this->_view->renderBadges($badges);
+  }
+
+
+  public function getActions() {
+    return [];
+  }
+
+
+  public function getEmbedMedia() {
+    return '';
+  }
+
+
+  public function getHtmlPicture() {
+    return '';
+  }
+
+
+  public function getOsmData() {
+    return null;
+  }
+}
\ No newline at end of file
diff --git a/library/templates/Intonation/Library/View/Wrapper/User/RichContent.php b/library/templates/Intonation/Library/View/Wrapper/User/RichContent.php
index c486e5c2285..77c83fe34a8 100644
--- a/library/templates/Intonation/Library/View/Wrapper/User/RichContent.php
+++ b/library/templates/Intonation/Library/View/Wrapper/User/RichContent.php
@@ -42,6 +42,7 @@ class Intonation_Library_View_Wrapper_User_RichContent extends Intonation_Librar
             new Intonation_Library_View_Wrapper_User_RichContent_Informations,
             new Intonation_Library_View_Wrapper_User_RichContent_Loans,
             new Intonation_Library_View_Wrapper_User_RichContent_Holds,
+            new Intonation_Library_View_Wrapper_User_RichContent_Suggestions,
             new Intonation_Library_View_Wrapper_User_RichContent_Selections,
             new Intonation_Library_View_Wrapper_User_RichContent_Reviews,
             new Intonation_Library_View_Wrapper_User_RichContent_Settings];
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 c38727cc70e..07d1dbd36c7 100644
--- a/library/templates/Intonation/Library/View/Wrapper/User/RichContent/ChangeImage.php
+++ b/library/templates/Intonation/Library/View/Wrapper/User/RichContent/ChangeImage.php
@@ -58,7 +58,7 @@ class Intonation_Library_View_Wrapper_User_RichContent_ChangeImage extends Inton
 
   protected function _cardify($image, $current) {
     $url = $image->getUrl();
-    $selected = ($current == $url) ? ' border border-primary disabled shadow-lg selected' : '';
+    $selected = ($current == $url) ? ' border border-primary disabled shadow-lg selected btn btn-primary' : '';
 
     return $this->_view->div(['class' => 'card shadow-sm' . $selected],
                             $this->_view->tagAnchor($this->_view->url(['controller' => 'abonne',
diff --git a/library/templates/Intonation/Library/View/Wrapper/User/RichContent/NewSuggestion.php b/library/templates/Intonation/Library/View/Wrapper/User/RichContent/NewSuggestion.php
new file mode 100644
index 00000000000..c3e43158a2f
--- /dev/null
+++ b/library/templates/Intonation/Library/View/Wrapper/User/RichContent/NewSuggestion.php
@@ -0,0 +1,130 @@
+<?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_NewSuggestion extends Intonation_Library_View_Wrapper_User_RichContent_Suggestions {
+
+  protected
+    $_form,
+    $_preferences,
+    $_records;
+
+
+  public function setForm($form) {
+    $this->_form = $form;
+    return $this;
+  }
+
+
+  public function setPreferences($preferences) {
+    $this->_preferences = $preferences;
+    return $this;
+  }
+
+
+  public function setRecords($records) {
+    $this->_records = $records;
+    return $this;
+  }
+
+  public function getTitle() {
+    return $this->_('Suggérer un achat');
+  }
+
+
+  public function getDBTitle() {
+    return $this->getTitle();
+  }
+
+
+  public function getContent() {
+    $html = [];
+
+    $send_form_button = '';
+
+    if ($this->_records) {
+      $html [] = $this->_view->tag('p', $this->_('Les documents suivants sont dans le catalogue.'));
+      $html [] = $this->_view->tag('p', $this->_('Votre suggestion %s de %s en fait-elle partie ?',
+                                                 $this->_form->getElement('Title')->getValue(),
+                                                 $this->_form->getElement('Author')->getValue()));
+
+      $html [] = $this->_view->button(new Class_Entity(['Text' => $this->_('Oui'),
+                                                        'Url' => $this->_view->url(['action' => 'suggestion-achat']),
+                                                        'Attribs' => ['class' => 'mr-5',
+                                                                      'title' => $this->_('Annuler et revenir à la liste des suggestions.')]]));
+      $send_form_button = new Class_Entity(['Text' => $this->_('Non'),
+                                            'Attribs' => ['title' => $this->_('Envoyer la suggestion'),
+                                                          'onclick' => "var form = $('#suggestion'); form.attr('action', form.attr('action') + '?validate_suggestion=1'); $('#submit').click();"]]);
+
+      $html [] = $this->_view->button($send_form_button);
+
+      $send_form_button->setText($this->_('Envoyer'));
+
+      $html [] = $this->_renderRecords();
+
+      $submit = $this->_form->getElement('submit');
+      $submit
+        ->setLabel($this->_('Relancer la recherche'))
+        ->setAttribs(['class' => 'my-3',
+                      'title' => $this->_('Relance la recherche ou envoie la suggestion en cas de recherche infructueuse')]);
+    }
+
+    if (trim($this->_preferences['help-text']))
+      $html [] = $this->tag('p',
+                            $this->_preferences['help-text'],
+                            ['class' => 'help-text']);
+
+    $html [] = $this->_view->renderForm($this->_form);
+
+    if ($send_form_button)
+      $html [] = $this->_view->button($send_form_button);
+
+    return implode($html);
+  }
+
+
+  protected function _renderRecords() {
+    if (!$this->_records)
+      return '';
+
+    $callback = function ($record) {
+      $wrapped =
+      (new Intonation_Library_View_Wrapper_Record)
+      ->setView($this->_view)
+      ->setModel($record);
+      return $this->_view->cardifyHorizontal($wrapped);
+    };
+
+    return $this->_view->div(['class' => 'col-12 mt-3 p-0'],
+                             $this->_view->renderList(new Storm_Collection($this->_records),
+                                                      $callback));
+  }
+
+
+  public function getClass() {
+    return 'user_new_suggestion';
+  }
+
+
+  public function getNavTitle() {
+    return $this->_('Suggérer un achat');
+  }
+}
diff --git a/library/templates/Intonation/Library/View/Wrapper/User/RichContent/Settings.php b/library/templates/Intonation/Library/View/Wrapper/User/RichContent/Settings.php
index 31259676cc2..ed1fbff6983 100644
--- a/library/templates/Intonation/Library/View/Wrapper/User/RichContent/Settings.php
+++ b/library/templates/Intonation/Library/View/Wrapper/User/RichContent/Settings.php
@@ -70,10 +70,12 @@ class Intonation_Library_View_Wrapper_User_RichContent_Settings extends Intonati
                                                                                                                 'action' => 'add-card']),
                                                                                     'Text' => $this->_('Ajouter une carte'),
                                                                                     'Image' => Class_Template::current()->getIco($this->_view, 'add_user', 'utils'),
+                                                                                    'Class' => 'btn btn-sm btn-success',
+                                                                                    'InlineText' => 1,
                                                                                     'Popup' => 1])))];
 
     if (!$cards->isEmpty())
-      $html [] = $this->_view->div(['class' => 'col-12'],
+      $html [] = $this->_view->div(['class' => 'mt-3 col-12'],
                                     $this->_renderCardsCarousel($cards));
 
     return $this->_view->grid(implode($html));
diff --git a/library/templates/Intonation/Library/View/Wrapper/User/RichContent/Suggestions.php b/library/templates/Intonation/Library/View/Wrapper/User/RichContent/Suggestions.php
new file mode 100644
index 00000000000..53a101f9b2b
--- /dev/null
+++ b/library/templates/Intonation/Library/View/Wrapper/User/RichContent/Suggestions.php
@@ -0,0 +1,68 @@
+<?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_Suggestions extends Intonation_Library_View_Wrapper_User_RichContent_Section {
+
+  public function getTitle() {
+    return $this->_('Mes suggestions');
+  }
+
+
+  public function getContent() {
+    $cards = new Class_User_Cards($this->_model);
+    $suggestions = $cards->getSuggestions();
+
+    if ($suggestions->isEmpty()) {
+      $local_suggestions = $this->_model->getSuggestionAchat();
+      $suggestions->addAll($local_suggestions);
+    }
+
+    if ($suggestions->isEmpty())
+      return $this->_('Vous n\'avez pas encore fait de suggestion. %s !',
+                      $this->_view->tagAnchor(['controller' => 'abonne',
+                                               'action' => 'suggestion-achat-add'],
+                                              $this->_('Commencer maintenant')));
+
+    return $this->_view->abonne_Suggestions($suggestions);
+  }
+
+
+  public function getClass() {
+    return 'user_suggestions';
+  }
+
+
+  public function getNavUrl() {
+    return ['controller' => 'abonne',
+            'action' => 'suggestions'];
+  }
+
+
+  public function getNavIco() {
+    return 'class fas fa-hand-holding-heart';
+  }
+
+
+  public function getNavTitle() {
+    return $this->_('Mes suggestions');
+  }
+}
diff --git a/library/templates/Intonation/Library/Widget/Carousel/Author/View.php b/library/templates/Intonation/Library/Widget/Carousel/Author/View.php
index 06a09e6d3ba..7a8108c05ea 100644
--- a/library/templates/Intonation/Library/Widget/Carousel/Author/View.php
+++ b/library/templates/Intonation/Library/Widget/Carousel/Author/View.php
@@ -24,7 +24,7 @@ class Intonation_Library_Widget_Carousel_Author_View extends Intonation_Library_
 
 
   protected function _findElements() {
-    return Class_CodifAuteur::filterByValidThumbnail(Class_CodifAuteur::findAllByPreferences($this->_settings->toArray()));
+    return Class_CodifAuteur::filterByValidThumbnail(Class_CodifAuteur::findAllByPreferences($this->_settings->getPreferences()));
   }
 
 
@@ -39,6 +39,31 @@ class Intonation_Library_Widget_Carousel_Author_View extends Intonation_Library_
 
 
   protected function _getWrapper() {
-     return 'Intonation_Library_View_Wrapper_Author';
+    return 'Intonation_Library_View_Wrapper_Author';
+  }
+
+
+  protected function _extendedActions() {
+    if (!Class_Users::isCurrentUserCanAccesBackend())
+      return null;
+
+    return [
+            function()
+            {
+              return $this->view->tagAnchor($this->view->url(['module' => 'admin',
+                                                              'controller' => 'widget',
+                                                              'action' => 'widget-action',
+                                                              'named' => 'refresh',
+                                                              'id_module' => $this->getId(),
+                                                              'id_profil' => Class_Profil::getCurrentProfil()->getId()]),
+                                            Class_Admin_Skin::current()
+                                            ->renderActionIconOn('images',
+                                                                 $this->view),
+                                            [
+                                             'title' => $this->view->_('Recharger les vignettes de la boite: %s',
+                                                                       htmlspecialchars($this->titre))
+                                            ]);
+            }
+    ];
   }
 }
\ No newline at end of file
diff --git a/library/templates/Intonation/Library/Widget/Carousel/Rss/Definition.php b/library/templates/Intonation/Library/Widget/Carousel/Rss/Definition.php
new file mode 100644
index 00000000000..0f6c2de7f0e
--- /dev/null
+++ b/library/templates/Intonation/Library/Widget/Carousel/Rss/Definition.php
@@ -0,0 +1,41 @@
+<?php
+/**
+ * Copyright (c) 2012-2017, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class Intonation_Library_Widget_Carousel_Rss_Definition extends Intonation_Library_Widget_Carousel_Definition {
+
+  const
+    CODE = 'RSS',
+    SORT_TITLE_ASC = 'titre asc';
+
+  protected $_group = Class_Systeme_ModulesAccueil::GROUP_INFO;
+
+
+  public function __construct() {
+    parent::__construct();
+    $this->_libelle = $this->_('Boite RSS');
+    $this->_form = 'Intonation_Library_Widget_Carousel_Rss_Form';
+    $this->_view_helper = 'Intonation_Library_Widget_Carousel_Rss_View';
+    $this->_defaultValues = array_merge($this->_defaultValues,
+                                        ['titre' => $this->_libelle,
+                                         'order' => static::SORT_TITLE_ASC]);
+  }
+}
\ No newline at end of file
diff --git a/library/templates/Intonation/Library/Widget/Carousel/Rss/Form.php b/library/templates/Intonation/Library/Widget/Carousel/Rss/Form.php
new file mode 100644
index 00000000000..5a6ae4eee73
--- /dev/null
+++ b/library/templates/Intonation/Library/Widget/Carousel/Rss/Form.php
@@ -0,0 +1,54 @@
+<?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_Library_Widget_Carousel_Rss_Form extends Intonation_Library_Widget_Carousel_Form {
+
+    public function customPopulate($datas, $form = null) {
+    parent::customPopulate($datas);
+
+    return $this->replaceWith('data_sources',
+                              ['treeSelect',
+                               'feed_source',
+                               ['IdItems' => isset($datas['id_items']) ? $datas['id_items'] : '',
+                                'ItemRenderer' => function($id) {
+                                   if ($model = Class_Rss::find($id))
+                                     return $model->getTitre();
+                                 },
+                                'IdCategories' => isset($datas['id_categorie']) ? $datas['id_categorie'] : '',
+                                'CategoryRenderer' => function($id) {
+                                  if ($model = Class_RssCategorie::find($id))
+                                    return $model->getLibelle();
+                                },
+                                'UrlDataSource' => Class_Url::assemble(['module' => 'admin',
+                                                                        'controller' => 'bib',
+                                                                        'action' => 'allitems',
+                                                                        'type' => 'rss',
+                                                                        'id_bib' => isset($datas['id_bib']) ? $datas['id_bib'] : ''])]]);
+  }
+
+
+  public function getOrders() {
+    return [Class_Systeme_ModulesAccueil_Rss::ORDER_RANDOM => $this->_('Aléatoire'),
+            Class_Systeme_ModulesAccueil_Rss::ORDER_ALPHA => $this->_('Alphabétique'),
+            Class_Systeme_ModulesAccueil_RSS::ORDER_SELECTION => $this->_('Sélection')];
+  }
+}
\ No newline at end of file
diff --git a/library/templates/Intonation/Library/Widget/Carousel/Rss/View.php b/library/templates/Intonation/Library/Widget/Carousel/Rss/View.php
new file mode 100644
index 00000000000..6e29d20ab1c
--- /dev/null
+++ b/library/templates/Intonation/Library/Widget/Carousel/Rss/View.php
@@ -0,0 +1,72 @@
+<?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_Widget_Carousel_Rss_View extends Intonation_Library_Widget_Carousel_View {
+
+
+  protected function _findElements() {
+    $categories_ids = explode('-', $this->_settings->getIdCategorie());
+    $rss_ids = explode('-', $this->_settings->getIdItems());
+    $order = $this->_settings->getOrder();
+
+    $rss =  ($rss = Class_Rss::getRssFrom($categories_ids,
+                                          $rss_ids))
+      ? $rss
+      : Class_Rss::findAllBy(['limit' => 100]);
+
+    if (Class_Systeme_ModulesAccueil_Rss::ORDER_SELECTION == $order)
+      return $rss;
+
+    if (Class_Systeme_ModulesAccueil_Rss::ORDER_RANDOM == $order) {
+      shuffle($rss);
+      return $rss;
+    }
+
+    if (Class_Systeme_ModulesAccueil_Rss::ORDER_ALPHA == $order)
+      usort($rss, function($instance_a, $instance_b)
+            {
+              if ($instance_a->getTitre() == $instance_b->getTitre())
+                return 0;
+
+              return $instance_a->getTitre() < $instance_b->getTitre()
+                ? 1
+                : -1;
+            });
+
+    return $rss;
+  }
+
+
+  protected function _getRSSUrl() {
+    return '';
+  }
+
+
+  protected function _getLinkToAllTitle() {
+    return $this->_('Voir tous les flux RSS de la boite %s dans une liste', $this->titre);
+  }
+
+
+  protected function _getWrapper() {
+     return 'Intonation_Library_View_Wrapper_Rss';
+  }
+}
\ No newline at end of file
diff --git a/library/templates/Intonation/Library/Widget/Carousel/View.php b/library/templates/Intonation/Library/Widget/Carousel/View.php
index d9192869279..302f0ea2214 100644
--- a/library/templates/Intonation/Library/Widget/Carousel/View.php
+++ b/library/templates/Intonation/Library/Widget/Carousel/View.php
@@ -24,6 +24,10 @@ abstract class Intonation_Library_Widget_Carousel_View extends Zendafi_View_Help
 
   protected $_register_for_head_scripts = [];
 
+  public function shouldCacheContent() {
+    return false;
+  }
+
 
   public function getHtml() {
     $this->titre = $this->_settings->getTitre();
@@ -161,8 +165,11 @@ abstract class Intonation_Library_Widget_Carousel_View extends Zendafi_View_Help
     if (Intonation_Library_Widget_Carousel_Definition::LISTING == $layout)
       return $this->view->renderList($elements, $content_callback);
 
-    if (Intonation_Library_Widget_Carousel_Definition::LISTING_WITH_OPTIONS == $layout)
-      return $this->view->renderTruncateList($elements, $content_callback);
+    if (Intonation_Library_Widget_Carousel_Definition::LISTING_WITH_OPTIONS == $layout) {
+      $renderer = (new Intonation_View_RenderTruncateList)->setView($this->view);
+      $this->_register_for_head_scripts [] = $renderer;
+      return $renderer->renderTruncateList($elements, $content_callback);
+    }
 
     if (Intonation_Library_Widget_Carousel_Definition::HORIZONTAL_LISTING == $layout)
       return $this->view->renderHorizontalList($elements, $content_callback);
diff --git a/library/templates/Intonation/Library/Widget/Search/View.php b/library/templates/Intonation/Library/Widget/Search/View.php
index e275ac2275e..0f1f9016965 100644
--- a/library/templates/Intonation/Library/Widget/Search/View.php
+++ b/library/templates/Intonation/Library/Widget/Search/View.php
@@ -172,7 +172,7 @@ abstract class IntonationSearchRenderAbstract {
                                                ['Title' => $this->_('Réinitialiser la recherche'),
                                                 'type' => 'button',
                                                 'class' => 'btn btn-sm btn-warning border-dark',
-                                                'onclick' => 'var form = $(this).closest(\'form\'); form.attr(\'action\', \'' . $this->_view->url(['controller' => 'recherche',                                                                                          'action' => 'simple'], null, true) .  '\'); form.find(\'.expressionRecherche\').attr(\'value\', \'\'); $(this).hide(); form.find(\'.criteres_recherche\').hide();']);
+                                                'onclick' => 'var form = $(this).closest(\'form\'); form.attr(\'action\', \'' . $this->_view->url(['controller' => 'recherche',                                                                                          'action' => 'simple'], null, true) .  '\'); form.find(\'.expressionRecherche\').attr(\'value\', \'\'); $(this).hide(); form.find(\'.criteres_recherche\').hide(); $.ajax(\'' . $this->_view->url(['controller' => 'recherche',                                                                                          'action' => 'clear-last-search-session'], null, true) . '\');']);
                     }])
 
       ->addElement('button',
diff --git a/library/templates/Intonation/Template.php b/library/templates/Intonation/Template.php
index bcfd94ebce7..55eeef175a5 100644
--- a/library/templates/Intonation/Template.php
+++ b/library/templates/Intonation/Template.php
@@ -171,7 +171,9 @@ class Intonation_Template extends Class_Template {
 
        Intonation_Library_Widget_Carousel_Newsletter_Definition::CODE => new Intonation_Library_Widget_Carousel_Newsletter_Definition,
 
-       Intonation_Library_Widget_Menu_Definition::CODE => new Intonation_Library_Widget_Menu_Definition
+       Intonation_Library_Widget_Menu_Definition::CODE => new Intonation_Library_Widget_Menu_Definition,
+
+       Intonation_Library_Widget_Carousel_Rss_Definition::CODE => new Intonation_Library_Widget_Carousel_Rss_Definition
       ];
   }
 
diff --git a/library/templates/Intonation/View/Abonne/ChangeImage.php b/library/templates/Intonation/View/Abonne/ChangeImage.php
index 7344a872221..dec9c83f67f 100644
--- a/library/templates/Intonation/View/Abonne/ChangeImage.php
+++ b/library/templates/Intonation/View/Abonne/ChangeImage.php
@@ -32,7 +32,7 @@ class Intonation_View_Abonne_ChangeImage extends Intonation_View_Abonne {
   protected function _hookOn($rich_content) {
     $sections = $rich_content->getSections();
 
-    $sections [6] = (new Intonation_Library_View_Wrapper_User_RichContent_ChangeImage)
+    $sections [7] = (new Intonation_Library_View_Wrapper_User_RichContent_ChangeImage)
       ->setModel($this->view->user)
       ->setView($this->view)
       ->beActive()
diff --git a/library/templates/Intonation/View/Abonne/NewSuggestion.php b/library/templates/Intonation/View/Abonne/NewSuggestion.php
new file mode 100644
index 00000000000..524d39b76c5
--- /dev/null
+++ b/library/templates/Intonation/View/Abonne/NewSuggestion.php
@@ -0,0 +1,61 @@
+<?php
+/**
+ * Copyright (c) 2012-2020, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class Intonation_View_Abonne_NewSuggestion extends Intonation_View_Abonne {
+
+  protected
+    $_form,
+    $_preferences,
+    $_records;
+
+
+  public function abonne_NewSuggestion($form, $preferences, $records = []) {
+    $this->_form = $form;
+    $this->_preferences = $preferences;
+    $this->_records = $records;
+
+    $html = $this->abonne(Class_Users::getIdentity());
+    $this->view->titre = $this->_('%s : Suggérer un achat',
+                                  $this->view->titre);
+    return $html;
+  }
+
+
+  protected function _hookOn($rich_content) {
+    $sections = $rich_content->getSections();
+
+    $sections [4] = (new Intonation_Library_View_Wrapper_User_RichContent_NewSuggestion)
+      ->setModel($this->view->user)
+      ->setView($this->view)
+      ->setForm($this->_form)
+      ->setPreferences($this->_preferences)
+      ->setRecords($this->_records)
+      ->beActive()
+      ->beVisible();
+
+    $rich_content->setSections($sections);
+  }
+
+
+  protected function _showSections($sections) {
+  }
+}
diff --git a/library/templates/Intonation/View/Abonne/Suggestions.php b/library/templates/Intonation/View/Abonne/Suggestions.php
new file mode 100644
index 00000000000..281f5a50282
--- /dev/null
+++ b/library/templates/Intonation/View/Abonne/Suggestions.php
@@ -0,0 +1,53 @@
+<?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_Suggestions extends ZendAfi_View_Helper_BaseHelper {
+  public function abonne_Suggestions($suggestions) {
+    if (empty($suggestions))
+      return $this->_('Aucune suggestion');
+
+    $suggestions = array_map(function($suggestion)
+                       {
+                         return (new Intonation_Library_View_Wrapper_Suggestion)
+                           ->setModel($suggestion)
+                           ->setView($this->view);
+                       }, $suggestions->getArrayCopy());
+
+    $callback = function($wrapped) {
+      return $this->view->cardifyOnlyDescription($wrapped);
+    };
+
+    $collection = new Storm_Collection($suggestions);
+
+    $actions = [new Intonation_Library_Link(['Url' => $this->view->url(['controller' => 'abonne',
+                                                                        'action' => 'suggestion-achat-add']),
+                                             'Text' => $this->_('Suggérer un achat'),
+                                             'InlineText' => 1,
+                                             'Class' => 'btn btn-sm btn-success',
+                                             'Title' => $this->_('Suggérer un achat à la bibliothèque'),
+                                             'Image' => Class_Template::current()->getIco($this->view,
+                                                                                          'add',
+                                                                                          'utils')])];
+
+    return $this->view->renderCollection($collection, $actions, $callback);
+  }
+}
diff --git a/library/templates/Intonation/View/Abonne/SuggestionsBoard.php b/library/templates/Intonation/View/Abonne/SuggestionsBoard.php
new file mode 100644
index 00000000000..2a62997ef9d
--- /dev/null
+++ b/library/templates/Intonation/View/Abonne/SuggestionsBoard.php
@@ -0,0 +1,31 @@
+<?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_SuggestionsBoard extends Intonation_View_Abonne {
+
+  protected $_show_current_section = 'user_suggestions';
+
+
+  public function abonne_SuggestionsBoard($user) {
+    return $this->_renderSection($user);
+  }
+}
\ No newline at end of file
diff --git a/library/templates/Intonation/View/CardifyHorizontal.php b/library/templates/Intonation/View/CardifyHorizontal.php
index ed8a25c55e9..bc721d5cce6 100644
--- a/library/templates/Intonation/View/CardifyHorizontal.php
+++ b/library/templates/Intonation/View/CardifyHorizontal.php
@@ -29,7 +29,7 @@ class Intonation_View_CardifyHorizontal extends Intonation_View_CardHelper {
 
     if ($second_link = $element->getSecondaryLink())
       $content [] = $this->_tag('div',
-                                $this->view->tagAction($second_link),
+                                $this->view->tagAction($second_link->setInlineText(1)),
                                 ['class' => 'card-subtitle']);
 
     if (!$second_link && $secondary_title = $element->getSecondaryTitle())
diff --git a/library/templates/Intonation/View/CardifyOnlyDescription.php b/library/templates/Intonation/View/CardifyOnlyDescription.php
index f5eec79900d..ff4040d63b3 100644
--- a/library/templates/Intonation/View/CardifyOnlyDescription.php
+++ b/library/templates/Intonation/View/CardifyOnlyDescription.php
@@ -29,7 +29,7 @@ class Intonation_View_CardifyOnlyDescription extends Intonation_View_CardHelper
     if ($second_link = $element->getSecondaryLink())
       $content [] = $this->_tag('div',
                                 $this->view->tagAction($second_link
-                                                       ->setNoButtonText(1)),
+                                                       ->setInlineText(1)),
                                 ['class' => 'card-subtitle']);
 
     if ($summary = $element->getDescription())
diff --git a/library/templates/Intonation/View/CardifyWithOverlay.php b/library/templates/Intonation/View/CardifyWithOverlay.php
index de7030ec15b..a663b8597eb 100644
--- a/library/templates/Intonation/View/CardifyWithOverlay.php
+++ b/library/templates/Intonation/View/CardifyWithOverlay.php
@@ -52,7 +52,7 @@ class Intonation_View_CardifyWithOverlay extends ZendAfi_View_Helper_BaseHelper
     $main_link = $element
       ->getMainLink()
       ->setText($content)
-      ->setNoButtonText(true)
+      ->setInlineText(1)
       ->setImage('');
 
     $link = $this->view->tagAction($main_link);
diff --git a/library/templates/Intonation/View/Jumbotron.php b/library/templates/Intonation/View/Jumbotron.php
index 2c2a2e7e83f..a430976059f 100644
--- a/library/templates/Intonation/View/Jumbotron.php
+++ b/library/templates/Intonation/View/Jumbotron.php
@@ -102,7 +102,7 @@ class Intonation_View_Jumbotron extends ZendAfi_View_Helper_BaseHelper {
     $html = $this->_tag('h1', $this->_element->getMainTitle());
 
     if ($secondary_link = $this->_element->getSecondaryLink())
-      $html .= $this->view->tagAction($secondary_link->setNoButtonText(1));
+      $html .= $this->view->tagAction($secondary_link->setInlineText(1));
 
     return $html . $this->_element->getBadges() . $this->_actions();
   }
diff --git a/library/templates/Intonation/View/RenderCollection.php b/library/templates/Intonation/View/RenderCollection.php
index 9c08dc20195..9cb7f83e4b6 100644
--- a/library/templates/Intonation/View/RenderCollection.php
+++ b/library/templates/Intonation/View/RenderCollection.php
@@ -21,14 +21,22 @@
 
 
 class Intonation_View_RenderCollection extends ZendAfi_View_Helper_BaseHelper {
-  public function renderCollection($collection, $actions = []) {
+  public function renderCollection($collection, $actions = [], $callback = null) {
+    $callback = $callback
+      ? $callback
+      : (function($item)
+        {
+          return $this->view->cardifyHorizontal($item);
+        });
+
+    foreach ($actions as $action) {
+      $action->setClass('btn btn-sm btn-success');
+    }
+
     $html = [$this->view->div(['class' => 'col-12'], $this->view->renderActions($actions)),
              $this->view->div(['class' => 'col-12'],
                               $this->view->renderTruncateList($collection,
-                                                              (function($item)
-                                                              {
-                                                                return $this->view->cardifyHorizontal($item);
-                                                              })))];
+                                                              $callback))];
 
     return $this->view->grid(implode($html));
   }
diff --git a/library/templates/Intonation/View/RenderTruncateList.php b/library/templates/Intonation/View/RenderTruncateList.php
index b24ccc143a7..b75152a65f7 100644
--- a/library/templates/Intonation/View/RenderTruncateList.php
+++ b/library/templates/Intonation/View/RenderTruncateList.php
@@ -22,6 +22,11 @@
 
 class Intonation_View_RenderTruncateList extends ZendAfi_View_Helper_BaseHelper {
 
+  protected
+    $_container_id,
+    $_input_id;
+
+
   public function renderTruncateList($collection, $callback) {
     $size = $collection->count();
 
@@ -37,40 +42,33 @@ class Intonation_View_RenderTruncateList extends ZendAfi_View_Helper_BaseHelper
                                   'class' => 'list-group-item bg-transparent border-0 px-0']);
     });
 
-    $id = uniqid();
+    $this->_container_id = $id = uniqid();
+    $this->_input_id = 'input_' . $this->_container_id;
 
     return
-      $this->_renderToolsIn($id, $size)
+      $this->_renderTools($size)
       . $this->_tag('div',
                     $html,
-                    ['id' => $id,
+                    ['id' => $this->_container_id,
                      'class' => 'list-group bg-transparent border-0']);
   }
 
 
-  protected function _renderToolsIn($container_id, $size) {
-    $input_id = 'input_' . $container_id;
+  protected function _renderTools($size) {
     $size_id = uniqid();
     $id = uniqid();
 
-    Class_ScriptLoader::getInstance()
-      ->addSearchInputToContainer('#' . $container_id,
-                                  '#' . $input_id,
-                                  '#' . $container_id . ' div.card *, .dropdown, .dropdown-menu')
-
-      ->addJQueryReady("var container = $('#" . $container_id . "');"
-                       . "container.children().slice(0,3).show();"
-                       . "$('#" . $input_id . "').attr('onkeypress', 'return event.keyCode != 13;');");
+    $this->renderHeadScriptsOn(Class_ScriptLoader::getInstance());
 
     $onchange =
-      "var container = $('#" . $container_id . "');"
+      "var container = $('#" . $this->_container_id . "');"
       . "container.children().hide();"
       . "var value=$('#" . $id . "').val();"
       . "container.children().not('.search_input_not_found').slice(0, value).show();";
 
     $input_keyup =
       "$('#" . $id . "').val(10000).change();"
-      . 'setTimeout(function() {$(\'#' . $size_id . '\').text($(\'#' . $container_id . '\').children(\':visible\').length);}, 10);';
+      . "setTimeout(function() { $('#" . $size_id . "').text($('#" . $this->_container_id . "').children(':visible').length); var searchText = $('#" . $this->_input_id . "').val(); if (!searchText || searchText == '' || searchText == '*') $('#" . $id . "').val(3).change();}, 10);";
 
     $multi_options = $this->_getTruncateOptions($size);
 
@@ -86,7 +84,7 @@ class Intonation_View_RenderTruncateList extends ZendAfi_View_Helper_BaseHelper
                     'multiOptions' => $multi_options])
 
       ->addElement('text',
-                   $input_id,
+                   $this->_input_id,
                    ['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'),
@@ -114,7 +112,7 @@ class Intonation_View_RenderTruncateList extends ZendAfi_View_Helper_BaseHelper
 
 
   protected function _getTruncateOptions($size) {
-    if ($size > 100)
+    if ($size > 50)
       return ['3' => $this->_('3'),
               '10' => $this->_('10'),
               '50' => $this->_('50'),
@@ -131,4 +129,18 @@ class Intonation_View_RenderTruncateList extends ZendAfi_View_Helper_BaseHelper
 
     return ['10000' => $this->_('Tout')];
   }
+
+
+  public function renderHeadScriptsOn($script_loader) {
+    $script_loader
+      ->addSearchInputToContainer('#' . $this->_container_id,
+                                  '#' . $this->_input_id,
+                                  '#' . $this->_container_id . ' div.card *, .dropdown, .dropdown-menu')
+
+      ->addJQueryReady("var container = $('#" . $this->_container_id . "');"
+                       . "container.children().slice(0,3).show();"
+                       . "$('#" . $this->_input_id . "').attr('onkeypress', 'return event.keyCode != 13;');");
+
+    return $this;
+  }
 }
\ No newline at end of file
diff --git a/library/templates/Intonation/View/Rss/RenderItems.php b/library/templates/Intonation/View/Rss/RenderItems.php
new file mode 100644
index 00000000000..e904ce346e7
--- /dev/null
+++ b/library/templates/Intonation/View/Rss/RenderItems.php
@@ -0,0 +1,41 @@
+<?php
+/**
+ * Copyright (c) 2012-2020, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class Intonation_View_Rss_RenderItems extends ZendAfi_View_Helper_BaseHelper {
+  public function rss_RenderItems($rss) {
+    $items = $rss->getFeedItems();
+
+    $items = array_map(function($element)
+                          {
+                            return (new Intonation_Library_View_Wrapper_RssItem)
+                              ->setView($this->view)
+                              ->setModel($element);
+                          }, $items);
+
+    $items = new Storm_Collection($items);
+
+    return $this->view->renderTruncateList($items, function($element)
+                                       {
+                                         return $this->view->cardifyOnlyDescription($element);
+                                       });
+  }
+}
diff --git a/library/templates/Intonation/View/Search/History.php b/library/templates/Intonation/View/Search/History.php
index 9204cd87d6a..97529085971 100644
--- a/library/templates/Intonation/View/Search/History.php
+++ b/library/templates/Intonation/View/Search/History.php
@@ -57,6 +57,8 @@ class Intonation_View_Search_History extends ZendAfi_View_Helper_BaseHelper {
                                                                         'action' => 'clear-history']),
                                              'Popup' => true,
                                              'Text' => $this->_('Vider'),
+                                             'Class' => 'btn btn-sm btn-warning',
+                                             'InlineText' => 1,
                                              'Title' => $this->_('Vider l\'historique de recherches'),
                                              'Image' => Class_Template::current()->getIco($this->view,
                                                                                           'delete',
diff --git a/library/templates/Intonation/View/Search/HtmlCriteria.php b/library/templates/Intonation/View/Search/HtmlCriteria.php
index 2df55b80997..5b3837b9ad6 100644
--- a/library/templates/Intonation/View/Search/HtmlCriteria.php
+++ b/library/templates/Intonation/View/Search/HtmlCriteria.php
@@ -87,7 +87,7 @@ class Intonation_View_Search_HtmlCriteria extends ZendAfi_View_Helper_TagCritere
                                                'Image' => Class_Template::current()->getIco($this->view, 'clean', 'utils'),
                                                'Text' => $label,
                                                'Title' => $this->_('Retirer le critère: %s', $label),
-                                               'NoButtonText' => 1,
+                                               'InlineText' => 1,
                                                'Class' => 'btn btn-warning btn-sm mb-2 mr-2 text-dark text-left']));
   }
 
diff --git a/library/templates/Intonation/View/Search/Result.php b/library/templates/Intonation/View/Search/Result.php
index 515f72d0ef1..60f545a2a78 100644
--- a/library/templates/Intonation/View/Search/Result.php
+++ b/library/templates/Intonation/View/Search/Result.php
@@ -111,14 +111,14 @@ class Intonation_View_Search_Result extends ZendAfi_View_Helper_BaseHelper {
                                                                   'Image' => Class_Template::current()->getIco($this->view, 'list', 'utils'),
                                                                   'Text' => $this->_tag('span', $this->_('Liste')),
                                                                   'Title' => $this->_('Afficher le résultat de recherche en mode liste'),
-                                                                  'NoButtonText' => 1,
+                                                                  'InlineText' => 1,
                                                                   'Attribs' => ['class' => 'btn-sm list_format' . ($wall_active ? '' : ' active')]])),
 
               $this->view->tagAction(new Intonation_Library_Link(['Url' => $this->view->url(['liste_format' => Class_Systeme_ModulesAppli::LISTE_FORMAT_MUR]),
                                                                   'Image' => Class_Template::current()->getIco($this->view, 'wall', 'utils'),
                                                                   'Text' => $this->_tag('span', $this->_('Mur')),
                                                                   'Title' => $this->_('Afficher le résultat de recherche en mode mur'),
-                                                                  'NoButtonText' => 1,
+                                                                  'InlineText' => 1,
                                                                   'Attribs' => ['class' => 'btn-sm list_format' . ($wall_active ? ' active' : '')]])),
 
               $this->view->search_PageSize($criteria),
diff --git a/library/templates/Intonation/View/TagAction.php b/library/templates/Intonation/View/TagAction.php
index 3417b0a48af..54b02b8a416 100644
--- a/library/templates/Intonation/View/TagAction.php
+++ b/library/templates/Intonation/View/TagAction.php
@@ -80,7 +80,7 @@ class Intonation_View_TagAction extends ZendAfi_View_Helper_BaseHelper {
     if (!$text = $action->getText())
       return '';
 
-    if ($action->getNoButtonText())
+    if ($action->getInlineText())
       return $this->_tag('div', $text, ['class' => 'd-inline text-left']);
 
     if (!$img)
diff --git a/library/templates/Intonation/View/User/Informations.php b/library/templates/Intonation/View/User/Informations.php
index a06c2a1ec0e..ec4f8d136d5 100644
--- a/library/templates/Intonation/View/User/Informations.php
+++ b/library/templates/Intonation/View/User/Informations.php
@@ -51,17 +51,22 @@ class Intonation_View_User_Informations extends ZendAfi_View_Helper_BaseHelper {
       $html [] = $this->_tag('dt', $label, ['class' => 'user_info'])
       . $this->_tag('dd', $value, ['class' => 'user_info']);
 
-    return $this->view->div(['class' => 'card'],
-                            $this->view->div(['class' => 'card-body'],
-                                             $this->_tag('dl', implode($html)))
+    $edit_link =
+      new Intonation_Library_Link(['Url' => $this->view->url(['controller' => 'abonne',
+                                                              'action' => 'modifier'], null, true),
+                                   'Image' => Class_Template::current()->getIco($this->view, 'edit', 'utils'),
+                                   'Text' => $this->_('Modifier mes informations'),
+                                   'Title' => $this->_('Modifier les informations me concernant'),
+                                   'Class' => 'btn btn-sm btn-success',
+                                   'InlineText' => 1,
+                                   'Popup' => true]);
 
-                            . $this->view->div(['class' => 'position-absolute'],
-                                               $this->view->tagAnchor($this->view->url(['controller' => 'abonne',
-                                                                                        'action' => 'modifier']),
-                                                                      Class_Template::current()->getIco($this->view, 'edit', 'utils'),
-                                                                      ['class' => 'ml-1',
-                                                                       'data-popup' => true,
-                                                                       'title' => $this->_('Modifier mes informations')])));
+    return
+      $this->view->div(['class' => 'card no_border'],
+                       $this->view->div(['class' => 'card-header pl-2 pb-0'],
+                                          $this->view->tagAction($edit_link))
+                       . $this->view->div(['class' => 'card-body pl-0 pt-2'],
+                                        $this->_tag('dl', implode($html))));
   }
 
 
diff --git a/public/opac/java/search_input/search_input.css b/public/opac/java/search_input/search_input.css
index 3e0f418d91d..7de7b992a9c 100644
--- a/public/opac/java/search_input/search_input.css
+++ b/public/opac/java/search_input/search_input.css
@@ -1,5 +1,5 @@
 .search_input_not_found {
-    display: none;
+    display: none !important;
 }
 
 
diff --git a/public/opac/java/search_input/search_input.js b/public/opac/java/search_input/search_input.js
index 2f0dbd28dc6..04ebd999390 100644
--- a/public/opac/java/search_input/search_input.js
+++ b/public/opac/java/search_input/search_input.js
@@ -49,45 +49,47 @@
     var html = $(this);
     var not_found_class = 'search_input_not_found';
 
-    var onSearchInputChange = function(searchText) {
-      
-      var resetAll = function() {
-	html
-	  .find('*')
-	  .not(default_fixed_display + options.fixed_display)
-	  .removeClass(not_found_class)
-	  .show();
-      }
+    var resetAll = function() {
+      html
+	.find('*')
+	.not(default_fixed_display + options.fixed_display)
+	.removeClass(not_found_class)
+	.show();
+    }
 
+    
+    var accentsTidy = function(s){
+      var r = s.toLowerCase();
+      r = r.replace(new RegExp("[àáâãäå]", 'g'),"a");
+      r = r.replace(new RegExp("æ", 'g'),"ae");
+      r = r.replace(new RegExp("ç", 'g'),"c");
+      r = r.replace(new RegExp("[èéêë]", 'g'),"e");
+      r = r.replace(new RegExp("[ìíîï]", 'g'),"i");
+      r = r.replace(new RegExp("ñ", 'g'),"n");                            
+      r = r.replace(new RegExp("[òóôõö]", 'g'),"o");
+      r = r.replace(new RegExp("Å“", 'g'),"oe");
+      r = r.replace(new RegExp("[ùúûü]", 'g'),"u");
+      r = r.replace(new RegExp("[ýÿ]", 'g'),"y");
+      return r;
+    };
+
+    
+    var highlightItems = function(elements) {
+      resetAll();
+      html.find('*').not(elements).not(default_fixed_display + options.fixed_display).addClass(not_found_class);
+      html
+	.find(elements)
+	.parentsUntil(html)
+	.not(default_fixed_display + options.fixed_display)
+	.removeClass(not_found_class)
+	.show();
+    }
+
+
+    var onSearchInputChange = function(searchText) {
       if (!searchText || searchText == "" || searchText == '*')
 	return resetAll();
 
-      var accentsTidy = function(s){
-        var r = s.toLowerCase();
-        r = r.replace(new RegExp("[àáâãäå]", 'g'),"a");
-        r = r.replace(new RegExp("æ", 'g'),"ae");
-        r = r.replace(new RegExp("ç", 'g'),"c");
-        r = r.replace(new RegExp("[èéêë]", 'g'),"e");
-        r = r.replace(new RegExp("[ìíîï]", 'g'),"i");
-        r = r.replace(new RegExp("ñ", 'g'),"n");                            
-        r = r.replace(new RegExp("[òóôõö]", 'g'),"o");
-        r = r.replace(new RegExp("Å“", 'g'),"oe");
-        r = r.replace(new RegExp("[ùúûü]", 'g'),"u");
-        r = r.replace(new RegExp("[ýÿ]", 'g'),"y");
-        return r;
-      };
-      
-      var highlightItems = function(elements) {
-	resetAll();
-	html.find('*').not(elements).not(default_fixed_display + options.fixed_display).addClass(not_found_class);
-	html
-	  .find(elements)
-	  .parentsUntil(html)
-	  .not(default_fixed_display + options.fixed_display)
-	  .removeClass(not_found_class)
-	  .show();
-      }
-
       searchText = accentsTidy(searchText);
       searchText = searchText.split(' ');
       searchText = searchText.filter(function(term){return term;});
@@ -123,7 +125,7 @@
 	: $(options.input);
 
     search_input
-      .keyup(function(event){
+      .on('keyup', function(event){
 	onSearchInputChange(this.value);
       })
       .end();
diff --git a/public/opac/js/calendrier.js b/public/opac/js/calendrier.js
index 13a3b78dfee..f1ccc2aa779 100644
--- a/public/opac/js/calendrier.js
+++ b/public/opac/js/calendrier.js
@@ -23,6 +23,9 @@ var ajaxify_calendars = function () {
     event.preventDefault();
   });
 
+  if (undefined != $.fn.masonry)
+    $(this).closest(".calendar").masonry();
+
   var month_no_event = $('.calendar .month_list a.no_event');
   month_no_event.click(function(event) {
     event.preventDefault();
diff --git a/scripts/emacs/phafi-mode.el b/scripts/emacs/phafi-mode.el
index 6070213d6d3..72e3cb26b21 100644
--- a/scripts/emacs/phafi-mode.el
+++ b/scripts/emacs/phafi-mode.el
@@ -83,7 +83,9 @@
 			   "--ignore-dir" "emacs" 
 			   "--ignore-dir" "fichiers" 
 			   "--ignore-dir" "jQuery" 
-			   "--ignore-dir" "ckeditor" 
+			   "--ignore-dir" "ckeditor"
+			   "--ignore-dir" "Bootstrap"
+			   "--ignore-dir" "Font-Awesome"
 			   "--ignore" "jquery.mobile-1.1.0.min.js" 
 			   "--ignore" "jquery.mobile-1.2.0.min.js"  
 			   "--" ))
diff --git a/tests/scenarios/Templates/PolygoneTemplateTest.php b/tests/scenarios/Templates/PolygoneTemplateTest.php
index 4ffedb44cdd..92a162878b3 100644
--- a/tests/scenarios/Templates/PolygoneTemplateTest.php
+++ b/tests/scenarios/Templates/PolygoneTemplateTest.php
@@ -167,7 +167,279 @@ class PolygoneTemplateOpacAdvancedSearchTest extends PolygoneTemplateTestCase {
 
   /** @test */
   public function advancedSearchSettingsShouldContainsCustomForm() {
-    $this->dispatch('/admin/widget/edit-action/id/recherche_avancee/id_profil/24');
+    $this->dispatch('/admin/widget/edit-action/id/recherche_avancee/id_profil/1');
     $this->assertXPath('//input[@id="forms"]');
   }
-}
\ No newline at end of file
+}
+
+
+
+class PolygoneTemplateSuggestTest extends PolygoneTemplateTestCase {
+
+
+  /** @test */
+  public function dispatchSuggestionsWithoutOneShouldRenderEmptyMessage() {
+    $this->dispatch('/opac/abonne/suggestions');
+    $this->assertXPathContentContains('//main//div', 'Vous n\'avez pas encore fait de suggestion.');
+  }
+
+
+  /** @test */
+  public function dispatchSuggestionsAddShouldRenderForm() {
+    $this->dispatch('/opac/abonne/suggestion-achat-add');
+    $this->assertXPath('//main//form');
+  }
+
+
+  /** @test */
+  public function dispatchLocalSuggestionsShouldRenderBookDocType() {
+    $this->fixture('Class_SuggestionAchat',
+                   ['id' => 3,
+                    'date_creation' => '2020-02-18',
+                    'type_doc_id' => 1,
+                    'titre' => 'Northlanders',
+                    'auteur' => 'Unknown',
+                    'user' => Class_Users::getIdentity()
+                   ]);
+
+    $this->dispatch('/opac/abonne/suggestions');
+    $this->assertXpath('//main//div//i[contains(@class, "fas fa-book")]');
+  }
+}
+
+
+
+class PolygoneTemplateKohaSuggestionsTest extends PolygoneTemplateTestCase {
+
+  public function setUp() {
+    parent::setUp();
+
+    $sigb_plage =
+      $this->fixture('Class_IntBib',
+                     [
+                      'id' => 3,
+                      'comm_params' => ['url_serveur' => 'http://plage.com/cgi-bin/koha/ilsdi.pl',
+                                        'restful' => '1'],
+                      'comm_sigb' => Class_IntBib::COM_KOHA
+                     ]);
+
+    $this->fixture('Class_Bib', ['id' => 11,
+                                 'libelle' => 'Bib de la plage',
+                                 'int_bib' => $sigb_plage]);
+
+    $this->fixture('Class_Bib', ['id' => 12,
+                                 'libelle' => 'Bib de la plage 2',
+                                 'int_bib' => $sigb_plage]);
+
+    $this->fixture('Class_Bib', ['id' => 14,
+                                 'libelle' => 'Bib de la montagne']);
+
+   $istres = $this->fixture('Class_CodifAnnexe',
+                             ['id' => 15,
+                              'libelle' => 'Istres',
+                              'id_origine' => 'IST']);
+
+    $borrower = new Class_WebService_SIGB_Emprunteur('2341', 'test');
+    $borrower->setLibraryCode('IST');
+
+    $user = $this->fixture('Class_Users',
+                           ['id' => 3,
+                            'login' => 'test',
+                            'password' => 'test',
+                            'id_site' => 12,
+                            'id_sigb' => 'azerty',
+                            'int_bib' => $sigb_plage,
+                            'fiche_sigb' => ['type_comm' => 0,
+                                             'fiche' => $borrower]]);
+
+    ZendAfi_Auth::getInstance()->logUser($user);
+
+    $this->mock_web_client = $this->mock();
+    $sigb_comm = Class_IntBib::find(3)->getSIGBComm();
+    $sigb_comm->setWebClient($this->mock_web_client);
+
+    $logger = $this->mock()
+                   ->whenCalled('log')->answers(true)
+
+                   ->whenCalled('logError')
+                   ->willDo(
+                            function($url, $message) {
+                              throw new RuntimeException($url . ' :: ' . $message);
+                            });
+
+    Class_WebService_SIGB_AbstractService::setLogger($logger);
+
+    $this->mock_web_client
+      ->whenCalled('open_url')
+      ->with('http://plage.com/cgi-bin/koha/rest.pl/suggestions?branchcode=__ANY__&suggestedby=azerty')
+      ->answers('[{
+      "date" : "2014-04-01 16:08:36",
+      "STATUS" : "ASKED",
+      "suggestionid" : "6197",
+      "branchcodemanagedby" : "",
+      "accepteddate" : "",
+      "borrnummanagedby" : "",
+      "emailmanagedby" : "",
+      "branchcodesuggestedby" : "IST",
+      "isbn" : "9782814101425 ",
+      "emailsuggestedby" : "",
+      "branchcode" : "IST",
+      "copyrightdate" : "0",
+      "budgetid" : "",
+      "reason" : null,
+      "ASKED" : 1,
+      "total" : "",
+      "surnamemanagedby" : "",
+      "branchnamesuggestedby" : "Test",
+      "price" : "",
+      "title" : "En piste ! : Créations en couture pour petits et grands Enfant",
+      "collectiontitle" : "",
+      "publicationyear" : "2012",
+      "itemtype" : "",
+      "place" : "",
+      "author" : "Laëtitia Gheno",
+      "suggesteddate" : "2013-11-14",
+      "currency" : "",
+      "borrnumsuggestedby" : "234",
+      "biblionumber" : "",
+      "categorycodesuggestedby" : "",
+      "manageddate" : "",
+      "acceptedby" : "",
+      "firstnamesuggestedby" : "",
+      "surnamesuggestedby" : "",
+      "publishercode" : "",
+      "suggestedby" : "azerty",
+      "rejecteddate" : null,
+      "firstnamemanagedby" : "",
+      "rejectedby" : null,
+      "quantity" : "0",
+      "note" : "Je veux le lire",
+      "patronreason" : "Je veux le lire",
+      "categorydescriptionsuggestedby" : "",
+      "volumedesc" : null,
+      "mailoverseeing" : "0",
+      "managedby" : ""}]')
+      ->beStrict();
+
+    $this->dispatch('/opac/abonne/suggestions');
+  }
+
+
+  public function tearDown() {
+    Class_WebService_SIGB_AbstractService::setLogger(null);
+    Class_IntBib::find(3)->getSIGBComm()->setWebClient(null);
+    parent::tearDown();
+  }
+
+
+  /** @test */
+  public function suggestionEnPisteShouldBeDisplay() {
+    $this->assertXPathContentContains('//main//div', 'En piste ! : Créations en couture pour petits et grands Enfant');
+  }
+
+
+  /** @test */
+  public function testShouldHaveWroteEnPisteSuggest() {
+    $this->assertXPathContentContains('//main//div', 'iste ! : Créations en couture pour petits et grands Enfant suggéré par  test');
+  }
+}
+
+
+
+require_once 'tests/fixtures/NanookFixtures.php';
+
+class PolygoneTemplateSuggestionsNanookTest extends PolygoneTemplateTestCase {
+
+  protected $_francis;
+
+
+  public function setUp() {
+    parent::setUp();
+
+    $logger = $this->mock()
+                   ->whenCalled('log')->answers(true)
+
+                   ->whenCalled('logError')
+                   ->willDo(
+                            function($url, $message) {
+                              throw new RuntimeException($url . ' :: ' . $message);
+                            });
+
+    Class_WebService_SIGB_AbstractService::setLogger($logger);
+
+
+    $sigb_conf = $this->fixture('Class_IntBib',
+                                ['id' => 3,
+                                 'comm_params' => ['url_serveur' => 'nanookService',
+                                                   'provide_suggest' => '1'],
+                                 'comm_sigb' => Class_IntBib::COM_NANOOK]);
+
+    $tatim = $this->fixture('Class_Bib', ['id' => 12,
+                                          'libelle' => 'Tatim bib',
+                                          'int_bib' => $sigb_conf]);
+
+    $sigb_conf->setIdBib(12);
+
+    $this->_francis = $this->fixture('Class_Users', ['id' => 3,
+                                                     'id_site' => 12,
+                                                     'id_sigb' => '187',
+                                                     'login' => 'francis',
+                                                     'password' => 'test',
+                                                     'idabon' => 'francis',
+                                                     'role_level' => 2,
+                                                     'int_bib' => $sigb_conf]);
+
+    ZendAfi_Auth::getInstance()->logUser($this->_francis);
+
+    $this->mock_web_client = $this->mock();
+    $this->mock_web_client
+      ->whenCalled('open_url')
+      ->with('http://nanookService/service/GetPatronInfo/patronId/187')
+      ->answers(NanookFixtures::albatorPatronInfoResponse())
+
+      ->whenCalled('postData')
+      ->with('http://nanookService/service/CreateSuggest/patronId/187',
+             ['site' => '12',
+              'title' => '[Livres] fu',
+              'author' => 'mf',
+              'isbnean' => '2-07-0541 27_4',
+              'desclink' => '',
+              'comment' => 'no'])
+      ->answers(NanookFixtures::createSuggestFuSucces())
+
+      ->whenCalled('postData')
+      ->with('http://nanookService/service/CreateSuggest/patronId/187',
+             ['site' => '12',
+              'title' => '[DVD] fu',
+              'author' => 'Ye',
+              'isbnean' => '2-07-0541 27_4',
+              'desclink' => '',
+              'comment' => 'no'])
+      ->answers(NanookFixtures::createSuggestFuError())
+
+      ->beStrict();
+
+    $sigb_comm = Class_IntBib::find(3)->getSIGBComm();
+    $sigb_comm->setWebClient($this->mock_web_client);
+
+    $this->dispatch('/opac/abonne/suggestions', true);
+  }
+
+
+  public function tearDown() {
+    Class_IntBib::find(3)->getSIGBComm()->setWebClient(null);
+    parent::tearDown();
+  }
+
+
+  /** @test */
+  public function purpleForTheWinShouldBeDisplay() {
+    $this->assertXPathContentContains('//main//div', 'Purple for the win');
+  }
+
+
+  /** @test */
+  public function francisShouldHaveWrotePurpleForTheWinSuggest() {
+    $this->assertXPathContentContains('//main//div', 'Purple for the win suggéré par francis francis');
+  }
+}
diff --git a/tests/scenarios/Templates/TemplatesTest.php b/tests/scenarios/Templates/TemplatesTest.php
index f6f1b53492d..d315359a6da 100644
--- a/tests/scenarios/Templates/TemplatesTest.php
+++ b/tests/scenarios/Templates/TemplatesTest.php
@@ -407,6 +407,10 @@ abstract class TemplatesIntonationTestCase extends TemplatesEnabledTestCase {
               '26' => ['division' => 2,
                        'type_module' => 'MENU',
                        'preferences' => ['layout' => Intonation_Library_Widget_Menu_Definition::LAYOUT_VERTICAL]],
+
+              '27' => ['division' => 4,
+                       'type_module' => 'RSS',
+                       'preferences' => ['layout' => Intonation_Library_Widget_Menu_Definition::LAYOUT_VERTICAL]],
              ],
 
              'section' => [ '1' => ['boite' => ['ultra_light_widget']]]
@@ -3368,6 +3372,7 @@ class TemplatesIntonationDispatchAbonneSelectionTest extends TemplatesIntonation
 
 
 class TemplatesIntonationDispatchAbonneAjouterASelectionTest extends TemplatesIntonationAccountTestCase {
+
   /** @test */
   public function titleShouldBeAjouterDesDocumentsALaSelection() {
     $this->dispatch('/opac/abonne/ajouter-a-la-selection/selection_id/2/id_profil/72');
@@ -3386,7 +3391,10 @@ class TemplatesIntonationDispatchAbonneAjouterASelectionTest extends TemplatesIn
                                        ->whenCalled('lancerRecherche')
                                        ->answers($this->mock()
                                                  ->whenCalled('fetchRecords')
-                                                 ->answers($records)));
+                                                 ->answers($records)
+                                                 ->whenCalled('getRecordsCount')
+                                                 ->answers(2)));
+
     $this->dispatch('/opac/abonne/ajouter-a-la-selection/selection_id/2/id_profil/72');
     $this->assertXPath('//a[contains(@onclick, "abonne/ajouter-le-document-a-la-selection/selection_id/2/id_profil/72/record_id/89")]');
   }
@@ -4261,22 +4269,26 @@ class TemplatesDispatchEditAllActionsTest extends TemplatesIntonationTestCase {
 
 
 class TemplatesSearchInSessionTest extends TemplatesIntonationTestCase {
-  protected $_storm_default_to_volatile = true;
 
 
   public function setUp() {
     parent::setUp();
-
     Zend_Registry::get('session')->last_search = ['expressionRecherche' => 'trolls de troy'];
-
-    $this->dispatch('/opac/index/index/id_profil/72');
   }
 
 
   /** @test */
   public function searchWidgetShouldContainsTrollDeTroy() {
+    $this->dispatch('/opac/index/index/id_profil/72');
     $this->assertXPath('//input[contains(@value, "trolls de troy")]');
   }
+
+
+    /** @test */
+  public function searchSessionShouldBeEmpty() {
+    $this->dispatch('/opac/recherche/clear-last-search-session');
+    $this->assertEquals(null, Zend_Registry::get('session')->last_search);
+  }
 }
 
 
@@ -4692,7 +4704,6 @@ class TemplatesDispatchWidgetRenderAllTest extends TemplatesIntonationTestCase {
 
 class TemplatesDispatchWidgetTemplateImageTest extends TemplatesIntonationTestCase {
 
-
   public function widgetTemplates() {
     return [[0, 0],
             [0, 1],
@@ -4759,4 +4770,40 @@ class TemplatesDispatchAbonneClearHistoryTest extends TemplatesIntonationAccount
     $this->dispatch('/opac/abonne/clear-history/id_profil/72');
     $this->assertRedirect();
   }
+}
+
+
+
+class TemplatesDispatchRssWidgetTest extends TemplatesIntonationTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    $this->fixture('Class_Rss',
+                   ['id' => 3,
+                    'titre' => 'Flux RSS Bokeh',
+                    'url' => 'https://bokeh-library-portal.org/news/rss',
+                    'description' => 'Les nouveautés de l\'OPAC BOKEH',
+                    'tags' => 'test;bokeh;libre;git']);
+  }
+
+
+  /** @test */
+  public function newsletterWidgetShouldBePresent() {
+    $this->dispatch('/opac/index/index/id_profil/72');
+    $this->assertXPathContentContains('//div', 'Boite RSS');
+  }
+
+
+  /** @test */
+  public function inputTitreTypeShouldBeText() {
+    $this->dispatch('/admin/widget/edit-widget/id/27/id_profil/72');
+    $this->assertXpath('//input[@name="titre"][@type="text"]');
+  }
+
+
+  /** @test */
+  public function loadRssItemShouldRenderHtml() {
+    $this->dispatch('/opac/rss/render-items/id/3/id_profil/72');
+    $this->assertXpath('//div');
+  }
 }
\ No newline at end of file
-- 
GitLab