diff --git a/VERSIONS_WIP/23802 b/VERSIONS_WIP/23802
new file mode 100644
index 0000000000000000000000000000000000000000..0abf962561e1f2ff2b5c017cbc7c2858d3c5b01d
--- /dev/null
+++ b/VERSIONS_WIP/23802
@@ -0,0 +1 @@
+ - ticket #23802: Ajout d'un mode de visualisation par arborescence paginée dans la boite sitothèque.
\ No newline at end of file
diff --git a/application/modules/admin/controllers/AccueilController.php b/application/modules/admin/controllers/AccueilController.php
index 547bb2c5d789635d43d4bb9fa4fe4b7999f690d2..b4b8830e12b0598918ccab9a06b6f54488ee4fac 100644
--- a/application/modules/admin/controllers/AccueilController.php
+++ b/application/modules/admin/controllers/AccueilController.php
@@ -104,7 +104,17 @@ class Admin_AccueilController extends Zend_Controller_Action {
 	}
 
 
+	public function preprocessSitoCategories() {
+		if ((int)$this->_getParam('type_aff',0)!=3)
+			return;
+
+		if ($categories=explode('-',$this->_getParam('id_categorie')))
+			$this->_request->setPost('id_categorie',$categories[0]);
+
+	}
 	public function sitothequeAction() {
+		$this->preprocessSitoCategories();
+		$this->_request->setPost('group_by_categorie','0');
 		$this->_simpleAction('SITO');
 	}
 
