diff --git a/library/Class/DigitalResource/Config.php b/library/Class/DigitalResource/Config.php
index b2b18e5a96bb015112b13848ddc846cabddb10e7..59319aa4994773b2804a0a37586aed805d95d39f 100644
--- a/library/Class/DigitalResource/Config.php
+++ b/library/Class/DigitalResource/Config.php
@@ -261,8 +261,12 @@ class Class_DigitalResource_Config extends Class_Entity {
 
 
   public function renderHarvestDiagOn($view) {
-    return $view->tag('p',
-                      $this->_('Cette ressource ne prend pas en charge l\'affichage du l\'url de moissonnage'), ['class' => 'error']);
+    return $view->tagInfo($this->_('Cette ressource ne prend pas en charge l\'affichage du l\'url de moissonnage'));
+  }
+
+
+  public function renderCustomDiagOn($view) {
+    return '';
   }
 
 
@@ -277,7 +281,8 @@ class Class_DigitalResource_Config extends Class_Entity {
     $group = $this->getTestGroup();
     $group->addUser($user)->save();
 
-    return $user;
+    Class_Users::clearCache();
+    return Class_Users::find($user->getId());
   }
 
 
@@ -294,7 +299,8 @@ class Class_DigitalResource_Config extends Class_Entity {
 
     $permission->permitTo($group, new Class_Entity());
 
-    return $group;
+    Class_UserGroup::clearCache();
+    return Class_UserGroup::find($group->getId());
   }
 
 
@@ -306,4 +312,18 @@ class Class_DigitalResource_Config extends Class_Entity {
   public function getSearchUrlForRecord($record) {
     return [];
   }
+
+
+  public function getPermittedGroups() {
+    $groups = new Storm_Collection(Class_UserGroup::findAll());
+    return array_filter(
+                        $groups
+                        ->select(function ($group)
+                                 {
+                                   $permission = $this->getPermission();
+                                   return $group->hasPermissionOn($permission, $this);
+                                 })
+                        ->getArrayCopy());
+
+  }
 }
\ No newline at end of file
diff --git a/library/ZendAfi/View/Helper/BaseHelper.php b/library/ZendAfi/View/Helper/BaseHelper.php
index 0ab8863fd0b07f9bced09f4f2fdb3507ce2b7b16..c03ffb0280c133a9e1bf6066f0c70903650089a4 100644
--- a/library/ZendAfi/View/Helper/BaseHelper.php
+++ b/library/ZendAfi/View/Helper/BaseHelper.php
@@ -18,9 +18,13 @@
  * 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_BaseHelper extends Zend_View_Helper_HtmlElement {
+
   use Trait_Translator;
 
+
   public function __construct() {
     $this->init();
   }
@@ -42,4 +46,24 @@ class ZendAfi_View_Helper_BaseHelper extends Zend_View_Helper_HtmlElement {
   protected function _tag() {
     return call_user_func_array([$this->view, 'tag'], func_get_args());
   }
+
+
+  protected function _tagAnchor() {
+    return call_user_func_array([$this->view, 'tagAnchor'], func_get_args());
+  }
+
+
+  protected function _tagError($message) {
+    return $this->view->tagError($message);
+  }
+
+
+  protected function _tagNotice($message) {
+    return $this->view->tagNotice($message);
+  }
+
+
+  protected function _tagWarning($message) {
+    return $this->view->tagWarning($message);
+  }
 }
\ No newline at end of file
diff --git a/library/ZendAfi/View/Helper/DigitalResource/Dashboard.php b/library/ZendAfi/View/Helper/DigitalResource/Dashboard.php
index d231b0c10f725292b8d9bdc58c9a642831541b0e..05881acb8ee0f78bd7ab223b34ac434e247d61b6 100644
--- a/library/ZendAfi/View/Helper/DigitalResource/Dashboard.php
+++ b/library/ZendAfi/View/Helper/DigitalResource/Dashboard.php
@@ -29,368 +29,11 @@ class ZendAfi_View_Helper_DigitalResource_Dashboard extends ZendAfi_View_Helper_
 
   public function DigitalResource_Dashboard($config) {
     $this->_config = $config;
-    return implode([$this->_renderStatus(),
-                    $this->_renderSettings(),
-                    $this->_renderRights(),
-                    $this->_renderSso(),
-                    $this->_renderHarvest()]);
-  }
-
-
-  protected function _renderStatus() {
-    return implode([$this->_globalStatus(),
-                    $this->_harvestStatus()]);
-  }
-
-
-  protected function _globalStatus() {
-    $label = $this->_('Désactivé');
-    $class = 'digital_connectors_status';
-
-    if ($this->_config->isEnabled()) {
-      $label = $this->_('Activé');
-      $class .= ' enabled';
-    }
-    return $this->view->button((new Class_Entity)
-                               ->setText($label)
-                               ->setAttribs(['disabled' => 'disabled',
-                                             'onclick' => 'return;',
-                                             'class' => $class]));
-  }
-
-
-  protected function _harvestStatus() {
-    if(!$batch = $this->_config->getBatch())
-      return '';
-
-    $label = $this->_('Moissonnage désactivé');
-    $class = 'digital_connectors_status';
-
-    if (Class_Batch::findFirstBy(['type' => $batch])) {
-      $label = $this->_('Moissonnage activé');
-      $class .= ' enabled';
-    }
-    return $this->view->button((new Class_Entity)
-                               ->setText($label)
-                               ->setAttribs(['disabled' => 'disabled',
-                                             'onclick' => 'return;',
-                                             'class' => $class]));
-  }
-
-
-  protected function _renderSettings() {
-    $description = (new Class_TableDescription('adminvars'))
-      ->addColumn($this->_('description'), 'description')
-      ->addColumn($this->_('Clef'), 'id')
-      ->addColumn($this->_('valeur'),
-                  function($model)
-                  {
-                    return ($renderer = $model->getRenderer())
-                      ? $renderer($model->getValeur(), $this->view)
-                      : $this->view->adminVar($model);
-                  })
-      ->addRowAction(function($model)
-                     {
-                       return $this->view->renderModelActions($model,
-                                                              [['url' => ['module' => 'admin',
-                                                                          'controller' => 'index',
-                                                                          'action' => 'adminvaredit',
-                                                                          'cle' => $model->getClef()],
-                                                                'icon' => 'edit',
-                                                                'anchorOptions' => ['data-popup' => true],
-                                                                'label' => $this->_('Modifier "%s"', $model->getClef())]]);
-                     });
-
-    $html = ($table = $this->view->renderTable($description,
-                                               $this->_config->getAdminVarsInstances(),
-                                               ['sorter' => true]))
-      ? $table
-      : $this->_tag('p', $this->_('Veuillez vérifier le fichier config.php de la ressource pour pouvoir gérer le paramétrage'), ['class' => 'error']);
-
-    return $this->_tag('h3', $this->_('Paramétrage')) . $html;
-  }
-
-
-  protected function _renderRights() {
-    $html = [$this->_tag('h3',$this->_('Gestion des  droits'))];
-
-    if (!$this->_config->isEnabled()) {
-      $html [] = $this->_tag('p', $this->_('Veuillez activer la ressource pour pouvoir gérer les droits d\'accès des groupes'), ['class' => 'error']);
-      return implode($html);
-    }
-
-    if(!$permission_label = $this->_config->getPermissionLabel()) {
-      $html [] = $this->_tag('p', $this->_('Cette ressource ne prend pas en charge la gestion des droits'), ['class' => 'error']) ;
-      return implode($html);
-    }
-
-    $html [] = $this->_tag('p', $this->_('Nom de la permission à donner : "%s"', $permission_label));
-
-    $groups = new Storm_Collection(Class_UserGroup::findAll());
-    $this->_permitted_groups = array_filter(
-                                            $groups
-                                            ->select(function ($group)
-                                                     {
-                                                       $permission = $this->_config->getPermission();
-                                                       return $group->hasPermissionOn($permission, $this->_config);
-                                                     })
-                                            ->getArrayCopy());
-
-    if (empty($this->_permitted_groups)) {
-      $html [] = $this->_tag('p', $this->_('Aucun groupe n\'a de droits d\'accès à la ressources'), ['class' => 'error'])
-        . $this->view->tagAnchor($this->view->absoluteUrl(['module' => 'admin',
-                                                           'controller' => 'usergroup',
-                                                           'action' => 'index'],
-                                                          null,
-                                                          true),
-                                 $this->_('Gérer les groupes'),
-                                 ['target' => '_blank']);
-      return implode($html);
-    }
-
-    $usergroup_description = (new Class_TableDescription('usergroups'))
-      ->addColumn($this->_('Groupes qui ont accès à la ressource'),
-                  'libelle')
-      ->addColumn($this->_('Nombre de membres'),
-                  function($model) { return $model->formatedCount();})
-      ->addRowAction(function($model)
-                     {
-                       return $this->view->renderModelActions($model,
-                                                              [['url' => ['module' => 'admin',
-                                                                          'controller' => 'usergroup',
-                                                                          'action' => 'edit',
-                                                                          'id' => $model->getId()],
-                                                                'icon' => 'edit',
-                                                                'anchorOptions' => ['data-popup' => true],
-                                                                'label' => $this->_('Modifier "%s"', $model->getLibelle())]]);
-
-                     });
-
-    $html [] = $this->view->renderTable($usergroup_description, $this->_permitted_groups, ['sorter' => true]);
-
-    $count_user = 0;
-    foreach($this->_permitted_groups as $group)
-      $count_user += $group->formatedCount();
-
-    if (0 == $count_user)
-      $html [] = $this->_tag('p', $this->_('Aucun utilisateur rattaché aux groupes'), ['class' => 'error']);
-
-    return implode($html);
-  }
-
-
-  protected function _renderSso() {
-    $html = [$this->_tag('h3', $this->_('Diagnostic SSO'))];
-
-    if(!$this->_config->getSsoAction()) {
-      $html [] = $this->_tag('p', $this->_('Cette ressource ne prend pas en charge la connexion SSO'), ['class' => 'error']) ;
-      return implode($html);
-    }
-
-    if(!$this->_permitted_groups) {
-      $html [] = $this->_tag('p', $this->_('Veuillez configurer les droits de cette ressource pour obtenir une connexion SSO'), ['class' => 'error']);
-      return implode($html);
-    }
-
-    $user = $this->_config->getTestUser();
-    $group = $this->_config->getTestGroup();
-
-    $html [] = $this->_tag('h5', $this->_('Groupe créé pour ce test'))
-      . $this->_tag('ul',
-                    $this->_tag('li', $this->_('Nom : %s', $group->getLibelle())));
-
-    Class_Users::clearCache();
-    $user = Class_Users::find($user->getId());
-
-    $html [] = $this->_tag('h5', $this->_('Utilisateur créé pour ce test'))
-      . $this->_tag('ul',
-                    $this->_tag('li', $this->_('Login : %s', $user->getLogin()))
-                    . $this->_tag('li', $this->_('Mot de passe : %s', $user->getLogin()))
-                    . $this->_tag('li', $this->_('Groupes : %s', implode(', ',$user->getUserGroupsLabels()))));
-
-    $html [] = $this->view->renderModelActions($user,
-                                               [['url' => ['module' => 'admin',
-                                                           'controller' => 'users',
-                                                           'action' => 'edit',
-                                                           'id' => $user->getId()],
-                                                 'icon' => 'edit',
-                                                 'anchorOptions' => ['data-popup' => true],
-                                                 'label' => $this->_('Modifier "%s"',
-                                                                     $user->getLogin())]]);
-
-    $url = $this->_config->urlFor($user);
-    $html [] = $this->_tag('h4', $this->_('URL SSO générée par /modules/%s pour l\'utilisateur "%s"',
-                                          $this->_config->getSsoAction(),
-                                          $user->getLogin()))
-      . $this->_tag('pre', $url)
-      . $this->view->button((new Class_Entity)
-                            ->setUrl($this->view->url(['action' => 'try-sso']))
-                            ->setText($this->_('Essayer le SSO avec l\'utilisateur "%s"',
-                                               $user->getLogin())));
-
-    if(!$this->_config->getSsoValidateUrl()) {
-      $html [] = $this->_tag('p', $this->_('Cette ressource ne prend pas en charge la validation du ticket de connexion SSO'), ['class' => 'error']) ;
-    }
-
-    if($this->_config->getSsoValidateUrl()) {
-      $url = $this->_config->validateUrlFor($user);
-      $html [] = $this->_tag('h4', $this->_('URL de validation du ticket de connexion générée pour l\'utilisateur "%s"',
-                                            $user->getLogin()))
-        . $this->_tag('pre', $url);
-    }
-
-
-    if(!$album = Class_Album::findFirstby(['type_doc_id' => $this->_config->getDocType()]))
-      return implode($html);
-
-    $album_url = $this->_config->getAlbumSsoUrl($user, $album);
-    $html [] = $this->_tag('h4', $this->_('URL SSO générée pour l\'utilisateur "%s" et l\'album "%s"',
-                                          $user->getLogin(),
-                                          $album->getTitre())
-                           . $this->view->tagAnchor(['module' => 'admin',
-                                                     'controller' => 'album',
-                                                     'action' => 'edit_album',
-                                                     'id' => $album->getId()], $this->_('Voir l\'album'),
-                                                    ['style' => 'margin-left: 1em',
-                                                     'data-popup' => 'true']))
-      . $this->view->tagAnchor($album_url, $album_url, ['target' => '_blank']);
-
-    return implode($html);
-  }
-
-
-  protected function _renderHarvest() {
-    $html = [$this->_tag('h3', $this->_('Diagnostic moissonnage'))];
-
-    if(!$batch_name = $this->_config->getBatch()) {
-      $html [] = $this->_tag('p', $this->_('Cette ressource ne prend pas en charge le moissonnage'), ['class' => 'error']) ;
-      return implode($html);
-    }
-
-    if (!$this->_config->isEnabled()) {
-      $html [] = $this->_tag('p', $this->_('Veuillez activer la ressource pour pouvoir gérer le moissonage'), ['class' => 'error']);
-      return implode($html);
-    }
-
-    if(!$batch = Class_Batch::findFirstBy(['type' => $batch_name])) {
-      $html [] = $this->_tag('p', $this->_('Le moissonnage n\'est pas programmé'), ['class' => 'error'])
-        . $this->view->button((new Class_Entity())
-                              ->setText($this->_('Activer le moissonnage'))
-                              ->setUrl($this->view->absoluteUrl(['module' => 'admin',
-                                                                 'controller' => 'batch',
-                                                                 'action' => 'activate',
-                                                                 'id' => $batch_name],
-                                                                null,
-                                                                true)));
-    }
-
-    $description = (new Class_TableDescription('batchs'))
-      ->addColumn($this->_('Batch'), function($model) { return $model->getLabel(); })
-      ->addColumn($this->_('Planification'),
-                  function($model)
-                  {
-                    return (new Class_Repeat_WeekDays())->humanReadable($model->getPickDay());
-                  })
-      ->addColumn($this->_('Dernière exécution'), function($model) { return $model->getLastRun(); })
-      ->addRowAction(function($model)
-                     {
-                       return $this->view->renderModelActions($model,
-                                                              [
-                                                               ['url' => '/admin/batch/delete/id/%s',
-                                                                'icon' => 'show',
-                                                                'label' => $this->_('Désactiver la tâche'),
-                                                                'condition' => function($model)
-                                                                 {
-                                                                   return $model->isDeletable();
-                                                                 },
-                                                                'anchorOptions' =>
-                                                                ['onclick' => 'return confirm(\''
-                                                                 . str_replace(['\'', '"'], '\\\'',
-                                                                               $this->_('Etes-vous sur de vouloir désactiver cette tâche ?'))
-                                                                 . '\')']],
-
-                                                               ['url' => '/admin/batch/plan/id/%s',
-                                                                'icon' => 'calendar',
-                                                                'label' => $this->_('Plannifier la tâche'),
-                                                                'condition' => function($model) {
-                                                                   return Class_Users::isCurrentUserSuperAdmin()
-                                                                   && $model->isActive();
-                                                                 },
-                                                                'anchorOptions' => ['data-popup' => 'true']],]);
-                     });
-
-    if($batch)
-      $html [] = $this->view->renderTable($description, [new Class_Batch_Definition($this->_config->getBatchInstance())]);
-
-    $html [] = ($url = $this->_config->getHarvestUrl())
-      ? ($this->_tag('h4',
-                    $this->_('URL de moissonnage générée pour la première page'))
-         . $this->_tag('pre', $url))
-      : $this->_config->renderHarvestDiagOn($this->view);
-
-    $html [] = $this->_tag('h4',
-                           $this->_('Image du type de document: ')
-                           . $this->view->tagImg($this->view->url(['module' => 'opac',
-                                                                   'controller' => 'digital-resource',
-                                                                   'action' => 'typedoc-icon',
-                                                                   'id' => $this->_config->getName()])));
-
-    $count = Class_Album::countBy(['type_doc_id' => $this->_config->getDocType()]);
-    $first_album = Class_Album::findFirstBy(['type_doc_id' => $this->_config->getDocType(),
-                                             'order' => 'titre asc']);
-    $albums_link = $count
-      ? $this->view->tagAnchor($this->view->absoluteUrl(['module' => 'admin',
-                                                         'controller' => 'album',
-                                                         'action' => 'index',
-                                                         'cat_id' => $first_album->getRootCategoryId()], null, true),
-                               $this->_('Voir les albums'),
-                               ['target' => '_blank'])
-      : '';
-
-    $html [] = $count
-      ? $this->_tag('h4',
-                    $this->_('Nombre d\'albums présents dans Bokeh : %d', $count))
-      . $albums_link
-      : $this->_tag('p', $this->_('Aucun album présent pour cette ressource'), ['class' => 'error']);
-
-    $html [] = $count
-      ? ($this->_tag('h4',
-                     $this->_('Tentative de vignettage de l\'album "%s" : ', $first_album->getTitre()))
-         . $this->_tag('pre',
-                       $this->_('Image source : %s', $first_album->getPoster()))
-         . $this->_renderThumbnailerLog($first_album))
-      : $this->_tag('p', $this->_('Le vignettage n\'est pas testable sans album'), ['class' => 'error']);
-
-    $count = Class_Notice::countBy(['type_doc' => $this->_config->getDocType()]);
-
-    $records_link = $count ?
-      $this->view->tagAnchor($this->view->absoluteUrl(['module' => 'opac',
-                                                       'controller' => 'recherche',
-                                                       'action' => 'simple',
-                                                       'facette' => 'T' . $this->_config->getDocType()], null, true),
-                               $this->_('Voir les notices'),
-                             ['target' => '_blank'])
-      : '';
-
-
-    $html [] = $count
-      ? $this->_tag('h4',
-                    $this->_('Nombre de notices présentes dans Bokeh : %d', $count))
-      . $records_link
-      : $this->_tag('p', $this->_('Aucune notice présente pour cette ressource'), ['class' => 'error']);
-
-    return implode($html);
-  }
-
-
-  protected function _renderThumbnailerLog($album) {
-    $thumbnailer = Class_WebService_BibNumerique_Vignette::getInstance();
-    $thumbnailer->setLogger(new Class_Log);
-
-    if(!$errors = $thumbnailer->getDownloadPosterLog($album))
-      return $this->_tag('p', $this->_('Vignette de l\'album : '
-                                       . $this->view->tagImg(Class_Url::absolute($album->getThumbnailUrl()))));
-
-    return $this->_tag('p', implode(BR, $errors), ['class' => 'error']);
+    return implode([$this->view->DigitalResource_Dashboard_Status($config),
+                    $this->view->DigitalResource_Dashboard_Settings($config),
+                    $this->view->DigitalResource_Dashboard_Rights($config),
+                    $this->view->DigitalResource_Dashboard_SSO($config),
+                    $this->view->DigitalResource_Dashboard_Harvest($config),
+                    $this->view->DigitalResource_Dashboard_Custom($config)]);
   }
 }
\ No newline at end of file
diff --git a/library/ZendAfi/View/Helper/DigitalResource/Dashboard/Custom.php b/library/ZendAfi/View/Helper/DigitalResource/Dashboard/Custom.php
new file mode 100644
index 0000000000000000000000000000000000000000..4a51d66e7ade9be2a330cc9516107e62b38aad57
--- /dev/null
+++ b/library/ZendAfi/View/Helper/DigitalResource/Dashboard/Custom.php
@@ -0,0 +1,27 @@
+<?php
+/**
+ * Copyright (c) 2012-2017, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class ZendAfi_View_Helper_DigitalResource_Dashboard_Custom extends ZendAfi_View_Helper_BaseHelper {
+  public function DigitalResource_Dashboard_Custom($config) {
+    return $config->renderCustomDiagOn($this->view);
+  }
+}
\ No newline at end of file
diff --git a/library/ZendAfi/View/Helper/DigitalResource/Dashboard/Harvest.php b/library/ZendAfi/View/Helper/DigitalResource/Dashboard/Harvest.php
new file mode 100644
index 0000000000000000000000000000000000000000..a7f435f219afbaa91b6aabd7b1250b6aeef350d0
--- /dev/null
+++ b/library/ZendAfi/View/Helper/DigitalResource/Dashboard/Harvest.php
@@ -0,0 +1,168 @@
+<?php
+/**
+ * Copyright (c) 2012-2017, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class ZendAfi_View_Helper_DigitalResource_Dashboard_Harvest extends ZendAfi_View_Helper_BaseHelper {
+
+
+  public function DigitalResource_Dashboard_Harvest($config) {
+    $html = [$this->_tag('h3', $this->_('Diagnostic moissonnage'))];
+
+    if(!$batch_name = $config->getBatch()) {
+      $html [] = $this->_tagNotice($this->_('Cette ressource ne prend pas en charge le moissonnage')) ;
+      return implode($html);
+    }
+
+    if (!$config->isEnabled()) {
+      $html [] = $this->_tagWarning($this->_('Veuillez activer la ressource pour pouvoir gérer le moissonage'));
+      return implode($html);
+    }
+
+    $html [] = $this->_getBatchHtml($config);
+    $html [] = $this->_getHarvestHtml($config);
+
+    $html [] = $this->_tag('h4',
+                           $this->_('Image du type de document: ')
+                           . $this->view->tagImg($this->view->url(['module' => 'opac',
+                                                                   'controller' => 'digital-resource',
+                                                                   'action' => 'typedoc-icon',
+                                                                   'id' => $config->getName()])));
+
+    $html [] = $this->_getAlbumsHtml($config);
+    $html [] = $this->_getRecordsHtml($config);
+
+    return implode($html);
+  }
+
+
+  protected function _renderThumbnailerLog($album) {
+    $thumbnailer = Class_WebService_BibNumerique_Vignette::getInstance();
+    $thumbnailer->setLogger(new Class_Log);
+
+    if(!$errors = $thumbnailer->getDownloadPosterLog($album))
+      return $this->_tag('p', $this->_('Vignette de l\'album : '
+                                       . $this->view->tagImg(Class_Url::absolute($album->getThumbnailUrl()))));
+
+    return $this->_tag('p', implode(BR, $errors), ['class' => 'error']);
+  }
+
+
+  protected function _getBatchHtml($config) {
+    if(!$batch = Class_Batch::findFirstBy(['type' => $batch_name]))
+      return implode([$this->_tagWarning($this->_('Le moissonnage n\'est pas programmé')),
+                      $this->view->button((new Class_Entity())
+                                          ->setText($this->_('Activer le moissonnage'))
+                                          ->setUrl($this->view->absoluteUrl(['module' => 'admin',
+                                                                             'controller' => 'batch',
+                                                                             'action' => 'activate',
+                                                                             'id' => $batch_name],
+                                                                            null,
+                                                                            true)))]);
+
+    $description = (new Class_TableDescription('batchs'))
+      ->addColumn($this->_('Batch'), function($model) { return $model->getLabel(); })
+      ->addColumn($this->_('Planification'),
+                  function($model)
+                  {
+                    return (new Class_Repeat_WeekDays())->humanReadable($model->getPickDay());
+                  })
+      ->addColumn($this->_('Dernière exécution'), function($model) { return $model->getLastRun(); })
+      ->addRowAction(function($model)
+                     {
+                       return $this->view->renderModelActions($model,
+                                                              [
+                                                               ['url' => '/admin/batch/delete/id/%s',
+                                                                'icon' => 'show',
+                                                                'label' => $this->_('Désactiver la tâche'),
+                                                                'condition' => function($model)
+                                                                 {
+                                                                   return $model->isDeletable();
+                                                                 },
+                                                                'anchorOptions' =>
+                                                                ['onclick' => 'return confirm(\''
+                                                                 . str_replace(['\'', '"'], '\\\'',
+                                                                               $this->_('Etes-vous sur de vouloir désactiver cette tâche ?'))
+                                                                 . '\')']],
+
+                                                               ['url' => '/admin/batch/plan/id/%s',
+                                                                'icon' => 'calendar',
+                                                                'label' => $this->_('Plannifier la tâche'),
+                                                                'condition' => function($model) {
+                                                                   return Class_Users::isCurrentUserSuperAdmin()
+                                                                   && $model->isActive();
+                                                                 },
+                                                                'anchorOptions' => ['data-popup' => 'true']],]);
+                     });
+
+    return $this->view->renderTable($description, [new Class_Batch_Definition($config->getBatchInstance())]);
+  }
+
+
+  protected function _getHarvestHtml($config) {
+    return ($url = $config->getHarvestUrl())
+      ? ($this->_tag('h4',
+                     $this->_('URL de moissonnage générée pour la première page'))
+         . $this->_tag('pre', $url))
+      : $config->renderHarvestDiagOn($this->view);
+  }
+
+
+  protected function _getAlbumsHtml($config) {
+    if(!$count = Class_Album::countBy(['type_doc_id' => $config->getDocType()]))
+      return $this->_tagWarning($this->_('Aucun album présent pour cette ressource'));
+
+    $first_album = Class_Album::findFirstBy(['type_doc_id' => $config->getDocType(),
+                                             'order' => 'titre asc']);
+
+    $albums_link = $this->_tagAnchor($this->view->absoluteUrl(['module' => 'admin',
+                                                               'controller' => 'album',
+                                                               'action' => 'index',
+                                                               'cat_id' => $first_album->getRootCategoryId()], null, true),
+                                     $this->_('Voir les albums'),
+                                     ['target' => '_blank']);
+
+    return implode([$this->_tag('h4',
+                                $this->_('Nombre d\'albums présents dans Bokeh : %d', $count)),
+                    $albums_link,
+                    $this->_tag('h4',
+                                $this->_('Tentative de vignettage de l\'album "%s" : ', $first_album->getTitre())),
+                    $this->_tag('pre',
+                                $this->_('Image source : %s', $first_album->getPoster())),
+                    $this->_renderThumbnailerLog($first_album)]);
+  }
+
+
+  protected function _getRecordsHtml($config) {
+    if(!$count = Class_Notice::countBy(['type_doc' => $config->getDocType()]))
+      return $this->_tagWarning($this->_('Aucune notice présente pour cette ressource'));
+
+    $records_link = $this->_tagAnchor($this->view->absoluteUrl(['module' => 'opac',
+                                                                'controller' => 'recherche',
+                                                                'action' => 'simple',
+                                                                'facette' => 'T' . $config->getDocType()], null, true),
+                                      $this->_('Voir les notices'),
+                                      ['target' => '_blank']);
+
+    return implode([$this->_tag('h4',
+                                $this->_('Nombre de notices présentes dans Bokeh : %d', $count)),
+                    $records_link]);
+  }
+}
\ No newline at end of file
diff --git a/library/ZendAfi/View/Helper/DigitalResource/Dashboard/Rights.php b/library/ZendAfi/View/Helper/DigitalResource/Dashboard/Rights.php
new file mode 100644
index 0000000000000000000000000000000000000000..f706720e898cc5c5963efa6c91ecd2d0f72b0a95
--- /dev/null
+++ b/library/ZendAfi/View/Helper/DigitalResource/Dashboard/Rights.php
@@ -0,0 +1,91 @@
+<?php
+/**
+ * Copyright (c) 2012-2017, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class ZendAfi_View_Helper_DigitalResource_Dashboard_Rights extends ZendAfi_View_Helper_BaseHelper {
+
+
+  public function DigitalResource_Dashboard_Rights($config) {
+    $html = [$this->_tag('h3', $this->_('Gestion des  droits'))];
+
+    if (!$config->isEnabled()) {
+      $html [] = $this->_tagWarning($this->_('Veuillez activer la ressource pour pouvoir gérer les droits d\'accès des groupes'));
+      return implode($html);
+    }
+
+    if(!$permission_label = $config->getPermissionLabel()) {
+      $html [] = $this->_tagNotice($this->_('Cette ressource ne prend pas en charge la gestion des droits'));
+      return implode($html);
+    }
+
+    $html [] = $this->_tag('p', $this->_('Nom de la permission à donner : "%s"', $permission_label));
+
+    $permitted_groups = $config->getPermittedGroups();
+    if (empty($permitted_groups)) {
+      $html [] = $this->_tagWarning($this->_('Aucun groupe n\'a de droits d\'accès à la ressources'));
+      $html [] = $this->_tagAnchor($this->view->absoluteUrl(['module' => 'admin',
+                                                             'controller' => 'usergroup',
+                                                             'action' => 'index'],
+                                                            null,
+                                                            true),
+                                   $this->_('Gérer les groupes'),
+                                   ['target' => '_blank']);
+      return implode($html);
+    }
+
+    $html [] = $this->_getGroupsTable($permitted_groups);
+
+    $count_user = 0;
+    foreach($permitted_groups as $group)
+      $count_user += $group->formatedCount();
+
+    if (0 == $count_user)
+      $html [] = $this->_tagWarning($this->_('Aucun utilisateur rattaché aux groupes'));
+
+    return implode($html);
+  }
+
+
+  protected function _getGroupsTable($groups) {
+    $usergroup_description = (new Class_TableDescription('usergroups'))
+      ->addColumn($this->_('Groupes qui ont accès à la ressource'),
+                  'libelle')
+      ->addColumn($this->_('Nombre de membres'),
+                  function($model) { return $model->formatedCount();})
+      ->addRowAction(function($model)
+                     {
+                       return $this->view->renderModelActions($model,
+                                                              [['url' => ['module' => 'admin',
+                                                                          'controller' => 'usergroup',
+                                                                          'action' => 'edit',
+                                                                          'id' => $model->getId()],
+                                                                'icon' => 'edit',
+                                                                'anchorOptions' => ['data-popup' => true],
+                                                                'label' => $this->_('Modifier "%s"', $model->getLibelle())]]);
+
+                     });
+
+    return $this->view->renderTable($usergroup_description,
+                                    $groups,
+                                    ['sorter' => true]);
+
+  }
+}
\ No newline at end of file
diff --git a/library/ZendAfi/View/Helper/DigitalResource/Dashboard/SSO.php b/library/ZendAfi/View/Helper/DigitalResource/Dashboard/SSO.php
new file mode 100644
index 0000000000000000000000000000000000000000..18af75f2feab1c72480dfd52dcca227fc0b3dcf3
--- /dev/null
+++ b/library/ZendAfi/View/Helper/DigitalResource/Dashboard/SSO.php
@@ -0,0 +1,105 @@
+<?php
+/**
+ * Copyright (c) 2012-2017, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class ZendAfi_View_Helper_DigitalResource_Dashboard_SSO extends ZendAfi_View_Helper_BaseHelper {
+  public function DigitalResource_Dashboard_SSO($config) {
+    $html = [$this->_tag('h3', $this->_('Diagnostic SSO'))];
+
+    if(!$config->getSsoAction()) {
+      $html [] = $this->_tag('p', $this->_('Cette ressource ne prend pas en charge la connexion SSO'), ['class' => 'error']) ;
+      return implode($html);
+    }
+
+    $permitted_groups = $config->getPermittedGroups();
+
+    if(!$permitted_groups) {
+      $html [] = $this->_tagWarning($this->_('Veuillez configurer les droits de cette ressource pour obtenir une connexion SSO'));
+      return implode($html);
+    }
+
+    $user = $config->getTestUser();
+    $group = $config->getTestGroup();
+
+    $html [] = $this->_tag('h5', $this->_('Groupe créé pour ce test'));
+    $html [] = $this->_tag('ul',
+                           $this->_tag('li', $this->_('Nom : %s', $group->getLibelle())));
+
+    $html [] = $this->_tag('h5', $this->_('Utilisateur créé pour ce test'));
+    $html [] = $this->_tag('ul',
+                           implode([$this->_tag('li', $this->_('Login : %s', $user->getLogin())),
+                                    $this->_tag('li', $this->_('Mot de passe : %s', $user->getLogin())),
+                                    $this->_tag('li', $this->_('Groupes : %s',
+                                                               implode(', ', $user->getUserGroupsLabels())))]));
+
+    $html [] = $this->view->renderModelActions($user,
+                                               [['url' => ['module' => 'admin',
+                                                           'controller' => 'users',
+                                                           'action' => 'edit',
+                                                           'id' => $user->getId()],
+                                                 'icon' => 'edit',
+                                                 'anchorOptions' => ['data-popup' => true],
+                                                 'label' => $this->_('Modifier "%s"',
+                                                                     $user->getLogin())]]);
+
+    $html [] = $this->_tag('h4', $this->_('URL SSO générée par /modules/%s pour l\'utilisateur "%s"',
+                                          $config->getSsoAction(),
+                                          $user->getLogin()));
+    $html [] = $this->_tag('pre', $config->urlFor($user));
+    $html [] = $this->view->button((new Class_Entity)
+                                   ->setUrl($this->view->url(['action' => 'try-sso']))
+                                   ->setText($this->_('Essayer le SSO avec l\'utilisateur "%s"',
+                                                      $user->getLogin())));
+
+    $html [] = $this->_getValidateUrlHtml($config, $user);
+
+    $html [] = $this->_getAlbumSSOUrlHtml($config, $user);
+
+    return implode($html);
+  }
+
+
+  protected function _getAlbumSSOUrlHtml($config, $user) {
+    if(!$album = Class_Album::findFirstby(['type_doc_id' => $config->getDocType()]))
+      return '';
+
+    $album_url = $config->getAlbumSsoUrl($user, $album);
+    return $this->_tag('h4', $this->_('URL SSO générée pour l\'utilisateur "%s" et l\'album "%s"',
+                                      $user->getLogin(),
+                                      $album->getTitre())
+                       . $this->_tagAnchor(['module' => 'admin',
+                                            'controller' => 'album',
+                                            'action' => 'edit_album',
+                                            'id' => $album->getId()], $this->_('Voir l\'album'),
+                                           ['style' => 'margin-left: 1em',
+                                            'data-popup' => 'true']))
+      . $this->_tagAnchor($album_url, $album_url, ['target' => '_blank']);
+  }
+
+
+  protected function _getValidateUrlHtml($config, $user) {
+    return $config->getSsoValidateUrl()
+      ? ($this->_tag('h4', $this->_('URL de validation du ticket de connexion générée pour l\'utilisateur "%s"',
+                                    $user->getLogin()))
+         . $this->_tag('pre', $config->validateUrlFor($user)))
+      : $this->_tagNotice($this->_('Cette ressource ne prend pas en charge la validation du ticket de connexion SSO'));
+  }
+}
diff --git a/library/ZendAfi/View/Helper/DigitalResource/Dashboard/Settings.php b/library/ZendAfi/View/Helper/DigitalResource/Dashboard/Settings.php
new file mode 100644
index 0000000000000000000000000000000000000000..87ef580d3f941cca96d83d6f3fcb6df38a6e450d
--- /dev/null
+++ b/library/ZendAfi/View/Helper/DigitalResource/Dashboard/Settings.php
@@ -0,0 +1,64 @@
+<?php
+/**
+ * Copyright (c) 2012-2017, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class ZendAfi_View_Helper_DigitalResource_Dashboard_Settings extends ZendAfi_View_Helper_BaseHelper {
+
+
+  public function DigitalResource_Dashboard_Settings($config) {
+    $html = [$this->_tag('h3', $this->_('Paramétrage'))];
+
+    $html [] = ($table = $this->_getTable($config))
+      ? $table
+      : $this->_tagError($this->_('Le fichier config.php de la ressource ne contient pas de variable.'));
+
+    return implode($html);
+  }
+
+
+  protected function _getTable($config) {
+    $description = (new Class_TableDescription('adminvars'))
+      ->addColumn($this->_('description'), 'description')
+      ->addColumn($this->_('Clef'), 'id')
+      ->addColumn($this->_('valeur'),
+                  function($model)
+                  {
+                    return ($renderer = $model->getRenderer())
+                      ? $renderer($model->getValeur(), $this->view)
+                      : $this->view->adminVar($model);
+                  })
+      ->addRowAction(function($model)
+                     {
+                       return $this->view->renderModelActions($model,
+                                                              [['url' => ['module' => 'admin',
+                                                                          'controller' => 'index',
+                                                                          'action' => 'adminvaredit',
+                                                                          'cle' => $model->getClef()],
+                                                                'icon' => 'edit',
+                                                                'anchorOptions' => ['data-popup' => true],
+                                                                'label' => $this->_('Modifier "%s"', $model->getClef())]]);
+                     });
+
+    return $this->view->renderTable($description,
+                                    $config->getAdminVarsInstances(),
+                                    ['sorter' => true]);
+  }
+}
\ No newline at end of file
diff --git a/library/ZendAfi/View/Helper/DigitalResource/Dashboard/Status.php b/library/ZendAfi/View/Helper/DigitalResource/Dashboard/Status.php
new file mode 100644
index 0000000000000000000000000000000000000000..da5b4e7bd836558c494345063957ec30ed027f2f
--- /dev/null
+++ b/library/ZendAfi/View/Helper/DigitalResource/Dashboard/Status.php
@@ -0,0 +1,70 @@
+<?php
+/**
+ * Copyright (c) 2012-2017, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class ZendAfi_View_Helper_DigitalResource_Dashboard_Status extends ZendAfi_View_Helper_BaseHelper {
+
+  protected $_config;
+
+
+  public function DigitalResource_Dashboard_Status($config) {
+    $this->_config = $config;
+
+    return implode([$this->_globalStatus(),
+                    $this->_harvestStatus()]);
+  }
+
+
+  protected function _globalStatus() {
+    $label = $this->_('Désactivé');
+    $class = 'digital_connectors_status';
+
+    if ($this->_config->isEnabled()) {
+      $label = $this->_('Activé');
+      $class .= ' enabled';
+    }
+
+    return $this->view->button((new Class_Entity)
+                               ->setText($label)
+                               ->setAttribs(['disabled' => 'disabled',
+                                             'onclick' => 'return;',
+                                             'class' => $class]));
+  }
+
+
+  protected function _harvestStatus() {
+    if(!$batch = $this->_config->getBatch())
+      return '';
+
+    $label = $this->_('Moissonnage désactivé');
+    $class = 'digital_connectors_status';
+
+    if (Class_Batch::findFirstBy(['type' => $batch])) {
+      $label = $this->_('Moissonnage activé');
+      $class .= ' enabled';
+    }
+    return $this->view->button((new Class_Entity)
+                               ->setText($label)
+                               ->setAttribs(['disabled' => 'disabled',
+                                             'onclick' => 'return;',
+                                             'class' => $class]));
+  }
+}
\ No newline at end of file
diff --git a/library/ZendAfi/View/Helper/TagError.php b/library/ZendAfi/View/Helper/TagError.php
new file mode 100644
index 0000000000000000000000000000000000000000..e2d8dac51586ba944e6af56d61e71f609c2b53d5
--- /dev/null
+++ b/library/ZendAfi/View/Helper/TagError.php
@@ -0,0 +1,30 @@
+<?php
+/**
+ * Copyright (c) 2012-2017, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class ZendAfi_View_Helper_TagError extends ZendAfi_View_Helper_BaseHelper {
+  public function tagError($message) {
+    return $this->_tag('p',
+                       $message,
+                       ['class' => 'error',
+                        'title' => $this->_('Erreur: "%s". Ce message indique un disfonctionnement.', $message)]);
+  }
+}
\ No newline at end of file
diff --git a/library/ZendAfi/View/Helper/TagNotice.php b/library/ZendAfi/View/Helper/TagNotice.php
new file mode 100644
index 0000000000000000000000000000000000000000..3e2e65098390b8d0a88591bb2fe8e8c0c3bf4852
--- /dev/null
+++ b/library/ZendAfi/View/Helper/TagNotice.php
@@ -0,0 +1,30 @@
+<?php
+/**
+ * Copyright (c) 2012-2017, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class ZendAfi_View_Helper_TagNotice extends ZendAfi_View_Helper_BaseHelper {
+  public function tagNotice($message) {
+    return $this->_tag('p',
+                       $message,
+                       ['class' => 'notice',
+                        'title' => $this->_('Information: "%s". Ce message vous indique un comportement attendu.', $message)]);
+  }
+}
\ No newline at end of file
diff --git a/library/ZendAfi/View/Helper/TagWarning.php b/library/ZendAfi/View/Helper/TagWarning.php
new file mode 100644
index 0000000000000000000000000000000000000000..058c75bd7bdb6af7885f2f7fed68d3f0acd030a8
--- /dev/null
+++ b/library/ZendAfi/View/Helper/TagWarning.php
@@ -0,0 +1,30 @@
+<?php
+/**
+ * Copyright (c) 2012-2017, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class ZendAfi_View_Helper_TagWarning extends ZendAfi_View_Helper_BaseHelper {
+  public function tagWarning($message) {
+    return $this->_tag('p',
+                       $message,
+                       ['class' => 'warning',
+                        'title' => $this->_('Attention: "%s". Ce message vous indique une incomplétude.', $message)]);
+  }
+}
\ No newline at end of file
diff --git a/library/digital_resources/Cvs/Config.php b/library/digital_resources/Cvs/Config.php
index d070e38c2abd47d399bc9abe9155126d9090cc7e..b28c65ded5ae266b4f95f9ad6fec0d499fa81e6e 100644
--- a/library/digital_resources/Cvs/Config.php
+++ b/library/digital_resources/Cvs/Config.php
@@ -108,4 +108,49 @@ class Cvs_Config extends Class_DigitalResource_Config {
     (new Cvs_Form_SearchResult($form))->addElements();
     return $this;
   }
+
+
+  public function renderCustomDiagOn($view) {
+    $search_terme = 'afrique';
+    $user = $this->getTestUser();
+
+    $service = new Cvs_Service;
+    $service->setUser($user);
+
+    $xml = $service->getSearchDocumentXML($search_terme, 1, 5);
+    $encoded_xml = $service->getEncodedXML($xml);
+    $original_response = $service->httpPost($xml);
+    $response = htmlentities($original_response);
+
+    $parser = new Cvs_Service_Parser_SearchDocument();
+    $parser->parseXML($original_response);
+
+    $result = new Cvs_Service_Records($parser->isSuccess() ? $parser->getNotices() : [],
+                                      1,
+                                      $parser->getTotalNotices());
+
+    $helper = (new Cvs_View_Helper_Records())->setView($view);
+    $search_result = $helper->records($result, Class_Profil::getCurrentProfil()
+                                      ->getCfgModulesPreferences('recherche',
+                                                                 'resultat',
+                                                                 'simple'));
+
+    $html = [$view->tag('h3', $this->_('Diagnostic de la recherche CVS')),
+             $view->tag('p', $this->_('Le connecteur CVS propose une intégration d\'une boite dans le résultat de recherche de Bokeh.')),
+             $view->tag('p', $this->_('Pour récupérer le contenu de la recherche plusieurs étapes sont nécessaires.')),
+             $view->tag('h4', $this->_('La première étape consiste à générer le XML qui servirat à la génération du paramètre "xml"')),
+             $view->tag('pre', htmlentities($xml)),
+             $view->tag('h4', $this->_('L\'étape suivante est une requête HTTP POST vers l\'API_URL avec le paramètre "xml" encodé')),
+             $view->tag('p', $this->_('API URL:')),
+             $view->tag('pre', $this->getAdminVar('API_URL')),
+             $view->tag('p', $this->_('XML encodé:')),
+             $view->tag('pre', $encoded_xml),
+             $view->tag('p', $this->_('Réponse reçue à la demande de catalogue :')),
+             $view->tag('pre', $response),
+             $view->tag('h4', $this->_('La dernière étape  parcourt la réponse afin de créer les notices qui seront affichées dans la boite CVS du résultat de recherche')),
+             $view->tag('pre', $search_result)
+    ];
+
+    return implode($html);
+  }
 }
\ No newline at end of file
diff --git a/library/digital_resources/Cvs/Service.php b/library/digital_resources/Cvs/Service.php
index c08f5bb5a80eaca6627f007c0a9c5cc848068613..b80e763c6afe178bc05ff1bc812ec2b4d52ff232 100644
--- a/library/digital_resources/Cvs/Service.php
+++ b/library/digital_resources/Cvs/Service.php
@@ -26,7 +26,9 @@ class Cvs_Service {
 
   const SOURCEXPIRATIONTIME = 30;
 
-  protected $_config;
+  protected
+    $_config,
+    $_user;
 
 
   public function __construct() {
@@ -34,13 +36,8 @@ class Cvs_Service {
   }
 
 
-  public function find($criteria, $page, $number) {
-    $response = $this->_call('search_document',
-                             ['q' => $criteria,
-                              'espace'=> '',
-                              'classement' => 'consultes',
-                              'page' => $page,
-                              'nombre_par_page' => $number]);
+  public function find($term, $page, $number) {
+    $response = $this->_callSearchDocument($term, $page, $number);
 
     $parser = new Cvs_Service_Parser_SearchDocument();
     $parser->parseXML($response);
@@ -52,9 +49,7 @@ class Cvs_Service {
 
 
   public function getSiteAccessFor($user) {
-    $response = $this->_call('acces_site',
-                             ['querystring' => '',
-                              'affichage' => 'complet']);
+    $response = $this->_callSiteAccess($user);
 
     $parser = new Cvs_Service_Parser_SiteAccess;
     $parser->parseXML($response);
@@ -71,37 +66,86 @@ class Cvs_Service {
   }
 
 
-  public function getEncodedXml($xml) {
+  public function getEncodedXML($xml) {
     return strtr(base64_encode($xml), '+/', '-_');
   }
 
 
-  protected function _call($action, $params) {
-    $xml = $this->_getXml($action);
+  protected function _callSearchDocument($term, $page, $number) {
+    $xml = $this->getSearchDocumentXML($term, $page, $number);
+    return $this->httpPost($xml);
+  }
 
-    ($action == 'acces_site')
-      ? $this->_accessAction($xml, $params)
-      : $this->_searchAction($xml, $params);
 
-    $this->_appendUser($xml);
+  protected function _callSiteAccess($user) {
+    $xml = $this->getSiteAccessXML($user);
+    return $this->httpPost($xml);
+  }
+
+
+  public function httpPost($xml) {
+    if(!$xml)
+      return '';
+
+    if(!$encoded_xml = $this->getEncodedXML($xml))
+      return '';
 
     try {
       return Class_WebService_Abstract::getHttpClient()
-        ->postData($this->_var('API_URL'), ['xml' => $this->getEncodedXml($xml->saveXML())]);
+        ->postData($this->_var('API_URL'),
+                   ['xml' => $encoded_xml]);
     } catch(Exception $e) {
-      echo $e->getMessage();
       return '';
     }
   }
 
 
+  public function getSearchDocumentXML($term, $page, $number) {
+    $params = ['q' => $term,
+               'espace'=> '',
+               'classement' => 'consultes',
+               'page' => $page,
+               'nombre_par_page' => $number];
+
+    $closure = function($xml, $params) {
+      $this->_appendSearchDocument($xml, $params);
+    };
+
+    return $this->_getXML('search_document', $params, $closure);
+  }
+
+
+  public function getSiteAccessXML($user) {
+    $this->_user = $user;
+
+    $params = ['querystring' => '',
+               'affichage' => 'complet'];
+
+    $closure = function($xml, $params) {
+      $this->_appendSiteAccess($xml, $params);
+    };
+
+    return $this->_getXML('acces_site', $params, $closure);
+  }
+
+
   protected function _getUser() {
-    return ($user = Class_Users::getIdentity())
+    $user = $this->_user
+      ? $this->_user
+      : Class_Users::getIdentity();
+
+    return $user
       ? $user
       : Class_Users::newInstance(['login' => $this->_config->getAdminVar('LOGINTEST')]);
   }
 
 
+  public function setUser($user) {
+    $this->_user = $user;
+    return $this;
+  }
+
+
 
   protected function _getKey($time, $login) {
     return md5($login
@@ -127,7 +171,7 @@ class Cvs_Service {
   }
 
 
-  protected function _getXml($action) {
+  protected function _getXML($action, $params, $closure) {
     $time = $this->getCurrentTime();
     $login = $this->_getLogin();
 
@@ -145,11 +189,16 @@ class Cvs_Service {
              'action' => $action] as $key => $value)
       $header->appendChild($xml->createElement($key, $value));
 
-    return $xml;
+    $closure($xml, $params);
+//      : $this->_searchAction($xml, $params);
+
+    $this->_appendUser($xml);
+
+    return $xml->saveXML();
   }
 
 
-  protected function _accessAction($xml, $params) {
+  protected function _appendSiteAccess($xml, $params) {
     $body = $xml->getElementsByTagName('body')->item(0);
 
     $this->_cdataIn($xml, $body, 'querystring', urldecode($params['querystring']));
@@ -164,7 +213,7 @@ class Cvs_Service {
   }
 
 
-  protected function _searchAction($xml, $params) {
+  protected function _appendSearchDocument($xml, $params) {
     $body = $xml->getElementsByTagName('body')->item(0);
 
     foreach(['q',
diff --git a/library/digital_resources/Cvs/controllers/SearchController.php b/library/digital_resources/Cvs/controllers/SearchController.php
index ed0f3ed8f180bb33399746582750beca7596c0bd..88afbb895f1b1622706f249a7dc8aa4ad6092b0f 100644
--- a/library/digital_resources/Cvs/controllers/SearchController.php
+++ b/library/digital_resources/Cvs/controllers/SearchController.php
@@ -48,7 +48,7 @@ class Cvs_Plugin_SearchController extends Class_DigitalResource_Controller {
 
     $query = (new Cvs_Query($criteria))->query();
 
-    $service = (new Cvs_Service($user));
+    $service = (new Cvs_Service)->setUser($user);
     $this->view->result = $service->find($query, $criteria->getPage(), $preferences['cvs_nb_result']);
     $this->view->preferences = $preferences;
   }
diff --git a/library/digital_resources/Cvs/tests/CvsTest.php b/library/digital_resources/Cvs/tests/CvsTest.php
index cce266db8c06d4ad061eff2c73ba9d4468569876..0452cd63a0be29d83afe96a37bc02569f08ebb7e 100644
--- a/library/digital_resources/Cvs/tests/CvsTest.php
+++ b/library/digital_resources/Cvs/tests/CvsTest.php
@@ -610,7 +610,7 @@ class CvsSearchWithDemoAccountTest extends CvsActivatedTestCase {
 
     $dom = $this->_cvs->createDomDocument();
     $dom->loadXML($xml);
-    return $this->_cvs->getEncodedXml($dom->saveXML());
+    return $this->_cvs->getEncodedXML($dom->saveXML());
   }
 
 
@@ -705,7 +705,7 @@ class CvsSearchWithJerryLoggedTest extends CvsActivatedTestCase {
 
     $dom = $this->_cvs->createDomDocument();
     $dom->loadXML($xml);
-    return $this->_cvs->getEncodedXml($dom->saveXML());
+    return $this->_cvs->getEncodedXML($dom->saveXML());
   }
 
 
@@ -819,7 +819,7 @@ class CvsLinkWithBorrowerTest extends CvsActivatedTestCase {
 
     $dom = $this->_cvs->createDomDocument();
     $dom->loadXML($xml);
-    return $this->_cvs->getEncodedXml($dom->saveXML());
+    return $this->_cvs->getEncodedXML($dom->saveXML());
   }
 
 
@@ -918,7 +918,7 @@ class CvsServiceWithBorrowerAndLibraryLabelTest extends CvsActivatedTestCase {
 
     $dom = $this->_cvs->createDomDocument();
     $dom->loadXML($xml);
-    return $this->_cvs->getEncodedXml($dom->saveXML());
+    return $this->_cvs->getEncodedXML($dom->saveXML());
   }
 
 
diff --git a/public/admin/css/global.css b/public/admin/css/global.css
index cc30c1dabdeface4cf409ccac67f6d7f3260be69..5a94f3952a0dd10bcd8246610f009c4ee38b88b4 100644
--- a/public/admin/css/global.css
+++ b/public/admin/css/global.css
@@ -408,7 +408,21 @@ div#permalink {
 .login input {width:150px;font-weight:bold;color:#000000;}
 
 /* Error */
-.error {color:red;font-weight:bold;}
+.error {
+    color:red;
+    font-weight:bold;
+}
+
+.warning {
+    color: #FF9900;
+    font-weight:bold;
+}
+
+.notice {
+    color: #3366FF;
+    font-weight:bold;
+}
+
 div.error{clear:both}
 
 /* Barre de nav */
diff --git a/public/admin/skins/bokeh74/colors.css b/public/admin/skins/bokeh74/colors.css
index 2d7af7a0ff29f24310628558a84d9f5e0165a995..2c4ff7cfc2eef931de5256c0fb02736ffa547897 100644
--- a/public/admin/skins/bokeh74/colors.css
+++ b/public/admin/skins/bokeh74/colors.css
@@ -19,6 +19,8 @@
     --error-text: #F00;
     --error-background: #F00;
     --success-background: #59E625;
+    --notice-text: #3366FF;
+    --warning-text: #FF9900;
 
     --bokeh-event: #0050C7;
     --bokeh-event-highlight: #6AA5FF;
diff --git a/public/admin/skins/bokeh74/global.css b/public/admin/skins/bokeh74/global.css
index f2563311e61a571dfa724a7c4442f0656fb74289..9ff4531f8ad8ea47a3de29fc18939be28ca3d2e6 100755
--- a/public/admin/skins/bokeh74/global.css
+++ b/public/admin/skins/bokeh74/global.css
@@ -16,6 +16,14 @@ body .error * {
     color: var(--error-text);
 }
 
+body .notice {
+    color: var(--notice-text)
+}
+
+body .warning {
+    color: var(--warning-text)
+}
+
 .modules a,
 .modules a:visited,
 .main > .left a,
@@ -211,7 +219,8 @@ a {
     font-size: 1em;
 }
 
-.error, .errors {
+.error,
+.errors {
     font-weight: bold;
 }
 
diff --git a/public/admin/skins/retro/colors.css b/public/admin/skins/retro/colors.css
index 2d7af7a0ff29f24310628558a84d9f5e0165a995..2c4ff7cfc2eef931de5256c0fb02736ffa547897 100644
--- a/public/admin/skins/retro/colors.css
+++ b/public/admin/skins/retro/colors.css
@@ -19,6 +19,8 @@
     --error-text: #F00;
     --error-background: #F00;
     --success-background: #59E625;
+    --notice-text: #3366FF;
+    --warning-text: #FF9900;
 
     --bokeh-event: #0050C7;
     --bokeh-event-highlight: #6AA5FF;
diff --git a/public/admin/skins/retro/global.css b/public/admin/skins/retro/global.css
index 5fc005922b551b7db669b6c1d5d48988b7a54233..34f03321a4942e5232a4a7274543c4920147c6fc 100755
--- a/public/admin/skins/retro/global.css
+++ b/public/admin/skins/retro/global.css
@@ -808,6 +808,14 @@ body .error * {
     color: var(--error-text);
 }
 
+body .notice {
+    color: var(--notice-text)
+}
+
+body .warning {
+    color: var(--warning-text)
+}
+
 .toggle_video {
     cursor: pointer;
 }