diff --git a/application/modules/admin/views/scripts/accueil/sitotheque.phtml b/application/modules/admin/views/scripts/accueil/sitotheque.phtml
index 6f3e6e4b36ce42286b67e6be95d80d2b42fa3771..d4a711ec1d21e3728fffd7cba0a6f41dca7bd29b 100644
--- a/application/modules/admin/views/scripts/accueil/sitotheque.phtml
+++ b/application/modules/admin/views/scripts/accueil/sitotheque.phtml
@@ -1,102 +1,111 @@
-<center>
-<h1>Propriétés du module sitothèque</h1><br>
-<div class="formTable">
-	<form method="post" action="<?php echo $this->url ?>">
-
-			<fieldset>
-			<legend>Affichage</legend>
-			<table cellspacing="2">
-
-				<tr>
-					<td class="droite">Style de boite&nbsp;</td>
-					<td class="gauche"><?php echo $this->combo_templates ?></td>
-				</tr>
-
-			</table>
-			</fieldset>
-			<br>
-
-			<fieldset>
-			<legend>Généralités</legend>
-			<table cellspacing="2">
-				<tr>
-					<td class="droite">Titre&nbsp;</td>
-					<td class="gauche"><input type="text" name="titre" size="50" value="<?php print($this->preferences["titre"]); ?>"></td>
-				</tr>
-			</table>
-			</fieldset>
-			<br>
-
-			<fieldset>
-					<legend>Sites à afficher</legend>
-					<table cellspacing="2" width="100%">
-						<tr>
-							<td class="droite" width="90px">Type&nbsp;</td>
-							<td class="gauche">
-								<?php
-									$onchange="$('.treeselect').treeselect('toggleVisibility', (this.value == '1'));";
-									echo $this->formSelect("type_aff",
-																				 $this->preferences["type_aff"],
-																				 array("onchange" => $onchange),
-																				 array("1" => "Une sélection de catégories ou de sites",
-																							 "2" => "Les sites les plus récents"))
-								?>
-							</td>
-						</tr>
-					</table>
-
-					<div	id="table_selection">
-						<?php
-								echo $this->treeSelect(
-																	$this->preferences["id_items"],
-																	$this->preferences["id_categorie"],
-																	($this->preferences["type_aff"] == "1"),
-																	BASE_URL.'/admin/bib/allitems?id_bib='.$this->id_bib.'&type=sito',
-																	"form");
-							?>
-					</div>
-
-					<table cellspacing="2" width="100%">
-
-
-					<tr id='option_display_order'>
-						<td class="droite" width="90px">Ordre d'affichage&nbsp;</td>
-						<td class="gauche">
-							<?php
-							echo $this->formRadio("display_order",
-																		$this->preferences["display_order"],
-																		[],
-																		['Random' => $this->_('Par ordre aléatoire'),
-																		 'Selection' => $this->_('Par ordre de sélection')]);
-						?>
-						</td>
-					</tr>
-
-
-					<tr id='option_nb_aff'>
-						<td class="droite" width="90px">Nbre à afficher&nbsp;</td>
-						<td class="gauche"><input type="text" name="nb_aff" size="3" maxlength="3" value="<?php print($this->preferences["nb_aff"]); ?>"></td>
-					</tr>
-
-					<tr>
-						<td class="droite" width="90px">Grouper par catégorie&nbsp;</td>
-						<td class="gauche">
-						<?php
-										echo $this->formCheckbox('group_by_categorie',
-																						 null,
-																						 array('checked' => $this->preferences["group_by_categorie"]));
-						?>
-						</td>
-					</tr>
-				</table>
-
-			</fieldset>
-			<br />
-
-		<?php echo $this->formSubmit("Valider","Valider",array("class" => "bouton")) ?>
-	</form>
-
-	<script type="text/javascript">
-	 formSelectToggleVisibilityForElement("input[name=display_order]", "#option_nb_aff", "Random");
-	</script>
- </div>
+<?php
+Class_ScriptLoader::getInstance()
+->addJQueryReady('
+function toggleDisplayTypeDependents() {
+  var dependents = $(".group_cat, #option_display_order, #option_nb_aff");
+	("3" == $("#type_aff").val()) ? dependents.hide() : dependents.show();
+}
+
+$("#type_aff").change(function () {
+	toggleDisplayTypeDependents();
+	var value = $("#type_aff").val();
+	$(".treeselect").treeselect("toggleVisibility", (value == "1" || value == "3"));
+});
+
+toggleDisplayTypeDependents();
+formSelectToggleVisibilityForElement("input[name=display_order]", "#option_nb_aff", "Random");
+');
+?>
+<center>
+	<h1>Propriétés du module sitothèque</h1><br>
+	<div class="formTable">
+		<form method="post" action="<?php echo $this->url; ?>">
+
+			<fieldset>
+				<legend>Affichage</legend>
+				<table cellspacing="2">
+					<tr>
+						<td class="droite">Style de boite&nbsp;</td>
+						<td class="gauche"><?php echo $this->combo_templates; ?></td>
+					</tr>
+				</table>
+			</fieldset>
+			<br>
+
+			<fieldset>
+				<legend>Généralités</legend>
+				<table cellspacing="2">
+					<tr>
+						<td class="droite">Titre&nbsp;</td>
+						<td class="gauche"><input type="text" name="titre" size="50" value="<?php print($this->preferences['titre']); ?>"></td>
+					</tr>
+				</table>
+			</fieldset>
+			<br>
+
+			<fieldset>
+				<legend>Sites à afficher</legend>
+				<table cellspacing="2" width="100%">
+					<tr>
+						<td class="droite" width="90px">Type&nbsp;</td>
+						<td class="gauche">
+							<?php
+							echo $this->formSelect('type_aff',
+																		 $this->preferences['type_aff'],
+																		 [],
+																		 ['1' => 'Une sélection de catégories ou de sites',
+																			'2' => 'Les sites les plus récents',
+																			'3' => 'Affichage hiérarchique par catégorie'])
+							?>
+						</td>
+					</tr>
+				</table>
+
+				<div id="table_selection">
+					<?php
+					echo $this->treeSelect($this->preferences['id_items'],
+																 $this->preferences['id_categorie'],
+																 ($this->preferences['type_aff'] != '2'),
+																 BASE_URL . '/admin/bib/allitems?id_bib=' . $this->id_bib . '&type=sito',
+																 "form");
+					?>
+				</div>
+
+				<table cellspacing="2" width="100%">
+					<tr id='option_display_order'>
+						<td class="droite" width="90px">Ordre d'affichage&nbsp;</td>
+						<td class="gauche">
+							<?php
+							echo $this->formRadio('display_order',
+																		$this->preferences['display_order'],
+																		[],
+																		['Random' => $this->_('Par ordre aléatoire'),
+																		 'Selection' => $this->_('Par ordre de sélection')]);
+							?>
+						</td>
+					</tr>
+
+					<tr id='option_nb_aff'>
+						<td class="droite" width="90px">Nbre à afficher&nbsp;</td>
+						<td class="gauche"><input type="text" name="nb_aff" size="3" maxlength="3" value="<?php print($this->preferences['nb_aff']); ?>"></td>
+					</tr>
+
+					<tr class="group_cat">
+						<td class="droite" width="90px">Grouper par catégorie&nbsp;</td>
+						<td class="gauche">
+							<?php
+							echo $this->formCheckbox('group_by_categorie',
+																			 null,
+																			 ['checked' => $this->preferences['group_by_categorie']]);
+							?>
+						</td>
+					</tr>
+				</table>
+
+			</fieldset>
+			<br />
+
+			<?php echo $this->formSubmit('Valider', 'Valider', ['class' => 'bouton']); ?>
+		</form>
+	</div>
+</center>
diff --git a/application/modules/opac/controllers/DomainsController.php b/application/modules/opac/controllers/DomainsController.php
index 89623455126484128b5756bf8a5ff4025ee3f070..cdc9eb5374f4821ee287e7e86b7e94b1776e956e 100644
--- a/application/modules/opac/controllers/DomainsController.php
+++ b/application/modules/opac/controllers/DomainsController.php
@@ -16,7 +16,7 @@
  *
  * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
  * along with BOKEH; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA 
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
 
 class DomainsController extends ZendAfi_Controller_Action {
@@ -41,7 +41,6 @@ class DomainsController extends ZendAfi_Controller_Action {
 
 		$this->view->current_domain = $catalog;
 		$this->view->id_module = $id_module;
-		$this->view->current_breadcrumb = $this->_getParam('parents');
 	}
 }
 ?>
\ No newline at end of file
diff --git a/application/modules/opac/controllers/SitoController.php b/application/modules/opac/controllers/SitoController.php
index 6700be3445c74b537831b58e3cb73316cc7be1c8..6f2b37a070f4db45316709868ca5e430d3a13d7c 100644
--- a/application/modules/opac/controllers/SitoController.php
+++ b/application/modules/opac/controllers/SitoController.php
@@ -60,6 +60,17 @@ class SitoController extends Zend_Controller_Action {
 		$this->renderScript('sito/viewsitos.phtml');
 	}
 
+	public function viewcategoryAction()	{
+		$this->view->id_category=$this->_getParam('id_cat');
+		$search = $this->_getParam('title_search', '');
+		$this->view->list = $this->_helper->listViewMode(
+																										 ['model' => Class_SitothequeCategorie::find($this->view->id_category),
+																											'strategy_label' => 'sitotheque',
+																											'search_value' => $search,
+																											'id_cat'=>$this->view->id_category,
+																											'page' => $this->_getParam('page',1)]);
+
+	}
 
 	public function viewselectionAction()	{
 		$id_module = $this->_getParam('id_module');
diff --git a/application/modules/opac/views/scripts/pagination.phtml b/application/modules/opac/views/scripts/pagination.phtml
new file mode 100644
index 0000000000000000000000000000000000000000..aa60d29e84bb94ec33efdc50d615f44afade7f5b
--- /dev/null
+++ b/application/modules/opac/views/scripts/pagination.phtml
@@ -0,0 +1,27 @@
+<?php if (1 >= $this->pageCount) return;?>
+<?php if ($this->pageCount): ?>
+<div class="paginationControl">
+<!-- Previous page link -->
+<?php if (isset($this->previous)): ?>
+  <a href="<?php echo $this->url(['page' => $this->previous]); ?>">&lt; <?php echo $this->_('Précédent'); ?></a> |
+<?php else: ?>
+  <span class="disabled">&lt; <?php echo $this->_('Précédent'); ?></span> |
+<?php endif; ?>
+
+<!-- Numbered page links -->
+<?php foreach ($this->pagesInRange as $page): ?>
+  <?php if ($page != $this->current): ?>
+    <a href="<?php echo $this->url(['page' => $page]); ?>"><?php echo $page; ?></a> |
+  <?php else: ?>
+    <?= $page; ?> |
+  <?php endif; ?>
+<?php endforeach; ?>
+
+<!-- Next page link -->
+<?php if (isset($this->next)): ?>
+  <a href="<?php echo $this->url(array('page' => $this->next)); ?>"><?php echo $this->_('Suivant'); ?> &gt;</a>
+<?php else: ?>
+  <span class="disabled"><?php echo $this->_('Suivant'); ?> &gt;</span>
+<?php endif; ?>
+</div>
+<?php endif; ?>
diff --git a/application/modules/opac/views/scripts/sito/viewcategory.phtml b/application/modules/opac/views/scripts/sito/viewcategory.phtml
new file mode 100644
index 0000000000000000000000000000000000000000..95955c14e0d5fa1f2fa261e2edcdaf51d1abfe5f
--- /dev/null
+++ b/application/modules/opac/views/scripts/sito/viewcategory.phtml
@@ -0,0 +1,3 @@
+<?php
+echo $this->publicListViewMode($this->list);
+?>
diff --git a/library/Class/CmsUrlTransformer.php b/library/Class/CmsUrlTransformer.php
index 4170d5df668173ba0de2aff2358a1c80158f0434..a9080bffd19ec75e0a9f48abfc67011dea795dc8 100644
--- a/library/Class/CmsUrlTransformer.php
+++ b/library/Class/CmsUrlTransformer.php
@@ -57,6 +57,7 @@ class Class_CmsUrlTransformer {
 												$rel);
 	}
 
+
 	public static function removeHostFromArray($hostname,$basedir, $contents, &$replace_count = null) {
 		if (is_array($contents)) {
 			foreach ($contents as $index => $content) {
diff --git a/library/Class/Sitotheque.php b/library/Class/Sitotheque.php
index 2dae95d5f2915995671a52cb087cc470a8ea8f18..961852f8c51b08db78b23310fb96cd8b50f3f034 100644
--- a/library/Class/Sitotheque.php
+++ b/library/Class/Sitotheque.php
@@ -36,6 +36,7 @@ class Class_SitothequeModelSite extends BaseItem {
 
 
 class SitothequeLoader extends Storm_Model_Loader {
+
 	/**
 	 * @param array $id_sites
 	 * @param array $id_categories
@@ -100,7 +101,8 @@ class SitothequeLoader extends Storm_Model_Loader {
 
 
 class Class_Sitotheque extends Storm_Model_Abstract {
-	use Trait_Translator, Trait_TreeViewableItem, Trait_HasManyDomaines, Trait_Indexable, Trait_CustomFields;
+	use Trait_Translator, Trait_TreeViewableItem, Trait_HasManyDomaines, Trait_Indexable, Trait_CustomFields,
+		Trait_TimeSource;
 
 
 	protected $_loader_class = 'SitothequeLoader';
@@ -125,6 +127,7 @@ class Class_Sitotheque extends Storm_Model_Abstract {
 
 
 	public function beforeSave() {
+		$this->setDateMaj($this->getCurrentDateTime());
 		$this->unindex();
 	}
 
diff --git a/library/Class/SitothequeCategorie.php b/library/Class/SitothequeCategorie.php
index 0fcf189d8fc6e969593f8f4ca69485bddfeeef75..fe11dbf037db8a563e9d5862822f16bf23ab2a52 100644
--- a/library/Class/SitothequeCategorie.php
+++ b/library/Class/SitothequeCategorie.php
@@ -32,9 +32,11 @@ class Class_SitothequeCategorie extends Storm_Model_Abstract {
 
 	protected $_has_many = ['sous_categories' => ['model' => 'Class_SitothequeCategorie',
 																								'role' => 'parent_categorie',
+																								'order' => 'libelle',
 																								'dependents' => 'delete'],
 
 													'sitotheques' => ['model' => 'Class_Sitotheque',
+																						'order' => 'titre',
 																						'role' => 'categorie',
 																						'dependents' => 'delete']];
 
diff --git a/library/Trait/TimeSource.php b/library/Trait/TimeSource.php
index e4be4f6aa7de04bc1ef4ab362880a61f444c6a87..93637558e061449d967bfc2440c2778de33e028f 100644
--- a/library/Trait/TimeSource.php
+++ b/library/Trait/TimeSource.php
@@ -42,6 +42,10 @@ trait Trait_TimeSource {
 		return date('Y-m-d',self::getTimeSource()->time());
 	}
 
+	public static function getCurrentDateTime() {
+		return date('Y-m-d H:i:s',self::getTimeSource()->time());
+	}
+
 
 	/** @return Class_TimeSource */
 	public static function getTimeSource() {
diff --git a/library/ZendAfi/Controller/Action/Helper/ListViewMode.php b/library/ZendAfi/Controller/Action/Helper/ListViewMode.php
index d7a7604b869ca8c3cb2da3aabd3993a6e6247164..eddc062b2fbc73bbaefde9f3c7b994072b1059c9 100644
--- a/library/ZendAfi/Controller/Action/Helper/ListViewMode.php
+++ b/library/ZendAfi/Controller/Action/Helper/ListViewMode.php
@@ -30,7 +30,7 @@ class ZendAfi_Controller_Action_Helper_ListViewMode extends Zend_Controller_Acti
 		$helper_class = 'ZendAfi_Controller_Action_Helper_ListViewMode_Strategy_'.ucfirst($strategy);
 
 		return $list
-			->setStrategy(new $helper_class())
+			->setStrategy(new $helper_class($this))
 			->buildList();
 	}
 
diff --git a/library/ZendAfi/Controller/Action/Helper/ListViewMode/List.php b/library/ZendAfi/Controller/Action/Helper/ListViewMode/List.php
index a9fb5b50b2746d4b2d9026c7d011894850a49e38..de34816f7ddcd45934fd9ca30a952dd0d6ddc8d6 100644
--- a/library/ZendAfi/Controller/Action/Helper/ListViewMode/List.php
+++ b/library/ZendAfi/Controller/Action/Helper/ListViewMode/List.php
@@ -176,6 +176,11 @@ class ZendAfi_Controller_Action_Helper_ListViewMode_List {
 	}
 
 
+	public function countRecursiveItemsFor($model) {
+		return $this->_strategy->countRecursiveItemsFor($model);
+	}
+
+
 	public function getBreadcrumbFor($model) {
 		return $this->_strategy->getBreadcrumbFor($model);
 	}
@@ -231,4 +236,11 @@ class ZendAfi_Controller_Action_Helper_ListViewMode_List {
 	public function isSearchEnabled() {
 		return $this->_strategy->isSearchEnabled();
 	}
+
+
+	public function getUrlParams($model) {
+		return $this->_strategy->getUrlParams($model);
+
+	}
+
 }
diff --git a/library/ZendAfi/Controller/Action/Helper/ListViewMode/Strategy.php b/library/ZendAfi/Controller/Action/Helper/ListViewMode/Strategy.php
index f3ccc9b96151a3d2e23b21dd1817a9b8c8af4c66..e25945f32b6821046951ac45229bb84cee2695a0 100644
--- a/library/ZendAfi/Controller/Action/Helper/ListViewMode/Strategy.php
+++ b/library/ZendAfi/Controller/Action/Helper/ListViewMode/Strategy.php
@@ -27,6 +27,13 @@ class ZendAfi_Controller_Action_Helper_ListViewMode_Strategy {
 
 	protected $_visitor;
 
+	protected $_helper;
+
+
+	public function __construct($helper) {
+		$this->_helper = $helper;
+	}
+
 
 	public function visit($visitor) {
 		$this->_visitor = $visitor;
@@ -42,15 +49,20 @@ class ZendAfi_Controller_Action_Helper_ListViewMode_Strategy {
 																	'title_search' => ''];
 	}
 
+	protected function _shouldCheckParent($start_key,$model) {
+		return ($start_key && ($this->getRequestParam($start_key,'')==$model->getId()));
+	}
+
 
-	public function getBreadcrumbFor($model, $breadcrumb = []) {
+	public function getBreadcrumbFor($model, $breadcrumb = [],$start_key='') {
 		if(!$model)
 			return $breadcrumb;
 
-		if($parent = $model->getParentCategorie())
-			$breadcrumb = $this->getBreadcrumbFor($parent, $breadcrumb);
+		if (!$this->_shouldCheckParent($start_key,$model) && $parent = $model->getParentCategorie())
+			$breadcrumb = $this->getBreadcrumbFor($parent, $breadcrumb,$start_key);
 
-		$breadcrumb[] = ['url' => array_merge($this->getBreadcrumbUrl(),[$this->getParamKey() => $model->getId()]),
+		$breadcrumb[] = ['url' => array_merge($this->getBreadcrumbUrl(),
+																					[$this->getParamKey() => $model->getId()]),
 										 'label' => $model->getLibelle(),
 										 'options' => []];
 
@@ -153,5 +165,20 @@ class ZendAfi_Controller_Action_Helper_ListViewMode_Strategy {
 	public function getItemsLabelAttrib() {
 		return '';
 	}
+
+
+	public function getRequestParam($key, $default = null) {
+		return $this->_helper->getRequest()->getParam($key, $default);
+	}
+
+	public function  countRecursiveItemsFor($model) {
+		return $this->countItemsFor($model);
+	}
+
+	public function getUrlParams($model) {
+		return array_merge($params,$this->getBaseUrl(),
+											 [ $this->getParamKey() => $model->getId()]);
+
+	}
 }
 ?>
\ No newline at end of file
diff --git a/library/ZendAfi/Controller/Action/Helper/ListViewMode/Strategy/Sitotheque.php b/library/ZendAfi/Controller/Action/Helper/ListViewMode/Strategy/Sitotheque.php
new file mode 100644
index 0000000000000000000000000000000000000000..dbe853b1deeaee9d83426fda570d1783809628a9
--- /dev/null
+++ b/library/ZendAfi/Controller/Action/Helper/ListViewMode/Strategy/Sitotheque.php
@@ -0,0 +1,162 @@
+<?php
+/**
+ * Copyright (c) 2012-2014, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class ZendAfi_Controller_Action_Helper_ListViewMode_Strategy_Sitotheque extends ZendAfi_Controller_Action_Helper_ListViewMode_Strategy {
+
+	const ITEMS_PAGINATION = 5;
+	protected $_filtred_categories,
+		$_filtred_categories_ids,
+		$_current_bib;
+
+	public function getBaseUrl() {
+		return ['module' => 'opac',
+						'controller' => 'sito',
+						'action' => 'viewcategory'];
+	}
+
+
+	public function getSearchUrl() {
+		return $this->getBaseUrl() + ['title_search' => ''];
+	}
+
+
+	public function getBreadcrumbUrl() {
+		return array_merge($this->getStartParams(),
+											 $this->getBaseUrl());
+	}
+
+
+	public function getParamKey() {
+		return 'id_cat';
+	}
+
+
+	public function getUrlParams($model) {
+		return array_merge($this->getStartParams(),
+											 $this->getBaseUrl(),
+											 [$this->getParamKey() => $model->getId()]);
+	}
+
+
+	protected function getStartParams() {
+		return ($start_cat = $this->getRequestParam('start_cat')) ?
+			['start_cat' => $start_cat] : [];
+	}
+
+
+	public function getCategories() {
+		$categories = (!$id_cat = $this->_visitor->getParams()['id_cat'])
+			? Class_SitothequeCategorie::findAll()
+			: Class_SitothequeCategorie::findAllBy(['id_cat_mere' => $id_cat]);
+
+		return $categories;
+	}
+
+
+	public function getItems() {
+		return Class_Sitotheque::findAllBy(array_merge(['order' => 'date_maj desc'],
+																									 $this->getItemsParams()));
+	}
+
+
+	public function getCountSearchResult() {
+		return Class_Article::countBy(parent::getSearchParams());
+	}
+
+
+	public function getBreadcrumb() {
+		$breadcrumb = [];
+		 if($this->_visitor->getSearchValue())
+			return $breadcrumb;
+
+		return array_merge($breadcrumb,
+											 $this->getBreadcrumbFor($this->_visitor->getModel(),
+																							 [],
+																							 'start_cat'));
+	}
+
+
+	protected function _shiftBreadcrumb($breadcrumb = []) {
+		$start_cat = $this->getRequestParam('start_cat');
+		foreach ($breadcrumb as $cat) {
+			if ($cat['url']['id_cat'] != $start_cat) {
+				array_shift($breadcrumb);
+				return $breadcrumb;}
+		}
+
+		return $breadcrumb;
+	}
+
+
+	public function getDefaultModel() {
+		return Class_SitothequeCategorie::find($this->_visitor->getParams()['id_cat']);
+
+	}
+
+
+	public function countItemsFor($model) {
+		$id = $model ? $model->getId() : 0;
+		return Class_Sitotheque::countBy([$this->getParamKey() => $id]);
+	}
+
+
+	public function countRecursiveItemsFor($model) {
+		if (!$model)
+			return Class_Sitotheque::count();
+
+		$ids = array_map(function($cat) {return $cat->getId();},
+										 $model->getRecursiveSousCategories());
+
+		$ids[] = $model->getId();
+		return Class_Sitotheque::countBy([$this->getParamKey() => $ids]);
+	}
+
+
+	public function getCategoriesCols() {
+		return [$this->_('Catégories d\'articles')];
+	}
+
+
+	public function getCategoriesLabelAttrib() {
+		return 'libelle';
+	}
+
+
+	public function getCategoriesId() {
+		return 'sitotheque-category';
+	}
+
+
+	public function getItemsId() {
+		return 'sitotheque';
+	}
+
+
+	public function getItemsCols() {
+		return [$this->_('Liste des articles')];
+	}
+
+
+	public function getItemsLabelAttrib() {
+		return 'titre';
+	}
+}
\ No newline at end of file
diff --git a/library/ZendAfi/View/Helper/Accueil/Base.php b/library/ZendAfi/View/Helper/Accueil/Base.php
index 9dab4f3b53e64dbf623e04d490ba036f065c708e..d145397e3cee706ecdecedc4a6596e70b63e8863 100644
--- a/library/ZendAfi/View/Helper/Accueil/Base.php
+++ b/library/ZendAfi/View/Helper/Accueil/Base.php
@@ -18,10 +18,7 @@
  * along with BOKEH; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
-//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-// OPAC 3 : classe de base pour le gestion des modules de la page D'ACCUEIL
-//
-//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
 
 class ZendAfi_View_Helper_Accueil_Base extends ZendAfi_View_Helper_ModuleAbstract {
 	protected static $modules_config;
@@ -58,13 +55,6 @@ class ZendAfi_View_Helper_Accueil_Base extends ZendAfi_View_Helper_ModuleAbstrac
 	}
 
 
-	public function setPreference($name, $value) {
-		$this->preferences[$name] = $value;
-		return $this;
-	}
-
-
-
 	public function setIdMenu($id_menu) {
 		$this->_id_menu = $id_menu;
 		return $this;
@@ -93,11 +83,6 @@ class ZendAfi_View_Helper_Accueil_Base extends ZendAfi_View_Helper_ModuleAbstrac
 	}
 
 
-	public function getPreferences() {
-		return $this->preferences;
-	}
-
-
 	public static function getComboTemplates($valeur_select, $profil=null)	{
 		if (!$profil)
 			$profil = Class_Profil::getCurrentProfil();
@@ -287,7 +272,12 @@ class ZendAfi_View_Helper_Accueil_Base extends ZendAfi_View_Helper_ModuleAbstrac
 		else
 			$html=$html_array["TITRE"].BR.$html_array["CONTENU"];
 
-		return '<div id="boite_'.$this->id_module.'" class="boite '.strtolower($this->type_module).'">'.$html.'</div>';
+		return '<div id="' . $this->_getBoxId() . '" class="boite '.strtolower($this->type_module).'">'.$html.'</div>';
+	}
+
+
+	protected function _getBoxId() {
+		return 'boite_' . $this->id_module;
 	}
 
 
diff --git a/library/ZendAfi/View/Helper/Accueil/Sito.php b/library/ZendAfi/View/Helper/Accueil/Sito.php
index d464440a47955463750f933dd0a592c5dd6702d2..c5457f290d0c7ce8e1f5feea9f9c866751b2d6b8 100644
--- a/library/ZendAfi/View/Helper/Accueil/Sito.php
+++ b/library/ZendAfi/View/Helper/Accueil/Sito.php
@@ -20,10 +20,20 @@
  */
 
 class ZendAfi_View_Helper_Accueil_Sito extends ZendAfi_View_Helper_Accueil_Base {
+	const ORDER_RANDOM = 'Random';
+	const ORDER_SELECTION = 'Selection';
+
+	const DISPLAY_SELECTION = 1;
+	const DISPLAY_NOVELTY = 2;
+	const DISPLAY_HIERARCHY = 3;
+
+	protected $sitotree_helper;
+
 	protected function _renderHeadScriptsOn($script_loader) {
-		if ($this->isGroupByCategorie())
+		if ($this->isGroupByCategorie()
+				&& !$this->isHierarchicalDisplay())
 			Class_ScriptLoader::getInstance()
-				->addJQueryReady('$("ul.sitotheque>li>h2>a").click(
+				->addJQueryReady('$("#'.$this->_getBoxId() .' ul.sitotheque>li>h2>a").click(
 																	 function(event){
 																				 event.preventDefault();
 																				 $(this).closest("li").find("ul").slideToggle();
@@ -32,78 +42,108 @@ class ZendAfi_View_Helper_Accueil_Sito extends ZendAfi_View_Helper_Accueil_Base
 
 
 	public function getHtml()	{
-		extract($this->preferences);
+		$this->sitotree_helper = $this->view->getHelper('SitoTree');
+
 		$contenu = '';
-		$nb_aff = $this->preferences['nb_aff'];
+		$nb_aff = $this->getPreference('nb_aff');
+
+		$box_title = $this->getPreference('titre') ?
+			$this->getPreference('titre') : $this->_('Derniers sites ajoutés');
+
+		if ($this->isHierarchicalDisplay()) {
+			$this->contenu = $this->sitotree_helper
+				->sitoTree($this->getPreference('id_categorie'));
+			$this->titre = $box_title;
+			return $this->getHtmlArray();
+		}
+
 
-		if ($this->isTypeAffichageSelection())	{
-			$sites = Class_Sitotheque::getSitesFromIdsAndCategories(
-																															explode('-', $this->preferences['id_items']),
-																															explode('-', $this->preferences['id_categorie']));
+		if ($this->isTypeAffichageSelection()) {
+			$sites = Class_Sitotheque::getSitesFromIdsAndCategories(explode('-', $this->getPreference('id_items')),
+																															explode('-', $this->getPreference('id_categorie')));
 
-			if ($this->preferences['display_order'] == 'Random')
+			if (static::ORDER_RANDOM == $this->getPreference('display_order'))
 				shuffle($sites);
 
-			if ($this->preferences['display_order'] == 'Selection')
+			if (static::ORDER_SELECTION == $this->getPreference('display_order'))
 				$nb_aff = count($sites);
 
-			$titre = sprintf('<a href="%s" title="%s">%s</a>',
-											htmlspecialchars(BASE_URL.'/opac/sito/viewselection/id_module/'.$this->id_module),
-											$this->translate()->_('Sélection de sites'),
-											$titre);
+			$this->titre = $this->renderTitleLink($box_title,
+																						$this->_('Sélection de sites'),
+																						$this->view->url(['module' => 'opac',
+																															'controller' => 'sito',
+																															'action' => 'viewselection',
+																															'id_module' => $this->id_module],
+																														 null, true));
 		}
 
 		if ($this->isTypeAffichagePlusRecents() && $nb_aff > 0) {
-			$sites = Class_Sitotheque::findAllBy(['limit' => 50]);
-			shuffle($sites);
+			$sites = Class_Sitotheque::findAllBy(['order' => 'date_maj desc',
+																						'limit' => 50]);
 
-			if(!$titre)
-				$titre = $this->translate()->_("Derniers sites ajoutés");
+			if (static::ORDER_RANDOM == $this->getPreference('display_order'))
+				shuffle($sites);
 
-			$titre = sprintf('<a href="%s" title="%s">%s</a>',
-											htmlspecialchars(BASE_URL.'/opac/sito/viewrecent/nb/50'),
-											$this->translate()->_('Liste des derniers sites ajoutés'),
-											$titre);
+			$this->titre = $this->renderTitleLink($box_title,
+																						$this->_('Liste des derniers sites ajoutés'),
+																						$this->view->url(['controller' => 'sito',
+																															'action' => 'viewrecent',
+																															'nb' => 50],
+																														 null,true));
 		}
 
-		$this->titre = $titre;
 		$this->contenu = $this->renderSitesSlice($sites, $nb_aff);
 		return $this->getHtmlArray();
 	}
 
 
+	public function renderTitleLink($content, $title, $url) {
+		return $this->view->tagAnchor($url, $content, ['title' => $this->translate()->_($title)]);
+	}
+
+
 	public function isTypeAffichageSelection() {
-		return $this->preferences['type_aff'] == 1;
+		return $this->_isDisplayType(static::DISPLAY_SELECTION);
 	}
 
 
 	public function isTypeAffichagePlusRecents() {
-		return $this->preferences['type_aff'] == 2;
+		return $this->_isDisplayType(static::DISPLAY_NOVELTY);
 	}
 
 
-	public function isGroupByCategorie() {
-		if (!array_isset('group_by_categorie', $this->preferences))
-			return false;
+	public function isHierarchicalDisplay() {
+		return $this->_isDisplayType(static::DISPLAY_HIERARCHY);
+	}
 
-		return $this->preferences['group_by_categorie'] == true;
+
+	protected function _isDisplayType($type) {
+		return $this->getPreference('type_aff') == $type;
+	}
+
+
+	public function isGroupByCategorie() {
+		return (array_isset('group_by_categorie', $this->preferences)
+					 && true == $this->getPreference('group_by_categorie'));
 	}
 
 
 	public function groupSitesByCategorie($sites) {
-		$categories = array();
+		$categories = [];
 		foreach ($sites as $site) {
 			$categorie = $site->getCategorieLibelle();
 			if (!array_isset($categorie, $categories))
-				$categories[$categorie] = array();
-			$categories[$categorie] []= $site;
+				$categories[$categorie] = [];
+			$categories[$categorie][] = $site;
 		}
+
 		return $categories;
 	}
 
 
-	protected function renderSitesSlice($sites,$nb_aff)	{
-		if(!$sites) return "";
+	protected function renderSitesSlice($sites, $nb_aff)	{
+		if (!$sites)
+			return '';
 
 		$sites = array_slice($sites, 0, $nb_aff);
 
@@ -111,45 +151,39 @@ class ZendAfi_View_Helper_Accueil_Sito extends ZendAfi_View_Helper_Accueil_Base
 			return $this->renderSites($sites);
 
 		$categories = $this->groupSitesByCategorie($sites);
-		$htmls = array();
+		$htmls = [];
 		foreach ($categories as $libelle_categorie => $sites)
-			$htmls []= sprintf('<li><h2><a href="#">%s</a></h2><ul><li>%s</li></ul></li>',
-												 $libelle_categorie,
-												 $this->renderSites($sites));
+			$htmls[] = $this->_renderGroup($libelle_categorie, $sites);
 
-		return sprintf('<ul class="sitotheque">%s</ul>',
-									 implode('', $htmls));
+		return $this->_tag('ul', implode('', $htmls),
+											 ['class' => 'sitotheque']);
 	}
 
 
-	protected function renderSites($sites) {
-		return implode('', array_map([$this, 'renderSite'], $sites));
+	protected function _renderGroup($label, $sites) {
+		return $this->_tag('li',
+											 $this->_tag('h2',
+																	 $this->_tag('a', $label, ['href' => '#']))
+											 . $this->_tag('ul',
+																		 $this->_tag('li', $this->renderSites($sites))));
 	}
 
 
-	protected function renderSite($site) {
-		if($this->division == 1)	{
-			$site->setDescription($this->extractHeader($site->getDescription()));
-		}
-
-		$html = sprintf('<h2><a href="%s" title="%s">',
-										$site->getUrl(),
-										$this->translate()->_('Aller sur le site'));
-
-		if ($img_url = $this->getThumbnail($site->getUrl()))
-			$html.= sprintf('<img src="%s" alt="%s" />',$img_url,	$this->translate()->_('vignette du site %s', $site->getTitre()));
+	protected function renderSites($sites) {
+		return implode('', array_map([$this, 'renderSiteDiv'], $sites));
+	}
 
-		$html .= $site->getTitre().'</a></h2>';
-		$html .= $site->getDescription();
 
-		return '<div class="sitotheque">'.$html.'</div>';
+	protected function renderSiteDiv($site) {
+		return $this->_tag('div', $this->renderSite($site),
+											 ['class' => 'sitotheque']);
 	}
 
 
-	public function getThumbnail($url) {
-		if (!isset($this->thumbnails_helper))
-			$this->thumbnails_helper = (new ZendAfi_View_Helper_WebThumbnail())
-				->setView($this->view);
-		return $this->thumbnails_helper->webThumbnail($url);
+	protected function renderSite($site) {
+		if ($this->division == 1)
+			$site->setDescription($this->extractHeader($site->getDescription()));
+
+		return $this->sitotree_helper->renderSite($site);
 	}
 }
\ No newline at end of file
diff --git a/library/ZendAfi/View/Helper/Admin/ListViewMode.php b/library/ZendAfi/View/Helper/Admin/ListViewMode.php
index 4b39f5c405ea83bed44dfd62fc98eb7a6c487798..32f451b8131301990fbd94ebbcccd11b48fe56c3 100644
--- a/library/ZendAfi/View/Helper/Admin/ListViewMode.php
+++ b/library/ZendAfi/View/Helper/Admin/ListViewMode.php
@@ -37,6 +37,7 @@ class ZendAfi_View_Helper_Admin_ListViewMode extends ZendAfi_View_Helper_BaseHel
 	}
 
 
+
 	protected function getSearchFormHTML() {
 		if(!$this->_list->isSearchEnabled())
 			return '';
diff --git a/library/ZendAfi/View/Helper/Menu/Profil.php b/library/ZendAfi/View/Helper/Menu/Profil.php
index c759646e3562a785943f9234ee972309a32b14dc..40e14574e356e4155cd38844144a6bba8e77fc77 100644
--- a/library/ZendAfi/View/Helper/Menu/Profil.php
+++ b/library/ZendAfi/View/Helper/Menu/Profil.php
@@ -16,14 +16,15 @@
  *
  * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
  * along with BOKEH; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA 
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
 
 class ZendAfi_View_Helper_Menu_Profil extends ZendAfi_View_Helper_Menu_Entry {
 	public function getBoite($li_class = '')	{
-		return (Class_Profil::getCurrentProfil()->getId() == (int)$this->_preferences['clef_profil'])
-			? parent::getBoite(trim($li_class.' selected_profil'))
-			: parent::getBoite($li_class);
+		if (isset($this->_preferences['clef_profil']) &&
+							(Class_Profil::getCurrentProfil()->getId() == (int)$this->_preferences['clef_profil']))
+			return parent::getBoite(trim($li_class.' selected_profil'));
+		return  parent::getBoite($li_class);
 	}
 }
 
diff --git a/library/ZendAfi/View/Helper/ModuleAbstract.php b/library/ZendAfi/View/Helper/ModuleAbstract.php
index f3419b75f08ce484d2ae98c542c821fe5ac5cc2c..474d3fc75e380dbc790549985410ed156a50d2cd 100644
--- a/library/ZendAfi/View/Helper/ModuleAbstract.php
+++ b/library/ZendAfi/View/Helper/ModuleAbstract.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
  */
 
 abstract class ZendAfi_View_Helper_ModuleAbstract extends ZendAfi_View_Helper_BaseHelper {
@@ -40,6 +40,23 @@ abstract class ZendAfi_View_Helper_ModuleAbstract extends ZendAfi_View_Helper_Ba
 	}
 
 
+	public function getPreference($name, $default=null) {
+		return array_key_exists($name, $this->preferences) ?
+			$this->preferences[$name] : $default;
+	}
+
+
+	public function setPreference($name, $value) {
+		$this->preferences[$name] = $value;
+		return $this;
+	}
+
+
+	public function getPreferences() {
+		return $this->preferences;
+	}
+
+
 	/**
 	 * @return string le contenu au format HTML
 	 */
diff --git a/library/ZendAfi/View/Helper/PublicListViewMode.php b/library/ZendAfi/View/Helper/PublicListViewMode.php
new file mode 100644
index 0000000000000000000000000000000000000000..313c4964b12308ce48f081f83b42d085845abcae
--- /dev/null
+++ b/library/ZendAfi/View/Helper/PublicListViewMode.php
@@ -0,0 +1,85 @@
+<?php
+/**
+ * Copyright (c) 2012-2014, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class ZendAfi_View_Helper_PublicListViewMode extends  ZendAfi_View_Helper_Admin_ListViewMode  {
+	public function publicListViewMode($list) {
+		if (!$this->_list = $list)
+			return '';
+
+		$html = $this->getSearchFormHTML()
+			. $this->getBreadcrumbHTML()
+			. $this->getCategoriesHTML()
+			. $this->getItemsHTML()
+			. $this->getItemsPaginatorHTML();
+
+		return $html;
+	}
+
+
+	protected function getCategoriesHTML() {
+		if($this->_list->getSearchValue())
+			return '';
+
+		$html = '';
+		foreach($this->_list->getCategories() as $category)
+			$html .= $this->getCategoryHtml($category);
+
+		return $this->_tag('ul', $html,
+											 ['class' => $this->_list->getCategoriesId()]);
+	}
+
+
+	protected function getCategoryHtml($category) {
+		$label_attrib = $this->_list->getCategoriesLabelAttrib();
+		$label = sprintf('%s (%s)',
+										 $category->$label_attrib,
+										 $this->_list->countRecursiveItemsFor($category));
+
+		return $this
+			->_tag('li',
+						 $this->_tag('a', $label,
+												 ['href' => $this->view->url($this->_list->getUrlParams($category),
+																										 null, true)]));
+	}
+
+
+	protected function getItemsHTML() {
+		if(!$this->_list->getModel() && !$this->_list->getSearchValue())
+			 return '';
+
+		$html = '';
+		foreach ($this->_list->getItems() as $item)
+			$html .= $this->getItemHtml($item);
+
+		return $html;
+	}
+
+
+	protected function getItemHtml($item) {
+		if (!$item)
+			return '';
+
+		return $this->_tag('div',
+											 $this->view->getHelper('SitoTree')->renderSite($item),
+											 ['class' => 'sitotheque']);
+	}
+}
\ No newline at end of file
diff --git a/library/ZendAfi/View/Helper/SitoTree.php b/library/ZendAfi/View/Helper/SitoTree.php
new file mode 100644
index 0000000000000000000000000000000000000000..ba615d26aa22b2e6b16590ecc8e8892007202a0f
--- /dev/null
+++ b/library/ZendAfi/View/Helper/SitoTree.php
@@ -0,0 +1,91 @@
+<?php
+/**
+ * Copyright (c) 2012-2014, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class ZendAfi_View_Helper_SitoTree extends ZendAfi_View_Helper_BaseHelper {
+	protected $titre, $contenu, $preferences, $division;
+
+	public function sitoTree($id_category) {
+		$category = Class_SitothequeCategorie::find($id_category);
+		return $this->renderHierarchicalCategories($category);
+	}
+
+
+	protected function renderHierarchicalCategories($category) {
+		if (!$category)
+			return $this->_('Aucune catégorie sélectionnée.');
+
+		$html = '';
+		$children = $category->getChildren();
+		foreach ($children as $child_category)
+			$html .= $this->renderHierarchicalCategory($child_category);
+
+		foreach ($category->getItems() as $site)
+			$html .= $this->_tag('li', $this->renderSite($site));
+
+		return $this->_tag('ul', $html, ['class' => 'sitotheque']);
+	}
+
+
+	protected function renderHierarchicalCategory($category) {
+		if (!$category)
+			return '';
+
+		return $this->_tag('li', $this->_renderCategory($category));
+	}
+
+
+	protected function _renderCategory($category) {
+		return $this->_tag('h2',
+											 $this->_tag('a', $category->getLibelle(),
+																	 ['href' => $this->_urlOf($category)]));
+	}
+
+
+	protected function _urlOf($category) {
+		return $this->view->url(['controller' => 'sito',
+														 'action' => 'viewcategory',
+														 'id_cat' => $category->getId(),
+														 'start_cat' => $category->getId()],
+														null,true);
+	}
+
+
+	public function renderSite($site) {
+		return $this->_renderLabelOf($site) . $site->getDescription();
+	}
+
+
+	protected function _renderLabelOf($site) {
+		return $this->_tag('h2',
+											 $this->_tag('a',
+																	 $this->_renderImageOf($site) . $site->getTitre(),
+																	 ['href' => $site->getUrl(),
+																		'title' => $this->_('Aller sur le site %s',
+																												$site->getTitre())]));
+	}
+
+
+	protected function _renderImageOf($site) {
+		return ($url = $this->view->webThumbnail($site->getUrl())) ?
+			$this->view->tagImg($url) : '';
+	}
+}
\ No newline at end of file
diff --git a/library/ZendAfi/View/Helper/TagList.php b/library/ZendAfi/View/Helper/TagList.php
new file mode 100644
index 0000000000000000000000000000000000000000..34261c8be7ebbb69039f297bdf367e8ad6622402
--- /dev/null
+++ b/library/ZendAfi/View/Helper/TagList.php
@@ -0,0 +1,65 @@
+<?php
+/**
+ * Copyright (c) 2012, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+class ZendAfi_View_Helper_TagList extends Zend_View_Helper_HtmlElement {
+	protected $id_list;
+
+	public function tagList($models, $attribs, $id, $callbacks = []) {
+		$this->id_list=$id;
+		return $this->renderModelsAsUlLi($models, $attribs, $callbacks);
+	}
+
+
+
+
+	public function renderModelsAsUlLi($models, $attribs, $callbacks) {
+		$rows = '';
+
+		foreach ($models as $model) {
+			$rows .= $this->renderModelAsUlLi($model, $attribs,$callbacks);
+		}
+
+		return $rows;
+	}
+
+
+	public function renderModelAsUlLi($model, $attribs, $callbacks) {
+		$cols = '';
+
+		$default_callback = function ($model, $attrib) {
+				return $this->view->escape($model->callGetterByAttributeName($attrib));
+		};
+
+		foreach ($attribs as $attrib) {
+			$callback = (array_key_exists($attrib, $callbacks)) ? $callbacks[$attrib] : $default_callback;
+			$cols .= $this->tag('li', $callback($model, $attrib));
+		}
+
+		return $this->tag('ul', $cols,['class' => $this->id_list]);
+	}
+
+
+	/** shortcut to $this->view->tag() */
+	protected function tag() {
+		return call_user_func_array([$this->view, 'tag'], func_get_args());
+	}
+}
+
+?>
diff --git a/tests/application/modules/admin/controllers/AccueilControllerTest.php b/tests/application/modules/admin/controllers/AccueilControllerTest.php
index f82c9f25adb18c56d9c720278b5d8d2b7fa6a58e..25620bc3fa7e73df35f7440d972d2cdaac66d2f1 100644
--- a/tests/application/modules/admin/controllers/AccueilControllerTest.php
+++ b/tests/application/modules/admin/controllers/AccueilControllerTest.php
@@ -209,6 +209,64 @@ class AccueilControllerConfigCalendrierTest extends Admin_AbstractControllerTest
 
 
 
+class AccueilControllerConfigSitothequePostTest extends Admin_AbstractControllerTestCase {
+	public function setUp() {
+		parent::setUp();
+		Class_Profil::beVolatile();
+		Class_Profil::getCurrentProfil()
+			->updateModuleConfigAccueil(25,
+																	array('type_module' => 'SITO',
+																				'division' => 1,
+																				'id_module' => 25,
+																				'preferences' => ['id_items' => '12-13']));
+
+	}
+
+
+	/** @test */
+	public function selectHierachicalDisplayShouldSaveOnlyFirstCategory() {
+		$this->postDispatch('/admin/accueil/sitotheque?config=accueil&id_profil=1&type_module=SITO&id_module=25',
+												[
+												 'type_aff' => 3,
+												 'nb_aff' => 2,
+												 'group_by_categorie' => true,
+												 'id_categorie' => '12-17'
+												]);
+
+		$this->assertEquals('12',Class_Profil::find(1)->getModuleAccueilConfig(25,'SITO')['preferences']['id_categorie']);
+
+	}
+
+	/** @test */
+	public function selectHierachicalDisplayShouldForceGroupByCategoryToFalse() {
+		$this->postDispatch('/admin/accueil/sitotheque?config=accueil&id_profil=1&type_module=SITO&id_module=25',
+												[
+												 'type_aff' => 3,
+												 'nb_aff' => 2,
+												 'group_by_categorie' => true,
+												 'id_categorie' => '12-17'
+												]);
+
+
+		$this->assertEquals('0', Class_Profil::find(1)->getModuleAccueilConfig(25,'SITO')['preferences']['group_by_categorie']);
+
+	}
+	/** @test */
+	public function selectHierachicalDisplayWithEmptyCategoriesShouldSaveOnlyFirstCategory() {
+		$this->postDispatch('/admin/accueil/sitotheque?config=accueil&id_profil=1&type_module=SITO&id_module=25',
+												[
+												 'type_aff' => 3,
+												 'nb_aff' => 2,
+												 'group_by_categorie' => 0,
+												 'id_categorie' => ''
+												]);
+
+		$this->assertEquals('',Class_Profil::find(1)->getModuleAccueilConfig(25,'SITO')['preferences']['id_categorie']);
+
+	}
+
+
+}
 class AccueilControllerConfigSitothequeDefaultsTest extends Admin_AbstractControllerTestCase {
 	public function setUp() {
 		parent::setUp();
@@ -296,6 +354,11 @@ class AccueilControllerConfigSitothequeWithPreferencesTest extends Admin_Abstrac
 	public function displayOrderSelectionShouldBeChecked() {
 		$this->assertXPath('//input[@type="radio"][@name="display_order"][@checked="checked"][@value="Selection"]');
 	}
+
+/** @test */
+	public function typeHierarchicCanBeSelected() {
+		$this->assertXPathContentContains('//select[@id="type_aff"]//option[@value=3]','Affichage hiérarchique par catégorie');
+	}
 }
 
 
diff --git a/tests/application/modules/admin/controllers/SitothequeControllerTest.php b/tests/application/modules/admin/controllers/SitothequeControllerTest.php
index b07d08fc6ce1c9ac62a25bb9487bffc8714f5ed0..59dd1f60caabcce03a89cd258bb28a63bee8b6fe 100644
--- a/tests/application/modules/admin/controllers/SitothequeControllerTest.php
+++ b/tests/application/modules/admin/controllers/SitothequeControllerTest.php
@@ -30,7 +30,7 @@ abstract class SitothequeControllerTestCase extends Admin_AbstractControllerTest
 		Class_Exemplaire::beVolatile();
 		Class_Notice::beVolatile();
 		Class_CodifThesaurus::beVolatile();
-
+		Storm_Model_Loader::defaultToVolatile();
 		$categorie_informations = $this->fixture('Class_SitothequeCategorie',
 																						 ['id' => 2,
 																							'libelle' => 'Informations',
@@ -59,6 +59,12 @@ abstract class SitothequeControllerTestCase extends Admin_AbstractControllerTest
 		Class_Bib::getPortail()->setSitothequeCategories([]);
 	}
 
+	public function tearDown() {
+		Storm_Model_Loader::defaultToDb();
+		parent::tearDown();
+	}
+
+
 
 	public function setupDomaines() {
 		$this->fixture('Class_Catalogue', ['id' => 10,
diff --git a/tests/application/modules/opac/controllers/SitoControllerTest.php b/tests/application/modules/opac/controllers/SitoControllerTest.php
index 0a18cc72cc260791aef4bf42e4744b803ad225c4..655189bcff56fd9b57bd4c7cc2a3139216dce780 100644
--- a/tests/application/modules/opac/controllers/SitoControllerTest.php
+++ b/tests/application/modules/opac/controllers/SitoControllerTest.php
@@ -19,9 +19,10 @@
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
 abstract class SitoControllerTestCase extends AbstractControllerTestCase {
+	protected $hackers;
 	public function setUp() {
 		parent::setUp();
-		$this->fixture('Class_SitothequeCategorie',
+		$this->hackers=$this->fixture('Class_SitothequeCategorie',
 									 ['id' => 3,
 										'libelle' => 'Hackers']);
 
@@ -57,6 +58,97 @@ abstract class SitoControllerTestCase extends AbstractControllerTestCase {
 }
 
 
+class SitoControllerHierarchicViewTest extends SitoControllerTestCase {
+	public function setUp() {
+		parent::setUp();
+		$collectif=$this->fixture('Class_SitothequeCategorie',
+									 ['id' => 12,
+										'parent_categorie' => $this->hackers,
+										'libelle' => 'Collectifs' ]);
+
+
+
+		$this->fixture('Class_SitothequeCategorie',
+									 ['id' => 19,
+										'parent_categorie' => 		$this->fixture('Class_SitothequeCategorie',
+																														 ['id' => 17,
+																															'parent_categorie' => $collectif,
+																															'libelle' => 'Associations' ]),
+
+										'libelle' => 'Libre' ]);
+
+
+		$this->fixture('Class_Sitotheque',
+									 ['id' => 280,
+										'id_cat' => 19,
+										'titre' => 'La quadrature du net',
+										'description' => 'Internet et libertés',
+										'url' => 'http://laquadrature.net']);
+
+
+		$this->fixture('Class_Sitotheque',
+									 ['id' => 282,
+										'id_cat' => 17,
+										'titre' => 'April',
+										'description' => 'Promouvoir le logiciel libre',
+										'url' => 'http://april.org']);
+
+
+		$this->fixture('Class_Sitotheque',
+									 ['id' => 281,
+										'id_cat' => 12,
+										'titre' => 'Framasoft',
+										'description' => 'Degooglisons internet',
+										'url' => 'http://framasoft.org']);
+
+		Class_Profil::getCurrentProfil()
+			->setCfgAccueil([
+												'modules' => [
+													'1' => 	[
+														'division' => '2',
+														'type_module' => 'SITO',
+														'preferences' => [
+																							'id_categorie' => '12',
+																							"type_aff" => "3",
+																							'id_items' => '280-281']
+														]
+													],
+												'options' => 	[]]);
+
+		$this->dispatch('/sito/viewcategory/id_cat/12/start_cat/12', true);
+	}
+
+
+  /** @test */
+	public function LinuxFrShouldNotBeShown() {
+		$this->assertNotXPath('//div[@class="sitotheque"]//a[@href="http://linuxfr.org"]',
+											 $this->_response->getBody());
+	}
+
+
+	/** @test */
+	public function FramasoftShouldBeShown() {
+		$this->assertXPath('//div[@class="sitotheque"]//a[@href="http://framasoft.org"]',
+											 $this->_response->getBody());
+	}
+
+
+	/** @test */
+	public function catAssocShouldContains2sites() {
+		$this->assertXPathContentContains('//div//a[@href="/sito/viewcategory/start_cat/12/id_cat/17"]','Associations (2)',
+																			$this->_response->getBody());
+	}
+
+
+	/** @test */
+	public function breadCrumbShouldStartWithCollectifs() {
+		$this->assertXPath('//div/a[1][@href="/sito/viewcategory/start_cat/12/id_cat/12"]',
+											 $this->_response->getBody());
+	}
+}
+
+
+
 
 class SitoControllerViewRecentTest extends SitoControllerTestCase {
 	public function setUp() {
@@ -79,7 +171,7 @@ class SitoControllerViewSelectionTest extends SitoControllerTestCase {
 	public function setUp() {
 		parent::setUp();
 
-		$this->dispatch('/sito/viewselection/id_module/1', true);
+		$this->dispatch('/sito/viewselection/id_module/1', true);;
 
 	}
 
diff --git a/tests/library/ZendAfi/View/Helper/Accueil/SitoTest.php b/tests/library/ZendAfi/View/Helper/Accueil/SitoTest.php
index 8026bc4b4cb3ae112966e7c362c9b4b87ee0b204..df66155a0a2f2dac4696e4c13a4e27626ea8516c 100644
--- a/tests/library/ZendAfi/View/Helper/Accueil/SitoTest.php
+++ b/tests/library/ZendAfi/View/Helper/Accueil/SitoTest.php
@@ -21,41 +21,76 @@
 require_once 'library/ZendAfi/View/Helper/ViewHelperTestCase.php';
 
 abstract class SitoViewHelperTestCase extends ViewHelperTestCase {
+	protected $_storm_default_to_volatile = true;
+
 	protected $html;
-	protected $_preferences = array();
+	protected $_preferences = [];
+	protected $_belgique;
+	protected $_france;
+	protected $_creuse;
+
 
 	public function setUp() {
 		parent::setUp();
-		Class_Sitotheque::beVolatile();
-		Class_SitothequeCategorie::beVolatile();
-		Class_AdminVar::beVolatile();
-		Class_AdminVar::newInstanceWithId('BLUGA_API_KEY', ['valeur' => ''])->save();
-
-		$belgique = Class_SitothequeCategorie::newInstanceWithId(5, ['libelle'=>'Belgique']);
-		$belgique->save();
-		$france = Class_SitothequeCategorie::newInstanceWithId(9,['libelle'=>'France']);
-
-		$france->save();
-		$site_fosdem = Class_Sitotheque::newInstanceWithId(12,
-																											 ['titre'=>'FOSDEM',
-																												'description' => 'plein de bières belges',
-																												'url' => 'http://www.fosdem.org',
-																												'categorie' => $belgique]);
-
-		$site_fosdem->save();
 
-		$site_rmll = Class_Sitotheque::newInstanceWithId(15,
-																										 ['titre'=>'RMLL',
-																											'description' => 'du vin du vin!',
-																											'url' => 'http://www.rmll.info',
-																											'categorie' => $france]);
+		Class_AdminVar::newInstanceWithId('BLUGA_API_KEY', ['valeur' => ''])->save();
 
+		$this->_belgique = $this->fixture('Class_SitothequeCategorie',
+																			['id' => 5, 'libelle'=>'Belgique']);
+
+		$this->_france = $this->fixture('Class_SitothequeCategorie',
+																		['id' => 9, 'libelle'=>'France']);
+
+		$this->_creuse = $this->fixture('Class_SitothequeCategorie',
+																		['id' => 23,
+																		 'parent_categorie' => $this->_france,
+																		 'libelle' => 'La Creuse']);
+
+		$this->_lozere = $this->fixture('Class_SitothequeCategorie',
+																		['id' => 32,
+																		 'parent_categorie' => $this->_france,
+																		 'libelle' => 'La Lozere']);
+
+		$this->fixture('Class_SitothequeCategorie',
+									 ['id' => 24,
+										'parent_categorie' => $this->_creuse,
+										'libelle' => 'Gueret']);
+
+		$this->fixture('Class_SitothequeCategorie',
+									 ['id' => 33,
+										'parent_categorie' => $this->_lozere,
+										'libelle' => 'Mende']);
+
+		Class_Sitotheque::setTimeSource(new TimeSourceForTest('2014-05-02 14:14:14'));
+
+		$site_fosdem = $this->fixture('Class_Sitotheque',
+																	['id' => 12,
+																	 'titre'=>'FOSDEM',
+																	 'description' => 'plein de bières belges',
+																	 'url' => 'http://www.fosdem.org',
+																	 'categorie' => $this->_belgique]);
+
+		for ($id = 16; $id < 25; $id++) {
+			$this->fixture('Class_Sitotheque',
+										 ['id' => $id,
+											'titre'=> 'Sito numero '.$id,
+											'description' => 'lien dupliques',
+											'url' => 'http://www.unlien.info',
+											'categorie' => $this->_france]);
+		}
 
-		$site_rmll->save();
+		Class_Sitotheque::setTimeSource(new TimeSourceForTest('2014-05-20 16:14:14'));
+		$this->fixture('Class_Sitotheque',
+									 ['id' => 15,
+										'titre'=>'RMLL',
+										'description' => 'du vin du vin!',
+										'url' => 'http://www.rmll.info',
+										'categorie' => $this->_france]);
 
 		$this->_helper = new ZendAfi_View_Helper_Accueil_Sito(2, ['division' => '1',
 																															'type_module' => 'SITO',
 																															'preferences' => $this->_preferences]);
+
 		$this->_helper->setView(new ZendAfi_Controller_Action_Helper_View());
 		$this->html = $this->_helper->getBoite();
 	}
@@ -83,7 +118,7 @@ class SitoViewHelperSelectItemsBySelectionOrderTest extends SitoViewHelperTestCa
 		public function secondSiteShouldBeRMLL() {
 			$this->assertXPathContentContains($this->html,
 																				'//div/div[2]//h2//a',
-																				'FOSDEM');
+																				'RMLL');
 		}
 }
 
@@ -97,12 +132,12 @@ class SitoViewHelperSelectItemsByRandomOrderTest extends SitoViewHelperTestCase
 														 'id_categorie' => '',
 														 'nb_aff' => 2,
 														 'display_order' => 'Random'];
+
 	/** @test */
 	public function itemsShouldBeShuffled() {
 		$htmls = [];
-		for($i=0; $i<10; $i++) {
-			$htmls []= $this->_helper->getBoite();
-		}
+		for($i=0; $i < 10; $i++)
+			$htmls[] = $this->_helper->getBoite();
 
 		$this->assertEquals(2, count(array_unique($htmls)));
 	}
@@ -147,9 +182,10 @@ class SitoViewHelperSelectItemsAndCatsTest extends SitoViewHelperTestCase {
 
 
 class SitoViewHelperLastTest extends SitoViewHelperTestCase {
-	protected $_preferences = array('titre' => 'Derniers sites',
-																	'type_aff' => 2,
-																	'nb_aff' => 2);
+	protected $_preferences = ['titre' => 'Derniers sites',
+														 'type_aff' => 2,
+														 'nb_aff' => 100,
+														 'display_order' => 'Random'];
 
 	/** @test */
 	public function titleShouldBeDerniersSites() {
@@ -163,29 +199,29 @@ class SitoViewHelperLastTest extends SitoViewHelperTestCase {
 	public function h2ShouldContainsRmllDotInfo() {
 		$this->assertXPathContentContains($this->html,
 																			'//h2//a[contains(@href, "rmll.info")]',
-																			'RMLL');
+																			'RMLL',
+																			$this->html);
 	}
 
+
 	/** @test */
 	public function itemsShouldBeShuffled() {
 		$htmls = [];
-		for($i=0; $i<10; $i++) {
-			$htmls []= $this->_helper->getBoite();
-		}
+		for ($i=0; $i<10; $i++)
+			$htmls[] = $this->_helper->getBoite();
 
-		$this->assertEquals(2, count(array_unique($htmls)));
+		$this->assertTrue(1 < count(array_unique($htmls)));
 	}
-
 }
 
 
 
 
 class SitoViewHelperGroupByCategorieTest extends SitoViewHelperTestCase {
-	protected $_preferences = array('titre' => 'Ma sito',
-																	'type_aff' => 2,
-																	'nb_aff' => 2,
-																	'group_by_categorie' => true);
+	protected $_preferences = ['titre' => 'Ma sito',
+														 'type_aff' => 2,
+														 'nb_aff' => 100,
+														 'group_by_categorie' => true];
 
 
 	/** @test */
@@ -211,4 +247,56 @@ class SitoViewHelperGroupByCategorieTest extends SitoViewHelperTestCase {
 	}
 }
 
-?>
\ No newline at end of file
+
+
+class SitoViewHelperHierarchicalCategoryTest extends SitoViewHelperTestCase {
+	protected $_preferences = ['titre' => 'My website library',
+														 'type_aff' => '3',
+														 'id_items' => '',
+														 'id_categorie' => '9',
+														 'nb_aff' => 1,
+														 'display_order' => 'Selection'];
+
+
+	/** @test */
+	public function shouldDisplayTwoCategories() {
+		$this->assertXPathCount($this->html,
+														'//a[contains(@href, "/sito/viewcategory")]',
+														2,
+														$this->html);
+	}
+
+
+	/** @test */
+	public function categoryGueretShouldNotBeDisplayed() {
+		$this->assertNotXPathContentContains($this->html,
+																				 '//ul[@class="sitotheque"]/li//a',
+																				 'Gueret');
+	}
+
+
+	/** @test */
+	public function rmllShouldBeDisplayed() {
+		$this->assertXPathContentContains($this->html,
+																			'//ul[@class="sitotheque"]//li//a',
+																			'RMLL');
+
+	}
+
+
+	/** @test */
+	public function fosdemShouldNotBeDisplayed() {
+		$this->assertNotXPathContentContains($this->html,
+																			'//ul[@class="sitotheque"]//li//a',
+																			'FOSDEM');
+
+	}
+
+
+	/** @test */
+	public function urlShouldRedirectToViewPage() {
+		$this->assertXPathContentContains($this->html,
+																			'//h2//a[contains(@href, "/sito/viewcategory/id_cat/23/start_cat/23")]',
+																			'La Creuse');
+	}
+}
\ No newline at end of file