diff --git a/.htaccess b/.htaccess
index 08872ab2457f411ae808abcf7e342be25651e77d..3aed662e90d9973eebb8ed8b473a254b55ad87c7 100644
--- a/.htaccess
+++ b/.htaccess
@@ -1,6 +1,6 @@
 RewriteEngine on
 RewriteCond %{REQUEST_URI} !/(xhprof_html|ckeditor)
-RewriteRule !(userfiles|public|tmp|temp|skins)/.*\.(js|ico|txt|gif|jpg|jpeg|png|css|xml|swf|mov|pdf|doc|docx|woff|eot|svg|ttf|xls|wsdl|mp3|m4v|ogg|ogv|epub|html|xhtml|asmx)$ index.php [NC,NE]
+RewriteRule !(userfiles|public|tmp|temp|skins)/.*\.(js|ico|txt|gif|jpg|jpeg|png|css|xml|swf|mov|pdf|doc|docx|woff|eot|svg|ttf|xls|wsdl|mp3|m4v|ogg|ogv|epub|html|xhtml|asmx|zip|sql)$ index.php [NC,NE]
 
 
 AddType application/x-javascript .js
diff --git a/VERSIONS b/VERSIONS
index a2b0cae7eebd5e118e76555b626c3cf646aa4e7a..f10a14ee8548e09287feb45997ecdfa24f7c528c 100644
--- a/VERSIONS
+++ b/VERSIONS
@@ -1,3 +1,225 @@
+19/02/2015 - v7.1.11
+
+ - ticket #21501 : Correction du bug supprimant la définition des permissions lors de la suppression d'un groupe d'utilisateur.
+
+
+17/02/2015 - v7.1.10
+
+ - ticket #21388: Ajout de la vérification des patchs SQL/PHP.
+
+17/02/2015 - v7.1.6 - v7.1.7 - v7.1.8 - v7.1.9
+
+ - ticket #15224
+   - Ajout de la possibilité de définir plusieurs niveaux de validation dans le
+     workflow des articles.
+
+ - ticket #18025
+   - CMS: Gestion de permissions par catégories d'articles
+
+ - ticket #17514
+   - Indexation des paniers dans les domaines : Les notices des paniers
+     rattachées à un domaine sont visible dans celui-ci
+
+ - ticket #20351
+   - Interface d'administration : ajout d'un mode de visualisation des articles
+     en listes paginées.  Ceci permet d'améliorer grandement la vitesse
+     d'affichage et de navigation dans les articles pour les grands volumes
+     d'articles et/ou de catégories.
+
+ - ticket #17629
+   - Domaines
+     - Un domaine sans paramètre ne retourne pas de notices
+     - Un domaine affiche toujours les notices qui lui sont rattachés (articles
+       et sitothèques)
+
+   - Articles
+     - La sauvegarde d'un article étant à indexer va l'indexer en temps réel.
+     - La sauvegarde d'un article rattaché à des domaines et étant à indexer va
+       l'indexer et le rattacher aux domaines en temps réel.
+
+   - Sitothèques
+     - La sauvegarde d'une sitothèque rattachée à des domaines va la rattacher
+       à ses domaines en temps réel.
+
+   - Cosmogramme
+     - Les articles et sitothèques qui sont déjà présent dans la base
+       bénéficiront de l'indexation et du rattachement aux domaines.
+
+ - ticket #18864
+   - L'import typo3 des TT_news ne génère plus de domaine lié à la catégorie
+
+ - ticket #18866
+   - L'import Typo3 ajout une date de début d'évènement et une date de fin
+     d'évènement basé sur datetime lorsque l'article appartient à la catégorie
+     "Action_culturelle"
+
+ - ticket #18867
+   - Import Typo3 "Dossiers Documentaires" :
+     - les articles de présentation des dossiers doc ( tt_news=36 ) sont mis
+       dans la catégorie  "Nos dossiers documentaires" et la date d'évenement
+       est récupérée
+     - Réorganisation des docs : création d'une sous catégorie de la catégorie
+       "Nos dossiers documentaires"  pour chaque page ayaint un pid=309
+     - Sauvegarde des uid typo3 pour les articles pour permettre les mises à
+       jour de cette table
+     - Mise à jour des catégories des articles ayant un pid=309 
+
+ - ticket #18868
+   - L'import typo3 ajoute l'image dans la description d'un article même si
+     elle est vide
+
+
+13/02/2015 - v7.1.5
+
+ - ticket #21243: Correction des boîtes qui remplacent les préférences volontairement vidées par les préférences par défauts.
+
+
+
+12/02/2015 - v7.1.3 - v7.1.4
+
+ - ticket #20588: Correction des problèmes d'affichage dans la liste des suggestions.
+
+ - ticket #20851: Ajout des métadonnées sur les articles pour les partages facebook.
+
+ - ticket #18075: Les profils sont privés par héritage. L'accès à un profil privé via sa réécriture d'url fonctionne correctement.
+
+ - ticket #20093: La sauvegarde de la configuration d'une boîte deux colonnes ne réinitialise plus les paramètres des boîtes déjà sélectionnées.
+
+ - ticket #20607: Correction d'un bug dans cosmogramme qui empêchait l'intégration des nouveaux fichiers.
+
+ - ticket #16604: Correction d'un bug dans la table stats_notices qui ne proposait pas de valeurs pas défaut dans les champs nb_visu et nb_resa.
+
+ - ticket #18368: Sur la carte des zones, le lien vers le profil bibliothèque utilise le paramètre de profil rewrite_url si défini.
+
+ - ticket #18372: Boîte sitothèque: ajout option ordre d'affichage aléatoire ou sélection.
+
+ - ticket #18531: Correction du bug de duplication de profil. Lorsqu'on duplique une page ou un profil, le champ "url du profil/page" est vidé pour éviter les conflits.
+
+ - ticket #18745: Correction de l'indexation des collections.
+
+
+
+06/02/2015 - v7.1.1 - v7.1.2
+
+ - ticket #16941: Modification du bouton Facebook pour s'adapter au nouveau fonctionnement de partage (Open Graph)
+
+ - ticket #18848: Retour à la ligne pour les contenus trop longs dans le tableau des suggestions en admin.
+
+ - ticket #20350: Ajout du prix dans les détails d'une notice en se basant sur le champ unimarc 010$d
+
+ - ticket #18504: SIGB Pergame: correction d'un bug dans l'affichage des réservations en cours qui affichait une ressource numérique.
+
+ - ticket #20677: Correction de la table Catalogue qui ne permettait pas d'enregistrer des trop grandes listes de critères d'indexation.
+
+ - ticket #18395: Le flux RSS des articles utilise la date d'évènement si elle existe dans la donnée pubDate
+
+ - ticket #20441: Unimarc21 déplacement des zones 200i en 200e.
+
+
+
+02/02/2015 - v7.1.0
+
+- ticket #14943: - Possibilité de lier directement un album à une notice dans l'édition d'un album
+				 		 		 - L'album s'affiche directement dans l'onglet ressources numérique de la notice liée
+
+
+
+02/02/2015 - v7.0.0
+- versions stable == 6.60.5
+
+
+28/01/2015 - v6.60.5
+
+- ticket #18634: Supprime l'envoi de notifications par mail de changement dans
+  les articles lorsque l'option 'Workflow' n'est pas activée
+
+- ticket #19797: Correction de l'affichage des extraits musicaux lorsque le
+  sous-champ 464$3 est présent
+
+- ticket #18294: Correction du nombre de notices affiché dans les paniers
+
+- ticket #18068: Correction de l'affichage du bouton 'Taille par défaut' dans
+  la fenêtre d'accessibilité
+
+
+26/01/2015 - v6.60.4
+
+- ticket #20109: Correction de l'accès à CVS
+
+
+26/01/2015 - v6.60.3
+
+- ticket #18395: Correction du flux RSS de la boîte agenda. Il n'affiche plus
+  que les évènements en cours ou futurs
+
+- ticket #20123: Correction de la préparation des liens externes avec IE 11
+
+- ticket #20010: Ajout de l'espace manquant entre le titre et le sous-titre
+  d'une notice sur la page de détail d'une notice
+
+
+21/01/2015 - v6.60.2
+
+- ticket #17314: Correction de la récupération de la date de nouveauté quand
+  les exemplaires sont en 852
+
+- ticket #18130: Corrige la récupération des albums sous l'onglet "Discographie"
+
+
+20/01/2015 - v6.60.1
+
+- tickets #19492 #19493 #19815 #19838: Suppression du lien
+  "Voir tous les tomes de ..." sur l'affichage détaillé de la notice lorsque la
+  notice ne fait pas partie d'une série.
+
+
+19/01/2015 - v6.60.0
+
+- ticket: #18291:
+  - Profil: Les profils public (uniquement) peuvent être ajoutés au plan du site + Ajout d'une checkbox pour ajouter/enlever le profil du plan.
+
+  - Batch: Ajout d'un nouveau batch: "Régénère le sitemap XML".
+
+  - Sitemap: Le plan du site est visible via le chemin /sitemap.xml.
+
+
+19/01/2015 - v6.59.3
+
+- ticket #16601: Génération des facettes emplacement lors de l'import/mise à
+  jour d'une notice.
+
+- ticket #18512: Corrige l'ajout d'avis sur les articles
+
+- ticket #19216: Supprime la limite "bokeh" pour l'upload de fichier multiple.
+  La seul restriction se situe au niveau de la configuration PHP.
+
+
+
+13/01/2015 - v6.59.2
+
+- ticket #19208: Change la configuration du helper ckeditor pour permettre l'utilisation d'ids sur tous les elements.
+
+- ticket #18076: Redirige les utilisateurs anonymes sur la page de login de l'opac au lieu de l'admin
+
+- ticket #18411: Corrige le script de redirection vers la plateforme vodeclic
+
+- ticket #18614: Flux RSS du calendrier: la date pour chaque article est la date de début d'événement si renseignée, sinon date de création
+
+- ticket #19137: Flux RSS du calendrier: les articles dont la date de fin de publication est dépassée ne sont plus inclus dans le flux.
+
+- ticket #18619: Localisation des exemplaires: amélioration de l'algorithme de recherche de la localisation sur le plan
+
+
+
+
+06/01/2015 - v6.59.1
+
+- ticket #16499: web services Karvi: les documents en rayon sont réservables
+
+- ticket #18684: plugin Datepicker - Corrige le formatage des dates
+
+
+
 05/01/2015 - v6.59.0
 
 - ticket #13769 : Import de la disponibilité des exemplaires unimarc (Nanook) et affichage de la facette en résultat de recherche.
diff --git a/application/modules/admin/controllers/AccueilController.php b/application/modules/admin/controllers/AccueilController.php
index 4d4435354e6c95d6cf26dcf76cf479f39d636d67..de17d8ce4886e1f5502d4d8a8c9327ade917176c 100644
--- a/application/modules/admin/controllers/AccueilController.php
+++ b/application/modules/admin/controllers/AccueilController.php
@@ -360,7 +360,7 @@ class Admin_AccueilController extends Zend_Controller_Action {
 		}
 
 		$modules_accueil = Class_Systeme_ModulesAccueil::getModules();
-		$modules = array();
+		$modules = [];
 		foreach ($modules_accueil as $key => $module)
 			$modules[$key] = $module->getLibelle();
 
diff --git a/application/modules/admin/controllers/AlbumController.php b/application/modules/admin/controllers/AlbumController.php
index 3f8765804ff8eaad7c27bd229eb47166f8588a87..0feb05be36b53b070e308bc05eed3869f1b533cf 100644
--- a/application/modules/admin/controllers/AlbumController.php
+++ b/application/modules/admin/controllers/AlbumController.php
@@ -16,14 +16,14 @@
  *
  * 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 Admin_AlbumController extends ZendAfi_Controller_Action {
 	use Trait_Translator;
 
 	protected $_baseUrlOptions = ['module' => 'admin',
 		                            'controller' => 'album'];
-		
+
 	public function init() {
 		$this->view->titre = 'Collections';
 
@@ -64,7 +64,7 @@ class Admin_AlbumController extends ZendAfi_Controller_Action {
 			$response[] = ['label' => $renderStrategy->render($item)];
 
 		echo json_encode($response);
-		exit;	
+		exit;
 	}
 
 
@@ -73,7 +73,7 @@ class Admin_AlbumController extends ZendAfi_Controller_Action {
 
 		$form = $this->_formImportEAD();
 		$this->view->form_import_ead = $form;
-		
+
 		if (!$this->_request->isPost())
 			return;
 
@@ -83,13 +83,13 @@ class Admin_AlbumController extends ZendAfi_Controller_Action {
 			$this->_helper->notify(sprintf('%d albums importés', count($ead->getAlbums())));
 			$this->_redirect('admin/album');
 			return;
-		} 
-			
+		}
+
 		$this->_helper->notify('Le fichier reçu n\'est pas valide');
 		$this->_redirect('admin/album/importead');
 	}
 
-	
+
 	protected function _formImportEAD() {
 		return $this->view
 			->newForm(['id' => 'import_ead', 'class' => 'form'])
@@ -126,7 +126,7 @@ class Admin_AlbumController extends ZendAfi_Controller_Action {
 		$categorie = Class_AlbumCategorie::newInstance()
 			->setParentCategorie($parent_categorie);
 
-		$titre = sprintf(((!$parent_categorie->hasParentCategorie()) 
+		$titre = sprintf(((!$parent_categorie->hasParentCategorie())
 											? 'Ajouter une catégorie à la collection "%s"'
 											: 'Ajouter une sous-catégorie à la catégorie "%s"'),
 										 $parent_categorie->getLibelle());
@@ -177,7 +177,7 @@ class Admin_AlbumController extends ZendAfi_Controller_Action {
 			$this->_redirect('admin/album');
 			return;
 		}
-			
+
 		$this->_renderAlbumForm($album, 'Modifier l\'album "' . $album->getTitre() . '"');
 	}
 
@@ -196,7 +196,7 @@ class Admin_AlbumController extends ZendAfi_Controller_Action {
 		}
 
 		$form = $this->_thumbnailsForm($album);
-		if ($form && $this->_request->isPost() 
+		if ($form && $this->_request->isPost()
 				&& $form->isValid($this->_request->getPost())
 				&& ($album->updateAttributes($this->_request->getPost())
 						->save())) {
@@ -258,7 +258,7 @@ class Admin_AlbumController extends ZendAfi_Controller_Action {
 		$this->view->errors = $ressource->getErrors();
 	}
 
-		
+
 	public function editressourceAction() {
 		if (null === ($ressource = Class_AlbumRessource::getLoader()
 																							->find($this->_getParam('id')))) {
@@ -296,7 +296,7 @@ class Admin_AlbumController extends ZendAfi_Controller_Action {
 
 	protected function _setupRessourceFormAndSave($model) {
 		$form = $this->_ressourceForm($model);
-		
+
 		$this->view->form = $form;
 
 		if (!$this->_request->isPost()
@@ -309,18 +309,18 @@ class Admin_AlbumController extends ZendAfi_Controller_Action {
 			$form->addModelErrors($model);
 			return false;
 		}
-		
+
 		return $model->save()
 			&& $model->receiveFiles()
 			&& $model->getAlbum()->save()
 			&& $model->getAlbum()->index();
   }
-		
+
 
 	public function sortressourcesAction() {
 		$album = Class_Album::getLoader()->find((int)$this->_getParam('id'));
 		$album->sortRessourceByFileName()->save();
-		$this->_helper->notify('Médias réordonnés par nom de fichier'); 
+		$this->_helper->notify('Médias réordonnés par nom de fichier');
 		$this->_redirect('admin/album/edit_images/id/'.$album->getId());
 	}
 
@@ -424,7 +424,7 @@ class Admin_AlbumController extends ZendAfi_Controller_Action {
 																														 'required' => true,
 																														 'allowEmpty' => false]))
 					->addDisplayGroup(['libelle'], 'categorie',
-														['legend' => (($categorie->hasParentCategorie()) ? 
+														['legend' => (($categorie->hasParentCategorie()) ?
 																			'Catégorie'
 																			: 'Collection')])
 					->populate($categorie->toArray());
@@ -502,18 +502,56 @@ class Admin_AlbumController extends ZendAfi_Controller_Action {
 
 		$droits_precision = $values['droits_precision'];
 		unset($values['droits_precision']);
-		$values['droits'] = (2 == $values['droits']) ?
-				$droits_precision : 'Domaine public';
+		$values['droits'] = ($form->isPublicDomain()) ?
+			$droits_precision : $form->getPublicDomain();
+
+		$frbr_multi = $values['frbr_multi'];
+		unset($values['frbr_multi']);
 
 		$album->updateAttributes($values);
+
 		if ($album->save()
 			  && $album->receiveFile()
 			  && $album->receivePDF()
 				&& $album->index()) {
+
+			$frbr_links = [];
+			foreach(Class_FRBR_Link::findAllRecordLinksForAlbum($album) as $link)
+				$frbr_links[md5($link->getSource().$link->getTarget())] = $link;
+
+			$absoluteUrl = $this->view->absoluteUrl($album->getPermalink());
+
+			$received_links = [];
+			$urls = $frbr_multi['frbr_url'];
+			$types = $frbr_multi['frbr_type'];
+			$count = count($urls);
+			for($i=0; $i < $count; $i++) {
+				if (!$types[$i] && !$urls[$i])
+					continue;
+
+				list($id, $type) = explode(':', $types[$i]);
+				$md5 = md5('target' == $type ? ($urls[$i] . $absoluteUrl) : ($absoluteUrl . $urls[$i]));
+
+				$received_links[$md5] = ['frbr_url' => $urls[$i], 'frbr_type' => $id, 'type' => $type];
+			}
+
+			foreach(array_diff_key($frbr_links, $received_links) as $link)
+				$link->delete();
+
+			foreach(array_diff_key($received_links, $frbr_links) as $new) {
+				$record_type = 'target' == $new['type'] ? 'source' : 'target';
+				$frbr_link = Class_FRBR_Link::newInstance([$new['type'] => $absoluteUrl,
+																									 $record_type => $new['frbr_url'],
+																									 'type_id' => $new['frbr_type']]);
+
+				if (!$frbr_link->save())
+					throw new Zend_Exception(json_encode($frbr_link->getErrors()));
+			}
+
 			(new Storm_Cache())->clean();
 			$this->_helper->notify('Album sauvegardé');
 			$this->_redirect('admin/album/edit_album/id/' . $album->getId());
-		} 
+		}
 	}
 
 
@@ -535,7 +573,7 @@ class Admin_AlbumController extends ZendAfi_Controller_Action {
 	 */
 	protected function _renderAlbumForm($album, $titre) {
 		$this->_validateAndSaveAlbum($album);
-		
+
 		$this->view->titre	= $titre;
 		$this->view->errors	= $album->getErrors();
 		$this->view->album = $album;
@@ -548,7 +586,7 @@ class Admin_AlbumController extends ZendAfi_Controller_Action {
 
 
 	protected function _getTreeViewContainerActions() {
-		$actions = Class_AdminVar::isBibNumEnabled() 
+		$actions = Class_AdminVar::isBibNumEnabled()
 			? [['url' => $this->_getUrlForAction('add_categorie_to'),
 					'icon' => 'ico/add_cat.gif',
 					'label' => 'Ajouter une sous-catégorie'],
@@ -559,7 +597,7 @@ class Admin_AlbumController extends ZendAfi_Controller_Action {
 
 			: [];
 
-		return array_merge($actions, 
+		return array_merge($actions,
 											 [['url' => $this->_getUrlForAction('edit_categorie'),
 												 'icon' => 'ico/edit.gif',
 												 'label' => 'Modifier la catégorie'],
diff --git a/application/modules/admin/controllers/BibController.php b/application/modules/admin/controllers/BibController.php
index 2bb0d2bd9de0bbdeddcf07d4ea981de4aa0da28a..ba783870a126af855493149d8d5b813a0ef85edd 100644
--- a/application/modules/admin/controllers/BibController.php
+++ b/application/modules/admin/controllers/BibController.php
@@ -636,6 +636,42 @@ class Admin_BibController extends Zend_Controller_Action {
 		$this->getResponse()->setHeader('Content-Type', 'application/json; charset=utf-8');
 		$this->getResponse()->setBody('['.implode(',', $jsons).']');
 	}
+
+
+	public function permissionsAction() {
+		if (!Class_Users::getIdentity()->isRoleMoreThanModoPortail()
+				|| (!$model = ('0' === $this->_getParam('id'))
+						? Class_Bib::getPortail() : Class_Bib::find((int)$this->_getParam('id', 0)))) {
+			$this->_redirect($this->view->url(['module' => 'admin',
+																				 'controller' => 'bib'],
+																				null, true),
+											 ['prependBase' => false]);
+			return;
+		}
+
+		if ($this->_request->isPost()) {
+			$this->_helper->groupPermissions($model, $this->_getParam('perms', []));
+			$this->_helper->notify('Permissions sauvegardées');
+			$this->_redirect('admin/cms');
+			return;
+		}
+
+		$this->view->titre = $this->view->_('Permissions par défaut de la bibliothèque: %s',
+																				$model->getLibelle());
+
+		Class_ScriptLoader::getInstance()
+			->addAdminScript('controle_maj')
+			->addJQueryReady('$("form input").change(function(){setFlagMaj(true)})');
+
+		$this->view->permissions = $this->view
+			->groupsPermissions($model,
+													Class_Permission::getCmsPermissions(),
+													$this->view->url(['module' => 'admin',
+																						'controller' => 'bib',
+																						'action' => 'permissions',
+																						'id' => $model->getId()],
+																					 null, true));
+	}
 }
 
 ?>
\ No newline at end of file
diff --git a/application/modules/admin/controllers/CatalogueController.php b/application/modules/admin/controllers/CatalogueController.php
index 7935bf1a8116514b05d2dbffb4bbe1df74023e19..a02187daa499c15ebe94f2f26beb8cadae728f7e 100644
--- a/application/modules/admin/controllers/CatalogueController.php
+++ b/application/modules/admin/controllers/CatalogueController.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
  */
 //////////////////////////////////////////////////////////////////////////////////////////
 // OPAC3 - Controleur CATALOGUES DYNAMIQUES
@@ -45,52 +45,51 @@ class Admin_CatalogueController extends ZendAfi_Controller_Action {
 
 		$this->view->titre = $this->_('Définition des domaines');
 	}
-	
-	//----------------------------------------------------------------------------------
-	// Tester un catalogue
-	//----------------------------------------------------------------------------------
+
+
 	function testerAction()	{
-		$id_catalogue=(int)$this->_getParam("id_catalogue");
-		if(!$id_catalogue) $this->_redirect("admin/catalogue/index");
-		
-		// Lire les notices
-		$catalogue = Class_Catalogue::getLoader()->find($id_catalogue);
+		if(!$id_catalogue=(int)$this->_getParam("id_catalogue"))
+			$this->_redirect("admin/catalogue/index");
 
-		$ret=$catalogue->getTestCatalogue();
+		if(!$catalogue = Class_Catalogue::find($id_catalogue))
+			$this->_redirect("admin/catalogue/index");
+
+		$ret = ['requete' => '',
+						'temps_execution' => '',
+						'nb_notices' => 0,
+						'avec_vignettes' => 0,
+						'notices' => ''];
+
+		$ret = array_merge($ret, $catalogue->getTestCatalogue());
 		if (defined('DEVELOPMENT')) {
 			$page=0;
-
 			if ($catalogue->getIndexer()) {
-
 				while ($catalogue->updateNoticesWithFacette(1000,$page)) {
-
 					$page++;
 				}
 			}
 		}
 		// Variables de vue
-		$this->view->requete=$ret["requete"];
-		$this->view->temps_execution=$ret["temps_execution"]." secs.";
-		$this->view->nb_notices=$ret["nb_notices"];
-		$this->view->avec_vignettes=$ret["avec_vignettes"];
-		$this->view->notices=$ret["notices"];
-		$this->view->id_catalogue=$id_catalogue;
-		$this->view->titre = 'Test du domaine: '.$catalogue->getLibelle();
+		$this->view->requete = $ret["requete"];
+		$this->view->temps_execution = $ret["temps_execution"] . " secs.";
+		$this->view->nb_notices = $ret["nb_notices"];
+		$this->view->avec_vignettes = $ret["avec_vignettes"];
+		$this->view->notices = $ret["notices"];
+		$this->view->id_catalogue = $id_catalogue;
+		$this->view->titre = 'Test du domaine: ' . $catalogue->getLibelle();
 	}
-	
-	//----------------------------------------------------------------------------------
-	// Ajout d'un catalogue
-	//----------------------------------------------------------------------------------
+
+
 	function addAction() {
 		$catalogue = Class_Catalogue::getLoader()
 			->newInstance()
 			->setLibelle("** nouveau domaine **")
 			->setParentId($this->_getParam('id_catalogue', null))
 			->setUser(Class_Users::getIdentity());
-		
+
 
 		if ($this->isSaved($catalogue)) {
-			if($id_module = $this->_getParam('id_module')) 
+			if($id_module = $this->_getParam('id_module'))
 				$this->updateConfigKiosque($id_module,$catalogue);
 
 			$this->_helper->notify($this->_('Domaine %s ajouté', $catalogue->getLibelle()));
@@ -113,7 +112,7 @@ class Admin_CatalogueController extends ZendAfi_Controller_Action {
 		$profil->save();
 		return $this;
 	}
-	
+
 
 	//----------------------------------------------------------------------------------
 	// modification d'un catalogue
@@ -133,7 +132,7 @@ class Admin_CatalogueController extends ZendAfi_Controller_Action {
 		$this->view->titre = $this->_('Modification du domaine: %s', $catalogue->getLibelle());
 		$this->view->catalogue=$catalogue;
 
-		
+
 		$this->getHelper('ViewRenderer')->renderScript('catalogue/form.phtml');
 	}
 
@@ -149,7 +148,7 @@ class Admin_CatalogueController extends ZendAfi_Controller_Action {
 			->setLibelle("** nouveau domaine **")
 			->setParentId($this->_getParam('id_catalogue', null))
 			->setUser(Class_Users::getIdentity());
-	
+
 		if ($this->isSaved($new_catalogue)) {
 			$this->_helper->notify($this->_('Domaine %s sauvegardé', $catalogue->getLibelle()));
 			$this->_redirect("admin/catalogue/index");
@@ -164,7 +163,7 @@ class Admin_CatalogueController extends ZendAfi_Controller_Action {
 
 
 	public function isSaved($catalogue) {
-		if (!$this->_request->isPost()) 
+		if (!$this->_request->isPost())
 			return false;
 		return $catalogue
 			->updatePostAttributes($this->_request->getPost())
@@ -189,22 +188,26 @@ class Admin_CatalogueController extends ZendAfi_Controller_Action {
 	function deleteAction()	{
 		$id_catalogue=(int)$this->_getParam("id_catalogue");
 		$user = Class_Users::getIdentity();
-		if ($catalogue = Class_Catalogue::getLoader()->find($id_catalogue))
+		if ($catalogue = Class_Catalogue::find($id_catalogue)) {
+			Class_NoticeDomain::deleteByDomain($catalogue);
 			$catalogue->deleteWithUser($user);
-
+		}
 		$this->_redirect("admin/catalogue/index");
 	}
 
 
 	public function paniersAction() {
 		$id_catalogue=(int)$this->_getParam("id_catalogue");
-		$catalogue = Class_Catalogue::find($id_catalogue);
+		if(!$catalogue = Class_Catalogue::find($id_catalogue))
+			return $this->_redirect("admin/catalogue/index");
 
 		if ($this->_request->isPost()) {
 			$panier = Class_PanierNotice::find($this->_request->getPost('id_panier'));
 			$catalogue
 				->addPanierNotice($panier)
 				->save();
+			$panier->addCatalogue($catalogue)->save();
+			$panier->index();
 			$this->_helper->notify($this->_('Panier "%s" ajouté', $panier->getLibelle()));
 			$this->_redirect('admin/catalogue/paniers/id_catalogue/'.$id_catalogue);
 			return;
@@ -215,11 +218,13 @@ class Admin_CatalogueController extends ZendAfi_Controller_Action {
 			$catalogue
 				->removePanierNotice($panier)
 				->save();
+			$panier->removeCatalogue($catalogue)->save();
+
 			$this->_helper->notify($this->_('Panier "%s" retiré', $panier->getLibelle()));
 			$this->_redirect('admin/catalogue/paniers/id_catalogue/'.$id_catalogue);
 			return;
 		}
-				
+
 
 		$this->view->form_paniers = $this->formAjoutPanier($catalogue);
 		$this->view->catalogue = $catalogue;
@@ -230,12 +235,12 @@ class Admin_CatalogueController extends ZendAfi_Controller_Action {
 	public function formAjoutPanier($catalogue) {
 		$options = [];
 		$paniers = Class_PanierNotice::findAllBelongsToAdmin();
-		foreach($paniers as $panier) 
+		foreach($paniers as $panier)
 			$options[$panier->getId()] = $panier->getLibelleForAdmins();
 
 		return (new ZendAfi_Form())
-			->addElement('select', 
-									 'id_panier', 
+			->addElement('select',
+									 'id_panier',
 									 ['label' => $this->_('Ajouter ce panier'),
 										'multiOptions' => $options
 ]);
@@ -248,11 +253,11 @@ class Admin_CatalogueController extends ZendAfi_Controller_Action {
 		$data = [];
 		$data[] = Class_Users::getIdentity()->getPaniersJson();
 		$data[] = (new Class_Catalogue())->getDomainesJson();
-		$data[] = (new Class_PanierNotice())->getPaniersAdminsNotInCatalogueJson(); 		
+		$data[] = (new Class_PanierNotice())->getPaniersAdminsNotInCatalogueJson();
 		$JSON = json_encode($data);
 
 		$this->getResponse()->setHeader('Content-Type', 'application/json; charset=utf-8');
 		$this->getResponse()->setBody($JSON);
 	}
-	
+
 }
\ No newline at end of file
diff --git a/application/modules/admin/controllers/CmsCategoryController.php b/application/modules/admin/controllers/CmsCategoryController.php
new file mode 100644
index 0000000000000000000000000000000000000000..d6934e5c2576af578b47dc1c324b9d2828b692f3
--- /dev/null
+++ b/application/modules/admin/controllers/CmsCategoryController.php
@@ -0,0 +1,153 @@
+<?php
+/**
+ * Copyright (c) 2012-2014, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * AFI-OPAC 2.0 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).
+ *
+ * AFI-OPAC 2.0 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 AFI-OPAC 2.0; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class Admin_CmsCategoryController extends ZendAfi_Controller_Action {
+	use Trait_Translator;
+	protected $_bib;
+
+	public function getRessourceDefinitions() {
+		return
+			['model' => ['class' => 'Class_ArticleCategorie',
+									 'name' => 'category'],
+
+			 'messages' => ['successful_save' => $this->_('Categorie "%s" sauvegardée'),
+											'successful_add' => $this->_('La catégorie "%s" a été sauvegardée'),
+											'successful_delete' => $this->_('Categorie "%s" supprimée')],
+
+			 'actions' => ['add' => ['title' => $this->_("Ajouter une catégorie")],
+										 'edit' => ['title' => $this->_("Modifier une catégorie")]],
+
+			 'after_add' => function ($model) {
+					$this->_redirectToTreeView($model);
+				},
+
+			 'after_edit' => function ($model) {
+				 $this->_redirectToTreeView($model);
+			 },
+
+			 'after_delete' => function($model) {
+				 $this->_redirect($this->_deleteBackUrl($model));
+			 },
+
+			 'form_class_name' => 'ZendAfi_Form_Admin_CmsCategory'];
+	}
+
+
+	protected function _updateNewModel($model) {
+		if ($parent = Class_ArticleCategorie::find($this->_getParam('id'))) {
+			$model->setParentCategorie($parent)
+						->setBib($parent->getBib());
+			return;
+		}
+
+		$this->_handleBibFor($model);
+	}
+
+
+	protected function _handleBibFor($category) {
+		if ($bib = $this->getDefaultBib())
+			$category->setBib($bib);
+	}
+
+
+	protected function _redirectToTreeView($model) {
+		$this->_redirect($this->_backUrl($model));
+	}
+
+
+	protected function _backUrl($model) {
+		$is_list_mode = Class_AdminVar::isArticlesListMode();
+		if (($model->isNew() || $is_list_mode)
+				&& $parent = $model->getParentCategorie())
+			return $this->_withPageUrl(sprintf('admin/cms/index/id_cat/%d', $parent->getId()));
+
+		return $this->_withPageUrl($is_list_mode ?
+															 sprintf('admin/cms/index/id_bib/%d',
+																			 ($bib = $model->getBib()) ? $bib->getId() : 0) :
+															 sprintf('admin/cms/index/id_cat/%d', $model->getId()));
+	}
+
+
+	protected function _deleteBackUrl($model) {
+		$is_list_mode = Class_AdminVar::isArticlesListMode();
+		if ($parent = $model->getParentCategorie())
+			return $this->_withPageUrl(sprintf('admin/cms/index/id_cat/%d', $parent->getId()));
+
+		return $this->_withPageUrl($is_list_mode ?
+															 sprintf('admin/cms/index/id_bib/%d',
+																			 ($bib = $model->getBib()) ? $bib->getId() : 0) :
+															 'admin/cms/index');
+	}
+
+
+	protected function _withPageUrl($url) {
+		return ($page = $this->_getParam('page'))
+			? $url . '/page/' . $page : $url;
+	}
+
+
+	protected function _postEditAction($model) {
+		if (null === $model->getBib())
+			$this->_handleBibFor($model);
+
+		if (Class_Users::getIdentity()->isRoleMoreThanModoPortail())
+			$this->view->permissions = $this->view
+				->groupsPermissions($model,
+														Class_Permission::getCmsPermissions(),
+														$this->view->url(['module' => 'admin',
+																							'controller' => 'cms-category',
+																							'action' => 'permissions',
+																							'id' => $model->getId()],
+																						 null, true));
+	}
+
+
+	protected function getDefaultBib() {
+		$identity = Class_Users::getIdentity();
+
+		return ZendAfi_Acl_AdminControllerRoles::ADMIN_BIB >= $identity->getRoleLevel()  ?
+			$identity->getBib() : Class_Bib::find((int)$this->_getParam('id_bib'));
+	}
+
+
+	public function permissionsAction() {
+		$this->_helper->getHelper('ViewRenderer')->setNoRender();
+		if (!$category = Class_ArticleCategorie::find($this->_getParam('id', 0)))
+			$this->_redirect('admin/cms/index');
+
+		$this->_helper->groupPermissions($category, $this->_getParam('perms', []));
+		$this->_helper->notify('Permissions sauvegardées');
+		$this->_redirect(sprintf('admin/cms-category/edit/id/%d', $category->getId()));
+	}
+
+
+	/**
+	 * @param Storm_Model_Abstract $model
+	 * @return Zend_Form
+	 */
+	protected function _getForm($model) {
+		$form = parent::_getForm($model);
+		$form->setAttrib('data-backurl', Class_Url::absolute($this->_backUrl($model)));
+		return $form;
+	}
+}
+?>
\ No newline at end of file
diff --git a/application/modules/admin/controllers/CmsController.php b/application/modules/admin/controllers/CmsController.php
index 26358a93290a6e4c41ef00b32c3c5cbb20343d9e..b6d421a6867ce6651605155f402f74027f6896e7 100644
--- a/application/modules/admin/controllers/CmsController.php
+++ b/application/modules/admin/controllers/CmsController.php
@@ -25,137 +25,211 @@ class Admin_CmsController extends ZendAfi_Controller_Action {
 	private $_bib;
 
 	public function getRessourceDefinitions() {
-		return [
-			'model' => [
-				'class' => 'Class_Article',
-				'name' => 'article',
-				'order' => 'id'],
+		return ['model' => ['class' => 'Class_Article',
+												'name' => 'article',
+												'order' => 'id'],
 
-			'messages' => [
-				'successful_save' => $this->_('Article "%s" sauvegardé'),
-				'successful_add' => $this->_('L\'article "%s" a été sauvegardé'),
-				'successful_delete' => $this->_('Article "%s" supprimé')],
+						'messages' => [
+													 'successful_save' => $this->_('Article "%s" sauvegardé'),
+													 'successful_add' => $this->_('L\'article "%s" a été sauvegardé'),
+													 'successful_delete' => $this->_('Article "%s" supprimé')],
 
-			'actions' => ['add' => ['title' => $this->_("Ajouter un article")]]];
+						'actions' => ['add' => ['title' => $this->_("Ajouter un article")]]];
 	}
 
 
 	public function init() {
 		parent::init();
-		$identity = Class_Users::getIdentity();
+		$this->identity = Class_Users::getIdentity();
 
-		$this->_bib = (ZendAfi_Acl_AdminControllerRoles::ADMIN_BIB >= $identity->getRoleLevel()) ?
-			$identity->getBib() :
+		$this->_bib = $this->identity->isRoleLibraryLimited() ?
+			$this->identity->getBib() :
 			$this->_bib = Class_Bib::getPortail();
 	}
 
 
-	public function indexAction()	{
-		$identity = Class_Users::getIdentity();
+	protected function _postEditAction($article) {
+		$article->index();
+	}
 
-		if (0 != $this->_bib->getId()) {
-			$bibs = [$this->_bib];
-		} else {
-			$bibs = Class_Bib::findAllBy(['order' => 'libelle']);
-			array_unshift($bibs, $this->_bib);
-		}
 
-		$add_link_label = $this->view->tagImg(URL_ADMIN_IMG . 'ico/add_cat.gif')
-			. $this->view->_(' Ajouter une catégorie');
+	protected function _getBibs() {
+		if (0 != $this->_bib->getId())
+			return [$this->_bib];
 
-		$add_link_options = ['module' => 'admin',
-												 'controller' => 'cms',
-												 'action' => 'catadd'];
+		$bibs = Class_Bib::findAllBy(['order' => 'libelle']);
+		array_unshift($bibs, $this->_bib);
+		return $bibs;
+	}
 
-		$categories = array_map(
-			function($bib) use ($add_link_options, $add_link_label){
-				return  ['bib'=> $bib,
-								 'containers' => $bib->getArticleCategories(),
-								 'add_link' => $this->view->tagAnchor($this->view->url(
-																												array_merge($add_link_options,
-																																		['id_bib' => $bib->getId()])),
-																											$add_link_label)];
-			},
 
-			$bibs);
+	protected function _renderList() {
+		$this->view->titre = $this->view->_('Mise à jour des articles');
 
-		$this->view->categories = $categories;
+		$bibs = $this->_getBibs();
+		$ids = array_map(function($model) {return $model->getId();}, $bibs);
 
-		$this->view->categorieActions = $this->_getTreeViewContainerActions();
-		$this->view->articleActions = $this->_getTreeViewItemActions();
+		$url_params = ['module' => 'admin',
+									 'controller' => 'cms',
+									 'action' => 'index'];
 
-		$this->view->titre = $this->view->_('Mise à jour des articles');
+		$root_label = $this->identity->isRoleLibraryLimited() ?
+			$this->_bib->getLibelle() : $this->_('Racine');
+		$breadcrumb = [$this->view->tag('a', $root_label,
+																		['href' => $this->view->url($url_params,
+																																null, true)])];
 
-		if ($article=Class_Article::find($this->_getParam('id')))
-			$this->view->headScript()->appendScript('var treeViewSelectedCategory = '
-			. $article->getIdCat() . ';');
+		$this->view->bibActions = $this->_getBibActions();
+		$this->view->categorieActions = $this->_getTreeViewContainerActions();
+		$this->view->articleActions = $this->_getTreeViewItemActions();
 
-		$this->view->headScript()->appendFile(URL_ADMIN_JS . 'tree-view.js');
+		$cms_permissions = Class_Permission::getCmsPermissions();
+		$categories_filter = function($category) use ($cms_permissions){
+			return $this->identity->hasAnyPermissionUnder($category, $cms_permissions)
+			|| $this->identity->hasAnyPermissionOn($category, $cms_permissions);
+		};
+
+		if (($id_cat = $this->_getParam('id_cat'))
+				&& ($category = Class_ArticleCategorie::find($id_cat))
+				&& ($current_bib = $category->getBib() ?
+						$category->getBib() : Class_Bib::getPortail())
+				&& in_array($current_bib->getId(), $ids)) {
+			$this->view->categories = array_filter($category->getChildren(),
+																						 $categories_filter);
+
+			if (!$this->identity->isRoleLibraryLimited())
+				$breadcrumb[] = $this->view->tag('a', $current_bib->getLibelle(),
+																				 ['href' => $this->view->url(array_merge($url_params, ['id_bib' => $current_bib->getId()]),
+																																		 null, true)]);
+
+			$render_cats = function ($model) use (&$breadcrumb, $url_params, $category) {
+				$options = ['href' => $this->view->url(array_merge($url_params, ['id_cat' => $model->getId()]),
+																							 null, true)];
+				if ($model->getId() == $category->getId())
+					$options['style'] = 'font-weight:bolder';
+
+				$breadcrumb[] = $this->view->tag('a', $model->getLibelle(), $options);
+			};
+
+			$parents_and_self = $category->getParentsAndSelf();
+			array_walk($parents_and_self, $render_cats);
+			$this->view->breadcrumb = implode(' > ', $breadcrumb)
+				. ' ' . $this->view->modelActions($category, $this->view->categorieActions, true);
+
+			$items_by_page = 25;
+			$page = $this->_getParam('page', 0);
+			$this->view->art_paginator = new Zend_Paginator(new Zend_Paginator_Adapter_Null(Class_Article::countBy(['id_cat' => $category->getId()])));
+			$this->view->art_paginator->setItemCountPerPage($items_by_page);
+			$this->view->art_paginator->setCurrentPageNumber($page);
+			$this->view->art_paginator->setView($this->view);
+
+			$this->view->articles = Class_Article::findAllBy(['id_cat' => $category->getId(),
+																												'order' => 'titre',
+																												'limitPage' => [$page, $items_by_page]]);
 
-	}
+			return;
+		}
 
+		if (null !== ($id_bib = $this->_getParam('id_bib'))
+				&& in_array($id_bib, $ids)) {
+			$current_bib = Class_Bib::find($id_bib);
 
-	public function cataddAction() {
-		$category = Class_ArticleCategorie::newInstance()->setLibelle('');
-		$parent = Class_ArticleCategorie::find((int)$this->_getParam('id'));
+			$breadcrumb[] = $this->view->tag('a', $current_bib->getLibelle(),
+																			 ['href' => $this->view->url(array_merge($url_params, ['id_bib' => $current_bib->getId()]),
+																																	 null, true)]);
 
-		if (null !== $parent) {
-			$category->setParentCategorie($parent);
-			// j'essaye d'avoir la même bib que mon parent
-			$category->setBib($parent->getBib());
-		} else {
-			$category->setBib(
-				(0 === $this->_bib->getId())
-					? Class_Bib::getLoader()->find((int)$this->_getParam('id_bib'))
-					: $this->_bib
-			);
+			$this->view->categories = array_filter($current_bib->getArticleCategories(),
+																						 $categories_filter);
+			$this->view->breadcrumb = implode(' > ', $breadcrumb)
+				. ' ' . $this->view->modelActions($current_bib, $this->view->bibActions, true);
+			return;
 		}
 
-		if ($this->_isCategorySaved($category)) {
-			$this->_redirect(sprintf('admin/cms/index/id_cat/%d', $category->getId()));
+		if (1 < count($bibs)) {
+			$this->view->bibs = $bibs;
 			return;
 		}
 
-		$this->view->category = $category;
-		$this->view->id_categorie = (int)$this->_getParam('id');
-		$this->view->titre = "Ajouter une catégorie d'articles";
-	}
+		if (0 == count($bibs))
+			return;
 
+		$this->view->categories = array_filter($bibs[0]->getArticleCategories(),
+																					 $categories_filter);
+		$this->view->breadcrumb =  implode(' > ', $breadcrumb)
+			. ' ' . $this->view->modelActions($bibs[0], $this->view->bibActions, true) ;
+	}
 
-	public function cateditAction() {
-		$category = Class_ArticleCategorie::getLoader()->find((int)$this->_getParam('id'));
 
-		if ((null == $category) || ($this->_isCategorySaved($category))) {
-			$this->_redirect(sprintf('admin/cms/index/id_cat/%d', $category->getId()));
+	public function indexAction()	{
+		if (Class_AdminVar::isArticlesListMode()) {
+			$this->_renderList();
+			$this->getHelper('ViewRenderer')->setScriptAction('list');
 			return;
 		}
 
-		if (null === $category->getBib())
-			$category->setBib($this->_bib);
+		$bibs = $this->_getBibs();
+
+		$add_link_builder = function($bib) {
+			$links = [];
+			if ($this->identity->isRoleMoreThanModoPortail()
+					|| $this->identity->hasPermissionOn(Class_Permission::createArticleCategory(),
+																				$bib)) {
+				$label = $this->view->tag('img', null,
+																	['src' => URL_ADMIN_IMG . 'ico/add_cat.gif'])
+					. $this->view->_(' Ajouter une catégorie');
+
+				$links[] = $this->view
+					->tag('a', $label,
+								['href' => $this->view->url(['module' => 'admin',
+																						 'controller' => 'cms-category',
+																						 'action' => 'add',
+																						 'id_bib' => $bib->getId()],
+																						null, true)]);
+			}
 
-		$this->view->category = $category;
-		$this->view->combo_cat = $this->view->comboParentCategorie($category);
-		$this->view->id_categorie = (int)$this->_getParam('id');
-		$this->view->titre = "Modifier une catégorie d'articles";
-	}
+			if ($this->identity->isRoleMoreThanModoPortail()) {
+				$label = $this->view->tag('img', null,
+																	['src' => URL_ADMIN_IMG . 'picto/groupes_16.png'])
+				. $this->view->_(' Permissions par défaut');
+
+				$links[] = $this->view->tag('a', $label,
+																		['href' => $this->view->url(['module' => 'admin',
+																																 'controller' => 'bib',
+																																 'action' => 'permissions',
+																																 'id' => $bib->getId()],
+																																null, true)]);
+			}
 
+			return implode('&nbsp;&nbsp;', $links);
+		};
 
-	/**
-	 * @param Class_ArticleCategorie $category
-	 * @return bool
-	 */
-	protected function _isCategorySaved($category) {
-		if ($this->_request->isPost()) {
-			$post = $this->_request->getPost();
-			$filter = new Zend_Filter_StripTags();
-			$post['libelle'] = trim($filter->filter($this->_request->getPost('libelle')));
-
-			return $category
-				->updateAttributes($post)
-				->save();
-		}
+		$identity = Class_Users::getIdentity();
+		$datas_closure = function($bib) use ($add_link_builder) {
+			$data = ['bib'=> $bib, 'containers' => $bib->getArticleCategories()];
+			if ($links = $add_link_builder($bib))
+				$data['add_link'] = $links;
+			return $data;
+		};
+
+		$categories = array_map($datas_closure, $bibs);
+		$this->view->categories = $categories;
+
+		$this->view->categorieActions = $this->_getTreeViewContainerActions();
+		$this->view->articleActions = $this->_getTreeViewItemActions();
+
+		$this->view->containersFilter = function($category) {
+			$permissions = Class_Permission::getCmsPermissions();
+			return $this->identity->hasAnyPermissionUnder($category, $permissions)
+			|| $this->identity->hasAnyPermissionOn($category, $permissions);
+		};
+
+		$this->view->titre = $this->view->_('Mise à jour des articles');
+
+		if ($article = Class_Article::find($this->_getParam('id')))
+			$this->view->headScript()->appendScript('var treeViewSelectedCategory = '
+			. $article->getIdCat() . ';');
 
-		return false;
+		$this->view->headScript()->appendFile(URL_ADMIN_JS . 'tree-view.js');
 	}
 
 
@@ -186,26 +260,24 @@ class Admin_CmsController extends ZendAfi_Controller_Action {
 	}
 
 
-	public function catdelAction() {
-		$categorie = Class_ArticleCategorie::getLoader()->find((int)$this->_getParam('id'));
-		if (null !== $categorie) {
-			$categorie->delete();
-			$this->_redirect(sprintf('admin/cms/index/id_cat/%d', $categorie->getIdCatMere()));
+	public function newsduplicateAction() {
+		if (!$model = Class_Article::find($this->_getParam('id'))) {
+			$this->_redirect('admin/cms');
 			return;
 		}
 
-		$this->_redirect('admin/cms');
-	}
-
+		$this->view->titre = $this->_('Dupliquer l\'article: %s', $model->getTitre());
 
-	public function newsduplicateAction() {
-		if (!$model = Class_Article::find($this->_getParam('id'))) {
-			$this->_redirect('admin/cms');
+		if (!$this->_canModify($model->getCategorie())) {
+			$this->_helper->notify($this->view->_('Vous n\'avez pas la permission "%s"',
+																						$this->view->titre));
+			$this->_redirectToIndex();
 			return;
 		}
 
 		$this->_setParam('id_cat', $model->getCategorie()->getId());
 		parent::addAction();
+
 		$this->view->titre = $this->_('Dupliquer l\'article: %s', $model->getTitre());
 		$this->view->form
 			->setAction($this->view->url(['module' => 'admin',
@@ -213,7 +285,8 @@ class Admin_CmsController extends ZendAfi_Controller_Action {
 																		'action' => 'add',
 																		'id_cat' => $model->getCategorie()->getId()],
 																	 null, true));
-		$this->render('add');
+
+		$this->getHelper('ViewRenderer')->setScriptAction('add');
 	}
 
 
@@ -251,39 +324,9 @@ class Admin_CmsController extends ZendAfi_Controller_Action {
 	}
 
 
-	/**
-	 * @param Class_Article $article
-	 * @return bool
-	 */
-	protected function _isArticleSaved($article) {
-		if ($this->_request->isPost()) {
-			$post = $this->_request->getPost();
-
-			unset($post['id_items']);
-			foreach(array('debut', 'fin', 'events_debut', 'events_fin') as $date_field) {
-				if (array_key_exists($date_field, $post))
-					$post[$date_field] = $this->_toDate($post[$date_field]);
-			}
-
-			foreach(['description', 'contenu'] as $content_field) {
-				$post[$content_field] = Class_CmsUrlTransformer::forSaving($post['ckeditor_'.$content_field]);
-				unset($post['ckeditor_'.$content_field]);
-			}
-
-			return $article
-				->updateAttributes($post)
-				->save();
-		}
-
-		foreach(array('description', 'contenu') as $content_field)
-			$article->_set($content_field,
-										Class_CmsUrlTransformer::forEditing($article->_get($content_field)));
-
-		return false;
-	}
-
-
 	protected function _notifyArticleChanged($article) {
+		if (!Class_AdminVar::isWorkflowEnabled())
+			return;
 		$this->_sendMailWhenUpdatedStatusToValidationPending($article);
 		$this->_sendMailWhenUpdatedStatusToRefused($article);
 		$this->_sendMailWhenUpdatedStatusToValidated($article);
@@ -307,60 +350,69 @@ class Admin_CmsController extends ZendAfi_Controller_Action {
 
 
 	protected function _sendMailWhenUpdatedStatusToValidationPending($article) {
-		if ($article->old_status != Class_Article::STATUS_VALIDATION_PENDING &&
-				$article->getStatus() == Class_Article::STATUS_VALIDATION_PENDING)  {
+		if (($article->old_status != Class_Article::STATUS_VALIDATION_PENDING &&
+				 $article->getStatus() == Class_Article::STATUS_VALIDATION_PENDING)
+				|| ($article->getStatus() > 5
+						&& $article->old_status != $article->getStatus()))  {
 			$this->_sendMailToAdmin($article);
 		}
 	}
 
 
-    protected function prepareMailForAuteur($article) {
-        $mail = new ZendAfi_Mail('utf8');
+	protected function prepareMailForAuteur($article) {
+		$mail = new ZendAfi_Mail('utf8');
 		if(!$article->getAuteur()) {
 			$this->_helper->notify('Mail non envoyé: article sans auteur');
 			return;
 		}
+
 		if(!$mail_address = $article->getAuteur()->getMail()) {
 			$this->_helper->notify('Mail non envoyé: '.$article->getNomCompletAuteur().' sans mail.');
 			return;
 		}
 
-        $mail->setFrom('no-reply@afi-sa.fr')
-             ->addTo($mail_address);
-        return $mail;
-    }
+		$mail->setFrom('no-reply@afi-sa.fr')
+				 ->addTo($mail_address);
+		return $mail;
+	}
 
 
-    protected function prepareBodyMail($article, $message) {
-			$message = str_replace('TITRE_ARTICLE', $article->getTitre(), $message);
-			$message = str_replace('URL_ARTICLE', $this->view->absoluteUrl($article->getUrl(), null, true), $message);
-			return $message;
-    }
+	protected function prepareBodyMail($article, $message) {
+		$message = str_replace('TITRE_ARTICLE', $article->getTitre(), $message);
+		$message = str_replace('URL_ARTICLE',
+													 $this->view->absoluteUrl($article->getUrl(), null, true),
+													 $message);
+		return $message;
+	}
 
 
 	protected function _sendRefusedMailToAuteur($article) {
 		if(!$mail = $this->prepareMailForAuteur($article))
-            return;
-        $body = $this->prepareBodyMail($article, $article->getRefusMessage());
-        $this->sendPreparedMail($mail, '[Bokeh] Refus de l\'article '.$article->getTitre(), $body);
+			return;
+		$body = $this->prepareBodyMail($article, $article->getRefusMessage());
+		$this->sendPreparedMail($mail,
+														'[Bokeh] Refus de l\'article '.$article->getTitre(),
+														$body);
 	}
 
 
-    protected function sendPreparedMail($mail, $subject, $body) {
-        $mail->setSubject(quoted_printable_decode($subject))
-             ->setBodyText($body,'utf-8',Zend_Mime::ENCODING_8BIT);
+	protected function sendPreparedMail($mail, $subject, $body) {
+		$mail->setSubject(quoted_printable_decode($subject))
+				 ->setBodyText($body,'utf-8',Zend_Mime::ENCODING_8BIT);
 
-		if($this->_sendMail($mail))
+		if ($this->_sendMail($mail))
 			$this->_helper->notify('Mail envoyé à: '.$mail->getRecipients()[0]);
-
-    }
+	}
 
 
 	protected function _sendValidatedMailToAuteur($article) {
-        if(!$mail = $this->prepareMailForAuteur($article))
-            return;
-				$body = $this->prepareBodyMail($article, $article->getValideMessage());
-				$this->sendPreparedMail($mail, '[Bokeh] Validation de l\'article '.$article->getTitre(), $body);
+		if(!$mail = $this->prepareMailForAuteur($article))
+			return;
+
+		$body = $this->prepareBodyMail($article, $article->getValideMessage());
+		$this->sendPreparedMail($mail,
+														'[Bokeh] Validation de l\'article '.$article->getTitre(),
+														$body);
 	}
 
 
@@ -368,6 +420,7 @@ class Admin_CmsController extends ZendAfi_Controller_Action {
 		$mail = new ZendAfi_Mail('utf8');
 		$all_admins=Class_Users::findAllBy(['role_level' => ZendAfi_Acl_AdminControllerRoles::ADMIN_PORTAIL]);
 		$mails=[];
+
 		foreach ($all_admins as $admin) {
 			if (!$mail_admin=$admin->getMail())
 				continue;
@@ -376,6 +429,7 @@ class Admin_CmsController extends ZendAfi_Controller_Action {
 
 		if(!$mail_address = implode(',',$mails))
 			return;
+
 		$body = Class_AdminVar::getWorkflowTextMailArticlePending();
 		$body = str_replace('TITRE_ARTICLE',
 												$article->getTitre(),
@@ -399,7 +453,8 @@ class Admin_CmsController extends ZendAfi_Controller_Action {
 		try {
 			$mail->send();
 			return true;
-		}catch (Exception $e) {
+
+		} catch (Exception $e) {
 			$this->_helper->notify('Mail non envoyé: vérifier la configuration du serveur de mail.');
 			return false;
 		}
@@ -412,7 +467,17 @@ class Admin_CmsController extends ZendAfi_Controller_Action {
 			return;
 		}
 
-		$this->view->titre = $this->view->_('Supprimer l\'article : %s', $article->getTitre());
+		$this->view->titre = $this->view->_('Supprimer l\'article : %s',
+																				$article->getTitre());
+
+
+		if (!$this->_canModify($article->getCategorie())) {
+			$this->_helper->notify($this->view->_('Vous n\'avez pas la permission "%s"',
+																						$this->view->titre));
+			$this->_redirectToIndex();
+			return;
+		}
+
 		$this->view->model = $article;
 	}
 
@@ -423,8 +488,16 @@ class Admin_CmsController extends ZendAfi_Controller_Action {
 			return;
 		}
 
+		if (!$this->_canModify($article->getCategorie())) {
+			$this->_helper->notify($this->view->_('Vous n\'avez pas la permission "%s"',
+																						$this->view->_('Supprimer l\'article : %s',
+																													 $article->getTitre())));
+			$this->_redirectToIndex();
+			return;
+		}
+
 		$article->delete();
-		$this->_redirect(sprintf('admin/cms/index/id_cat/%d', $article->getIdCat()));
+		$this->_redirect($this->_backDeleteUrl($article));
 	}
 
 
@@ -435,24 +508,33 @@ class Admin_CmsController extends ZendAfi_Controller_Action {
 
 
 	public function makevisibleAction() {
-		if ($article = Class_Article::getLoader()->find((int)$this->_getParam('id'))) {
-			$article->beVisible();
-			$this->_redirect(sprintf('admin/cms/index/id_cat/%d', $article->getIdCat()));
-			return;
-		}
-
-		$this->_redirect('admin/cms');
+		$this->_toggleVisibility('visible');
 	}
 
 
 	public function makeinvisibleAction()	{
-		if ($article = Class_Article::getLoader()->find((int)$this->_getParam('id'))) {
-			$article->beInvisible();
-			$this->_redirect(sprintf('admin/cms/index/id_cat/%d', $article->getIdCat()));
+		$this->_toggleVisibility('invisible');
+	}
+
+
+	protected function _toggleVisibility($visibility) {
+		if (!$article = Class_Article::getLoader()->find((int)$this->_getParam('id'))) {
+			$this->_redirect('admin/cms');
+			return;
+		}
+
+		if (!$this->_canModify($article->getCategorie())) {
+			$this->_helper->notify($this->view->_('Vous n\'avez pas la permission "%s"',
+																						$this->view->_('Rendre %s l\'article : %s',
+																													 $visibility,
+																													 $article->getTitre())));
+			$this->_redirectToIndex();
 			return;
 		}
 
-		$this->_redirect('admin/cms');
+		$method = 'be' . ucfirst($visibility);
+		$article->$method();
+		$this->_redirect($this->_backUrl($article));
 	}
 
 
@@ -460,60 +542,143 @@ class Admin_CmsController extends ZendAfi_Controller_Action {
 	 * @return array
 	 */
 	private function _getTreeViewContainerActions() {
-		return [
-			['url' => $this->_getUrlForActionAndIdName('catedit'),
-			 'icon'			=> 'ico/edit.gif',
-			 'label'			=> 'Modifier'],
-
-			['url' => $this->_getUrlForActionAndIdName('catdel'),
-			 'icon'			=> 'ico/del.gif',
-			 'label'			=> 'Supprimer',
-			 'condition' => 'hasNoChild',
-			 'anchorOptions' => [
-				 'onclick' => "return confirm('Etes-vous sûr de vouloir supprimer cette catégorie ?')"]],
-
-			['url' => $this->_getUrlForActionAndIdName('add', 'id_cat'),
-			 'icon'			=> 'ico/add_news.gif',
-			 'label'			=> 'Ajouter un article'],
-
-			['url' => $this->_getUrlForActionAndIdName('catadd'),
-			 'icon'			=> 'ico/add_cat.gif',
-			 'label'			=> 'Ajouter une sous-catégorie'],
-			];
+		$parent_permission = function($model) {
+			return $this->identity
+			->hasParentPermissionOn(Class_Permission::createArticleCategory(),
+															$model);
+		};
+
+		$actions[] = ['url' => $this->_getUrlForCategoryActionAndIdName('edit'),
+									'icon'	=> 'ico/edit.gif',
+									'label' => 'Modifier',
+									'condition' => $parent_permission];
+
+		$actions[] = [
+									'url' => $this->_getUrlForCategoryActionAndIdName('delete'),
+									'icon' => 'ico/del.gif',
+									'label' => 'Supprimer',
+									'condition' => function($model) use ($parent_permission) {
+										return $parent_permission($model) && $model->hasNoChild();
+									},
+									'anchorOptions' => [
+																			'onclick' => "return confirm('Etes-vous sûr de vouloir supprimer cette catégorie ?')"]];
+
+
+		$actions[] = [
+									'url' => $this->_getUrlForActionAndIdName('add', 'id_cat'),
+									'icon' => 'ico/add_news.gif',
+									'label' => 'Ajouter un article',
+									'condition' => function($model) {
+										return $this->identity
+										->hasAnyPermissionOn($model,
+																				 [Class_Permission::createArticle(),
+																					Class_Permission::createArticleCategory()]);
+									}];
+
+		$actions[] = [
+									'url' => $this->_getUrlForCategoryActionAndIdName('add'),
+									'icon'	=> 'ico/add_cat.gif',
+									'label' => 'Ajouter une sous-catégorie',
+									'condition' => function($model) {
+										return $this->identity
+										->hasPermissionOn(Class_Permission::createArticleCategory(),
+																			$model);
+									}];
+		return $actions;
 	}
 
 
 	private function _getTreeViewItemActions() {
-		return [
-			['url' => $this->_getUrlForActionAndIdName('makeinvisible'),
-			 'icon'			=> 'ico/show.gif',
-			 'label'			=> 'Rendre cet article invisible',
-			 'condition' => 'isVisible'],
-
-			['url' => $this->_getUrlForActionAndIdName('makevisible'),
-			 'icon'			=> 'ico/hide.gif',
-			 'label'			=> 'Rendre cet article visible',
-			 'condition' => 'isNotVisible'],
-
-			['url' => $this->_getUrlForActionAndIdName('edit'),
-			 'icon'			=> 'ico/edit.gif',
-			 'label'			=> 'Modifier'],
+		$permission_closure = function($model) {
+			return $this->identity
+			->hasAnyPermissionOn($model->getCategorie(),
+													 [Class_Permission::createArticle(),
+														Class_Permission::createArticleCategory()]);
+		};
+
+		$actions[] = [
+									'url' => $this->_getUrlForActionAndIdName('makeinvisible'),
+									'icon' => 'ico/show.gif',
+									'label' => 'Rendre cet article invisible',
+									'condition' => function($model) use ($permission_closure) {
+										return $permission_closure($model) && $model->isVisible();
+									}];
+
+		$actions[] = [
+									'url' => $this->_getUrlForActionAndIdName('makevisible'),
+									'icon'	=> 'ico/hide.gif',
+									'label' => 'Rendre cet article visible',
+									'condition' => function($model) use ($permission_closure) {
+										return $permission_closure($model) && $model->isNotVisible();
+									}];
+
+		$actions[] = ['url' => $this->_getUrlForActionAndIdName('edit'),
+									'icon'	=> 'ico/edit.gif',
+									'label' => 'Modifier',
+									'condition' => $permission_closure];
+
+		$actions[] = ['url' => $this->_getUrlForActionAndIdName('newsduplicate'),
+									'icon'	=> 'ico/copier.gif',
+									'label' => 'Dupliquer',
+									'condition' => $permission_closure];
+
+		$actions[] = ['url' => $this->_getUrlForActionAndIdName('delete'),
+									'icon'	=> 'ico/del.gif',
+									'label' => 'Supprimer',
+									'condition' => $permission_closure];
+
+		return $actions;
+	}
+
+
+	private function _getBibActions() {
+		$actions = [];
+		$actions[] = [
+									'url' => $this->_getUrlForCategoryActionAndIdName('add', 'id_bib'),
+									'icon' => 'ico/add_cat.gif',
+									'label' => $this->_('Ajouter une catégorie'),
+									'condition' => function($model) {
+										return $this->identity->isRoleMoreThanModoPortail()
+											|| $this->identity->hasPermissionOn(Class_Permission::createArticleCategory(),
+																													$model);
+									}];
+
+		$permissions_link = $this->view->url(['module' => 'admin',
+																					'controller' => 'bib',
+																					'action' => 'permissions'],
+																				 null, true)
+			.  ($page = $this->_getParam('page') ? '/page/' . $page : '')
+			. '/id/%s';
+
+		$actions[] = [
+									'url' => $permissions_link,
+									'icon' => 'picto/groupes_16.png',
+									'label' => $this->_('Permissions par défaut'),
+									'condition' => function($model) {
+										return $this->identity->isRoleMoreThanModoPortail();
+									}];
+
+		return $actions;
+	}
 
-			['url' => $this->_getUrlForActionAndIdName('newsduplicate'),
-			 'icon'			=> 'ico/copier.gif',
-			 'label'			=> 'Dupliquer'],
 
-			['url' => $this->_getUrlForActionAndIdName('delete'),
-			 'icon'			=> 'ico/del.gif',
-			 'label'			=> 'Supprimer']];
+	protected function _getUrlForActionAndIdName($action, $idName = 'id') {
+		return $this->view->url(['module' => 'admin',
+														 'controller'=> 'cms',
+														 'action'		=> $action],
+														null, true)
+			. (($page = $this->_getParam('page')) ? '/page/' . $page : '')
+			. '/' . $idName . '/%s';
 	}
 
 
-	protected function _getUrlForActionAndIdName($action, $idName = 'id') {
-		return $this->view->url(array(
-				'module' => 'admin',
-				'controller'=> 'cms',
-				'action'		=> $action), null, true) . '/' . $idName . '/%s';
+	protected function _getUrlForCategoryActionAndIdName($action, $idName = 'id') {
+		return $this->view->url(['module' => 'admin',
+														 'controller'=> 'cms-category',
+														 'action'		=> $action],
+														null, true)
+			. (($page = $this->_getParam('page')) ? '/page/' . $page : '')
+			. '/' . $idName . '/%s';
 	}
 
 
@@ -558,7 +723,26 @@ class Admin_CmsController extends ZendAfi_Controller_Action {
 			->setFormClassName($model->isTraduction()
 												 ? 'ZendAfi_Form_Admin_NewsTranslation'
 												 : 'ZendAfi_Form_Admin_News');
-		return parent::_getForm($model);
+		$form = parent::_getForm($model);
+		$form->setAttrib('data-backurl', Class_Url::absolute($this->_backUrl($model)));
+		return $form;
+	}
+
+
+	protected function _backUrl($model) {
+		if (!Class_AdminVar::isArticlesListMode())
+			return 'admin/cms/index' . (!$model->isNew() ? '/id/' . $model->getId() : '');
+
+		return sprintf('admin/cms/index/id_cat/%d%s',
+									 ($cat = $model->getCategorie()) ? $cat->getId() : 0,
+									 ($page = $this->_getParam('page')) ? '/page/'.$page : '');
+	}
+
+
+	protected function _backDeleteUrl($model) {
+		return sprintf('admin/cms/index/id_cat/%d%s',
+									 ($cat = $model->getCategorie()) ? $cat->getId() : 0,
+									 ($page = $this->_getParam('page')) ? '/page/'.$page : '');
 	}
 
 
@@ -569,6 +753,31 @@ class Admin_CmsController extends ZendAfi_Controller_Action {
 
 		return $this->_($template, $model->getLibelle());
 	}
+
+
+	protected function _canAdd() {
+		$category = Class_ArticleCategorie::find($this->_getParam('id_cat'));
+		return $category && $this->_canModify($category);
+	}
+
+
+	protected function _canEdit($model) {
+		return $this->_canModify($model->getCategorie());
+	}
+
+
+	protected function _canModify($category) {
+		return $this->identity
+			->hasAnyPermissionOn($category,
+													 [Class_Permission::createArticle(),
+														Class_Permission::createArticleCategory()]);
+	}
+
+
+	protected function _getEditUrl($model) {
+		return parent::_getEditUrl($model)
+			. (($page = $this->_getParam('page')) ? '/page/'.$page : '');
+	}
 }
 
 ?>
\ No newline at end of file
diff --git a/application/modules/admin/controllers/IndexController.php b/application/modules/admin/controllers/IndexController.php
index 36b57acc04d9a567b9db436abe4abac1bff7c637..c105f1a0f96648020a0f63f0014b2ff9ea81f659 100644
--- a/application/modules/admin/controllers/IndexController.php
+++ b/application/modules/admin/controllers/IndexController.php
@@ -75,35 +75,37 @@ class Admin_IndexController extends ZendAfi_Controller_Action {
 
 	public function adminvareditAction() {
 		$id = $this->_getParam('cle');
-		$cle = Class_AdminVar::getLoader()->find($id);
+		$var = Class_AdminVar::find($id);
 
-		if ($this->_request->isPost())	{
+		if ($this->_request->isPost()) {
 			$filter = new Zend_Filter_StripTags();
 			$new_valeur = $this->_request->getPost('valeur');
 
-			if ($this->shouldEncodeVar($cle)) {
-				$cle->setValeur(urlencode($new_valeur));
+			if ($this->shouldEncodeVar($var)) {
+				$var->setValeur(urlencode($new_valeur));
 
-			} else if ($cle->getId() == 'JS_STAT') {
-				$cle->setValeur(addslashes($new_valeur));
+			} else if ($var->getId() == 'JS_STAT') {
+				$var->setValeur(addslashes($new_valeur));
 
 			} else {
-				$cle->setValeur(trim($filter->filter($new_valeur)));
+				$var->setValeur(trim($filter->filter($new_valeur)));
 			}
 
-			$cle->save();
-			$this->_helper->notify('Variable '.$id.' sauvegardée');
+			$this->_helper->notify($var->save() ?
+														 'Variable '.$id.' sauvegardée' :
+														 'Erreur(s) : ' . implode(', ', $var->getErrors())
+															 . ', variable '.$id.' NON sauvegardée');
 			$this->_redirect('admin/index/adminvaredit/cle/'.$id);
 			return;
 		}
 
-		$this->view->var_valeur	= $this->shouldEncodeVar($cle)
-			? urldecode($cle->getValeur())
-			: $cle->getValeur();
+		$this->view->var_valeur	= $this->shouldEncodeVar($var)
+			? urldecode($var->getValeur())
+			: $var->getValeur();
 
-		$this->view->var_cle		= $cle->getId();
-		$this->view->tuto				= Class_AdminVar::helpFor($cle->getId());
-		$this->view->titre			= 'Modifier la variable: ' . $cle->getId();
+		$this->view->var_cle		= $var->getId();
+		$this->view->tuto				= Class_AdminVar::helpFor($var->getId());
+		$this->view->titre			= 'Modifier la variable: ' . $var->getId();
 	}
 
 
@@ -119,7 +121,7 @@ class Admin_IndexController extends ZendAfi_Controller_Action {
 
 
 	public function clearcacheAction() {
-		Zend_Registry::get('cache')->clean(Zend_Cache::CLEANING_MODE_ALL);
+		(new Storm_Cache())->clean();
 		$this->_redirect('admin/index/adminvar');
 	}
 
diff --git a/application/modules/admin/controllers/ModoController.php b/application/modules/admin/controllers/ModoController.php
index cdcc14fa778691fa094d159e1af0dddbd8b73143..67f2cac2d50e68c127976d7c2c1c435bea788746 100644
--- a/application/modules/admin/controllers/ModoController.php
+++ b/application/modules/admin/controllers/ModoController.php
@@ -99,7 +99,7 @@ class Admin_ModoController extends ZendAfi_Controller_Action {
 	public function deleteCmsAvisAction() {
 		$avis = Class_Avis::find($this->_getParam('id'));
 		$avis->delete();
-		$avis->maj_note_cms($avis->getIdCms(), $avis->getAbonOuBib());
+		$avis->majNoteCms($avis->getIdCms(), $avis->getAbonOuBib());
 		$this->_redirect('opac/cms/articleview/id/'.$avis->getIdCms());
 
 		$this->_helper->notify($this->view->_('Avis %s supprimé', $avis->getEntete()));
@@ -163,7 +163,7 @@ class Admin_ModoController extends ZendAfi_Controller_Action {
 	public function delAviscmsAction() {
 		$avis = Class_Avis::find($this->_getParam('id'));
 		$avis->delete();
-		$avis->maj_note_cms($avis->getIdCms(), $avis->getAbonOuBib());
+		$avis->majNoteCms($avis->getIdCms(), $avis->getAbonOuBib());
 		$this->_helper->notify($this->view->_('Avis %s supprimé', $avis->getEntete()));
 		$this->_redirect('/admin/modo/aviscms');
 
@@ -173,7 +173,7 @@ class Admin_ModoController extends ZendAfi_Controller_Action {
 	public function validateAviscmsAction() {
 		$avis = Class_Avis::find($this->_getParam('id'));
 		$avis->beValid()->save();
-		$avis->maj_note_cms($avis->getIdCms(), $avis->getAbonOuBib());
+		$avis->majNoteCms($avis->getIdCms(), $avis->getAbonOuBib());
 		$this->_helper->notify($this->view->_('Avis %s validé', $avis->getEntete()));
 		$this->_redirect('/admin/modo/aviscms');
 	}
diff --git a/application/modules/admin/controllers/ModulesnoticeController.php b/application/modules/admin/controllers/ModulesnoticeController.php
index 0283367227f9faaec6934df3929147d34f6ada60..723ca18efd46d72b3f1be168ee24d0efa7311a36 100644
--- a/application/modules/admin/controllers/ModulesnoticeController.php
+++ b/application/modules/admin/controllers/ModulesnoticeController.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
  */
 //////////////////////////////////////////////////////////////////////////////////////////
 // OPAC3 - Propriétés des modules des notices
@@ -32,11 +32,11 @@ class Admin_ModulesnoticeController extends ZendAfi_Controller_Action {
 		// Changer le layout
 		$viewRenderer = $this->getHelper('ViewRenderer');
 		$viewRenderer->setLayoutScript('subModal.phtml');
-		
+
 		// Recup des parametres
 		$this->type_module=$this->_getParam('action');
 		$this->id_profil = $this->_getParam("id_profil");
-		
+
 		// Lire la definition du module
 		$module=new Class_Systeme_ModulesNotice();
 		$def_module=$module->getModule($this->type_module);
@@ -47,7 +47,7 @@ class Admin_ModulesnoticeController extends ZendAfi_Controller_Action {
 		$cfg = $profil->getCfgNoticeAsArray();
 		$preferences=$cfg[$this->type_module];
 		if(!$preferences) $preferences=$module->getValeursParDefaut($this->type_module);
-		
+
 		// Variables de vue
 		$this->view->titre_module=$def_module["libelle"];
 		$this->view->preferences=$preferences;
@@ -66,7 +66,7 @@ class Admin_ModulesnoticeController extends ZendAfi_Controller_Action {
 //------------------------------------------------------------------------------------------------------
 	function exemplairesAction(){
 		$this->view->titre = $this->view->_('Propriété du bloc des exemplaires');
-		
+
 		// Retour du formulaire
 		if ($this->_request->isPost())
 		{
@@ -83,7 +83,7 @@ class Admin_ModulesnoticeController extends ZendAfi_Controller_Action {
 		if (!array_isset("en_pret", $this->view->preferences) || !trim($this->view->preferences["en_pret"]))
 			$this->view->preferences["en_pret"]="emprunté";
 	}
-	
+
 //------------------------------------------------------------------------------------------------------
 // Validation et retour config admin de la page d'accueil
 //------------------------------------------------------------------------------------------------------
@@ -99,5 +99,5 @@ class Admin_ModulesnoticeController extends ZendAfi_Controller_Action {
 		$viewRenderer = $this->getHelper('ViewRenderer');
 		$viewRenderer->renderScript('modulesnotice/_retour.phtml');
 	}
-	
+
 }
\ No newline at end of file
diff --git a/application/modules/admin/controllers/OuverturesController.php b/application/modules/admin/controllers/OuverturesController.php
index e1149fb3b149878fb4fdaf4981b1f8a7ee2e79e2..4f222939aadda5ecbbbc8abbe4dd8e081041a31d 100644
--- a/application/modules/admin/controllers/OuverturesController.php
+++ b/application/modules/admin/controllers/OuverturesController.php
@@ -16,12 +16,12 @@
  *
  * 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 Admin_OuverturesController extends ZendAfi_Controller_Action {
 	public function getRessourceDefinitions() {
-		$hours_select = Class_Multimedia_Location::getLoader()->getPossibleHours(30);		
+		$hours_select = Class_Multimedia_Location::getLoader()->getPossibleHours(30);
 
 		$fields = ['id_site' => ['element' => 'hidden'],
 							 'jour_semaine' => ['element' => 'select',
@@ -37,12 +37,12 @@ class Admin_OuverturesController extends ZendAfi_Controller_Action {
 																								]
 																	],
 
-							 'jour' => ['element' => 'datePicker', 
+							 'jour' => ['element' => 'datePicker',
 													'options' => ['label' => $this->view->_('Jour')]]];
 
-		$field_labels = ['debut_matin' => $this->view->_('Début matinée'), 
-										 'fin_matin' => $this->view->_('Fin matinée'), 
-										 'debut_apres_midi' =>	$this->view->_('Début après-midi'), 
+		$field_labels = ['debut_matin' => $this->view->_('Début matinée'),
+										 'fin_matin' => $this->view->_('Fin matinée'),
+										 'debut_apres_midi' =>	$this->view->_('Début après-midi'),
 										 'fin_apres_midi' => $this->view->_('Fin après-midi')];
 
 		foreach ($field_labels as $field => $label)
@@ -61,8 +61,8 @@ class Admin_OuverturesController extends ZendAfi_Controller_Action {
 													 'successful_save' => 'Plage d\'ouverture %s sauvegardée',
 													 'successful_delete' => 'Plage d\'ouverture %s supprimée'],
 
-						'after_add' => function() {	$this->_redirectToIndex(); },
-						'after_edit' => function() {	$this->_redirectToIndex(); },
+						'after_add' => function($model) {	$this->_redirectToIndex(); },
+						'after_edit' => function($model) {	$this->_redirectToIndex(); },
 
 						'display_groups' => ['plage_ouverture' => ['legend' => 'Plage d\'ouverture',
 																											 'elements' => $fields
@@ -100,7 +100,7 @@ class Admin_OuverturesController extends ZendAfi_Controller_Action {
 
 
 	public function formatTitreWithLibelleBib($template) {
-		$this->view->titre = $this->view->_($template, 
+		$this->view->titre = $this->view->_($template,
 																				Class_Bib::find($this->_getParam('id_site'))->getLibelle());
 	}
 
diff --git a/application/modules/admin/controllers/ProfilController.php b/application/modules/admin/controllers/ProfilController.php
index 3b60396de63bee6d090f0380abceb8d7225dd8a4..05ba269cdf5ff14d4cb76fec72cb3a6857979b37 100644
--- a/application/modules/admin/controllers/ProfilController.php
+++ b/application/modules/admin/controllers/ProfilController.php
@@ -298,8 +298,13 @@ class Admin_ProfilController extends ZendAfi_Controller_Action {
 
 	public function deepcopyAction() {
 		$copy = $this->_profil->deepCopy();
-		$copy->save();
-		$this->_redirect('admin/profil/edit/id_profil/'.$copy->getId());
+		if ($copy->save()) {
+			$this->_redirect('admin/profil/edit/id_profil/'.$copy->getId());
+			return;
+		}
+
+		$this->_redirect('admin/profil');
+		$this->_helper->notify($this->_('Duplication impossible: ').implode(',', $copy->getErrors()));
 	}
 
 
@@ -311,6 +316,7 @@ class Admin_ProfilController extends ZendAfi_Controller_Action {
 		$enreg = $this->_parseSaveContentString($cfg_module);
 
 		$enreg["use_parent_css"] = $this->_getParam('use_parent_css', $profil->getUseParentCss());
+		$enreg['sitemap'] = $this->_getParam('sitemap');
 		$enreg["page_css"]=$this->_getParam('page_css');
 		$profil->setRewriteUrl($this->_getParam('rewrite_url'));
 
diff --git a/application/modules/admin/controllers/SessionFormationController.php b/application/modules/admin/controllers/SessionFormationController.php
index e22d7d36c6f3a01e9a15d256950c733330da452c..638a00368fcd48e8abb92878e93b941d8f91c705 100644
--- a/application/modules/admin/controllers/SessionFormationController.php
+++ b/application/modules/admin/controllers/SessionFormationController.php
@@ -39,7 +39,7 @@ class Admin_SessionFormationController extends ZendAfi_Controller_Action {
 
 						'form_class_name' => 'ZendAfi_Form_Admin_SessionFormation',
 
-						'after_delete' => function() { $this->_redirect('/admin/formation/index');},
+						'after_delete' => function($model) { $this->_redirect('/admin/formation/index');},
 
 						'after_add' => function($session) { $this->afterAdd($session); }
 		];
diff --git a/application/modules/admin/controllers/TypeDocsController.php b/application/modules/admin/controllers/TypeDocsController.php
index 56aef7161a99c5f13b3115b4763979def02e5266..130dbde4a4d454223d381944db2b523eb3fb5d9f 100644
--- a/application/modules/admin/controllers/TypeDocsController.php
+++ b/application/modules/admin/controllers/TypeDocsController.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 Admin_TypeDocsController extends ZendAfi_Controller_Action {
@@ -25,7 +25,7 @@ class Admin_TypeDocsController extends ZendAfi_Controller_Action {
 
 	public function getRessourceDefinitions() {
 		return [
-			'model' => ['class' => 'Class_TypeDoc', 
+			'model' => ['class' => 'Class_TypeDoc',
 									'name' => 'type_doc',
 									'order' => 'libelle'],
 			'messages' => ['successful_save' => $this->_('Type de document %s modifié')],
@@ -33,7 +33,7 @@ class Admin_TypeDocsController extends ZendAfi_Controller_Action {
 			'actions' => ['edit' => ['title' => 'Modification du type de document: %s'],
 										'index' => ['title' => 'Types de documents']
 				],
-			'after_edit' => function() {$this->_redirect('/admin/type-docs');},
+			'after_edit' => function($model) {$this->_redirect('/admin/type-docs');},
 			'form' => ZendAfi_Form_TypeDocs_Edit::newWith($this->_getParam('id'))];
 	}
 
diff --git a/application/modules/admin/controllers/UsergroupController.php b/application/modules/admin/controllers/UsergroupController.php
index 4949899675be533cf5acba9911dc9960d7ef9d7d..177f47f20b36def890852e28b7c256bb5d98629f 100644
--- a/application/modules/admin/controllers/UsergroupController.php
+++ b/application/modules/admin/controllers/UsergroupController.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 Admin_UsergroupController extends ZendAfi_Controller_Action {
 	use Trait_Translator;
@@ -24,7 +24,7 @@ class Admin_UsergroupController extends ZendAfi_Controller_Action {
 	public function getRessourceDefinitions() {
 		return [
 			'model' => [
-				'class' => 'Class_UserGroup', 
+				'class' => 'Class_UserGroup',
 				'name' => 'user_group',
 				'order' => 'id'],
 
@@ -54,10 +54,10 @@ class Admin_UsergroupController extends ZendAfi_Controller_Action {
 												 'id' => null,
 												 'id_cat' => null];
 		$this->view->categories=[];
-		$this->view->categories[] = 
+		$this->view->categories[] =
 			['bib'=> $bib,
 			 'containers' => Class_UserGroupCategorie::getBibRootCategories(),
-			 'add_link' => $this->view->tagAnchor($this->view->url(array_merge($add_link_options, 
+			 'add_link' => $this->view->tagAnchor($this->view->url(array_merge($add_link_options,
 																																				 ['id_bib' => $bib->getId()])),
 																						$add_link_label,[])];
 		$this->view->categorieActions = $this->_getTreeViewContainerActions();
@@ -76,14 +76,14 @@ class Admin_UsergroupController extends ZendAfi_Controller_Action {
 
 
 	public function editmembersAction() {
-		if (!$group = Class_UserGroup::getLoader()->find((int)$this->_getParam('id'))) {
+		if (!$group = Class_UserGroup::find((int)$this->_getParam('id'))) {
 			$this->_redirect('admin/usergroup');
 			return;
 		}
 
 		if ($id_user_to_delete = $this->_getParam('delete')) {
 			$group
-				->removeUser(Class_Users::getLoader()->find($id_user_to_delete))
+				->removeUser(Class_Users::find($id_user_to_delete))
 				->save();
 
 			$redirect_url = '/admin/usergroup/editmembers/id/'.$group->getId();
@@ -93,15 +93,13 @@ class Admin_UsergroupController extends ZendAfi_Controller_Action {
 			return;
 		}
 
-
-		if ($this->_request->isPost() 
+		if ($this->_request->isPost()
 				&& ($ids_users_to_add = $this->_request->getPost('users'))) {
 			foreach($ids_users_to_add as $id)
-				$group->addUser(Class_Users::getLoader()->find($id));
+				$group->addUser(Class_Users::find($id));
 			$group->save();
 		}
 
-
 		$this->view->titre = "Membres du groupe: ".$group->getLibelle();
 		$this->view->group_id = $this->_getParam('id');
 		$this->view->search = $this->_getParam('search');
@@ -113,35 +111,27 @@ class Admin_UsergroupController extends ZendAfi_Controller_Action {
 	 * @return array
 	 */
 	private function _getTreeViewContainerActions() {
-		return array(
-			array(
-				'url' => $this->_getUrlForActionAndIdName('catedit'),
-				'icon'			=> 'ico/edit.gif',
-				'label'			=> 'Modifier'
-			),
-			array(
-				'url' => $this->_getUrlForActionAndIdName('catdel'),
-				'icon'			=> 'ico/del.gif',
-				'label'			=> 'Supprimer',
-				'condition' => 'hasNoChild',
-				'anchorOptions' => array(
-					'onclick' => "return confirm('Etes-vous sûr de vouloir supprimer cette catégorie ?')"
-				)
-			),
-			array(
-				'url' => $this->_getUrlForActionAndIdName('add', 'id_cat'),
-				'icon'			=> 'ico/add_news.gif',
-				'label'			=> 'Ajouter un groupe',
-			),
-			array(
-				'url' => $this->_getUrlForActionAndIdName('catadd'),
-				'data-popup' => true,
-				'icon'			=> 'ico/add_cat.gif',
-				'label'			=> 'Ajouter une sous-catégorie'
-			),
-		);
+		return [['url' => $this->_getUrlForActionAndIdName('catedit'),
+						 'icon' => 'ico/edit.gif',
+						 'label' => 'Modifier'],
+
+						['url' => $this->_getUrlForActionAndIdName('catdel'),
+						 'icon'	=> 'ico/del.gif',
+						 'label' => 'Supprimer',
+						 'condition' => 'hasNoChild',
+						 'anchorOptions' => ['onclick' => "return confirm('Etes-vous sûr de vouloir supprimer cette catégorie ?')"]],
+
+						['url' => $this->_getUrlForActionAndIdName('add', 'id_cat'),
+						 'icon'	=> 'ico/add_news.gif',
+						 'label' => 'Ajouter un groupe'],
+
+						['url' => $this->_getUrlForActionAndIdName('catadd'),
+						 'data-popup' => true,
+						 'icon'	=> 'ico/add_cat.gif',
+						 'label' => 'Ajouter une sous-catégorie']];
 	}
 
+
 	protected function _getUrlForActionAndIdName($action, $idName = 'id') {
 		return $this->view->url(array(
 				'module' => 'admin',
@@ -206,7 +196,7 @@ class Admin_UsergroupController extends ZendAfi_Controller_Action {
 
 
 	private function _getTreeViewItemActions() {
-		return 
+		return
 			[
 				[
 					'url' => $this->_getUrlForActionAndIdName('editmembers'),
@@ -239,7 +229,7 @@ class Admin_UsergroupController extends ZendAfi_Controller_Action {
 	 * @return bool
 	 */
 	protected function _isCategorieSaved($categorie,$form) {
-		if(!$this->_request->isPost()) 
+		if(!$this->_request->isPost())
 			return false;
 
 		$post = $this->_request->getPost();
@@ -253,10 +243,10 @@ class Admin_UsergroupController extends ZendAfi_Controller_Action {
 	}
 
 
-	
+
 	public function listJsonAction() {
 		$groups = Class_UserGroupCategorie::getTopCategories();
-		foreach($groups as $group) 
+		foreach($groups as $group)
 			$json_groups []= $group->toJSON(true);
 
 		$jsons =  '[{'.
diff --git a/application/modules/admin/views/scripts/accueil/sitotheque.phtml b/application/modules/admin/views/scripts/accueil/sitotheque.phtml
index 993fe967c0c0dd566132139ae717b089116bd934..6f3e6e4b36ce42286b67e6be95d80d2b42fa3771 100644
--- a/application/modules/admin/views/scripts/accueil/sitotheque.phtml
+++ b/application/modules/admin/views/scripts/accueil/sitotheque.phtml
@@ -2,20 +2,20 @@
 <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">
@@ -26,37 +26,53 @@
 			</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 
+								<?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")) 
+																							 "2" => "Les sites les plus récents"))
 								?>
 							</td>
 						</tr>
 					</table>
-						
+
 					<div	id="table_selection">
-						<?php	
+						<?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>
@@ -66,17 +82,21 @@
 						<td class="droite" width="90px">Grouper par catégorie&nbsp;</td>
 						<td class="gauche">
 						<?php
-										echo $this->formCheckbox('group_by_categorie', 
-																						 null, 
+										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>
diff --git a/application/modules/admin/views/scripts/bib/permissions.phtml b/application/modules/admin/views/scripts/bib/permissions.phtml
new file mode 100644
index 0000000000000000000000000000000000000000..9ae68b21be93ea360cbcac351304a89af26d65a8
--- /dev/null
+++ b/application/modules/admin/views/scripts/bib/permissions.phtml
@@ -0,0 +1 @@
+<?php echo $this->permissions; ?>
diff --git a/application/modules/admin/views/scripts/catalogue/tester.phtml b/application/modules/admin/views/scripts/catalogue/tester.phtml
index 6f15ad32a81a2824bf51f8ae07308533bb3fd36a..7a1ee2f72500cced48013ac6a19b2c29cac6a8f0 100644
--- a/application/modules/admin/views/scripts/catalogue/tester.phtml
+++ b/application/modules/admin/views/scripts/catalogue/tester.phtml
@@ -1,4 +1,4 @@
-<?php 
+<?php
 if($this->authUser->ROLE_LEVEL > 6) echo '<p style="font-weight:bold; font-size: 0.9em">'.$this->requete.'</p>';
 if(!$this->notices) echo '<p align="center" class="error">Ce catalogue ne renvoie aucun résultat.</p>';
 else {
@@ -9,7 +9,7 @@ else {
 
 if($this->notices)
 {
-	
+
 ?>
 	<br>
 	<table cellspacing="0" cellpadding="0">
@@ -19,15 +19,15 @@ if($this->notices)
 			<td style="width:38%">Auteur</td>
 		</tr>
 		<tr><td colspan="6"  class="separ"></td></tr>
-<?php	
+<?php
 	$ligne = 0;
 	foreach ($this->notices as $notice)
 	{
 		$ligne ++ ; if ($ligne & 1) $class="first"; else $class="second";
-		echo '<tr class="'.$class.'">
-						<td align="center" style="padding:3px 5px 3px 0px">'.$this->iconeSupport($notice->getTypeDoc()).'</td>
-						<td>'.$notice->getTitrePrincipal().'</td>
-						<td>'.$notice->getAuteurPrincipal().'</td>
+		echo '<tr class="' . $class . '">' .
+				 '<td align="center" style="padding:3px 5px 3px 0px">'.$this->iconeSupport($notice->getTypeDoc()).'</td>' .
+				 '<td>' . $notice->getTitrePrincipal() . '</td>'.
+				 '<td>' . $notice->getAuteurPrincipal() . '</td>
    				</tr>';
   }
   echo '<tr><td colspan="6"  class="separ"></td></tr>';
diff --git a/application/modules/admin/views/scripts/cms-category/add.phtml b/application/modules/admin/views/scripts/cms-category/add.phtml
new file mode 100644
index 0000000000000000000000000000000000000000..8ef71f348899c8ac664f19f45b17e5ccd97785c3
--- /dev/null
+++ b/application/modules/admin/views/scripts/cms-category/add.phtml
@@ -0,0 +1,10 @@
+<p style="color:#0058A5;font-weight:bold;margin-top:20px">
+	<?php echo $this->_('Localisation: ').$this->category->getBib()->getLibelle(); ?>
+</p>
+<?php echo $this->renderForm($this->form);?>
+<?php if (Class_Users::getIdentity()->isRoleMoreThanModoPortail()) { ?>
+	<h2><?php echo $this->_('Permissions'); ?></h2>
+	<div>
+		<?php echo $this->_('La gestion des permissions sera activée après la création de cette catégorie'); ?>
+	</div>
+<?php } ?>
diff --git a/application/modules/admin/views/scripts/cms-category/edit.phtml b/application/modules/admin/views/scripts/cms-category/edit.phtml
new file mode 100644
index 0000000000000000000000000000000000000000..6e6dd435811e0693490f1496b6a8cb8936c03e59
--- /dev/null
+++ b/application/modules/admin/views/scripts/cms-category/edit.phtml
@@ -0,0 +1,9 @@
+<p style="color:#0058A5;font-weight:bold;margin-top:20px">
+	<?php echo $this->_('Localisation: ').$this->category->getBib()->getLibelle(); ?>
+</p>
+<?php echo $this->renderForm($this->form); ?>
+<?php if ($this->permissions) { ?>
+	<h2><?php echo $this->_('Permissions de la catégorie %s',
+													$this->category->getLibelle()); ?></h2>
+	<?php echo $this->permissions; ?>
+<?php } ?>
diff --git a/application/modules/admin/views/scripts/cms/catadd.phtml b/application/modules/admin/views/scripts/cms/catadd.phtml
deleted file mode 100644
index 774b0e414d1bad7205bc8af7eaf4a59b5e6fd9db..0000000000000000000000000000000000000000
--- a/application/modules/admin/views/scripts/cms/catadd.phtml
+++ /dev/null
@@ -1 +0,0 @@
-<?php echo $this->render('cms/catform.phtml'); ?>
\ No newline at end of file
diff --git a/application/modules/admin/views/scripts/cms/catedit.phtml b/application/modules/admin/views/scripts/cms/catedit.phtml
deleted file mode 100644
index 774b0e414d1bad7205bc8af7eaf4a59b5e6fd9db..0000000000000000000000000000000000000000
--- a/application/modules/admin/views/scripts/cms/catedit.phtml
+++ /dev/null
@@ -1 +0,0 @@
-<?php echo $this->render('cms/catform.phtml'); ?>
\ No newline at end of file
diff --git a/application/modules/admin/views/scripts/cms/catform.phtml b/application/modules/admin/views/scripts/cms/catform.phtml
deleted file mode 100644
index 7a004793c07c3dc7edaa35110f89823aa45f7012..0000000000000000000000000000000000000000
--- a/application/modules/admin/views/scripts/cms/catform.phtml
+++ /dev/null
@@ -1,50 +0,0 @@
-<p style="color:#0058A5;font-weight:bold;margin-top:20px"><?php echo $this->_('Localisation') . ' : ' . $this->category->getBib()->getLibelle(); ?></p>
-<center>
-	<div class="form" style="text-align:center;">
-		<div class="formTable">
-			<form name="form" action="<?php echo $this->url();?>" method="post">
-			<span class="error"><?php echo implode('<br/>', $this->category->getErrors()); ?></span>
-			<fieldset>
-				<legend><?php echo $this->traduire('Catégorie'); ?></legend>
-				<table>
-					<tr>
-						<td class="droite"><?php echo $this->traduire('Nom'); ?>*</td>
-						<td class="gauche">
-							<input type="text" id="libelle" name="libelle" maxlength="80" onkeypress="if (event.keyCode == 13) {javascript:PicToolbarOver( getElementById('menu_item975'), 'menu_item975');this.form.submit();return false;}" value="<?php echo $this->escape(trim($this->category->getLibelle()));?>" style="width:536px;"/>
-						</td>
-					</tr>
-					<?php if ($this->combo_cat) { ?>
-					<tr>
-						<td class="droite"><?php echo $this->traduire('Catégorie parente') ?></td>
-						<td class="gauche"><?php echo $this->combo_cat ;?></td>
-					</tr>
-					<?php } ?>
-					<tr>
-						<td colspan="2" align="center">
-							<table>
-								<tr>
-									<td align="right" style="padding-right:5px;"><?php echo $this->bouton('type=V'); ?> </td>
-									<td align="left" style="padding-left:5px;"> 
-										<?php	echo $this->bouton('id=c_2', 
-																						 'picto=del.gif', 
-																						 'texte=Annuler', 
-																						 'url='.$this->url(['controller' => 'cms', 
-																																'action' => 'index',
-																																'id' => null,
-																																'id_cat' => $this->id_categorie]), 
-																						 'largeur=120px'); ?>
-									</td>
-								</tr>
-							</table>
-						</tr>
-					</table>
-				</fieldset>
-			</form>
-		</div>
-	</div>
-</center>
-
-<script>
-oField = document.getElementById('libelle');
-oField.focus();
-</script>
diff --git a/application/modules/admin/views/scripts/cms/index.phtml b/application/modules/admin/views/scripts/cms/index.phtml
index cd0e85511e8e73ddbf4936a781b01ab349182dd8..cfb790d95bc747491316e50e9197846b021d6f4c 100644
--- a/application/modules/admin/views/scripts/cms/index.phtml
+++ b/application/modules/admin/views/scripts/cms/index.phtml
@@ -1,7 +1,9 @@
-<?php
-echo $this->treeView(
-	$this->categories,
-	$this->categorieActions,
-	$this->articleActions
-);
-?>
\ No newline at end of file
+<?php
+echo $this->treeView(
+	$this->categories,
+	$this->categorieActions,
+	$this->articleActions,
+	true,
+	$this->containersFilter
+);
+?>
diff --git a/application/modules/admin/views/scripts/cms/list.phtml b/application/modules/admin/views/scripts/cms/list.phtml
new file mode 100644
index 0000000000000000000000000000000000000000..edd38afb5960fa175b200385d33b8f109cb5aa31
--- /dev/null
+++ b/application/modules/admin/views/scripts/cms/list.phtml
@@ -0,0 +1,78 @@
+<?php
+echo $this->tag('div', $this->breadcrumb,
+								['style' => 'font-size:140%;']);
+
+if ($this->bibs) {
+	$label = function($model, $attrib) {
+		return $this->tag('a',
+											$this->tagImg(URL_ADMIN_IMG . 'ico/cat.gif')
+																	 . $model->$attrib,
+											['href' => $this->url(['module' => 'admin',
+																						 'controller' => 'cms',
+																						 'action' => 'index',
+																						 'id_bib' => $model->getId()],
+																						null, true)]);
+	};
+
+	echo $this->tagModelTable(
+		$this->bibs,
+		[$this->_('Localisation')],
+		['libelle'],
+		[function($model) {
+			return $this->modelActions($model, $this->bibActions);
+		}],
+		'cms-categories',
+		null,
+		['libelle' => $label]);
+
+	return;
+}
+
+
+$label = function($model, $attrib) {
+	$sub_categories = $model->getRecursiveSousCategories();
+	$ids = array_map(function($item) {return $item->getId();}, $sub_categories);
+	$ids[] = $model->getId();
+
+	return $this->tag('a',
+										$this->tagImg(URL_ADMIN_IMG . 'ico/cat.gif')
+																 . $model->$attrib
+																 . ' (' . Class_Article::countBy(['id_cat' => $ids]) . ')',
+										['href' => $this->url(['module' => 'admin',
+																					 'controller' => 'cms',
+																					 'action' => 'index',
+																					 'id_cat' => $model->getId()],
+																					null, true)]);
+};
+
+
+echo $this->tagModelTable(
+	$this->categories,
+	[$this->_('Catégories')],
+	['libelle'],
+	[function($model) {
+		return $this->modelActions($model, $this->categorieActions);
+	}],
+	'cms-categories',
+	null,
+	['libelle' => $label]);
+
+
+
+$label = function($model, $attrib) {
+	return $this->tagImg(URL_ADMIN_IMG . 'picto/article.png') . ' ' . $model->getTitre();
+};
+
+if ($this->articles) {
+	echo $this->tagModelTable(
+		$this->articles,
+		[$this->_('Articles')],
+		['libelle'],
+		[function($model) {
+			return $this->modelActions($model, $this->articleActions);
+		}],
+		'cms-articles',
+		null,
+		['libelle' => $label]);
+	echo $this->paginationControl($this->art_paginator);
+}
diff --git a/application/modules/admin/views/scripts/frbr-link/index.phtml b/application/modules/admin/views/scripts/frbr-link/index.phtml
index 18710e3a146d3671640f396fa9324cda63eba918..a1c1fbf461a36b4f90c8fbd9bb340d1a30d5541f 100644
--- a/application/modules/admin/views/scripts/frbr-link/index.phtml
+++ b/application/modules/admin/views/scripts/frbr-link/index.phtml
@@ -13,20 +13,12 @@ echo $this->bouton('id=add_link',
 	                 'largeur=250px;'
 									 );
 
-$label = function ($model, $attrib) {
-	if ($notice = $model->{'get'. ucfirst($attrib) . 'Notice'}()) {
-		return '<a href="' . $model->{'get' . ucfirst($attrib)}() . '" onclick="window.open(this.href);return false;">' . $notice->getTitrePrincipal() . '</a>'
-					 . '<br>' . $notice->getAuteurPrincipal();
-	}
-
-	$value = $model->callGetterByAttributeName($attrib);
-	if (40 < mb_strlen($value))
-		$value = mb_substr($value, 0, 40) . '...';
-	return $value;
+$label = function ($link, $attrib) {
+	return $this->frbrLabel($link, $attrib);
 };
 
-echo $this->tagModelTable($this->relations, 
-													
+echo $this->tagModelTable($this->relations,
+
 													[$this->_('Objet A'), $this->_('Relation'), $this->_('Objet B')],
 
 													['source', 'link_complete_label', 'target'],
@@ -37,10 +29,10 @@ echo $this->tagModelTable($this->relations,
 	                        'relations',
 
 	                        null,
-	
+
 	                        ['source' => $label,
 													 'target' => $label,
 													 'link_complete_label' => function($model, $attrib) {
 															 return '<pre>' . $model->callGetterByAttributeName($attrib) . '</pre>';
 													 },]);
-?>
\ No newline at end of file
+?>
diff --git a/application/modules/admin/views/scripts/profil/accueil.phtml b/application/modules/admin/views/scripts/profil/accueil.phtml
index df29dfc0c96b8acf91e1ff1d281986a85e8b6f4e..0bf31068521502f1457f34c7fe7c7814d7c06132 100644
--- a/application/modules/admin/views/scripts/profil/accueil.phtml
+++ b/application/modules/admin/views/scripts/profil/accueil.phtml
@@ -36,6 +36,17 @@
 																						 array('1', '0')) ?>
 						</td>
 					</tr>
+	        <tr>
+						<td class="droite">
+							<?php echo $this->_("Ajouter au plan du site"); ?>
+						</td>
+						<td class="gauche">
+							<?php echo $this->formCheckbox('sitemap',
+																						 $this->profil->getCfgAccueilParam('sitemap'),
+																						 null,
+																						 array('1', '0')) ?>
+						</td>
+					</tr>
 	        <tr>
 					  <td class="droite"><?php echo $this->traduire('Page css:'); ?></td>
 					  <td class="gauche" style="padding-left:5px">
diff --git a/application/modules/opac/controllers/AbonneController.php b/application/modules/opac/controllers/AbonneController.php
index d3ab612feeffc6fbd5c62383d48de161e1507c71..1860dfdd57f9e27b3522571ed1da2a7115d010c5 100644
--- a/application/modules/opac/controllers/AbonneController.php
+++ b/application/modules/opac/controllers/AbonneController.php
@@ -132,83 +132,6 @@ class AbonneController extends ZendAfi_Controller_Action {
 	}
 
 
-	private function handleAvis($readSourceMethod, $writeAvisMethod) {
-		$cls_user= new Class_Users();
-
-		$avis = new Class_Avis();
-
-		// Validation du formulaire
-		if ($this->_request->isPost()) {
-			// Bornage du texte
-			$longueur_min = Class_AdminVar::get("AVIS_MIN_SAISIE");
-			$longueur_max = Class_AdminVar::get("AVIS_MAX_SAISIE");
-			if(!$longueur_min) $longueur_min=10;
-			if(!$longueur_max) $longueur_max=250;
-
-
-			$filter = new Zend_Filter_StripTags();
-			$avisSignature = trim($filter->filter($this->_request->getPost('avisSignature')));
-			$avisEntete = trim($filter->filter($this->_request->getPost('avisEntete')));
-			$avisTexte = trim($filter->filter($this->_request->getPost('avisTexte')));
-			$avisNote = trim($filter->filter($this->_request->getPost('avisNote')));
-			$id = trim($filter->filter($this->_request->getPost('id')));
-
-			if ($avisEntete != '' and (strlen($avisTexte)>= $longueur_min and strlen($avisTexte)<= $longueur_max ) and $avisSignature != '')
-			{
-				$avis->$writeAvisMethod($this->_user->ID_USER,$this->_user->ROLE_LEVEL,$id,$avisNote,$avisEntete,$avisTexte);
-				$cls_user->updatePseudo($this->_user, $avisSignature);
-
-				$this->_renderRefreshOnglet();
-			}
-			else
-			{
-				if(strlen($avisTexte)< $longueur_min or strlen($avisTexte) > $longueur_max)
-					$this->view->message = $this->_("L'avis doit avoir une longueur comprise entre %d et %d caractères", $longueur_min, $longueur_max);
-				else
-					$this->view->message = $this->_('Il faut compléter tous les champs.');
-				$this->view->avisSignature = $avisSignature;
-				$this->view->avisEntete = $avisEntete;
-				$this->view->avisTexte = $avisTexte;
-				$this->view->avisNote = $avisNote;
-				$this->view->id = $id;
-
-				$viewRenderer = $this->getHelper('ViewRenderer');
-				$viewRenderer->setLayoutScript('subModal.phtml');
-			}
-		}
-		// Saisie du formulaire
-		else
-		{
-			$id = $this->_request->getParam('id', 0);
-			$this->view->message = '';
-			$this->view->id = $id;
-			$this->view->avisSignature = $cls_user->getNomAff($this->_user->ID_USER);
-			$data = $avis->$readSourceMethod($this->_user->ID_USER, $id);
-
-			$this->view->avisEntete = $data[0]["ENTETE"];
-			$this->view->avisTexte = $data[0]["AVIS"];
-			if(!$data[0]["NOTE"]) $data[0]["NOTE"]="0";
-			$this->view->avisNote = $data[0]["NOTE"];
-			if($this->view->avisEntete) $this->view->mode_modif=true;
-			else $this->view->mode_modif=false;
-
-			$viewRenderer = $this->getHelper('ViewRenderer');
-			$viewRenderer->setLayoutScript('subModal.phtml');
-		}
-	}
-
-
-	protected function _renderRefreshOnglet() {
-		$this->getResponse()->setHeader('Content-Type', 'text/html;charset=utf-8');
-		$js = 'location.reload()';
-		if (array_key_exists('onglets', $_SESSION))
-			$js = "refreshOnglet('" . $_SESSION["onglets"]["avis"] . "')";
-		$this->getResponse()->setBody("<script>window.top.hidePopWin(false);window.top." . $js. ";</script>");
-		$viewRenderer = $this->getHelper('ViewRenderer');
-		$viewRenderer->setNoRender();
-	}
-
-
 	public function avisAction()	{
 		$id_notice = $this->_request->getParam('id_notice', 0);
 
@@ -317,7 +240,68 @@ class AbonneController extends ZendAfi_Controller_Action {
 
 
 	public function cmsavisAction()	{
-		$this->handleAvis('getCmsAvisById', 'ecrireCmsAvis');
+		$this->view->titre = $this->_('Votre avis');
+
+		// Validation du formulaire
+		if ($this->_request->isPost()) {
+			// Bornage du texte
+			$longueur_min = Class_AdminVar::get("AVIS_MIN_SAISIE");
+			$longueur_max = Class_AdminVar::get("AVIS_MAX_SAISIE");
+			if(!$longueur_min) $longueur_min=10;
+			if(!$longueur_max) $longueur_max=250;
+
+
+			$filter = new Zend_Filter_StripTags();
+			$avisSignature = trim($filter->filter($this->_request->getPost('avisSignature')));
+			$avisEntete = trim($filter->filter($this->_request->getPost('avisEntete')));
+			$avisTexte = trim($filter->filter($this->_request->getPost('avisTexte')));
+			$avisNote = trim($filter->filter($this->_request->getPost('avisNote')));
+			$id = trim($filter->filter($this->_request->getParam('id')));
+
+			if ($avisEntete != '' and (strlen($avisTexte)>= $longueur_min and strlen($avisTexte)<= $longueur_max ) and $avisSignature != '')	{
+				Class_Avis::ecrireCmsAvis($this->_user->ID_USER,
+																	$this->_user->ROLE_LEVEL,
+																	$id,
+																	$avisNote,
+																	$avisEntete,
+																	$avisTexte);
+				$this->_user
+					->setPseudo($avisSignature)
+					->save();
+
+				$this->_redirect('/opac/cms/articleview/id/'.$id);
+				return;
+			}
+			else
+			{
+				if(strlen($avisTexte)< $longueur_min or strlen($avisTexte) > $longueur_max)
+					$this->view->message = $this->_("L'avis doit avoir une longueur comprise entre %d et %d caractères", $longueur_min, $longueur_max);
+				else
+					$this->view->message = $this->_('Il faut compléter tous les champs.');
+				$this->view->avisSignature = $avisSignature;
+				$this->view->avisEntete = $avisEntete;
+				$this->view->avisTexte = $avisTexte;
+				$this->view->avisNote = $avisNote;
+				$this->view->id = $id;
+			}
+		}
+		// Saisie du formulaire
+		else
+		{
+			$id = $this->_request->getParam('id', 0);
+			$this->view->message = '';
+			$this->view->id = $id;
+			$this->view->avisSignature = $this->_user->getNomAff();
+
+			if (!$avis = Class_Avis::findFirstBy(['id_user' => $this->_user->getId(),
+																						'id_cms' => $id]))
+				$avis = Class_Avis::newInstance(['id_user' => $this->_user->getId(),
+																				 'id_cms' => $id]);
+
+			$this->view->avisEntete = $avis->getEntete();
+			$this->view->avisTexte = $avis->getAvis();
+			$this->view->avisNote = $avis->getNote();
+		}
 	}
 
 
diff --git a/application/modules/opac/controllers/BlogController.php b/application/modules/opac/controllers/BlogController.php
index 33338e491ca39faa521f38c821e32dc66fa1acfc..5788de638ef22b102ac4d1630ec58307808e9748 100644
--- a/application/modules/opac/controllers/BlogController.php
+++ b/application/modules/opac/controllers/BlogController.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
  */
 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 //  OPAC3: Blog
@@ -28,7 +28,7 @@ class BlogController extends ZendAfi_Controller_Action {
 	private $_user = null;								// Le user connecté
 	var $modo_blog;
 	var $_today;
-    
+
 	function init()
 	{
 		//Verify that the user has successfully authenticated.  If not, we redirect to the login.
@@ -39,15 +39,15 @@ class BlogController extends ZendAfi_Controller_Action {
 		$class_date = new Class_Date();
 		$this->_today = $class_date->DateTimeDuJour();
 	}
-    
+
 	function indexAction()
 	{
 		$this->_redirect('opac/blog/lastcritique/nb/10');
 	}
-	
-	//------------------------------------------------------------------------------------------------------  
+
+	//------------------------------------------------------------------------------------------------------
 	// Donner son avis
-	//------------------------------------------------------------------------------------------------------  
+	//------------------------------------------------------------------------------------------------------
 	function viewauteurAction()	{
 		$id_user = (int)$this->_request->getParam('id', $this->_user->ID_USER);
 		if ($auteur = Class_Users::getLoader()->find($id_user)) {
@@ -57,7 +57,7 @@ class BlogController extends ZendAfi_Controller_Action {
 			$this->view->liste_avis = '';
 			$this->view->name = 'Auteur introuvable';
 		}
-		
+
 		$this->view->title = "Avis";
 		$this->view->id_user = $id_user;
 
@@ -84,12 +84,12 @@ class BlogController extends ZendAfi_Controller_Action {
 		$this->_javascriptRedirectToReferrer();
 	}
 
-    
+
 	function lastcritiqueAction()	{
 		$nb_avis = (int)$this->_request->getParam('nb', 20);
 		$liste_avis = Class_AvisNotice::getLoader()->findAllBy(array('order' => 'date_avis desc',
 																																 'limit' => $nb_avis));
-		
+
 		$this->view->nb_aff = $nb_avis;
 		$this->view->liste_avis = Class_AvisNotice::filterVisibleForUser($this->_user, $liste_avis);
 		$this->view->title = $this->view->_("Dernières critiques");
@@ -102,7 +102,7 @@ class BlogController extends ZendAfi_Controller_Action {
 		$preferences = Class_Profil::getCurrentProfil()
 			->getModuleAccueilPreferences($id_module, 'CRITIQUES');
 		$avis = Class_AvisNotice::getAvisFromPreferences($preferences);
-		
+
 		$this->view->nb_aff = 50;
 		$this->view->liste_avis = $avis;
 		$this->view->title = 'Dernières critiques';
@@ -119,13 +119,13 @@ class BlogController extends ZendAfi_Controller_Action {
 			->getHelper('ViewRenderer')
 			->setLayoutScript('readspeaker.phtml');
 
-		$id_avis = $this->_request->getParam('id'); 
+		$id_avis = $this->_request->getParam('id');
 		$this->view->avis = Class_AvisNotice::getLoader()->find($id_avis);
 	}
 
-    
+
 	function viewavisAction()	{
-		$id_avis = $this->_request->getParam('id'); 
+		$id_avis = $this->_request->getParam('id');
 		$avis = Class_AvisNotice::getLoader()->find($id_avis);
 
 		$this->view->avis = $avis;
@@ -134,52 +134,9 @@ class BlogController extends ZendAfi_Controller_Action {
 		$this->view->user_co = ($this->_user->ID_USER != '');
 		$this->view->user = $this->_user;
 	}
-    
-
-	function addcmtAction()	{
-		$filter = new Zend_Filter_StripTags();
-		$contenu = $filter->filter($this->_request->getPost('cmt'));
-		$id_avi = $this->_request->getPost('id_avis'); $id_avis = explode('-',$id_avi);
-		$pseudo = $this->_request->getPost('pseudo');
-		$type = $this->_request->getPost('type');
-
-		if(trim($pseudo)=="") {$this->_redirect('blog/viewavis/id/'.$id_avi);}
-        
-        
-		if($type == "notice") $id_notice = $id_avis[0]; else $id_notice = 0;
-		if($type == "cms") $id_cms = $id_avis[0]; else $id_cms = 0;
-		if($this->modo_blog == 1) $statut =0; else $statut=1;
-
-		if($this->_user->LOGIN)	{
-				if($this->_user->ROLE_LEVEL >=3) $abon_ou_bib = 1;
-				else $abon_ou_bib = 0;
-
-				$cls_user= new Class_Users();
-				$cls_user->updatePseudo($this->_user, $pseudo);
-		}	else 	{
-			$abon_ou_bib = 0;
-		}
-        
-		$data = array(
-									'ID_CMT' => '',
-									'ID_USER' => $this->_user->ID_USER,
-									'ID_NOTICE' => $id_notice,
-									'ID_CMS' => $id_cms,
-									'DATE_CMT' => $this->_today,
-									'DATE_MOD' => null,
-									'CMT' => $contenu,
-									'STATUT' => $statut,
-									'ABON_OU_BIB' => $abon_ou_bib,
-									'SIGNATURE' => $pseudo,
-									);
-        
-		$class_blog = new Class_Blog();
-		$class_blog->addCmt($data);
-		$this->_redirect('blog/viewavis/id/'.$id_avi);
-	}
-    
-	function alertAction()
-	{
+
+
+	function alertAction()	{
 		$class_blog = new Class_Blog();
 		$type = $this->_request->getParam('type');
 		$id = $this->_request->getParam('id_avis');
diff --git a/application/modules/opac/controllers/CmsController.php b/application/modules/opac/controllers/CmsController.php
index 11bd6686999f668f187d516569c2a6b1580dfb25..11b138a97b31a4895b20df5fd11561ba47d264b1 100644
--- a/application/modules/opac/controllers/CmsController.php
+++ b/application/modules/opac/controllers/CmsController.php
@@ -19,6 +19,8 @@
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
 class CmsController extends Zend_Controller_Action {
+	use Trait_TimeSource;
+
 	public function init() {
 		parent::init();
 
@@ -81,33 +83,16 @@ class CmsController extends Zend_Controller_Action {
 	public function calendarrssAction() {
 		$id_profil = (int)$this->_getParam('id_profil');
 		$id_module = (int)$this->_getParam('id_module');
-
 		$profil				= Class_Profil::find($id_profil);
-		$preferences	= $profil->getModuleAccueilPreferences($id_module, 'NEWS');
-
-		if (
-			array_key_exists('id_categorie', $preferences)
-			&& ('' != $preferences['id_categorie'])
-		) {
-			if ('Random' == $preferences['display_order']) {
-				$preferences['display_order'] = 'DateCreation';
-				$preferences['nb_aff'] = $preferences['nb_analyse'];
-			}
-		}	else {
-			$preferences['event_date'] = strftime('%Y-%m');
-		}
 
-		$preferences['events_only'] = true;
-		$preferences['published'] = false;
-
-		$articles = Class_Article::getArticlesByPreferences($preferences);
-		$articles = Class_Article::filterByLocaleAndWorkflow($articles);
+		$preferences	= $profil->getModuleAccueilPreferences($id_module, 'NEWS');
+		$articles=Class_Calendar::getAllNextEvents($id_module,$profil);
 
 		$data_rss = [
-			'title' 	=> $preferences['titre'],
-			'link'  	=> $profil->urlForModule('cms', 'articleviewbydate', $id_module),
-			'charset'	  => 'utf-8',
-			'description' => 'Agenda: ' . $preferences['titre'],
+								 'title' 	=> trim($preferences['titre']) ? trim($preferences['titre']) : $profil->getLibelle(),
+								 'link'  	=> $profil->urlForModule('cms', 'articleviewbydate', $id_module),
+								 'charset'	  => 'utf-8',
+								 'description' => 'Agenda: ' . $preferences['titre'],
 			'lastUpdate'  => time()
 		];
 
@@ -132,6 +117,7 @@ class CmsController extends Zend_Controller_Action {
 
 		$this->view->article = $article;
 		$this->view->titreAdd($article->getTitre());
+		Class_ScriptLoader::getInstance()->addCmsMeta($article);
 	}
 
 
@@ -313,6 +299,18 @@ class CmsController extends Zend_Controller_Action {
 	}
 
 
+	/**
+	 * @param array $article
+	 */
+	protected function _getPubDate($article) {
+		if ($article->hasEventsDebut())
+			return $article->getEventsDebut();
+
+		if ($article->hasDebut())
+			return $article->getDebut();
+
+		return $article->getDateMaj();
+	}
 
 	/**
 	 * @param array $articles
@@ -327,10 +325,9 @@ class CmsController extends Zend_Controller_Action {
 				 'link'        => $this->_request->getScheme() . '://'
 				 . $this->_request->getServer('HTTP_HOST')
 				 . $this->view->url($article->getUrl()),
-				 'description' => html_entity_decode(Class_CmsUrlTransformer::imgUrlRelativeToAbsolute($article->getFullContent())),
-				 'lastUpdate'	 => strtotime($article->getDateMaj())
-				];
-		}
+				 'lastUpdate'	 => strtotime($this->_getPubDate($article)),
+				 'description' => html_entity_decode(Class_CmsUrlTransformer::imgUrlRelativeToAbsolute($this->view->tagArticleEvent($article).$article->getFullContent()))];
+				}
 
 		$rss_array['entries'] = $entries;
 
diff --git a/application/modules/opac/controllers/IndexController.php b/application/modules/opac/controllers/IndexController.php
index c74fc8a767d4d0f4c278e6c82e322c505c36aec3..a56bbc4cbd41fffaf541a055d9db22db7df9e2c5 100644
--- a/application/modules/opac/controllers/IndexController.php
+++ b/application/modules/opac/controllers/IndexController.php
@@ -86,6 +86,19 @@ class IndexController extends Zend_Controller_Action {
 
 	public function formulairecontactsentAction() {}
 
+	public function sitemapAction() {
+		if (!Class_Sitemap::sitemap_exists()) {
+			$this->_redirect('/index');
+			return;
+		}
+
+		$xml = Class_Sitemap::getSitemap();
+
+		$this->getHelper('ViewRenderer')->setNoRender();
+		$this->_response->setHeader('Content-Type', 'text/xml; charset=utf-8', true);
+		$this->_response->setBody($xml);
+	}
+
 
 	protected function _sendFormulaireContact() {
 		$mail_address = ($bib = Class_Bib::find($this->_getParam('bib_selector', 0)))
diff --git a/application/modules/opac/controllers/NoticeajaxController.php b/application/modules/opac/controllers/NoticeajaxController.php
index 4a0f120f8b33cd64cf53af72411443900b0e0f6b..15e7b8b6ae36a84640f5ce30a3c019bfa0375bae 100644
--- a/application/modules/opac/controllers/NoticeajaxController.php
+++ b/application/modules/opac/controllers/NoticeajaxController.php
@@ -160,10 +160,9 @@ class NoticeAjaxController extends Zend_Controller_Action {
 		$code_barres=$this->_request->getParam("code_barres");
 
 		// Recup des donnees
-		$cls_loc=new Class_Localisation();
 		$data = [];
-		if ($bib=Class_Bib::find($id_bib))
-			$data=$cls_loc->getLocFromExemplaire($bib,$cote,$code_barres);
+		if ($bib = Class_Bib::find($id_bib))
+			$data = Class_Localisation::getLocFromExemplaire($bib,$cote,$code_barres);
 
 		// Retour
 		$ret =json_encode($data);
@@ -289,7 +288,7 @@ class NoticeAjaxController extends Zend_Controller_Action {
 		$notice = $this->notice->getNotice("JA");
 
 		$biblio = ($notice["type_doc"]==3)
-			? Class_WebService_Lastfm::getInstance()->getBibliographie($notice["A"])
+			? Class_WebService_Lastfm::getInstance()->getDiscographie($notice["A"])
 			: [];
 		$html = $this->notice_html->getBibliographie($biblio, $notice["A"]);
 
@@ -298,11 +297,16 @@ class NoticeAjaxController extends Zend_Controller_Action {
 
 
 	public function resnumeriquesAction() {
-		$html = sprintf('<p>%s</p>', $this->view->_('Aucune ressource correspondante'));
-		if (null !== $exemplaire = Class_Exemplaire::getLoader()->findFirstBy(array('id_notice' => $this->id_notice))) {
-			$html = $this->view->renderAlbum($exemplaire->getAlbum());
-		}
-		$this->_sendResponse($html.Class_ScriptLoader::getInstance()->html());
+		$html = '';
+		if (null !== $exemplaire = Class_Exemplaire::findFirstBy(['id_notice' => $this->id_notice]))
+			$html .= $this->view->renderAlbum($exemplaire->getAlbum());
+
+		$html .= $this->view->recordAlbums($this->notice);
+
+		if ('' == $html)
+			$html = sprintf('<p>%s</p>', $this->view->_('Aucune ressource correspondante'));
+
+		$this->_sendResponse($html . Class_ScriptLoader::getInstance()->html());
 	}
 
 
diff --git a/application/modules/opac/controllers/PanierController.php b/application/modules/opac/controllers/PanierController.php
index eb6fe92c64f7af2fa5a5066edde390f11545f9b3..7c43319ff392ef6ac90c4fc4f1e717d5660ec186 100644
--- a/application/modules/opac/controllers/PanierController.php
+++ b/application/modules/opac/controllers/PanierController.php
@@ -172,6 +172,7 @@ class PanierController extends ZendAfi_Controller_Action {
 
 
 		$panier->addNotice($notice)->save();
+		$panier->index();
 		$this->_helper->notify($this->_('Notice "%s" ajoutée au panier "%s"',
 																		$notice->getTitrePrincipal(),
 																		$panier->getLibelle()));
@@ -232,10 +233,10 @@ class PanierController extends ZendAfi_Controller_Action {
 
 		$panier->addNotice($notice)->save();
 		$panier = $this->addDomainesTo($panier);
+		$panier->index();
 		$this->_user->setPanierCourant($panier);
 		$this->_user->save();
 
-
 		$this->_forward('ajout-ajax-success','panier','opac',['id_notice' => $notice->getId(),
 																													'id_panier' => $panier->getId()]);
 	}
@@ -252,6 +253,7 @@ class PanierController extends ZendAfi_Controller_Action {
 		}
 
 		$panier->removeNotice($notice)->save();
+		$panier->index();
 
 		$this->_helper->notify($this->_('Notice "%s" retirée du panier', $notice->getTitrePrincipal()));
 
@@ -287,6 +289,8 @@ class PanierController extends ZendAfi_Controller_Action {
 			->setDomaineIds($list_cat)
 			->save();
 
+		$panier->index();
+
 		return $this->_plural($nb_cat,
 													'',
 													$this->_('Panier: ').$panier->getLibelle().$this->_(' ajouté au domaine sélectionné.'),
diff --git a/application/modules/opac/controllers/RechercheController.php b/application/modules/opac/controllers/RechercheController.php
index a389ddf98cc43a37e0536ef36c323e1ee1d98ea4..d9a9145bf9634c9bc14a97a81aebee493af82378 100644
--- a/application/modules/opac/controllers/RechercheController.php
+++ b/application/modules/opac/controllers/RechercheController.php
@@ -84,9 +84,9 @@ class RechercheController extends ZendAfi_Controller_Action {
 		$criteres_recherche->setParams($params);
 
 		$this->getFrontController()->getRouter()->getCurrentRoute()
-			->match(str_replace(BASE_URL,
-													'',
-													$this->view->url($criteres_recherche->getCriteres())));
+				 ->match(str_replace(BASE_URL,
+														 '',
+														 $this->view->url($criteres_recherche->getCriteres())));
 
 		$this->view->titre = $this->getTitreRechercheSimple($criteres_recherche);
 
@@ -103,7 +103,7 @@ class RechercheController extends ZendAfi_Controller_Action {
 	protected function showDomainBreadcrumbOnDomainBrowsing() {
 		$id_module = $this->_getParam('id_module');
 		if (($config = Class_Profil::getCurrentProfil()->getLocalModuleAccueilConfig($id_module))
-				 && ($config['type_module'] == 'DOMAIN_BROWSER')) {
+				&& ($config['type_module'] == 'DOMAIN_BROWSER')) {
 			$this->view->show_domain_browser = true;
 			$this->view->domain_id_module = $id_module;
 			$this->view->domain_preferences = $config['preferences'];
@@ -183,16 +183,16 @@ class RechercheController extends ZendAfi_Controller_Action {
 		$this->view->url_retour = $this->view->url($criteres_recherche->getUrlRetourListe());
 
 		if (isset($ret['fil_ariane']))
-				$this->view->fil_ariane = $ret['fil_ariane'];
+			$this->view->fil_ariane = $ret['fil_ariane'];
 		if (isset($ret['rubriques']))
 			$this->view->rubriques = $ret['rubriques'];
 
 		if (isset($ret["statut"]) && $ret["statut"]=="erreur") {
-				$ret['nombre'] = 0;
-				$ret['page_cours'] = 0;
-				$this->view->liste = [];
-				$this->view->resultat = $ret;
-				return;
+			$ret['nombre'] = 0;
+			$ret['page_cours'] = 0;
+			$this->view->liste = [];
+			$this->view->resultat = $ret;
+			return;
 		}
 
 		$this->addHistoRecherche(1,$criteres_recherche);
@@ -229,6 +229,7 @@ class RechercheController extends ZendAfi_Controller_Action {
 
 
 	public function viewnoticeAction() {
+
 		$id_notice = (int)$this->_getParam('id');
 		$clef_alpha = $this->_getParam('clef');
 
@@ -241,38 +242,36 @@ class RechercheController extends ZendAfi_Controller_Action {
 			}
 
 			$ids = array_map(
-				function($notice) {
-					return $notice->getId();
-				},
-				$notices);
+											 function($notice) {
+												 return $notice->getId();
+											 },
+											 $notices);
 
 			if (!in_array($id_notice, $ids))
 				$id_notice = $notices[0]->getId();
 		}
 
-
 		if (!$notice = Class_Notice::find($id_notice)) {
 			$this->_redirect('opac/recherche/simple');
 			return;
 		}
 
+		Class_ScriptLoader::getInstance()->addRecordMeta($notice);
 
 		if (($ig = Zend_Controller_Front::getInstance()
 				 ->getPlugin('ZendAfi_Controller_Plugin_InspectorGadget'))
 				&& $ig->isEnabled())
 			$ig->logRecord($this->view->notice_Unimarc($notice));
 
-
 		$notice_navigation = new Class_Notice_NavigationRecherche(
-			(new Class_CriteresRecherche())->setParams($this->_request->getParams()),
-			$this->moteur,
-			$notice);
+																															(new Class_CriteresRecherche())->setParams($this->_request->getParams()),
+																															$this->moteur,
+																															$notice);
 
 		if ($navigation=$this->_getParam('navigation')) {
-			$notice_to_show = $navigation ==='suivant'
-				? $notice_navigation->getNoticeSuivante()
-				: $notice_navigation->getNoticePrecedente();
-
+			$notice_to_show = ($navigation ==='suivant'
+												 ? $notice_navigation->getNoticeSuivante()
+												 : $notice_navigation->getNoticePrecedente());
 			return $this->_redirect($this->view->absoluteUrl(['id'=>$notice_to_show->getId(),
 																												'clef'=>$notice_to_show->getClefAlpha(),
 																												'navigation'=> null]));
@@ -283,11 +282,10 @@ class RechercheController extends ZendAfi_Controller_Action {
 						 || $this->_getParam('retour_avis', false)))
 			$this->view->notice_navigation = $notice_navigation;
 
-
 		$current_module = $this->_getParam('current_module');
 
 		$current_module['preferences'] = $this->preferences = $this->extractProfilFromUrl()
-			->getCfgModulesPreferences('recherche', 'viewnotice', $notice->getTypeDoc());
+																															 ->getCfgModulesPreferences('recherche', 'viewnotice', $notice->getTypeDoc());
 
 		$current_module['action2'] = $notice->getTypeDoc();
 		$this->view->_current_module = $current_module;
@@ -310,7 +308,7 @@ class RechercheController extends ZendAfi_Controller_Action {
 		if (($genres = $notice->getChampNotice("G", $notice->getFacettes()))
 				&& ($genre = Class_CodifGenre::find($genres[0]['id']))
 				&& ('_vide.gif' != $genre->getPicto()))	{
-				$this->view->picto_genre = $genre->getPicto();
+			$this->view->picto_genre = $genre->getPicto();
 		}
 
 		$this->view->url_panier = $this->view->url(['controller' => 'panier',
@@ -325,7 +323,7 @@ class RechercheController extends ZendAfi_Controller_Action {
 																'tome' => $notice->getTomeAlpha(),
 																'support' => $notice->getTypeDocLabel(),
 																'ressource' => $this->view->absoluteUrl($this->view->urlNotice($notice))
-																 ],
+															 ],
 															 $notice->getId());
 
 		$stat= new Class_StatsNotices();
@@ -475,22 +473,22 @@ class RechercheController extends ZendAfi_Controller_Action {
 				$this->_redirect('opac/recherche/viewnotice/id/'.$id_notice."?type_doc=".$notice["type_doc"]);
 			}
 			else
-			{
-				$bib = Class_Bib::find($id_bib);
-				$this->view->id_bib = $id_bib;
-				$this->view->nom_bib = $bib->LIBELLE;
-				$this->view->mail_bib = $bib->MAIL;
-				$this->view->id_notice = $id_notice;
-				$this->view->errorMessage = $errorMessage;
-				$this->view->user_name = $user_name;
-				$this->view->demande = $demande;
-				$this->view->id_notice = $id_notice;
-				$this->view->user_mail = $user_mail;
-
-				$resa = getVar("RESA_CONDITION");
-				$resa_condition = str_replace('%0D%0A','<br />',$resa);
-				$this->view->condition_resa = urldecode($resa_condition);
-			}
+				{
+					$bib = $class_bib->getBibById($id_bib);
+					$this->view->id_bib = $id_bib;
+					$this->view->nom_bib = $bib->LIBELLE;
+					$this->view->mail_bib = $bib->MAIL;
+					$this->view->id_notice = $id_notice;
+					$this->view->errorMessage = $errorMessage;
+					$this->view->user_name = $user_name;
+					$this->view->demande = $demande;
+					$this->view->id_notice = $id_notice;
+					$this->view->user_mail = $user_mail;
+
+					$resa = getVar("RESA_CONDITION");
+					$resa_condition = str_replace('%0D%0A','<br />',$resa);
+					$this->view->condition_resa = urldecode($resa_condition);
+				}
 		}
 		// Entree dans le formulaire
 		else
@@ -614,11 +612,11 @@ class RechercheController extends ZendAfi_Controller_Action {
 		$form = $this->view->newForm(['id' => 'pickup',
 																	'class' => 'zend_form reservation_pickup',
 																	'action' => $this->view->url(['controller' => 'recherche', 'action' => 'reservation-pickup-ajax'])])
-			->addElement('Radio', 'code_annexe', array('required' => true,
-																								 'allowEmpty' => false))
-			->addDisplayGroup(array('code_annexe'), 'group', array('legend' => 'Site de retrait'))
-			->addElement('Submit', 'Valider')
-			->addElement('Button', 'Annuler', array('onclick' => 'opacDialogClose();return false;'));
+											 ->addElement('Radio', 'code_annexe', array('required' => true,
+																																	'allowEmpty' => false))
+											 ->addDisplayGroup(array('code_annexe'), 'group', array('legend' => 'Site de retrait'))
+											 ->addElement('Submit', 'Valider')
+											 ->addElement('Button', 'Annuler', array('onclick' => 'opacDialogClose();return false;'));
 
 		$radio = $form->getElement('code_annexe');
 		foreach ($annexes as $annexe)
diff --git a/application/modules/opac/controllers/RssController.php b/application/modules/opac/controllers/RssController.php
index 662337585f0538d949d3fa55b2d96e5cb9e5f99d..eac46ef9facda098f6aa80c32bde10cd7ad3de78 100644
--- a/application/modules/opac/controllers/RssController.php
+++ b/application/modules/opac/controllers/RssController.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
  */
 /////////////////////////////////////////////////////////////////////////////////////////////////////////////
 // OPAC3 - FLUX RSS
@@ -246,7 +246,8 @@ class RssController extends Zend_Controller_Action
 		}
 
 		if (count($data_rss['items']) == 0)
-			$data_rss["items"][]= array('titre' => $this->view->_('Aucune donnée à modérer'));
+			$data_rss["items"][]= ['titre' => $this->view->_('Aucune donnée à modérer'),
+														 'lien' => ''];
 
 		$this->getHelper('ViewRenderer')->setNoRender();
 		$this->getResponse()->setHeader('Content-Type', 'text/html;charset=utf-8') ;
@@ -268,7 +269,7 @@ class RssController extends Zend_Controller_Action
 		$data_rss["description"] = $this->view->_("Critiques de la sélection: %s", $preferences['titre']);
 		$data_rss["lien"] = $profil->urlForModule('blog', 'viewcritiques', $id_module);
 		$data_rss["items"] = [];
-		
+
 		$avis_helper = $this->view->getHelper('Avis');
 		foreach($liste_avis as $avis) {
 			if (!$avis->hasFirstNotice())
@@ -314,7 +315,7 @@ class RssController extends Zend_Controller_Action
 									'desc' => '<![CDATA[' . $this->filtreNews($desc) . ']]>',
 									'pubDate' => strtotime($avis->getDateAvis())];
 		}
-			
+
 		$data_rss = ['titre' => sprintf('Avis de %s', $username),
 									'lien' => $this->view->absoluteUrl([
 										'controller' => 'blog',
diff --git a/application/modules/opac/views/scripts/abonne/avis.phtml b/application/modules/opac/views/scripts/abonne/avis.phtml
index 205501b56bc9825953a303d1295ac5f4c38c6f57..6ca3fc9bd736d670eed1d613e177592af94dfafc 100644
--- a/application/modules/opac/views/scripts/abonne/avis.phtml
+++ b/application/modules/opac/views/scripts/abonne/avis.phtml
@@ -1,9 +1,9 @@
 <?php
-echo $this->partial("abonne/avis_partial.phtml", 
+echo $this->partial("abonne/avis_partial.phtml",
 										array(
 													'message' => $this->message,
-													'action' => $this->url(array('controller' => 'abonne', 
-																											 'action' => 'avis', 
+													'action' => $this->url(array('controller' => 'abonne',
+																											 'action' => 'avis',
 																											 'id_notice' => $this->id_notice,
 																											 'id' => $this->id)),
 													'id' => $this->id,
@@ -11,19 +11,5 @@ echo $this->partial("abonne/avis_partial.phtml",
 													'avisEntete' => $this->avisEntete,
 													'avisTexte' => $this->avisTexte,
 													'avisNote' => $this->avisNote,
-													));  
+													));
 ?>
-
-<script> 
-$('#opac-dialog form').submit(function(event) {
-  event.preventDefault();
-  var form = $(this);
-  $.post(form.attr('action'),
-         form.serialize(),
-         function(data) { opacDialogClose(); 
-                          opacDialogFromData(data); 
-                         }
-         );
-}
-);
-</script>
\ No newline at end of file
diff --git a/application/modules/opac/views/scripts/abonne/avis_partial.phtml b/application/modules/opac/views/scripts/abonne/avis_partial.phtml
index 7f00091c6a9f74622b197f3f7edf2068044292af..f86c9fa10a269238713933d8ebcb58908d2d7d00 100644
--- a/application/modules/opac/views/scripts/abonne/avis_partial.phtml
+++ b/application/modules/opac/views/scripts/abonne/avis_partial.phtml
@@ -37,4 +37,20 @@
 		</form>
 		<br>
 	</div>
-</center>
\ No newline at end of file
+</center>
+
+
+
+<script>
+$('#opac-dialog form').submit(function(event) {
+  event.preventDefault();
+  var form = $(this);
+  $.post(form.attr('action'),
+         form.serialize(),
+         function(data) { opacDialogClose();
+                          opacDialogFromData(data);
+                         }
+         );
+}
+);
+</script>
diff --git a/application/modules/opac/views/scripts/head.phtml b/application/modules/opac/views/scripts/head.phtml
index d20f9e0c1ddccb4a8670bf2ad6c8178e89b0e50f..c0bbb9b972608c9db3ac1f761bd97a021b69176b 100644
--- a/application/modules/opac/views/scripts/head.phtml
+++ b/application/modules/opac/views/scripts/head.phtml
@@ -10,13 +10,15 @@
 	<meta content="all" name="robots" />
 	<meta content="10 days" name="revisit-after" />
 	<?php
+	Class_ScriptLoader::getInstance()->loadMeta();
+
 	echo $current_profil->getStyleCss();
   if ($current_profil->hasFavicon())
 		echo sprintf('<link rel="shortcut icon" href="%s"/>', $current_profil->getFavicon());
 
 	$head_scripts = Class_ScriptLoader::newInstance()
-		->loadJQuery()
-		->loadJQueryUI()
+											->loadJQuery()
+											->loadJQueryUI()
 		->addOPACStyleSheet('global')
 		->addSkinStyleSheets(['global', 'erreur', 'dialog', 'popup', 'nuage_tags', 'bib'])
 		->addAdminStyleSheet('subModal')
@@ -57,11 +59,11 @@
 	}
 
 
-	if ($this->header_css && $current_profil->getUseParentCss() && !$current_profil->hasPageCss()) 
+	if ($this->header_css && $current_profil->getUseParentCss() && !$current_profil->hasPageCss())
 		$head_scripts->addStyleSheet($this->header_css, ['id' => 'profil_css',
 																										 'media' => 'all']);
 
-	if ($this->header_css && $current_profil->getUseParentCss() && $current_profil->hasPageCss()) 
+	if ($this->header_css && $current_profil->getUseParentCss() && $current_profil->hasPageCss())
 		$head_scripts->addStyleSheet($this->header_css);
 
 	if($current_profil->hasPageCss())
@@ -80,7 +82,7 @@
 			->addOPACStyleSheet('bleu_sur_jaune', ['rel' => 'alternate stylesheet',
 																						 'title' => $this->_('Bleu sur jaune'),
 																						 'data-name' => 'style_bleu_sur_jaune'])
-			->cssAddLine($this->_('<link rel="alternate stylesheet" type="text/css" href="#" title="%s" data-name="style_defaut">', 
+			->cssAddLine($this->_('<link rel="alternate stylesheet" type="text/css" href="#" title="%s" data-name="style_defaut">',
 														'Style par défaut'))
 
 			->cssAddLine('<link id="accessibility_stylesheet" rel="stylesheet" type="text/css" href="#" title="CSS accessibilité">')
@@ -98,12 +100,12 @@
 	$script_loader = Class_ScriptLoader::getInstance();
 	foreach([7,8] as $ie)
 		$script_loader->addSkinStyleSheet('ie' . $ie, ['ie_version' => $ie])
-									->addUserFilesStylSheet($current_profil->getHeaderCssIE($ie), 
+									->addUserFilesStylSheet($current_profil->getHeaderCssIE($ie),
 																					['ie_version' => $ie]);
 
 	$head_scripts->renderStyleSheets();
 	$script_loader->renderStyleSheets();
-	
+
 	$head_scripts->renderJavaScripts();
 	$script_loader->renderJavaScripts();
 ?>
diff --git a/application/modules/rest/controllers/IndexController.php b/application/modules/rest/controllers/IndexController.php
new file mode 100644
index 0000000000000000000000000000000000000000..2f0fa14c12c6903ea2bae7c92488172a60e697d3
--- /dev/null
+++ b/application/modules/rest/controllers/IndexController.php
@@ -0,0 +1,30 @@
+<?php
+/**
+ * Copyright (c) 2012-2014, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * AFI-OPAC 2.0 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).
+ *
+ * AFI-OPAC 2.0 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 AFI-OPAC 2.0; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class Rest_IndexController extends Restful_Controller {
+	public function catalogAction() {
+		parent::catalogAction();
+		$this->_helper->getHelper('ViewRenderer')->setNoRender();
+		$this->_response->setBody($this->view->catalog());
+	}
+}
+?>
\ No newline at end of file
diff --git a/ckeditor/core_five_filemanager/scripts/filemanager.js b/ckeditor/core_five_filemanager/scripts/filemanager.js
index 984590c6727fda31ef3056afcd36873ac6f43b25..7e2ec29e587c2e25ee94bc2d6f59c2c790ab4518 100644
--- a/ckeditor/core_five_filemanager/scripts/filemanager.js
+++ b/ckeditor/core_five_filemanager/scripts/filemanager.js
@@ -952,10 +952,10 @@ $(function(){
 
 			if (is_image) {
 				$.prompt(
-								 [{title: 'Redimmensionner automatiquement l\'image si la résolution est supérieure à:',
+								 [{title: 'Redimensionner automatiquement l\'image si la résolution est supérieure à:',
 									buttons: {
 										'OK': true,
-										'Ne pas redimmensionner': false},
+										'Ne pas redimensionner': false},
 									html: '<label><input type="radio" name="geometry" value="800">800x600</label>'
 									 +'<label><input type="radio" name="geometry" value="1024" checked="checked">1024x768</label>'
 									 +'<label><input type="radio" name="geometry" value="1280">1280x1024</label>',
diff --git a/ckeditor/core_five_filemanager/scripts/languages/fr.js b/ckeditor/core_five_filemanager/scripts/languages/fr.js
index 983ad12276de0e38660b3859fbb1ea6e48997d8c..b1d5a942a687cd4984c07b6f6b988d333d220094 100644
--- a/ckeditor/core_five_filemanager/scripts/languages/fr.js
+++ b/ckeditor/core_five_filemanager/scripts/languages/fr.js
@@ -53,5 +53,5 @@
 	"kb": "ko",
 	"mb": "mo",
 	"gb": "go",
-	"resize" : "redimmensionner automatiquement les images"
+	"resize" : "redimensionner automatiquement les images"
 }
diff --git a/cosmogramme/php/_init.php b/cosmogramme/php/_init.php
index 0393cf289fcdf3e8d08d91c92b6a2eb8e271d722..ab235f99dd25229a1b91a67126caec4ab244ec7c 100644
--- a/cosmogramme/php/_init.php
+++ b/cosmogramme/php/_init.php
@@ -1,7 +1,8 @@
 <?php
 // Constantes
 error_reporting(E_ERROR | E_PARSE);
-define("PATCH_LEVEL","226");
+
+define("PATCH_LEVEL","234");
 
 define("APPLI","cosmogramme");
 define("COSMOPATH", "/var/www/html/vhosts/opac2/www/htdocs");
diff --git a/cosmogramme/php/classes/classe_notice_integration.php b/cosmogramme/php/classes/classe_notice_integration.php
index 2c4961256987a8d76259f81ea44b281deda68496..16d52290440b283878afbc9c3afc7867b1d47dc7 100644
--- a/cosmogramme/php/classes/classe_notice_integration.php
+++ b/cosmogramme/php/classes/classe_notice_integration.php
@@ -537,7 +537,7 @@ class notice_integration {
 	public function noticeToDBEnreg() {
 		return [
 			"type_doc" => $this->notice["type_doc"],
-						"alpha_titre" => $this->notice["alpha_titre"],
+			"alpha_titre" => $this->notice["alpha_titre"],
 			"alpha_auteur" => $this->notice["alpha_auteur"],
 
 			"titres" => $this->indexation->getfullText(array_merge($this->notice["titres"],
@@ -569,8 +569,9 @@ class notice_integration {
 	}
 
 
-	private function updateNotice($id_notice, $qualite) {
-		$existing_notice = Class_Notice::find($id_notice);
+	public function updateNotice($id_notice, $qualite) {
+		if(!$existing_notice = Class_Notice::find($id_notice))
+			return $id_notice;
 		$this->notice["qualite"] = $existing_notice->getQualite();
 		$this->notice["facette"] = $existing_notice->getFacettes();
 
@@ -684,7 +685,7 @@ class notice_integration {
 	}
 
 
-	private function ecrireExemplaires($id_notice) {
+	public function ecrireExemplaires($id_notice) {
 		$code_barres = [];
 		$exemplaires = [];
 		foreach ($this->notice['exemplaires'] as $ex) {
@@ -721,9 +722,8 @@ class notice_integration {
 		foreach($exemplaires as $exemplaire)
 			$exemplaire->save();
 
-		Class_Notice::find($id_notice)
-			->setDateMaj(dateDuJour(2))
-			->save();
+		if($record = Class_Notice::find($id_notice))
+			$record->setDateMaj(dateDuJour(2))->save();
 
 		Class_Exemplaire::clearCache();
 		Class_Notice::clearCache();
@@ -846,7 +846,7 @@ class notice_integration {
 					continue;
 
 				if (!$centre_interet = Class_CodifCentreInteret::findFirstBy(['code_alpha' => $code_alpha])) {
-					$centre_interet = Class_CodifCentreInteret::newInstance(['libelle' => $matiere,
+					$centre_interet = Class_CodifCentreInteret::newInstance(['libelle' => $interet,
 																																	 'code_alpha' => $code_alpha]);
 					$centre_interet->save();
 				}
@@ -878,6 +878,10 @@ class notice_integration {
 			}
 		}
 
+		// emplacements
+		if (count($this->notice['emplacements']))
+				$facettes = array_merge($facettes, array_map(function($e) { return "E$e";}, $this->notice['emplacements']));
+
 		// Maj enreg facette
 		$this->notice["facettes"] = trim(implode(' ', array_unique($facettes)));
 	}
diff --git a/cosmogramme/php/classes/classe_notice_marc21.php b/cosmogramme/php/classes/classe_notice_marc21.php
index 2ccaed3de2740ca5f3b034b7b2a5f4dfd591e5b4..49f66827b5f8eef1e00a947f08cf2294f37d900b 100644
--- a/cosmogramme/php/classes/classe_notice_marc21.php
+++ b/cosmogramme/php/classes/classe_notice_marc21.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
  */
 //////////////////////////////////////////////////////////////////////////////////////
 // CLASSE MARC21 (Surcharge la classe notice_unimarc)
@@ -30,7 +30,7 @@ class notice_marc21 extends notice_unimarc
 	private $map;
 
 // ----------------------------------------------------------------
-// Constructeur 
+// Constructeur
 // ----------------------------------------------------------------
 	function __construct()
 	{
@@ -104,11 +104,75 @@ class notice_marc21 extends notice_unimarc
 		$this->setNotice($this->marc21ToUnimarc());
 		return true;
 	}
-	
+
 	public function setNotice($string, $type_accents = 0)	{
 		parent::setNotice($string, 4);
 	}
 
+
+	protected function append200IToFirst200E($new_subfields, $i) {
+		$e_subfield_found = false;
+		foreach ($new_subfields as $index => $subfield) {
+			if ($subfield[0] == 'e') {
+				$e_subfield_found = true;
+				$subfield[1] .= ' ' . $i;
+				$new_subfields[$index] = $subfield;
+				break;
+			}
+		}
+		if (!$e_subfield_found) {
+			$new_subfields[] = ['e', $i];
+		}
+
+		return $new_subfields;
+	}
+
+
+	protected function getCodeIFromSubfields($subfields) {
+		foreach ($subfields as $subfield) {
+			if ($subfield['code'] == 'i')
+				return $subfield['valeur'];
+		}
+		return '';
+	}
+
+
+	protected function prepareSubfieldsFor200E($subfields) {
+		$new_subfields = [];
+		foreach ($subfields as $index => $subfield) {
+			if ($subfield['code'] == 'i')
+				continue;
+
+			$new_subfields[] = [$subfield['code'], $subfield['valeur']];
+		}
+		return $new_subfields;
+	}
+
+
+	protected function getNewSubfields($field_block) {
+		$subfields = $this->decoupe_bloc_champ($field_block);
+		$i = $this->getCodeIFromSubfields($subfields);
+		$new_subfields = $this->prepareSubfieldsFor200E($subfields);
+
+		return $i
+			? $this->append200IToFirst200E($new_subfields, $i)
+			: $new_subfields;
+
+	}
+
+	// Special case: append 200$i to 200$e so it appears in clef_alpha
+	// and can be used for deduping
+	protected function unimarc200iTo200e() {
+		if(!$field_blocks = $this->notice_unimarc->get_subfield('200'))
+			return ;
+
+		$this->notice_unimarc->delete_field('200');
+
+		foreach($field_blocks as $field_block)
+			$this->notice_unimarc->add_field('200', '01', $this->getNewSubfields($field_block));
+	}
+
+
 // ----------------------------------------------------------------
 // Transco marc21 to unimarc
 // ----------------------------------------------------------------
@@ -125,7 +189,7 @@ class notice_marc21 extends notice_unimarc
 		// identifiants
 		$bloc=$this->get_subfield('001');
 		$this->notice_unimarc->add_field("001","",$bloc[0]);
-		
+
 		// date de nouveaute
 		$bloc=$this->get_subfield('008');
 		$this->notice_unimarc->add_field("005","",$bloc[0]);
@@ -143,7 +207,8 @@ class notice_marc21 extends notice_unimarc
 
 		// titres
 		$this->traiteZone("245","200");
-		$this->traiteZone("246","510");	
+		$this->unimarc200iTo200e();
+		$this->traiteZone("246","510");
 		$this->traiteZone("130","500");	 // titre uniforme
 		$this->traiteZone("505","464");	 // titres de depouillement
 		$this->traiteZone("856","464");	 // morceaux docs sonores
@@ -152,7 +217,7 @@ class notice_marc21 extends notice_unimarc
 		$this->traiteZone("765","510");	 // titre original
 		$this->traiteZone("780","432");	 // titre précédent pour périodiques
 		$this->traiteZone("785","440");	 // titre successeur
-		
+
 		// auteurs
 		$this->traiteZone("100","700");
 		$this->traiteZone("110","710");
diff --git a/cosmogramme/php/classes/classe_unimarc.php b/cosmogramme/php/classes/classe_unimarc.php
index 16fc66d38fe592796a62342b50046073dcea5895..9b3190d688708b8c6cfc09eb1f27250dd10c1629 100644
--- a/cosmogramme/php/classes/classe_unimarc.php
+++ b/cosmogramme/php/classes/classe_unimarc.php
@@ -282,6 +282,18 @@ class notice_unimarc extends iso2709_record {
 		return ('#' == $value) ? 0 : $value;
 	}
 
+	protected function getChampNouveauteAttribute() {
+		$champs_nouveaute = $this->profil['attributs'][4];
+
+		if(count($champs_nouveaute)) {
+			foreach($champs_nouveaute as $clef => $valeur) {
+				$champs_nouveaute[$clef] = trim($valeur);
+			}
+		}
+
+		return $champs_nouveaute;
+	}
+
 	public function getExemplaires() {
 		$champ_code_barres = trim($this->profil['attributs'][0]['champ_code_barres']);
 
@@ -302,11 +314,7 @@ class notice_unimarc extends iso2709_record {
 		$champ_annexe = $this->getProfilNumericAttribute('champ_annexe');
 		$champ_availability = $this->getProfilNumericAttribute('champ_availability');
 
-		$champs_nouveaute = $this->profil['attributs'][4];
-		if(count($champs_nouveaute)) {
-			foreach($champs_nouveaute as $clef => $valeur)
-				$champs_nouveaute[$clef] = trim($valeur);
-		}
+		$champs_nouveaute = $this->getChampNouveauteAttribute();
 		if ($champs_nouveaute['zone'] != '995'
 				and $champs_nouveaute['zone'] > '000') {
 			$data = $this->get_subfield($champs_nouveaute['zone'], $champs_nouveaute['champ']);
@@ -522,11 +530,12 @@ class notice_unimarc extends iso2709_record {
 		$exemplaires = $data=$this->get_subfield("852");
 		$ret["warnings"] = [];
 		$codes_barres = $cotes = false;
+		$champs_nouveaute = $this->getChampNouveauteAttribute();
 
 		for ($i = 0; $i < count($exemplaires); $i++) {
 			$ex = $this->withExemplaireDo(
 				$exemplaires[$i],
-				function ($champ, &$ex) use ($ret, $codes_barres, $cotes) {
+				function ($champ, &$ex) use ($ret, $codes_barres, $cotes, $champs_nouveaute) {
 					if($champ["code"] == "g") {
 						$this->setCodeBarre($champ['valeur'], $ex, $ret, $codes_barres);
 						return;
@@ -559,6 +568,12 @@ class notice_unimarc extends iso2709_record {
 						return;
 					}
 
+					if ( $champs_nouveaute['zone'] == '852'
+					  && $champs_nouveaute['champ'] == $champ['code'] )
+					{
+						$ex['date_nouveaute'] = $this->calculDateNouveaute($champ['valeur']);
+					}
+
 					if ($champ["code"]=="u" && $champ["valeur"] == "1")
 						$ex["activite"]="d";
 				});
diff --git a/cosmogramme/php/integration/domaines.php b/cosmogramme/php/integration/domaines.php
index ef2805687447e20170b926830ca6cefb1f05f271..45dedf8405f91ae67e7142f9bcefbb2ef9509668 100644
--- a/cosmogramme/php/integration/domaines.php
+++ b/cosmogramme/php/integration/domaines.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
  */
 /////////////////////////////////////////////////////////////////////////
 // INTEGRATION DES RESERVATIONS
@@ -32,7 +32,7 @@ if ($phase==14) {
 	$phase_data["timeStart"]=time();
 	$phase_data["pointeur"]=0;
 	$phase_data["domaine"]=0;
-	
+
 	$phase=15;
 }
 
@@ -40,7 +40,7 @@ if ($phase==15) {
 	$position_domaine=$phase_data["domaine"];
 // Indexation des catalogues dynamiques
 	$catalogues = array_slice(Class_Catalogue::findAllCataloguesAIndexer(),$position_domaine);
-	
+
 	foreach ($catalogues as $catalogue) {
 		$page=$phase_data["pointeur"];
 		$log->ecrire("Indexation du domaine : ".$catalogue->getLibelle()."<br/>");
@@ -53,6 +53,14 @@ if ($phase==15) {
 		$phase_data["pointeur"]=0;
 	}
 
+	$log->ecrire('<h4>Indexation des paniers dans les domaines</h4>');
+	Class_PanierNotice::indexAll();
+
+	$log->ecrire("<h4>Indexation des articles dans les domaines</h4>");
+	Class_Article::indexAll();
+
+	$log->ecrire("<h4>Indexation des sitothèques dans les domaines</h4>");
+	Class_Sitotheque::indexAll();
 }
 
 ?>
diff --git a/cosmogramme/php/integration/facettes.php b/cosmogramme/php/integration/facettes.php
index e9eb4325d6690c9950f16d095acf9ef8fc0ee983..08d5a8ca2a7f040a9d7fe617bc1c2192b08b8c8c 100644
--- a/cosmogramme/php/integration/facettes.php
+++ b/cosmogramme/php/integration/facettes.php
@@ -42,12 +42,17 @@ if ($phase == 7.1) {
 		print("<h4>Mise à jour des facettes exemplaires</h4>");
 
 	while(true) {
+    Storm_Model_Abstract::unsetLoaders();
+		Zend_Db_Table::getDefaultAdapter()->closeConnection();
+		setupDatabase(loadConfig());
+    gc_collect_cycles();
+
 		$notices = Class_Notice::findAllBy([
 																				"where" =>
-																				"id_notice > ".$phase_data["pointeur_notice"]
-																				." and date_maj >='" . $phase_data["pointeur"]."'",
+																				  "id_notice > ".$phase_data["pointeur_notice"]
+																				  ." and date_maj >='" . $phase_data["pointeur"]."'",
 																				"order" => "id_notice",
-																				"limit" => "0,1000"]);
+																				"limit" => "1000"]);
 		if (!$notices) break;
 
 		foreach ($notices as $notice) {
diff --git a/cosmogramme/php/integration/pseudo_notices.php b/cosmogramme/php/integration/pseudo_notices.php
index 3a1bc6697dbc349eef1a797beca3200a14f143c5..fca388507c6e0a9428397668dc4fd39f32f7e6b6 100644
--- a/cosmogramme/php/integration/pseudo_notices.php
+++ b/cosmogramme/php/integration/pseudo_notices.php
@@ -57,7 +57,9 @@ if ($phase > 0 and $phase < 0.2) {
 	}
 }
 
-
+function deletePseudoNotice($id_notice) {
+	Class_Notice::find($id_notice)->delete();
+}
 // ----------------------------------------------------------------
 // CMS
 // ----------------------------------------------------------------
@@ -84,8 +86,7 @@ if ($phase > 0.1 and $phase < 0.202)
 		{
 			if (!$mode_cron and $chrono->tempsPasse() > 10) sauveContexte();
 			$id_notice = $item["id_notice"];
-			sqlExecute("delete from exemplaires where id_notice=$id_notice");
-			sqlExecute("delete from notices where id_notice=$id_notice");
+			deletePseudoNotice($id_notice);
 		}
 	}
 	$phase = 0.2002;
@@ -146,8 +147,7 @@ if ($phase < 0.3)
 		{
 			if (!$mode_cron and $chrono->tempsPasse() > 10) sauveContexte();
 			$id_notice = $item["id_notice"];
-			sqlExecute("delete from exemplaires where id_notice=$id_notice");
-			sqlExecute("delete from notices where id_notice=$id_notice");
+			deletePseudoNotice($id_notice);
 		}
 	}
 	$phase = 0.3;
@@ -201,8 +201,7 @@ if ($phase < 0.6)
 		{
 			if (!$mode_cron and $chrono->tempsPasse() > 10) sauveContexte();
 			$id_notice = $item["id_notice"];
-			sqlExecute("delete from exemplaires where id_notice=$id_notice");
-			sqlExecute("delete from notices where id_notice=$id_notice");
+			deletePseudoNotice($id_notice);
 		}
 	}
 	$phase = 0.5;
@@ -258,8 +257,7 @@ if ($phase < 0.7)
 		{
 			if (!$mode_cron and $chrono->tempsPasse() > 10) sauveContexte();
 			$id_notice = $item["id_notice"];
-			sqlExecute("delete from exemplaires where id_notice=$id_notice");
-			sqlExecute("delete from notices where id_notice=$id_notice");
+			deletePseudoNotice($id_notice);
 		}
 	}
 	$phase = 0.7;
diff --git a/cosmogramme/php/integre_traite_main.php b/cosmogramme/php/integre_traite_main.php
index 82a66419bacd0061be104c3129c572202811406a..6d1d7ae1ec29472d43d061302f27feeaeafeff86 100644
--- a/cosmogramme/php/integre_traite_main.php
+++ b/cosmogramme/php/integre_traite_main.php
@@ -201,6 +201,7 @@ while ($ligne = $sql->fetchNext($resultat)) {
 			$nb1 = $sql->execute("delete from notices_succintes where id_bib=$id_bib");
 			$log->ecrire('<span class="vert">' . $nb . ' exemplaires supprimés</span>' . BR);
 			$log->ecrire('<span class="vert">' . $nb1 . ' notices succintes supprimées</span>' . BR . BR);
+			$log->ecrire('<span class="vert">' . 'table notice_domain nettoyée</span>' . BR . BR);
 		}
 		else $log->ecrire('<span class="rouge">Le fichier d\'import total est vide : aucun exemplaire supprimé.</span>' . BR . BR);
 	}
diff --git a/cosmogramme/sql/patch/patch_228.php b/cosmogramme/sql/patch/patch_228.php
new file mode 100644
index 0000000000000000000000000000000000000000..f821461c778e35438eb7cebe5bb9b1857bf3e91b
--- /dev/null
+++ b/cosmogramme/sql/patch/patch_228.php
@@ -0,0 +1,7 @@
+<?php
+
+$profiles = Class_Profil::findAll();
+
+foreach ($profiles as $profile) {
+	$profile->setCfgAccueilParam('sitemap', 1)->save();
+}
\ No newline at end of file
diff --git a/cosmogramme/sql/patch/patch_229.php b/cosmogramme/sql/patch/patch_229.php
new file mode 100644
index 0000000000000000000000000000000000000000..516e56e5133833c81a92ea5b2d14788096d3a907
--- /dev/null
+++ b/cosmogramme/sql/patch/patch_229.php
@@ -0,0 +1,9 @@
+<?php
+$adapter = Zend_Db_Table_Abstract::getDefaultAdapter();
+$adapter->query('ALTER TABLE frbr_link ADD source_key VARCHAR(255) null default null');
+$adapter->query('ALTER TABLE frbr_link ADD KEY source_key (source_key)');
+$adapter->query('ALTER TABLE frbr_link ADD target_key VARCHAR(255) null default null');
+$adapter->query('ALTER TABLE frbr_link ADD KEY target_key (target_key)');
+$adapter->query('ALTER TABLE frbr_link ADD KEY source_type (source_type)');
+$adapter->query('ALTER TABLE frbr_link ADD KEY target_type (target_type)');
+?>
diff --git a/cosmogramme/sql/patch/patch_230.php b/cosmogramme/sql/patch/patch_230.php
new file mode 100644
index 0000000000000000000000000000000000000000..4dd6d5eeeba28fadd67e4cf030bd0f3781540a4d
--- /dev/null
+++ b/cosmogramme/sql/patch/patch_230.php
@@ -0,0 +1,14 @@
+<?php
+$adapter = Zend_Registry::get('sql');
+$adapter->query('ALTER TABLE catalogue MODIFY libelle VARCHAR(255)');
+$adapter->query('ALTER TABLE catalogue MODIFY matiere TEXT');
+$adapter->query('ALTER TABLE catalogue MODIFY genre TEXT');
+$adapter->query('ALTER TABLE catalogue MODIFY section TEXT');
+$adapter->query('ALTER TABLE catalogue MODIFY tags TEXT');
+$adapter->query('ALTER TABLE catalogue MODIFY interet TEXT');
+$adapter->query('ALTER TABLE catalogue MODIFY langue TEXT');
+$adapter->query('ALTER TABLE catalogue MODIFY annexe TEXT');
+$adapter->query('ALTER TABLE catalogue MODIFY emplacement TEXT');
+$adapter->query('ALTER TABLE catalogue MODIFY auteur TEXT');
+$adapter->query('ALTER TABLE catalogue MODIFY bibliotheque TEXT');
+?>
\ No newline at end of file
diff --git a/cosmogramme/sql/patch/patch_231.php b/cosmogramme/sql/patch/patch_231.php
new file mode 100644
index 0000000000000000000000000000000000000000..2c658ec853d461151b03c31640c3f941966fdf1e
--- /dev/null
+++ b/cosmogramme/sql/patch/patch_231.php
@@ -0,0 +1,5 @@
+<?php
+$adapter = Zend_Registry::get('sql');
+$adapter->query('ALTER TABLE stats_notices MODIFY nb_visu int(11) default 0');
+$adapter->query('ALTER TABLE stats_notices MODIFY nb_resa int(11) default 0');
+?>
diff --git a/cosmogramme/sql/patch/patch_232.php b/cosmogramme/sql/patch/patch_232.php
new file mode 100644
index 0000000000000000000000000000000000000000..ca8301b5ac3b33356aeaa8bae2fb10429e6ed294
--- /dev/null
+++ b/cosmogramme/sql/patch/patch_232.php
@@ -0,0 +1,3 @@
+<?php
+	Zend_Registry::get('sql')->query('CREATE TABLE IF NOT EXISTS notice_domain (id int( 11 ) NOT NULL AUTO_INCREMENT, record_alpha_key varchar( 250 ) NOT NULL, domain_id int( 11 ) NOT NULL default 0, panier_id int( 11 ) NOT NULL DEFAULT 0, PRIMARY KEY ( id )) ENGINE = MYISAM DEFAULT CHARSET = utf8;');
+?>
diff --git a/cosmogramme/sql/patch/patch_233.php b/cosmogramme/sql/patch/patch_233.php
new file mode 100644
index 0000000000000000000000000000000000000000..2af5f2bb4a39a749d4d841b8468715d848fc6417
--- /dev/null
+++ b/cosmogramme/sql/patch/patch_233.php
@@ -0,0 +1,12 @@
+<?php
+$adapter = Zend_Registry::get('sql');
+$adapter->query("CREATE TABLE `permission` ( id INT(11) unsigned NOT NULL AUTO_INCREMENT, code varchar(255) NOT NULL, module varchar(255) NOT NULL, type varchar(255) NOT NULL, sorting int(11) NOT NULL, description text NULL, PRIMARY KEY (id), KEY (code), KEY (module), KEY (type), KEY (sorting)) ENGINE = MYISAM, character set=UTF8;");
+
+$adapter->query("insert into `permission` (code, module, type, sorting, description) values ('CATEGORY', 'ARTICLE', 'Droits', 1, 'Créer des sous-catégories et des articles'), ('ARTICLE', 'ARTICLE', 'Droits', 2, 'Créer des articles'), ('PENDING', 'ARTICLE', 'Nouveaux statuts autorisés', 1, 'À valider'), ('VALIDATED', 'ARTICLE', 'Nouveaux statuts autorisés', 101, 'Validé'), ('REFUSED', 'ARTICLE', 'Nouveaux statuts autorisés', 102, 'Refusé'), ('ARCHIVED', 'ARTICLE', 'Nouveaux statuts autorisés', 103, 'Archivé');");
+
+$adapter->query('CREATE TABLE `user_group_permission` ( id INT(11) unsigned NOT NULL AUTO_INCREMENT, id_group INT(11) unsigned NOT NULL, id_permission INT(11) unsigned NOT NULL, id_model INT(11) unsigned NOT NULL, model_class varchar(255) NOT NULL, PRIMARY KEY (id), KEY(id_group), KEY(id_permission), KEY(id_model), KEY(model_class)) ENGINE = MYISAM, character set=UTF8;');
+
+$adapter->query("UPDATE  `user_group_categorie` SET `libelle` =  'Général' WHERE  `user_group_categorie`.`libelle` =  'Catégorie par défaut' LIMIT 1;");
+
+$adapter->query("ALTER TABLE `user_groups` add column `id_bib` INT(11) NOT NULL DEFAULT '0', add key `id_bib` (`id_bib`);");
+?>
\ No newline at end of file
diff --git a/cosmogramme/sql/patch/patch_234.php b/cosmogramme/sql/patch/patch_234.php
new file mode 100644
index 0000000000000000000000000000000000000000..936e261818e8b7009188899ed77bb8e0c0fa844f
--- /dev/null
+++ b/cosmogramme/sql/patch/patch_234.php
@@ -0,0 +1,18 @@
+<?php
+$adapter = Zend_Registry::get('sql');
+$adapter->query('CREATE TABLE `patch_hash` ( '
+								. 'id int(11) unsigned not null auto_increment,'
+								. '`value` char(40) not null,'
+								. 'primary key (id),'
+								. 'key (`value`)'
+								. ') engine=MyISAM default charset=utf8');
+
+
+$migration = new Class_Migration_Patchs();
+$patch_level = (int)Class_CosmoVar::get('patch_level');
+$patches = $migration->getPatchesFilteredBy(
+																						function($version) use ($patch_level) {
+																							return $version <= $patch_level;
+																						});
+foreach($patches as $script)
+	$migration->hashScriptAndDo($script, function() { return true; });
diff --git a/cosmogramme/tests/php/classes/AbonneIntegrationTest.php b/cosmogramme/tests/php/classes/AbonneIntegrationTest.php
index bd54d41af726b61a4ceef9c5aee9d849d13c51f8..ae4e0d20f7c0bbf32a508d28b42e2b8aa508fae0 100644
--- a/cosmogramme/tests/php/classes/AbonneIntegrationTest.php
+++ b/cosmogramme/tests/php/classes/AbonneIntegrationTest.php
@@ -29,11 +29,16 @@ abstract class AbonneIntegrationTestCase extends ModelTestCase {
 
 	public function setup(){
 		parent::setup();
-		Class_Users::beVolatile();
-		Class_Newsletter::beVolatile();
+		Storm_Model_Loader::defaultToVolatile();
 		$this->abon_config = new abonne();
 		$this->abon_config->setIdBib(2);
 	}
+
+
+	public function tearDown() {
+		Storm_Model_Loader::defaultToDb();
+		parent::tearDown();
+	}
 }
 
 
diff --git a/cosmogramme/tests/php/classes/NoticeIntegrationTest.php b/cosmogramme/tests/php/classes/NoticeIntegrationTest.php
index f47e0ee0f56e26025976e82913116294a6e6f1d4..dce95e10e9601cf629e4911a5b51f6053d80d814 100644
--- a/cosmogramme/tests/php/classes/NoticeIntegrationTest.php
+++ b/cosmogramme/tests/php/classes/NoticeIntegrationTest.php
@@ -410,8 +410,7 @@ abstract class NoticeIntegrationMarc21ToUnimarcTest extends NoticeIntegrationTes
 }
 
 
-
-class NoticeIntegrationMarc21CoupCavalierToUnimarcTest extends NoticeIntegrationTestCase {
+abstract class NoticeIntegrationMarc21DynixTestCase extends NoticeIntegrationTestCase {
 	protected $_profil_donnees = ['id_profil' => 150,
 																'libelle' => 'MARC21 Dynix',
 																'accents' => '4',
@@ -420,7 +419,9 @@ class NoticeIntegrationMarc21CoupCavalierToUnimarcTest extends NoticeIntegration
 																'type_fichier' => '0',
 																'format' => '6',
 																'attributs' => 'a:7:{i:0;a:8:{s:8:"type_doc";a:12:{i:0;a:3:{s:4:"code";s:1:"0";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:1;a:3:{s:4:"code";s:1:"1";s:5:"label";s:5:"am;na";s:8:"zone_995";s:22:"LIV;MS;LDV;LVI;LV;LIVC";}i:2;a:3:{s:4:"code";s:1:"2";s:5:"label";s:2:"as";s:8:"zone_995";s:12:"PER;REVC;REV";}i:3;a:3:{s:4:"code";s:1:"3";s:5:"label";s:3:"i;j";s:8:"zone_995";s:17:"CD;LIVCD;LIVK7;K7";}i:4;a:3:{s:4:"code";s:1:"4";s:5:"label";s:1:"g";s:8:"zone_995";s:25:"DIAPO;DVD;VHS;VHD;VD;DVDJ";}i:5;a:3:{s:4:"code";s:1:"5";s:5:"label";s:3:"l;m";s:8:"zone_995";s:3:"CDR";}i:6;a:3:{s:4:"code";s:1:"7";s:5:"label";s:0:"";s:8:"zone_995";s:7:"LCA;LCD";}i:7;a:3:{s:4:"code";s:1:"8";s:5:"label";s:0:"";s:8:"zone_995";s:3:"DOS";}i:8;a:3:{s:4:"code";s:1:"9";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:9;a:3:{s:4:"code";s:2:"10";s:5:"label";s:0:"";s:8:"zone_995";s:6:"WEB;MF";}i:10;a:3:{s:4:"code";s:2:"11";s:5:"label";s:0:"";s:8:"zone_995";s:2:"JV";}i:11;a:3:{s:4:"code";s:3:"100";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}}s:17:"champ_code_barres";s:3:"999";s:10:"champ_cote";s:1:"k";s:14:"champ_type_doc";s:1:"r";s:11:"champ_genre";s:0:"";s:13:"champ_section";s:1:"z";s:17:"champ_emplacement";s:1:"u";s:12:"champ_annexe";s:1:"b";}i:1;a:1:{s:6:"champs";s:0:"";}i:2;a:1:{s:6:"champs";s:0:"";}i:3;a:1:{s:6:"champs";s:0:"";}i:5;a:3:{s:6:"champs";s:0:"";s:17:"xml_balise_abonne";s:0:"";s:17:"xml_champs_abonne";a:11:{s:6:"IDABON";s:0:"";s:9:"ORDREABON";s:0:"";s:3:"NOM";s:0:"";s:6:"PRENOM";s:0:"";s:9:"NAISSANCE";s:0:"";s:8:"PASSWORD";s:0:"";s:4:"MAIL";s:0:"";s:10:"DATE_DEBUT";s:0:"";s:8:"DATE_FIN";s:0:"";s:7:"ID_SIGB";s:0:"";s:9:"NUM_CARTE";s:0:"";}}i:4;a:5:{s:4:"zone";s:3:"995";s:5:"champ";s:1:"v";s:6:"format";s:1:"3";s:5:"jours";s:0:"";s:7:"valeurs";s:1:"n";}i:6;a:2:{s:4:"zone";s:3:"901";s:5:"champ";s:1:"a";}}'];
+}
 
+class NoticeIntegrationMarc21CoupCavalierToUnimarcTest extends NoticeIntegrationMarc21DynixTestCase {
 	public function setUp() {
 		parent::setUp();
 
@@ -478,6 +479,17 @@ class NoticeIntegrationMarc21CoupCavalierToUnimarcTest extends NoticeIntegration
 }
 
 
+class NoticeIntegrationMarc21BorisToUnimarcTest extends NoticeIntegrationMarc21DynixTestCase {
+	public function setUp() {
+		parent::setUp();
+		$this->loadNotice('marc21_boris');
+	}
+
+	/** @test */
+	public function sectionShouldHaveId2() {
+		$this->assertEquals('BORIS-JAI1AN1AN1AN1AN---TMAGNIER-2014-0', $this->notice_data['clef_alpha']);
+	}
+}
 
 
 class NoticeIntegrationBourdieuWithElectreGeneratedNoticeRecordTest extends NoticeIntegrationTestCase {
@@ -1116,7 +1128,8 @@ class NoticeIntegrationItemsIn852Test extends NoticeIntegrationTestCase {
 						['JBD HER 16', 0, 'cote'],
 						[2, 0, 'section'],
 						['AVER', 0, 'annexe'],
-						[false, 0, 'ignore_exemplaire']];
+						[false, 0, 'ignore_exemplaire'],
+						['2005-08-15', 0, 'date_nouveaute']];
 	}
 
 
@@ -1158,6 +1171,38 @@ class NoticeIntegrationKohaBadUnimarcTest extends NoticeIntegrationTestCase {
 
 
 
+class NoticeIntegrationKohaBdMilleniumTest extends NoticeIntegrationTestCase {
+	public function getProfilDonnees() {
+		return Class_IntProfilDonnees::forKoha()->getRawAttributes();
+	}
+
+
+	public function setUp() {
+		parent::setUp();
+		$this->loadNotice('unimarc_bd_millenium');
+		$this->millenium = Class_Notice::find(1);
+	}
+
+	/** @test */
+	public function titreShouldBeHommesQuiNaimaientPasLesFemmes() {
+		$this->assertEquals('Les hommes qui n\'aimaient pas les femmes', $this->millenium->getTitrePrincipal());
+	}
+
+
+	/** @test */
+	public function collectionShouldContainsMillenium() {
+		$this->assertContains('Millenium', $this->millenium->getCollections());
+	}
+
+	/** @test */
+	public function collectionMilleniumShouldBeIndexed() {
+		$this->assertEquals('MILLENIUM MILENIUM', $this->millenium->getRawAttributes()['collection']);
+	}
+}
+
+
+
+
 class NoticeIntegrationKohaUnimarcWithoutLabelBlocTest extends NoticeIntegrationTestCase {
 	public function getProfilDonnees() {
 		return Class_IntProfilDonnees::forKoha()->getRawAttributes();
@@ -2126,10 +2171,15 @@ class NoticeIntegrationLeChatonDansLaSouriciereTest extends NoticeIntegrationTes
 
 	/** @test */
 	public function collectionShouldBeMiniSourisNoire() {
-		$this->assertEquals('Mini souris noire', $this->_notice->getCollection()[0]);
+		$this->assertEquals('Mini souris noire', $this->_notice->getCollections()[0]);
 	}
 
 
+	/** @test */
+	public function noticeShouldNotHaveTomes() {
+		$this->assertEquals([],$this->_notice->getNoticesMemeSeries());
+	}
+
 		/** @test */
 	public function clefChapeauShouldBeMiniSourisNoire() {
 		$this->assertEquals('MINI SOURIS NOIRE', $this->_notice->getClefChapeau());
@@ -2150,11 +2200,6 @@ class NoticeIntegrationOblivionTest extends NoticeIntegrationTestCase {
 	public function setUp() {
 		parent::setUp();
 
-		$this->fixture('Class_CodifCentreInteret',
-									 ['id' => 2,
-										'code_alpha' => 'ACTION',
-										'libelle' => 'Action']);
-
 		$this->fixture('Class_CodifCentreInteret',
 									 ['id' => 24,
 										'code_alpha' => 'SF   FANTASTIQUE   FANTASY',
@@ -2195,6 +2240,11 @@ class NoticeIntegrationOblivionTest extends NoticeIntegrationTestCase {
 	}
 
 
+	/** @test */
+	public function codifCentreInteretId25LibelleShouldBeAction() {
+		$this->assertEquals('Action', Class_CodifCentreInteret::find(25)->getLibelle());
+	}
+
 	/** @test */
 	public function genreShouldBeFilm() {
 		$genre = $this->_notice->getGenres()[0];
@@ -2204,8 +2254,85 @@ class NoticeIntegrationOblivionTest extends NoticeIntegrationTestCase {
 
 	/** @test */
 	public function facettesShouldContainsF2andF24() {
-		$this->assertEquals('T4 A12 F24 F2 Lfre G96', $this->_notice->getFacettes());
+		$this->assertEquals('T4 A12 F24 F25 Lfre G96', $this->_notice->getFacettes());
+	}
+}
+
+
+
+
+class NoticeIntegrationPommeDeReinetteTest extends NoticeIntegrationTestCase {
+	protected $_profil_donnees = ['id_profil' => 111,
+																'id' => 111,
+																'libelle' => 'Unimarc Casqy',
+																'accents' => '1',
+																'rejet_periodiques' =>  '0',
+																'id_article_periodique' => '2',
+																'type_fichier' => '0',
+																'format' => '0',
+																'attributs' => 'a:6:{i:0;a:8:{s:8:"type_doc";a:26:{i:0;a:3:{s:4:"code";s:1:"0";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:1;a:3:{s:4:"code";s:1:"1";s:5:"label";s:5:"am;na";s:8:"zone_995";s:6:"LIV;MS";}i:2;a:3:{s:4:"code";s:1:"2";s:5:"label";s:2:"as";s:8:"zone_995";s:3:"PER";}i:3;a:3:{s:4:"code";s:1:"3";s:5:"label";s:3:"i;j";s:8:"zone_995";s:17:"CD;LIVCD;LIVK7;K7";}i:4;a:3:{s:4:"code";s:1:"4";s:5:"label";s:1:"g";s:8:"zone_995";s:3:"DVD";}i:5;a:3:{s:4:"code";s:1:"5";s:5:"label";s:3:"l;m";s:8:"zone_995";s:3:"CDR";}i:6;a:3:{s:4:"code";s:1:"6";s:5:"label";s:2:"cm";s:8:"zone_995";s:3:"PAR";}i:7;a:3:{s:4:"code";s:1:"7";s:5:"label";s:0:"";s:8:"zone_995";s:3:"BRO";}i:8;a:3:{s:4:"code";s:1:"8";s:5:"label";s:0:"";s:8:"zone_995";s:3:"DOS";}i:9;a:3:{s:4:"code";s:1:"9";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:10;a:3:{s:4:"code";s:2:"10";s:5:"label";s:0:"";s:8:"zone_995";s:6:"WEB;MF";}i:11;a:3:{s:4:"code";s:2:"11";s:5:"label";s:0:"";s:8:"zone_995";s:3:"MET";}i:12;a:3:{s:4:"code";s:2:"12";s:5:"label";s:0:"";s:8:"zone_995";s:3:"JEU";}i:13;a:3:{s:4:"code";s:2:"13";s:5:"label";s:0:"";s:8:"zone_995";s:3:"CAR";}i:14;a:3:{s:4:"code";s:2:"14";s:5:"label";s:0:"";s:8:"zone_995";s:3:"DDD";}i:15;a:3:{s:4:"code";s:2:"15";s:5:"label";s:0:"";s:8:"zone_995";s:3:"DIA";}i:16;a:3:{s:4:"code";s:2:"16";s:5:"label";s:0:"";s:8:"zone_995";s:3:"DIS";}i:17;a:3:{s:4:"code";s:2:"17";s:5:"label";s:0:"";s:8:"zone_995";s:3:"CDJ";}i:18;a:3:{s:4:"code";s:2:"18";s:5:"label";s:0:"";s:8:"zone_995";s:4:"LDVD";}i:19;a:3:{s:4:"code";s:2:"19";s:5:"label";s:0:"";s:8:"zone_995";s:3:"LIA";}i:20;a:3:{s:4:"code";s:2:"20";s:5:"label";s:0:"";s:8:"zone_995";s:3:"LIS";}i:21;a:3:{s:4:"code";s:2:"21";s:5:"label";s:0:"";s:8:"zone_995";s:3:"TXT";}i:22;a:3:{s:4:"code";s:2:"22";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}i:23;a:3:{s:4:"code";s:2:"23";s:5:"label";s:0:"";s:8:"zone_995";s:14:"VDD;VID;UMATIC";}i:24;a:3:{s:4:"code";s:2:"24";s:5:"label";s:0:"";s:8:"zone_995";s:4:"METI";}i:25;a:3:{s:4:"code";s:3:"100";s:5:"label";s:0:"";s:8:"zone_995";s:0:"";}}s:17:"champ_code_barres";s:1:"f";s:10:"champ_cote";s:1:"k";s:14:"champ_type_doc";s:0:"";s:11:"champ_genre";s:0:"";s:13:"champ_section";s:1:"q";s:17:"champ_emplacement";s:1:"u";s:12:"champ_annexe";s:1:"b";}i:1;a:1:{s:6:"champs";s:0:"";}i:2;a:1:{s:6:"champs";s:0:"";}i:3;a:1:{s:6:"champs";s:0:"";}i:5;a:3:{s:6:"champs";s:0:"";s:17:"xml_balise_abonne";s:0:"";s:17:"xml_champs_abonne";a:11:{s:6:"IDABON";s:0:"";s:9:"ORDREABON";s:0:"";s:3:"NOM";s:0:"";s:6:"PRENOM";s:0:"";s:9:"NAISSANCE";s:0:"";s:8:"PASSWORD";s:0:"";s:4:"MAIL";s:0:"";s:10:"DATE_DEBUT";s:0:"";s:8:"DATE_FIN";s:0:"";s:7:"ID_SIGB";s:0:"";s:9:"NUM_CARTE";s:0:"";}}i:4;a:5:{s:4:"zone";s:3:"995";s:5:"champ";s:1:"v";s:6:"format";s:1:"3";s:5:"jours";s:0:"";s:7:"valeurs";s:1:"n";}}'
+	];
+
+
+	public function setUp() {
+		parent::setUp();
+
+		VariableCache::getInstance()
+			->setListeCache([
+											 'nature_docs'=> "1:Collection\r\n2:Dataset\r\n3:Event\r\n4:Image",
+											 'types_docs' => "0:non identifié\r\n1:livres\r\n2:périodiques\r\n3:disques\r\n4:DVD\r\n5:cédéroms\r\n8:articles cms\r\n9:fils rss\r\n10:sites internet\r\n15:Liseuse\r\n21:Texte audio\r\n100:Livre Numérique\r\n101:Diaporamas\r\n102:Type doc\r\n103:OAI\r\n104:Type doc\r\n105:Formation Vodéclic\r\n106:Livres Numériques\r\n107:Vidéos à la demande\r\n108:Tout apprendre\r\n109:Enregistrement audio\r\n110:Numérique Premium"
+											 ]);
+
+		$this->fixture('Class_CodifEmplacement',
+									 ['id' => 46,
+										'libelle' => 'Espace Bébé',
+										'regles' => '995$u=59;Espace bébé']);
+
+		$this->loadNotice('unimarc_pomme_de_reinette');
+
+		$this->_notice = Class_Notice::find(1);
+		$this->_exemplaire = Class_Exemplaire::findFirstBy(['id_notice' => 1,
+																												'code_barres' => '1203195946']);
+	}
+
+
+	/** @test */
+	public function exemplaireShouldHaveEmplacementE46() {
+		$this->assertEquals(46, $this->_exemplaire->getEmplacement());
+	}
+
+
+	/** @test */
+	public function facettesShouldContainsE46() {
+		$this->assertContains(' E46', $this->_notice->getFacettes());
 	}
 }
 
-?>
\ No newline at end of file
+
+
+
+class NoticeIntegrationNoNoticeTest extends NoticeIntegrationTestCase {
+	public function tearDown() {
+		Storm_Model_Loader::defaultToDb();
+		parent::tearDown();
+	}
+
+
+	public function setUp() {
+		parent::setUp();
+		Storm_Model_Loader::defaultToVolatile();
+		$this->_notice_integration = new notice_integration();
+	}
+
+
+	/** @test */
+	public function updateNoticeShouldNotCrash() {
+		$this->assertEquals(1, $this->_notice_integration->updateNotice(1,5));
+	}
+
+
+	/** @test */
+	public function writeItemShouldNotCrash() {
+		$this->assertNull($this->_notice_integration->ecrireExemplaires(1));
+	}
+}
\ No newline at end of file
diff --git a/cosmogramme/tests/php/classes/marc21_boris.txt b/cosmogramme/tests/php/classes/marc21_boris.txt
new file mode 100644
index 0000000000000000000000000000000000000000..190a8dbab16d5c0782b86f9811f77e99b1c61bc0
--- /dev/null
+++ b/cosmogramme/tests/php/classes/marc21_boris.txt
@@ -0,0 +1 @@
+01213     2200157   4500001000700000008004100007020003400048245005200082260003100134300003300165520018300198520015400381520019600535520019600731999012800927425542140505s2014    FR     ar|||| 0|| 0|fre|d  a978-2-36474-495-0 (Cartonné)  aBorispJ'ai 1 an + 1 an + 1 an + 1 an /cMathis  aParis :bT. Magnier,c2014  a1 vol. (32 p.) ;c15 x 15 cm  aLa nature a tout prévu : l'herbe pour ne pas se faire mal en tombant, la pluie pour se laver, le soleil pour se sécher... et surtout une maman pour préparer de bons goûters !  aBoris se dispute avec son reflet dans le miroir. Il ne dit que des choses désagréables et clame haut et fort qu'un miroir dit toujours la vérité.  aAujourd'hui, Boris fête ses 4 ans, ce qui signifie quatre fois plus de gâteaux et de cadeaux. Le magnifique robot provoque l'indignation des autres jouets par ses moqueries à leur égard.   aAujourd'hui, Boris fête ses 4 ans, ce qui signifie quatre fois plus de gâteaux et de cadeaux. Le magnifique robot provoque l'indignation des autres jouets par ses moqueries à leur égard.   aM (JAUNE)wASISc1i00688997d7/1/2015e7/1/2015kCHECKEDOUTl10JALBTPmALFMEDAn5pE6.60rMsYt1IMPu14/5/2014xALBzTPET
\ No newline at end of file
diff --git a/cosmogramme/tests/php/classes/unimarc_bd_millenium.txt b/cosmogramme/tests/php/classes/unimarc_bd_millenium.txt
new file mode 100644
index 0000000000000000000000000000000000000000..e1d61aeb811df8583babce2f4be217a8ffa013b1
--- /dev/null
+++ b/cosmogramme/tests/php/classes/unimarc_bd_millenium.txt
@@ -0,0 +1 @@
+01290     2200289   4500001000700000010002400007035002100031073001800052090001900070091000900089099000700098100004100105101000800146102000700154106000600161200019000167210004300357215006700400225001700467700004500484701002200529702004300551801006700594995010900661995012100770995010900891260265  a9782800157771brel.  a(OCoLC)868591687 1a9782800157771  9260265a260265  a2c1  tBD  a20140318              frey50          afre  aFR  ar1 aLes hommes qui n'aimaient pas les femmesbTexte impriméeseconde partiefscénario,  Sylvain Runberggdessin,  José Homsgd'après la trilogie de Stieg LarssonhTome 2hseconde partie  aPariscDupuisd2013eimpr. en Belgique  a1 vol. (64 p.)ctout ill. en coul., couv. ill. en coul.d32 cm  aMilleniumv2  9495032aRunbergbSylvainf1971-....4690  9485177aHoms4440  9545077aLarssonbStiegf1954-20044100 3gAFNORaFRbMediatheque intercommunale Ouest Provencec20140225  f31301011644247m2014-07-0840009492216bFOS20kBD RUN62014-03-11o0cLLv14.50rBDaFOSeL1l00qNL4  f31301011644122m2014-10-0440009492217bMIR20kBD RUN62014-03-11n2014-10-25o0cLLv14.50rBDaMIReL1l00qNL4  f31301011644189m2014-10-1040009492218bENT20kBD RUN62014-03-11o0cLLv14.50rBDaENTeL1l00qNL4
\ No newline at end of file
diff --git a/cosmogramme/tests/php/classes/unimarc_pomme_de_reinette.txt b/cosmogramme/tests/php/classes/unimarc_pomme_de_reinette.txt
new file mode 100644
index 0000000000000000000000000000000000000000..9506e2bdc68aff4d92a17e7fb31019233a029f8e
--- /dev/null
+++ b/cosmogramme/tests/php/classes/unimarc_pomme_de_reinette.txt
@@ -0,0 +1 @@
+01368nam0 2200325   450 0010008000000100035000080730018000431000041000611010008001021020007001101050018001171060006001352000098001412100058002392150063002973300227003606060014005876860027006016860026006286860026006547000038006808010033007188010021007518010033007728010021008059020011008269950078008379950052009159950075009672209406  a2-7470-1757-5brel.d14,90 EUR 0a9782747017572  a20051003d2005    m  y0frey0103    ba| afre  aFR  ay   z   000a|  ar1 aPomme de reinette et pomme d'apieles comptines des petitsfIllustrations de Antonin Louchard  a[Paris]cBayard jeunessedDL 2005eimpr. Áa Singapour  a1 vol. (227 p.)cill. en coul., couv. ill. en coul.d17 cm  a23 comptines connues des tout-petits, dÂeclinÂees chacune en 4 sÂequences. DiffÂerentes techniques graphiques (la photo, le dessin, la gravure, la peinture, les collages, etc.) sont employÂees pour illustrer ces comptines.| aComptines  aJ0001002thÁemeelectre  aGJEU0022genreelectre  aPJ02012publicelectre |aLouchardbAntoninf1954-....4070 3aFRbElectrec20050930gAFNOR |aFRbROU Juliette 3aFRbElectrec20050930gAFNOR |aFRbROU Juliette  aHumour  32209418aJean RousselotbROUf1208051453k804 LOU oEXCLU DU PRETq8rLIV  32239820aCRPEbCRPEf1204069881kA LOU q5rLIV  32986171aAnatole FrancebTRAf1203195946kBB C q8rLIVuEspace bÂebÂe
\ No newline at end of file
diff --git a/includes.php b/includes.php
index 4394d33ac6305a588d56f39882d5acec366859c5..12dbbf146c838418b938987da9909570efb216db 100644
--- a/includes.php
+++ b/includes.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
  */
 $base_path = realpath(dirname(__FILE__));
 set_include_path($base_path . '/library' . PATH_SEPARATOR .
@@ -35,6 +35,13 @@ if(!file_exists("../" . end($parts))) {
 
 define("BASE_URL", "/" . $site) ;
 
+function rootUrl() {
+	return (isset($_SERVER['HTTPS']) && !in_array($_SERVER['HTTPS'],  ['', 'off']) ? 'https://' : 'http://')
+		. $_SERVER['SERVER_NAME']
+		. ($_SERVER['SERVER_PORT'] == 80 ? '' : ':'.$_SERVER['SERVER_PORT']);
+}
+define('ROOT_URL', rootUrl());
+
 include_once "fonctions/fonctions.php";
 require_once "Zend/Loader.php";
 require_once "library/startup.php";
diff --git a/index.php b/index.php
index 0ad5a9d2d09e315cde215eb039e5bad05ebe62ea..92222100a805f5fe82dfe97230d0ce3153d51525 100644
--- a/index.php
+++ b/index.php
@@ -2,7 +2,7 @@
 /**
  * Copyright (c) 2012, Agence Française Informatique (AFI). All rights reserved.
  *
- * BOKEH is free software; you can redistribute it and/or modify
+  * 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.
  *
@@ -16,12 +16,12 @@
  *
  * 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
  */
 
 require('includes.php');
 
-try { 
+try {
 	if (isUserAgentBotAndNotAllowed())
 		exit;
 
diff --git a/library/Class/AdminVar.php b/library/Class/AdminVar.php
index 00013e3c2ad57bbd45fb1b4b76bab4b0b935e0c8..efa8b7c97ed0a2b5cf5dc0d79a5162c39fca8057 100644
--- a/library/Class/AdminVar.php
+++ b/library/Class/AdminVar.php
@@ -19,7 +19,7 @@
 	* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
 	*/
 
- class Class_AdminVar extends Storm_Model_Abstract {
+class Class_AdminVar extends Storm_Model_Abstract {
 	 protected $_table_name = 'bib_admin_var';
 	 protected $_table_primary = 'CLEF';
 
@@ -107,7 +107,7 @@
 		* @return bool
 		*/
 	 public static function isWorkflowEnabled() {
-		 return self::isModuleEnabled('WORKFLOW');
+		 return !in_array(self::get('WORKFLOW'), ['', '0']);
 	 }
 
 
@@ -277,98 +277,104 @@
 		*/
 	 public static function getKnownVars() {
 		 if (null === self::$_knownVars)
-			 self::$_knownVars = [
-				 'AVIS_MAX_SAISIE' => 'Nombre de caractères maximum autorisé à saisir dans les avis.',
-				 'AVIS_MIN_SAISIE' => 'Nombre de caractères minimum autorisé à saisir dans les avis.',
-				 'BLOG_MAX_NB_CARAC' => 'Nombre de caractères maximum à afficher dans le bloc critiques.',
-				 'NB_AFFICH_AVIS_PAR_AUTEUR'  => 'Nombre d\'avis maximum à afficher par utilisateur.',
-				 'CLEF_GOOGLE_MAP' => 'Clef d\'activation pour le plan d\'accès google map. <a target="_blank" href="http://code.google.com/apis/maps/signup.html">Obtenir la clé google map</a>',
-				 'MODO_AVIS' => 'Modération des avis des lecteurs.<br /> 0 = Affichage sans attente de validation<br /> 1 = Affichage seulement après validation.',
-				 'MODO_AVIS_BIBLIO' => 'Modération des avis des bibliothèquaires.<br />  0 = Affichage sans attente de validation<br /> 1 = Affichage seulement après validation.',
-				 'AVIS_BIB_SEULEMENT' => '0 = Les lecteurs peuvent donner leur avis. <br /> 1 = Seuls les bibliothèquaires peuvent donner leur avis',
-				 'MODO_BLOG' => '0 = Ne requiert pas d\'identification pour saisir des  commentaires. <br /> 1 = Requiert l\'identification pour saisir des commentaires.',
-				 'REGISTER_OK' => 'Texte visible par l\'internaute après son inscription.',
-				 'RESA_CONDITION' => 'Texte visible après l\'envoi d\'e-mail de demande de réservation.',
-				 'SITE_OK' => '0 = Site en maintenance. <br /> 1 = Site ouvert.',
-				 'ID_BIBLIOSURF' => 'Nom de la bibliothèque chez bibliosurf (en minuscules)',
-				 'JS_STAT' => 'Javascript code for statistics',
-				 'ID_READ_SPEAKER' => 'Numéro de client Read Speaker <a target="_blank" href="http://webreader.readspeaker.com">http://webreader.readspeaker.com</a>',
-				 'BLUGA_API_KEY' => 'Clé API Bluga Webthumb <a target="_blank" href="http://webthumb.bluga.net/home">http://webthumb.bluga.net/home</a>',
-				 'AIDE_FICHE_ABONNE' => 'Texte d\'aide affiché dans la fiche abonné',
-				 'INTERDIRE_ENREG_UTIL' => 'Supprime l\'affichage du lien d\'enregistrement dans les différents formulaires de connexion et interdit l\'enregistrement d\'utilisateurs',
-				 'LANGUES' => 'Liste des codes langue utilisées en plus du français séparées par des ;. Exemple: en;ro;es',
-				 'CACHE_ACTIF' => implode('<br/>',
-																	['Activer le cache des boîtes (meilleure performance mais mise à jour toutes les ' . ((int)Zend_Registry::get('cache')->getOption('lifetime')) / 60 . 'mn)',
-																	 '0 = inactif',
-																	 '1 = actif',
-																	 sprintf('<a href="%s" >Vider le cache</a>',
-																					 Class_Url::assemble(['module' => 'admin',
-																															 'action' => 'clearcache']))]),
-				'WORKFLOW' => 'Activer ou désactiver la gestion des validations des articles<br />1 = Activé, Autre valeur = désactivé',
-				'WORKFLOW_TEXT_MAIL_ARTICLE_PENDING' => 'Contenu de l\'email de notification d\'article en attente de validation',
-				'WORKFLOW_TEXT_MAIL_ARTICLE_REFUSED' => 'Contenu de l\'email de notification de refus d\'un article à valider',
-				'WORKFLOW_TEXT_MAIL_ARTICLE_VALIDATED' => 'Contenu de l\'email de notification de validation d\'un article',
-				'BIBNUM' => 'Activer ou désactiver la bibliothèque numérique<br />1 = Activé, Autre valeur = désactivé',
-				'FORMATIONS' => 'Activer ou désactiver le module formation<br />1 = Activé, Autre valeur = désactivé',
-				'VODECLIC_KEY' => 'Clé de sécurité Vodeclic',
-				'VODECLIC_ID' => 'Identifiant partenaire Vodeclic',
-				'VODECLIC_BIB_ID' => 'Identifiant code bibliothèque Vodeclic',
-				'CVS_BMKEY' => 'Paramétrage CVS',
-				'CVS_BMID' => 'Paramétrage CVS',
-				'CVS_SOURCENAME' => 'Paramétrage CVS',
-				'CVS_SOURCEID' => 'Paramétrage CVS',
-				'CVS_SOURCEKEY' => 'Paramétrage CVS',
-				'CVS_SOURCEPASSWORD' => 'Paramétrage CVS',
-				'CVS_LOGINTEST' => 'Paramétrage CVS',
-				'NUMILOG_URL' => 'Paramétrage <a href="http://forge.afi-sa.fr/projects/opac3/wiki/Ressources_externes_enrichissements#Numilog">Numilog</a>',
-				'NUMILOG_OAI_URL' => 'Paramétrage <a href="http://forge.afi-sa.fr/projects/opac3/wiki/Ressources_externes_enrichissements#Numilog">Numilog</a>',
-				'NUMILOG_OAI_IDBIB' => 'Paramétrage <a href="http://forge.afi-sa.fr/projects/opac3/wiki/Ressources_externes_enrichissements#Numilog">Numilog</a>',
-				'CYBERLIBRIS_URL' => 'Adresse du serveur OAI Cyberlibris',
-				'CYBERLIBRIS_ID' => 'Identifiant SSO Cyberlibris',
-				'OAI_SERVER' => 'Activation du serveur OAI. 0 = inactif, 1 = actif',
-				'PACK_MOBILE' => 'Activation des fonctions avancées du téléphone.  0 = inactif, 1 = actif',
-				'ARTE_VOD_LOGIN' => 'Login ARTE VOD',
-				'ARTE_VOD_KEY' => 'Clé ARTE VOD',
-				'ARTE_VOD_SSO_KEY' => 'Clé ARTE VOD Single Sign-On',
-				'BABELTHEQUE_JS' => 'URL du javascript Babelthèque à insérer dans l\'OPAC',
-				'MULTIMEDIA_KEY' => 'Clé publique pour le cryptage des données AFI-Multimédia',
-				'WEBKIOSK_KEY' => 'Clé publique pour le cryptage des données Aesis Webkiosk',
-				'WEBKIOSK_RESERVATION_URL' => 'URL d\'accès à l\'interface de réservation des postes Aesis Webkiosk',
-				'CSS_EDITOR' => '',
-				'CMS_FORMULAIRES' => 'Activation des formulaires.  0 = inactif, 1 = actif',
-				'MENU_BOITE' => 'Activation des boîtes dans les menus.  0 = inactif, 1 = actif',
-				'INTERDIRE_MODIF_FICHE_ABONNE' => 'Interdire la modification de la fiche abonne 0 = inactif, 1 = actif',
-				'ROOT_URL_ECOUTE' => '',
-				'URL_TYPO3' => 'Url d\'import d\'un agenda TYPO3',
-				'CHAMPS_FICHE_UTILISATEUR' => 'Liste des champs que l\'utilisateur peux modifier. <br/>Ex: nom;prenom;pseudo;adresse;<br/>code_postal;ville;mail;is_contact_mail;<br/>telephone;is_contact_telephone;',
-				'FACETTE_PCDM4_LIBELLE' => 'Libellé pour la PCDM4',
-				'FACETTE_DEWEY_LIBELLE' => 'Libellé pour la Dewey',
-				'FACETTE_TYPE_DOC_LIBELLE' => '',
-				'FACETTE_LANGUE_LIBELLE' => '',
-				'FACETTE_GENRE_LIBELLE' => '',
-				'FACETTE_SITE_LIBELLE' => '',
-				'FACETTE_SECTION_LIBELLE' => '',
-				'FACETTE_BIBLIOTHEQUE_LIBELLE' => '',
-				'FACETTE_AUTEUR_LIBELLE' => '',
-				'FACETTE_INTERET_LIBELLE' => '',
-				'FACETTE_MATIERE_LIBELLE' => '',
-				'FACETTE_TAG_LIBELLE' => '',
-				'AFFICHER_DISPONIBILITE_SUR_RECHERCHE' => 'Activation de la disponibilite dans le resultat de recherche.  0 = inactif, 1 = actif',
-				'NOM_DOMAINE' => 'Nom de domaine principal de l\'OPAC, ex: monopac.macommune.fr',
-				'TOUTAPPRENDRE_BIB_ID' => 'Paramétrage <a href="http://forge.afi-sa.fr/projects/opac3/wiki/Ressources_externes_enrichissements#Tout-Apprendre">ToutApprendre</a>',
-				'TOUTAPPRENDRE_KEY' => 'Paramétrage <a href="http://forge.afi-sa.fr/projects/opac3/wiki/Ressources_externes_enrichissements#Tout-Apprendre">ToutApprendre</a>',
-				'NUMERIQUE_PREMIUM_URL' => '',
-				'MUSICME_URL' => '',
-				'MUSICME_BIB_ID' => '',
-				'DATE_LAST_FULL_INTEGRATION_USERS' => 'Date du dernier import total des abonnés (modifié par cosmogramme)',
-				'BOITE_PANIER_AUTO' => 'Ajouter automatiquement une boîte panier dans la division flottante.  0 = inactif, 1 = actif',
-				'EXTRA_SKIN_PATH' => 'Chemin vers les skins personnalisées, relatif à ' . Class_Profil_Skin::EXTRA_PATH,
-				'ENABLE_COLLABORATIVE_BROWSING' => 'Activation de la navigation collaborative. 0 = inactif, 1 = actif',
-				'KOHA_MULTI_SITES' => 'WS KOHA : Reservation d\'exemplaires pour les multi sites. 0 = inactif, 1 = actif',
-				'LECTURA_DOMAIN' => 'Domaine utilisée par le serveur lectura pour authentification',
-  		  'LEKIOSK_ID' => 'Identifiant fournit par lekiosk.com',
-				'TEXT_REPLACEMENTS' => 'Remplacement de textes à la volée. <br/>Ex:<br/>Panier;Sélection<br/>Vous avez %d paniers;Vous avez %d sélections'
-				];
+			 self::$_knownVars =
+				 ['AVIS_MAX_SAISIE' => 'Nombre de caractères maximum autorisé à saisir dans les avis.',
+					'AVIS_MIN_SAISIE' => 'Nombre de caractères minimum autorisé à saisir dans les avis.',
+					'BLOG_MAX_NB_CARAC' => 'Nombre de caractères maximum à afficher dans le bloc critiques.',
+					'NB_AFFICH_AVIS_PAR_AUTEUR'  => 'Nombre d\'avis maximum à afficher par utilisateur.',
+					'CLEF_GOOGLE_MAP' => 'Clef d\'activation pour le plan d\'accès google map. <a target="_blank" href="http://code.google.com/apis/maps/signup.html">Obtenir la clé google map</a>',
+					'MODO_AVIS' => 'Modération des avis des lecteurs.<br /> 0 = Affichage sans attente de validation<br /> 1 = Affichage seulement après validation.',
+					'MODO_AVIS_BIBLIO' => 'Modération des avis des bibliothèquaires.<br />  0 = Affichage sans attente de validation<br /> 1 = Affichage seulement après validation.',
+					'AVIS_BIB_SEULEMENT' => '0 = Les lecteurs peuvent donner leur avis. <br /> 1 = Seuls les bibliothèquaires peuvent donner leur avis',
+					'MODO_BLOG' => '0 = Ne requiert pas d\'identification pour saisir des  commentaires. <br /> 1 = Requiert l\'identification pour saisir des commentaires.',
+					'REGISTER_OK' => 'Texte visible par l\'internaute après son inscription.',
+					'RESA_CONDITION' => 'Texte visible après l\'envoi d\'e-mail de demande de réservation.',
+					'SITE_OK' => '0 = Site en maintenance. <br /> 1 = Site ouvert.',
+					'ID_BIBLIOSURF' => 'Nom de la bibliothèque chez bibliosurf (en minuscules)',
+					'JS_STAT' => 'Javascript code for statistics',
+					'ID_READ_SPEAKER' => 'Numéro de client Read Speaker <a target="_blank" href="http://webreader.readspeaker.com">http://webreader.readspeaker.com</a>',
+					'BLUGA_API_KEY' => 'Clé API Bluga Webthumb <a target="_blank" href="http://webthumb.bluga.net/home">http://webthumb.bluga.net/home</a>',
+					'AIDE_FICHE_ABONNE' => 'Texte d\'aide affiché dans la fiche abonné',
+					'INTERDIRE_ENREG_UTIL' => 'Supprime l\'affichage du lien d\'enregistrement dans les différents formulaires de connexion et interdit l\'enregistrement d\'utilisateurs',
+					'LANGUES' => 'Liste des codes langue utilisées en plus du français séparées par des ;. Exemple: en;ro;es',
+					'CACHE_ACTIF' => implode('<br/>',
+																	 ['Activer le cache des boîtes (meilleure performance mais mise à jour toutes les ' . CACHE_LIFETIME / 60 . 'mn)',
+																		'0 = inactif',
+																		'1 = actif',
+																		sprintf('<a href="%s" >Vider le cache</a>',
+																						Class_Url::assemble(['module' => 'admin',
+																																 'action' => 'clearcache']))]),
+					'WORKFLOW' => implode('<br/>',
+																['Activer ou désactiver la gestion des validations des articles',
+																 'Vide ou 0 = Désactivé, 1 = Activé avec les statuts par défaut, JSON = Activé avec des statuts de validation supplémentaire',
+																 'Exemple JSON :',
+																 '[{"id":10, "label":"À valider niveau 2"}, {"id":11, "label":"À valider niveau 3"}]',
+																 'Les identifiants 1 à 5 sont réservés']),
+					'WORKFLOW_TEXT_MAIL_ARTICLE_PENDING' => 'Contenu de l\'email de notification d\'article en attente de validation',
+					'WORKFLOW_TEXT_MAIL_ARTICLE_REFUSED' => 'Contenu de l\'email de notification de refus d\'un article à valider',
+					'WORKFLOW_TEXT_MAIL_ARTICLE_VALIDATED' => 'Contenu de l\'email de notification de validation d\'un article',
+					'BIBNUM' => 'Activer ou désactiver la bibliothèque numérique<br />1 = Activé, Autre valeur = désactivé',
+					'FORMATIONS' => 'Activer ou désactiver le module formation<br />1 = Activé, Autre valeur = désactivé',
+					'VODECLIC_KEY' => 'Clé de sécurité Vodeclic',
+					'VODECLIC_ID' => 'Identifiant partenaire Vodeclic',
+					'VODECLIC_BIB_ID' => 'Identifiant code bibliothèque Vodeclic',
+					'CVS_BMKEY' => 'Paramétrage CVS',
+					'CVS_BMID' => 'Paramétrage CVS',
+					'CVS_SOURCENAME' => 'Paramétrage CVS',
+					'CVS_SOURCEID' => 'Paramétrage CVS',
+					'CVS_SOURCEKEY' => 'Paramétrage CVS',
+					'CVS_SOURCEPASSWORD' => 'Paramétrage CVS',
+					'CVS_LOGINTEST' => 'Paramétrage CVS',
+					'NUMILOG_URL' => 'Paramétrage <a href="http://forge.afi-sa.fr/projects/opac3/wiki/Ressources_externes_enrichissements#Numilog">Numilog</a>',
+					'NUMILOG_OAI_URL' => 'Paramétrage <a href="http://forge.afi-sa.fr/projects/opac3/wiki/Ressources_externes_enrichissements#Numilog">Numilog</a>',
+					'NUMILOG_OAI_IDBIB' => 'Paramétrage <a href="http://forge.afi-sa.fr/projects/opac3/wiki/Ressources_externes_enrichissements#Numilog">Numilog</a>',
+					'CYBERLIBRIS_URL' => 'Adresse du serveur OAI Cyberlibris',
+					'CYBERLIBRIS_ID' => 'Identifiant SSO Cyberlibris',
+					'OAI_SERVER' => 'Activation du serveur OAI. 0 = inactif, 1 = actif',
+					'PACK_MOBILE' => 'Activation des fonctions avancées du téléphone.  0 = inactif, 1 = actif',
+					'ARTE_VOD_LOGIN' => 'Login ARTE VOD',
+					'ARTE_VOD_KEY' => 'Clé ARTE VOD',
+					'ARTE_VOD_SSO_KEY' => 'Clé ARTE VOD Single Sign-On',
+					'BABELTHEQUE_JS' => 'URL du javascript Babelthèque à insérer dans l\'OPAC',
+					'MULTIMEDIA_KEY' => 'Clé publique pour le cryptage des données AFI-Multimédia',
+					'WEBKIOSK_KEY' => 'Clé publique pour le cryptage des données Aesis Webkiosk',
+					'WEBKIOSK_RESERVATION_URL' => 'URL d\'accès à l\'interface de réservation des postes Aesis Webkiosk',
+					'CSS_EDITOR' => '',
+					'CMS_FORMULAIRES' => 'Activation des formulaires.  0 = inactif, 1 = actif',
+					'MENU_BOITE' => 'Activation des boîtes dans les menus.  0 = inactif, 1 = actif',
+					'INTERDIRE_MODIF_FICHE_ABONNE' => 'Interdire la modification de la fiche abonne 0 = inactif, 1 = actif',
+					'ROOT_URL_ECOUTE' => '',
+					'URL_TYPO3' => 'Url d\'import d\'un agenda TYPO3',
+					'CHAMPS_FICHE_UTILISATEUR' => 'Liste des champs que l\'utilisateur peux modifier. <br/>Ex: nom;prenom;pseudo;adresse;<br/>code_postal;ville;mail;is_contact_mail;<br/>telephone;is_contact_telephone;',
+					'FACETTE_PCDM4_LIBELLE' => 'Libellé pour la PCDM4',
+					'FACETTE_DEWEY_LIBELLE' => 'Libellé pour la Dewey',
+					'FACETTE_TYPE_DOC_LIBELLE' => '',
+					'FACETTE_LANGUE_LIBELLE' => '',
+					'FACETTE_GENRE_LIBELLE' => '',
+					'FACETTE_SITE_LIBELLE' => '',
+					'FACETTE_SECTION_LIBELLE' => '',
+					'FACETTE_BIBLIOTHEQUE_LIBELLE' => '',
+					'FACETTE_AUTEUR_LIBELLE' => '',
+					'FACETTE_INTERET_LIBELLE' => '',
+					'FACETTE_MATIERE_LIBELLE' => '',
+					'FACETTE_TAG_LIBELLE' => '',
+					'AFFICHER_DISPONIBILITE_SUR_RECHERCHE' => 'Activation de la disponibilite dans le resultat de recherche.  0 = inactif, 1 = actif',
+					'NOM_DOMAINE' => 'Nom de domaine principal de l\'OPAC, ex: monopac.macommune.fr',
+					'TOUTAPPRENDRE_BIB_ID' => 'Paramétrage <a href="http://forge.afi-sa.fr/projects/opac3/wiki/Ressources_externes_enrichissements#Tout-Apprendre">ToutApprendre</a>',
+					'TOUTAPPRENDRE_KEY' => 'Paramétrage <a href="http://forge.afi-sa.fr/projects/opac3/wiki/Ressources_externes_enrichissements#Tout-Apprendre">ToutApprendre</a>',
+					'NUMERIQUE_PREMIUM_URL' => '',
+					'MUSICME_URL' => '',
+					'MUSICME_BIB_ID' => '',
+					'DATE_LAST_FULL_INTEGRATION_USERS' => 'Date du dernier import total des abonnés (modifié par cosmogramme)',
+					'BOITE_PANIER_AUTO' => 'Ajouter automatiquement une boîte panier dans la division flottante.  0 = inactif, 1 = actif',
+					'EXTRA_SKIN_PATH' => 'Chemin vers les skins personnalisées, relatif à ' . Class_Profil_Skin::EXTRA_PATH,
+					'ENABLE_COLLABORATIVE_BROWSING' => 'Activation de la navigation collaborative. 0 = inactif, 1 = actif',
+					'KOHA_MULTI_SITES' => 'WS KOHA : Reservation d\'exemplaires pour les multi sites. 0 = inactif, 1 = actif',
+					'LECTURA_DOMAIN' => 'Domaine utilisée par le serveur lectura pour authentification',
+					'LEKIOSK_ID' => 'Identifiant fournit par lekiosk.com',
+					'TEXT_REPLACEMENTS' => 'Remplacement de textes à la volée. <br/>Ex:<br/>Panier;Sélection<br/>Vous avez %d paniers;Vous avez %d sélections',
+					'ARTICLES_LIST_MODE' => 'Le gestionnaire de contenu affiche les articles sous forme de liste paginée au lieu de d\'une arborescence. Cet affichage est adapté lorsque le nombre d\'article devient trop important.<br /> 1 = Affichage en liste<br /> Autre valeur = Affichage en arborescence.'
+				 ];
 
 		return self::$_knownVars;
 	}
@@ -482,6 +488,50 @@
 		return static::get('WORKFLOW_TEXT_MAIL_ARTICLE_VALIDATED');
 	}
 
+
+	public function validate() {
+		$mapping = ['WORKFLOW' => new ZendAfi_Validate_WorkflowVar(isset($this->_attributes_in_db['valeur']) ? $this->_attributes_in_db['valeur']:'')];
+
+		if (array_key_exists($this->getId(), $mapping)) {
+			if (!$mapping[$this->getId()]->isValid($this->getValeur())) {
+				foreach($mapping[$this->getId()]->getMessages() as $error)
+					$this->addError($error);
+			}
+		}
+	}
+
+
+	public function afterSave() {
+		if ('WORKFLOW' != $this->getId())
+			return;
+
+		$statuses = json_decode($this->getValeur());
+		if (!is_array($statuses)) {
+			Class_Permission::cleanDynamicWorkflow();
+			return;
+		}
+
+		$ids = [];
+		$sorting = 2;
+		foreach ($statuses as $status) {
+			if (!$permission = Class_Permission::findDynamicWorkflow($status->id))
+				$permission = Class_Permission::newDynamicWorkflow($status->id);
+
+			$permission
+				->setDescription($status->label)
+				->setSorting($sorting)
+				->save();
+			$ids[] = $permission->getId();
+			++$sorting;
+		}
+
+		Class_Permission::cleanDynamicWorkflow($ids);
+	}
+
+
+	public static function isArticlesListMode() {
+		return static::isModuleEnabled('ARTICLES_LIST_MODE');
+	}
 }
 
 ?>
diff --git a/library/Class/Album.php b/library/Class/Album.php
index 1020469f4c012c084d8de8999d88c284f61aa645..e797960d9427104414d1c499c105eb4ad253e011 100644
--- a/library/Class/Album.php
+++ b/library/Class/Album.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
  */
 /**
  * utlisation Storm :
@@ -65,7 +65,7 @@ class AlbumLoader extends Storm_Model_Loader {
 
 
 class Class_Album extends Storm_Model_Abstract {
-	use Trait_TimeSource;
+	use Trait_TimeSource, Trait_Indexable;
 
 	const BASE_PATH			= 'album/';
 	const THUMBS_PATH		= 'thumbs/';
@@ -85,8 +85,8 @@ class Class_Album extends Storm_Model_Abstract {
 	const COLLECTION_FIELD = '225';
 	const DISTRIBUTOR_FIELD = '306$a';
 	const THUMB_PREFIX = 'thumb_';
-	
-	
+
+
 	protected static $DEFAULT_THUMBNAIL_VALUES;
 
   protected $_table_name = 'album';
@@ -215,7 +215,7 @@ class Class_Album extends Storm_Model_Abstract {
 	/**
 	 * @param Class_Matiere $matiere
 	 * @return Class_Album
-	 */	
+	 */
 	public function addMatiere($matiere) {
 		$matieres = explode(';', $this->getMatiere());
 		$matieres []= $matiere->getId();
@@ -266,11 +266,11 @@ class Class_Album extends Storm_Model_Abstract {
 		$permalink['action'] = 'notice-thumbnail';
 		return $permalink;
 	}
-	
+
 
 	/**
 	 * @return int
-	 */	
+	 */
 	public function indexOfRessourceByFolio($folio) {
 		$pageno = 1;
 		$ressources = $this->getRessources();
@@ -285,7 +285,7 @@ class Class_Album extends Storm_Model_Abstract {
 
 	/**
 	 * @return int
-	 */	
+	 */
 	public function getRessourceByFolio($folio) {
 		$ressources = $this->getRessources();
 		foreach($ressources as $ressource) {
@@ -312,7 +312,7 @@ class Class_Album extends Storm_Model_Abstract {
 	 * @return mixed
 	 */
 	public function _get($field) {
-		if ($field !== 'cfg_thumbnails' and 
+		if ($field !== 'cfg_thumbnails' and
 				array_key_exists($field, $this->getThumbnailAttributes()))
 			return $this->getThumbnailAttribute($field);
 
@@ -442,7 +442,7 @@ class Class_Album extends Storm_Model_Abstract {
 		return $this->getTypeDocId() == Class_TypeDoc::EPUB;
 	}
 
-	
+
 	public function isAudioRecord() {
 		return $this->getTypeDocId() == Class_TypeDoc::AUDIO_RECORD;
 	}
@@ -484,7 +484,7 @@ class Class_Album extends Storm_Model_Abstract {
 	 * @return string
 	 */
 	public function getBase($prefix) {
-		return str_replace('//', '/', 
+		return str_replace('//', '/',
 											 $prefix.'/'.self::BASE_PATH.'/'.$this->getId().'/');
 	}
 
@@ -604,7 +604,7 @@ class Class_Album extends Storm_Model_Abstract {
 			return true;
 
 		$oldFile = $this->getFichier();
-		if (!$this->_uploadFile('fichier', 
+		if (!$this->_uploadFile('fichier',
 														['jpg', 'gif', 'png', 'jpeg'],
 														[$this->getBasePath() . $oldFile,
 														 $this->getBasePath() . 'thumb_'.$oldFile]))
@@ -622,7 +622,7 @@ class Class_Album extends Storm_Model_Abstract {
 			return true;
 
 		return $this->_uploadFile('pdf',
-															['pdf'], 
+															['pdf'],
 															[$this->getBasePath() . $this->getPdf()]);
 	}
 
@@ -632,7 +632,7 @@ class Class_Album extends Storm_Model_Abstract {
 	}
 
 
-	protected function _uploadFile($name, $extensions, $delete_files) {		
+	protected function _uploadFile($name, $extensions, $delete_files) {
 		$upload = $this
 			->getUploadHandler($name)
 			->setAllowedExtensions($extensions);
@@ -831,7 +831,7 @@ class Class_Album extends Storm_Model_Abstract {
 				$this->addAuthor($v, $authors['fonction'][$k]);
 	}
 
-	
+
 	public function collectEditors() {
 	  $editors = $this->_collect('editors', self::EDITOR_FIELD);
 		if(isset($editors['editor']))
@@ -847,17 +847,17 @@ class Class_Album extends Storm_Model_Abstract {
 				$this->addCollection($v);
 	}
 
-	
+
 	protected function _collect($field, $zone) {
 		if (!isset($this->_attributes[$field]))
 			return [];
 		$fields = $this->_attributes[$field];
 		unset($this->_attributes[$field]);
-	
+
 		$this->clearZones($zone);
 		return $fields;
 	}
-	
+
 
 	public function updateDateMaj() {
 		$this->setDateMaj(date('Y-m-d H:i:s', self::getTimeSource()->time()));
@@ -931,15 +931,15 @@ class Class_Album extends Storm_Model_Abstract {
 	 * @param string $name
 	 * @param array $arguments
 	 * @return mixed
-	 */ 
+	 */
 	public function __call($name, $arguments) {
 		$supportedTypes = ['Image', 'Flash', 'Video', 'File'];
 		$supportedPluralTypes = ['Images', 'Flashs', 'Videos', 'Files'];
 
 		if ('has' === substr($name, 0, 3)) {
 			$type = substr($name, 3);
-			if (('Only' == substr($type, -4)) 
-					&& (in_array(substr($type, 0, strlen($type) - 4), 
+			if (('Only' == substr($type, -4))
+					&& (in_array(substr($type, 0, strlen($type) - 4),
 											 $supportedTypes))) {
 				return $this->_hasOnlyKindOf(substr($type, 0, strlen($type) - 4));
 			}
@@ -959,7 +959,7 @@ class Class_Album extends Storm_Model_Abstract {
 	}
 
 
-	/** 
+	/**
 	 * @param string $type
 	 * @return bool
 	 */
@@ -970,7 +970,7 @@ class Class_Album extends Storm_Model_Abstract {
 		return false;
 	}
 
-	
+
 	/**
 	 * @param string $type
 	 * @return bool
@@ -983,7 +983,7 @@ class Class_Album extends Storm_Model_Abstract {
 	}
 
 
-	/** 
+	/**
 	 * @param string $type
 	 * @return array
 	 */
@@ -997,7 +997,7 @@ class Class_Album extends Storm_Model_Abstract {
 	}
 
 
-	/** 
+	/**
 	 * @return string
 	 */
 	public function getAbsolutePath() {
@@ -1006,7 +1006,7 @@ class Class_Album extends Storm_Model_Abstract {
 
 
 	public function getCategorie() {
-		return ($parent = parent::getCategorie()) ? 
+		return ($parent = parent::getCategorie()) ?
 			$parent : Class_AlbumCategorie::defaultCategory();
 	}
 
@@ -1014,17 +1014,17 @@ class Class_Album extends Storm_Model_Abstract {
 	public function setAnnee($annee) {
 		if (!$annee)
 			return $this->_set('annee', '');
-		return $this->_set('annee', sprintf('%04d', $annee)); 
+		return $this->_set('annee', sprintf('%04d', $annee));
 	}
 
 
 	public function validate() {
 		$next_year = date('Y', strtotime('+1 year'));
-		$this->check((0==(int)$this->getAnnee()) 
-								 || (($this->getAnnee() >= self::ANNEE_MIN) 
-										 and ($this->getAnnee() <= $next_year)), 
-								 sprintf("L'année doit être comprise entre %s et %s", 
-												 self::ANNEE_MIN, 
+		$this->check((0==(int)$this->getAnnee())
+								 || (($this->getAnnee() >= self::ANNEE_MIN)
+										 and ($this->getAnnee() <= $next_year)),
+								 sprintf("L'année doit être comprise entre %s et %s",
+												 self::ANNEE_MIN,
 												 $next_year));
 
 		$this->check(0 < (int)$this->getTypeDocId(), 'L\'album doit avoir un type de document');
@@ -1073,17 +1073,17 @@ class Class_Album extends Storm_Model_Abstract {
 		}
 	}
 
-	
+
 	public function getDatasOfField($field) {
 		return array_filter(
 			array_map(
 				function($note) use ($field) {
-					return is_array($note) && $field === $note['field'] ? 
+					return is_array($note) && $field === $note['field'] ?
 						$note['data'] : null;
 				},
 				$this->getNotesAsArray()));
 	}
-	
+
 
 	/** @return string */
 	public function getVideoUrl() {
@@ -1091,15 +1091,15 @@ class Class_Album extends Storm_Model_Abstract {
 			                                    ['x' => self::VIDEO_URL_TYPE]);
 	}
 
-		
+
 	/**
 	 * @param $url string
 	 * @return Class_Album
 	 */
 	public function setVideoUrl($url) {
 		return $this->setNotes([[
-													 'field' => self::VIDEO_URL_FIELD, 
-													 'data' => ['x' => self::VIDEO_URL_TYPE, 
+													 'field' => self::VIDEO_URL_FIELD,
+													 'data' => ['x' => self::VIDEO_URL_TYPE,
 																			'a' => $url]]]);
 	}
 
@@ -1108,15 +1108,15 @@ class Class_Album extends Storm_Model_Abstract {
 		return $this->getNote(self::DURATION_FIELD);
 	}
 
-	
+
 	public function setDuration($hh_mm_ss) {
 		return $this->addNote(self::DURATION_FIELD, $hh_mm_ss);
 	}
 
-		
+
 	public function setNotes($array_or_string) {
-		parent::setNotes((is_array($array_or_string)) ? 
-										 serialize($array_or_string) : 
+		parent::setNotes((is_array($array_or_string)) ?
+										 serialize($array_or_string) :
 										 $array_or_string);
 		return $this;
 	}
@@ -1142,7 +1142,7 @@ class Class_Album extends Storm_Model_Abstract {
 	}
 
 
-	/** 
+	/**
 	 * @return int
 	 */
 	public function getThumbnailHeight() {
@@ -1153,7 +1153,7 @@ class Class_Album extends Storm_Model_Abstract {
 	}
 
 
-	/** 
+	/**
 	 * @return array
 	 */
 	public function getThumbnailAttributes() {
@@ -1163,7 +1163,7 @@ class Class_Album extends Storm_Model_Abstract {
 	}
 
 
-	/** 
+	/**
 	 * @return int
 	 */
 	public function getFirstThumbnailSize() {
@@ -1177,7 +1177,7 @@ class Class_Album extends Storm_Model_Abstract {
 	}
 
 
-	/** 
+	/**
 	 * @return int
 	 */
 	public function getNavigatorThumbnailWidth() {
@@ -1185,7 +1185,7 @@ class Class_Album extends Storm_Model_Abstract {
 	}
 
 
-	/** 
+	/**
 	 * @return array
 	 */
 	public function toArray() {
@@ -1207,8 +1207,8 @@ class Class_Album extends Storm_Model_Abstract {
 		return parent::toArray();
 	}
 
-	
-	/** 
+
+	/**
 	 * @return array
 	 */
 	public function getNatureDocIds() {
@@ -1217,13 +1217,13 @@ class Class_Album extends Storm_Model_Abstract {
 
 
 	public function setNatureDocIds($ids) {
-		if (!is_array($ids)) 
+		if (!is_array($ids))
 			$ids = [];
 		return $this->setNatureDoc(implode(';', $ids));
 	}
 
 
-	/** 
+	/**
 	 * @return Class_Album
 	 */
 	public function sortRessourceByFileName() {
@@ -1244,7 +1244,7 @@ class Class_Album extends Storm_Model_Abstract {
 		return sprintf('%03d', $this->getRessourcesCount());
 	}
 
-	
+
 	public function isGallica() {
 		return ($this->isOAI() && (false !== strpos($this->getIdOrigine(), 'gallica')));
 	}
@@ -1257,9 +1257,9 @@ class Class_Album extends Storm_Model_Abstract {
 	}
 
 
-	/** 
+	/**
 	 * Return arteVOD trailer video list
-	 * @return array 
+	 * @return array
 	 */
 	public function getTrailers() {
 		$trailers = [];
@@ -1270,9 +1270,9 @@ class Class_Album extends Storm_Model_Abstract {
 	}
 
 
-	/** 
+	/**
 	 * Return arteVOD poster url
-	 * @return string 
+	 * @return string
 	 */
 	public function getPoster() {
 		if ($posters_url = $this->getUnimarc856Values('poster'))
@@ -1297,18 +1297,18 @@ class Class_Album extends Storm_Model_Abstract {
 	}
 
 
-	/** 
+	/**
 	 * Return arteVOD poster url
-	 * @return string 
+	 * @return string
 	 */
 	public function getUnimarc856Values($field) {
 		$values = [];
 		$unimarc_array = $this->getNotesAsArray();
 		foreach($unimarc_array as $unimarc_value) {
-			if (!is_array($unimarc_value) 
-					|| !isset($unimarc_value['field']) 
-					|| '856' !== $unimarc_value['field'] 
-					|| !isset($unimarc_value['data']) 
+			if (!is_array($unimarc_value)
+					|| !isset($unimarc_value['field'])
+					|| '856' !== $unimarc_value['field']
+					|| !isset($unimarc_value['data'])
 					|| !isset($unimarc_value['data']['x'])
 					|| !isset($unimarc_value['data']['a'])
 					|| $field !== $unimarc_value['data']['x']
@@ -1325,7 +1325,7 @@ class Class_Album extends Storm_Model_Abstract {
 	 * @return array key/value pour les zones 856$x (donnée privé/ type URI) $a (URI)
 	 */
 	protected function _getUnimarcForType($type, $url) {
-		return ['field' => '856', 
+		return ['field' => '856',
 						'data' => ['x' => $type, 'a' => $url]];
 	}
 
@@ -1334,12 +1334,12 @@ class Class_Album extends Storm_Model_Abstract {
 		return $this->addURIInNotes(self::URI_TYPE_POSTER, $uri);
 	}
 
-	
+
 	public function addThumbnailURI($uri) {
 		return $this->addURIInNotes(self::URI_TYPE_THUMNAIL, $uri);
 	}
 
-	
+
 	public function addTrailerURI($uri) {
 		return $this->addURIInNotes(self::URI_TYPE_TRAILER, $uri);
 	}
@@ -1371,7 +1371,7 @@ class Class_Album extends Storm_Model_Abstract {
 
 	public function addZone($zone, $sous_zones) {
 		$notes = $this->getNotesAsArray();
-		$notes[] = ['field' => $zone, 
+		$notes[] = ['field' => $zone,
 								'data' => $sous_zones];
 		return $this->setNotes($notes);
 	}
@@ -1402,13 +1402,13 @@ class Class_Album extends Storm_Model_Abstract {
 	protected function _clearZones($zones) {
 		$notes = $this->getNotesAsArray();
 		$to_delete = [];
-		
+
 		foreach($zones as $zone)
 			$to_delete = array_merge($to_delete, $this->_clearZone($zone,$notes));
-		
+
 		foreach($to_delete as $i)
 			unset($notes[$i]);
-		
+
 		return $notes;
 	}
 
@@ -1428,8 +1428,8 @@ class Class_Album extends Storm_Model_Abstract {
 			$visitor->visitRessource($ressource, $index);
 	}
 
-	
-	/** 
+
+	/**
 	 * @return boolean
 	 */
 	public function isVisible() {
@@ -1450,18 +1450,28 @@ class Class_Album extends Storm_Model_Abstract {
 	public function getIcoInfoTitle() {
 		return '';
 	}
-	
+
 
 	public function getIcoInfo() {
 			return 'ico/liste.gif';
 	}
 
-	
+
 	public function index() {
 		return Class_Indexation_PseudoNotice::index($this);
 	}
 
 
+	/**
+	 * Main author
+	 * @return string
+	 */
+	public function getMainAuthor() {
+		$authors = $this->getAuthorsNames();
+		return isset($authors[0]) ? $authors[0] : '';
+	}
+
+
 	/**
 	 * Author names from unimarc container
 	 * @return array
@@ -1470,9 +1480,9 @@ class Class_Album extends Storm_Model_Abstract {
 		return array_filter(
 			array_map(
 				function($item) {
-					return (!is_array($item) || !isset($item['a'])) ? 
+					return (!is_array($item) || !isset($item['a'])) ?
 						null : $item['a'];
-				}, 
+				},
 				$this->getAuthors()));
 	}
 
@@ -1486,9 +1496,9 @@ class Class_Album extends Storm_Model_Abstract {
 			array_map(
 				function($item) {
 					return (!is_array($item) || self::AUTHOR_FIELD !== $item['field']
-									|| !isset($item['data']) || !isset($item['data']['a'])) ? 
+									|| !isset($item['data']) || !isset($item['data']['a'])) ?
 						null : $item['data'];
-				}, 
+				},
 				$this->getNotesAsArray()));
 	}
 
@@ -1519,23 +1529,23 @@ class Class_Album extends Storm_Model_Abstract {
 			array_map(
 				function($item) {
 					return (!is_array($item) || self::COLLECTION_FIELD !== $item['field']
-									|| !isset($item['data']) || !isset($item['data']['a'])) ? 
+									|| !isset($item['data']) || !isset($item['data']['a'])) ?
 						null : $item['data']['a'];
-				}, 
+				},
 				$this->getNotesAsArray()));
 	}
-	
+
 
 	public function getDistributor() {
 		return $this->getNote(self::DISTRIBUTOR_FIELD);
 	}
 
-	
+
 	public function setDistributor($distributor) {
 		$this->addNote(self::DISTRIBUTOR_FIELD, $distributor);
 		return $this;
 	}
-	
+
 
 	/**
 	 * add author in unimarc container if not present with the same function
@@ -1551,7 +1561,7 @@ class Class_Album extends Storm_Model_Abstract {
 		if (!in_array((string)$name, $this->getAuthorsNames()))
 			return $this->addZone(self::AUTHOR_FIELD, $datas);
 
-		if (!$function) 
+		if (!$function)
 			return $this;
 
 		$found = false;
@@ -1559,7 +1569,7 @@ class Class_Album extends Storm_Model_Abstract {
 			if ($name == $author['a'] && $function == $author['4']) {
 				$found = true;
 				break;
-			}	
+			}
 		}
 
 		return (!$found) ? $this->addZone(self::AUTHOR_FIELD, $datas) : $this;
@@ -1572,7 +1582,7 @@ class Class_Album extends Storm_Model_Abstract {
 	public function addEditor($name) {
 		if (!$name)
 			return $this;
-		
+
 		return $this->addSubField('210', 'c', $name);
 	}
 
@@ -1583,8 +1593,21 @@ class Class_Album extends Storm_Model_Abstract {
 	public function addCollection($name) {
 		if (!$name)
 			return $this;
-		
+
 		return $this->addZone(self::COLLECTION_FIELD,['a' => $name]);
 	}
+
+
+	public function acceptClefAlphaVisitor($visitor) {
+		$authors = $this->getAuthorsNames();
+		$editors = $this->getEditors();
+
+		$visitor->visitTitre($this->getTitre())
+						->visitComplementTitre($this->getSousTitre())
+						->visitAuteur(implode($authors))
+						->visitTypeDocId($this->getTypeDocId())
+						->visitEditeur(array_shift($editors))
+						->visitAnnee($this->getAnnee());
+	}
 }
 ?>
\ No newline at end of file
diff --git a/library/Class/Article.php b/library/Class/Article.php
index 7f89f542a43e74fdd8878d161489fb0e0e30df37..c4125907fdda387a8593a368b59f725db9b30cac 100644
--- a/library/Class/Article.php
+++ b/library/Class/Article.php
@@ -20,6 +20,7 @@
  */
 
 class ArticleLoader extends Storm_Model_Loader {
+
 	/** @var Zend_Db_Table_Select */
 	protected $_select;
 	protected $_sort_order;
@@ -413,13 +414,25 @@ class ArticleLoader extends Storm_Model_Loader {
 		return Class_Article::findAll('select id_article,titre from cms_article where id_article in (select distinct id_article from formulaires)');
 	}
 
+
+	public function indexAll() {
+		$page = 1;
+		while($articles_to_index = Class_Article::findAllBy(['indexation' => '1',
+																												 'limitPage' => [$page, 500]])) {
+			foreach($articles_to_index as $article)
+				$article->index();
+
+			Class_Article::clearCache();
+			$page++;
+		}
+	}
 }
 
 
 
 
 class Class_Article extends Storm_Model_Abstract {
-	use Trait_TreeViewableItem, Trait_HasManyDomaines, Trait_TimeSource;
+	use Trait_TreeViewableItem, Trait_HasManyDomaines, Trait_TimeSource, Trait_Indexable, Trait_CustomFields;
 
 	const END_TAG='{FIN}';
 
@@ -470,6 +483,9 @@ class Class_Article extends Storm_Model_Abstract {
 														'article_original' => ['model' => 'Class_Article',
 																									 'referenced_in' => 'parent_id'],
 
+														'notice' => ['model' => 'Class_Notice',
+																				 'referenced_in' => 'id_notice'],
+
 														'bib' => ['through' => 'categorie'],
 
 														'lieu' => ['model' => 'Class_Lieu',
@@ -621,7 +637,7 @@ class Class_Article extends Storm_Model_Abstract {
 
 	public function getRefusMessage() {
 		if (!parent::_get('refus_message'))
-			return  Class_AdminVar::getWorkflowTextMailArticleRefused();
+			return Class_AdminVar::getWorkflowTextMailArticleRefused();
 		return parent::_get('refus_message');
 	}
 
@@ -733,26 +749,37 @@ class Class_Article extends Storm_Model_Abstract {
 	 */
 	public function _set($field, $value) {
 		if ($this->shouldOverrideAttribute($field))
-			return parent::_set($field, $value);
+			return parent::_set($field,$value);
 
-		if ($this->isTraduction()) {
+		if ($this->isTraduction())
 			return $this->getArticleOriginal()->_set($field, $value);
-		} else {
-			return parent::_set($field, $value);
-		}
+
+		return parent::_set($field, $value);
+
 	}
 
 
 	protected function _updateDateMaj() {
-		$date = new Class_Date();
-		$this->setDateMaj($date->DateTimeDuJour());
+		$this->setDateMaj(date('Y-m-d H:i:s', $this->getCurrentTime()));
 	}
 
 	public function beforeSave() {
 		$this->_updateDateMaj();
 
-		if ($this->isNew())
+		if ($this->isNew() && !$this->getDateCreation())
 			$this->setDateCreation($this->getDateMaj());
+
+		$this->unindex();
+	}
+
+
+	protected function hasToBeIndexed() {
+		return $this->_get('indexation') == 1;
+	}
+
+
+	public function getTypeDocId() {
+		return Class_TypeDoc::ARTICLE;
 	}
 
 
@@ -923,12 +950,10 @@ class Class_Article extends Storm_Model_Abstract {
 	 * @return Class_Article
 	 */
 	public function setStatus($value) {
-		if (
-			Class_AdminVar::isWorkflowEnabled()
-			&& (array_key_exists((int)$value, self::$_knownStatus))
-			) {
+		if (Class_AdminVar::isWorkflowEnabled()
+				&& (array_key_exists((int)$value, self::getKnownStatus()))
+		)
 			return $this->_set('status', (int)$value);
-		}
 
 		return $this;
 	}
@@ -1038,7 +1063,33 @@ class Class_Article extends Storm_Model_Abstract {
 	 * @return array
 	 */
 	public static function getKnownStatus() {
-		return self::$_knownStatus;
+		$statuses = [];
+		foreach (self::$_knownStatus as $k => $v) {
+			if (self::STATUS_VALIDATED == $k)
+				self::_addDynamicStatuses($statuses);
+			$statuses[$k] = $v;
+		}
+
+		return $statuses;
+	}
+
+
+	protected static function _addDynamicStatuses(&$statuses) {
+		if (!self::hasCustomStatuses())
+			return;
+
+		foreach(self::getDynamicStatuses() as $custom_status)
+			$statuses[$custom_status->id] = $custom_status->label;
+	}
+
+
+	public static function hasCustomStatuses() {
+		return is_array(self::getDynamicStatuses());
+	}
+
+
+	protected static function getDynamicStatuses() {
+		return json_decode(Class_AdminVar::get('WORKFLOW'));
 	}
 
 
@@ -1066,7 +1117,9 @@ class Class_Article extends Storm_Model_Abstract {
 
 
 	public function getRank() {
-		return Class_CmsRank::getLoader()->findFirstBy(array('id_cms' => $this->getId()));
+		if (!$rank = Class_CmsRank::findFirstBy(['id_cms' => $this->getId()]))
+			$rank = Class_CmsRank::newInstance(['id_cms' => $this->getId()]);
+		return $rank;
 	}
 
 
@@ -1169,11 +1222,21 @@ class Class_Article extends Storm_Model_Abstract {
 			return $matches[3] . '-' . $matches[2] . '-' . $matches[1] . $matches[4];
 		}
 
+
+		if (preg_match('/^(\d{4})-(\d{2})-(\d{2})( \d{2}:\d{2})?/', $str, $matches)) {
+			$matches[4] = isset($matches[4]) ? $matches[4] : ' 00:00';
+			return $matches[1] . '-' . $matches[2] . '-' . $matches[3] . $matches[4];
+		}
+
 		$locale = Zend_Registry::get('locale');
 		$date = new Zend_Date($str, null, $locale);
 		return $date->toString('YYYY-MM-dd HH:mm');
 	}
 
-}
 
-?>
+	public function acceptClefAlphaVisitor($visitor) {
+		$visitor->visitTitre($this->getTitre())
+						->visitTypeDocId($this->getTypeDocId());
+	}
+}
+?>
\ No newline at end of file
diff --git a/library/Class/ArticleCategorie.php b/library/Class/ArticleCategorie.php
index 736af092b8b994c4e408e78c6b90bc27c8d19d0a..2b0d8dfe264f7c0637cc817686364e9129dd532c 100644
--- a/library/Class/ArticleCategorie.php
+++ b/library/Class/ArticleCategorie.php
@@ -21,6 +21,7 @@
 
 
 class ArticleCategorieLoader extends Storm_Model_Loader {
+
 	public function getRoot() {
 		return Class_ArticleCategorie::newInstanceWithId(null,
 																										 ['sous_categories' => Class_ArticleCategorie::findTopCategories()]);
@@ -36,37 +37,39 @@ class ArticleCategorieLoader extends Storm_Model_Loader {
 
 
 class Class_ArticleCategorie extends Storm_Model_Abstract {
-	use
-		Trait_TreeViewableCategorie,
-		Trait_TreeNode;
+	use	Trait_TreeViewableCategorie, Trait_TreeNode,Trait_PermissionTargetable, Trait_CustomFields;
+
 
 	protected
 		$_table_name = 'cms_categorie',
 		$_table_primary = 'ID_CAT',
 		$_loader_class = 'ArticleCategorieLoader',
 
-		$_belongs_to = array('parent_categorie' => array('model' => 'Class_ArticleCategorie',
-																										 'referenced_in' => 'id_cat_mere'),
-												 'bib' => array('model' => 'Class_Bib',
-																				'referenced_in' => 'id_site')),
+		$_belongs_to = ['parent_categorie' => ['model' => 'Class_ArticleCategorie',
+																					 'referenced_in' => 'id_cat_mere'],
+
+										'bib' => ['model' => 'Class_Bib',
+															'referenced_in' => 'id_site']],
+
+		$_has_many = ['sous_categories' => ['model' => 'Class_ArticleCategorie',
+																				'role' => 'parent_categorie',
+																				'dependents' => 'delete',
+																				'order' => 'libelle'],
 
-		$_has_many = array('sous_categories' => array('model' => 'Class_ArticleCategorie',
-																									'role' => 'parent_categorie',
-																									'dependents' => 'delete',
-																									'order' => 'libelle'),
+									'articles' => ['model' => 'Class_Article',
+																 'role' => 'categorie',
+																 'dependents' => 'delete',
+																 'order' => 'titre']],
 
-											 'articles' => array('model' => 'Class_Article',
-																					 'role' => 'categorie',
-																					 'dependents' => 'delete',
-																					 'order' => 'titre')),
+	$_default_attribute_values = ['id_site' => 0,
+																'id_cat_mere' => 0,
+																'libelle' => ''];
 
-	$_default_attribute_values = array(
-		'id_site' => 0,
-		'id_cat_mere' => 0,
-		'libelle' => '');
 
-	public static function getLoader() {
-		return self::getLoaderFor(__CLASS__);
+	public function beforeSave() {
+		$filter = new Zend_Filter_StripTags();
+		$this->setLibelle(trim($filter->filter($this->getLibelle())));
+
 	}
 
 
@@ -139,5 +142,46 @@ class Class_ArticleCategorie extends Storm_Model_Abstract {
 
 		return '['.implode(',', $data).']';
 	}
+
+
+	public function afterDelete() {
+		$ids = array_map(function($model) { return $model->getId(); },
+										 Class_Permission::getCmsPermissions());
+
+		if (empty($ids))
+			return;
+
+		Class_UserGroup_Permission::deleteBy(['id_permission' => $ids,
+																					'id_model' => $this->getId()]);
+	}
+
+
+	/** @see Trait_PermissionTargetable */
+	public function getPermissionsEditUrl($view) {
+		return $view->url(['module' => 'admin',
+											 'controller' => 'cms-category',
+											 'action' => 'edit',
+											 'id' => $this->getId()],
+											null, true);
+	}
+
+
+	/** @see Trait_PermissionTargetable */
+	public function getDefaultPermissionsHolder() {
+		return $this->getBib();
+	}
+
+
+	/** @see Trait_PermissionTargetable */
+	public function getPermissionsParent() {
+		return $this->getParentCategorie();
+	}
+
+
+	/** @see Trait_PermissionTargetable */
+	public function getPermissionsChildren() {
+		return $this->getSousCategories();
+	}
 }
-?>
+
+?>
\ No newline at end of file
diff --git a/library/Class/Avis.php b/library/Class/Avis.php
index f878a6cb81cf6aadfaeceaa967f61297ff6bc31f..9181071006baa41775c32bba0f207e5011db1499 100644
--- a/library/Class/Avis.php
+++ b/library/Class/Avis.php
@@ -23,7 +23,7 @@
  * Gestion des Avis sur les articles
  */
 class Class_Avis extends Storm_Model_Abstract {
-	use Trait_Avis;
+	use Trait_Avis, Trait_TimeSource, Trait_Translator;
 
 	protected $_table_name = 'cms_avis';
 	protected $_belongs_to = ['auteur' => ['model' => 'Class_Users',
@@ -35,37 +35,15 @@ class Class_Avis extends Storm_Model_Abstract {
 														'user' => ['model' => 'Class_Users',
 																			 'referenced_in' => 'id_user']];
 
-	var $sql;	// Curseur sql
-	var $_today;
+	protected $_default_attribute_values = ['entete' => '',
+																					'avis' => '',
+																					'note' => 0	];
 
 
-	public function __construct() {
-		$this->sql = Zend_Registry::get('sql');
-		$class_date = new Class_Date();
-		$this->_today = $class_date->DateTimeDuJour();
-	}
-
-
-	public function getNomAff() {
-		if (!$auteur = $this->getAuteur())
-			return 'Anonyme';
-		return $auteur->getNomAff();
-	}
-
-
-	public function beValid() {
-		return $this->setStatut(1);
-	}
-
-
-	public function getCmsAvisById($id_user,$id_news) {
-		$req_news = "Select * from cms_avis Where ID_CMS=$id_news AND ID_USER=$id_user";
-		return $this->sql->fetchAll($req_news);
-	}
 
 
 	/** Ecrire 1 avis (update si existe déjà) */
-	public function ecrireCmsAvis($id_user, $role_level, $id_news, $note, $entete, $avis) {
+	public static function ecrireCmsAvis($id_user, $role_level, $id_news, $note, $entete, $content) {
 		$modo_avis_abo = getVar('MODO_AVIS'); // 0 apres / 1 avant de publier sur le site
 		$modo_avis_bib = getVar('MODO_AVIS_BIBLIO'); // 0 apres / 1 avant de publier sur le site
 
@@ -79,85 +57,69 @@ class Class_Avis extends Storm_Model_Abstract {
 			if($modo_avis_bib == 0) $statut = 1;
 		}
 
-		try {
-			$avis = Class_Avis::getLoader()->findFirstBy(['id_user' => $id_user,
-																										'id_cms' => $id_news]);
-
-			if (!$avis)
-				$avis = Class_Avis::newInstance(['id_user' => $id_user,
-																				 'id_notice' => '',
-																				 'id_cms' => $id_news,
-																				 'date_avis' => $this->_today,
-																				 'date_mod' => '']);
-
-			$avis
-				->setEntete(strLeft($entete, 100))
-				->setAvis($avis)
-				->setNote($note)
-				->setStatut($statut)
-				->setAbonOuBib($abon_ou_bib)
-				->save();
-
-			if (($modo_avis_abo == 0 && $role_level < 3)
-					|| ($modo_avis_bib == 0 && $role_level >= 3))
-				$this->maj_note_cms($id_news, $abon_ou_bib);
-
-		} catch (Exception $e) {
-			logErrorMessage('Class: Class_Avis; Function: ecrireCmsAvis' . NL . $req . NL . $e->getMessage());
-			return false;
-		}
+		$avis = Class_Avis::getLoader()->findFirstBy(['id_user' => $id_user,
+																									'id_cms' => $id_news]);
+
+		if (!$avis)
+			$avis = Class_Avis::getLoader()->newInstance(['id_user' => $id_user,
+																										'id_notice' => '',
+																										'id_cms' => $id_news,
+																										'date_avis' => date('Y-m-d H:i:s', self::getTimeSource()->time()),
+																										'date_mod' => '']);
+		$avis
+			->setEntete(strLeft($entete, 100))
+			->setAvis($content)
+			->setNote($note)
+			->setStatut($statut)
+			->setAbonOuBib($abon_ou_bib)
+			->save();
+
+		if (($modo_avis_abo == 0 && $role_level < 3)
+				|| ($modo_avis_bib == 0 && $role_level >= 3))
+			$avis->majNoteCms($id_news, $abon_ou_bib);
+
+		return $avis;
 	}
 
 
-	public function maj_note_cms($id_news, $abon_ou_bib)	{
-		$sqlStmt = "Select count(*), avg(NOTE) From cms_avis Where ID_CMS=$id_news and ABON_OU_BIB='$abon_ou_bib' AND STATUT=1";
-		$data = $this->sql->fetchAll($sqlStmt);
-		$note=round($data[0]["avg(NOTE)"],1);
-		if (strlen($note)==1)
-			$note.=".0";
-		else	{
-			$dec=strRight($note,1);
-			if($dec<3) $dec="0"; elseif($dec<"8")$dec="5";else {$note+=1; $dec=0;}
-			$note=strLeft($note,1).".".$dec;
-		}
 
-		$nombre=$data[0]["count(*)"];
-				// Lire enreg rank
-		$enreg=fetchEnreg("select * from cms_rank where ID_CMS=$id_news");
-		$sqlStmt = "Delete from cms_rank Where ID_CMS=$id_news";
-		$stmt = $this->sql->query($sqlStmt);
-		if($abon_ou_bib==1) {
-			$abon_nombre_avis=$enreg["ABON_NOMBRE_AVIS"];
-			$abon_note=$enreg["ABON_NOTE"];
-			$bib_nombre_avis=$nombre;
-			$bib_note=$note;
-		} else {
-			$abon_nombre_avis=$nombre;
-			$abon_note=$note;
-			$bib_nombre_avis=$enreg["BIB_NOMBRE_AVIS"];
-			$bib_note=$enreg["BIB_NOTE"];
-		}
+	public function getNomAff() {
+		if (!$auteur = $this->getAuteur())
+			return $this->_('Anonyme');
+		return $auteur->getNomAff();
+	}
 
-		if(!$abon_nombre_avis) $abon_nombre_avis=0;
-		if(!$bib_nombre_avis) $bib_nombre_avis=0;
 
-		$sqlStmt = "Insert Into cms_rank(ID_CMS,ABON_NOMBRE_AVIS,ABON_NOTE,BIB_NOMBRE_AVIS,BIB_NOTE) Values($id_news,$abon_nombre_avis,'$abon_note',$bib_nombre_avis,'$bib_note')";
-		$stmt = $this->sql->query($sqlStmt);
+	public function beValid() {
+		return $this->setStatut(1);
 	}
 
 
-	public function HtmlCmsAvecAvis($id_news) {
-		$req_news = "Select * from cms_article Where ID_ARTICLE=$id_news";
-		$news = $this->sql->fetchAll($req_news);
+	public function majNoteCms($id_news, $abon_ou_bib)	{
+		$note = 0;
+		if ( $all_avis = Class_Avis::getLoader()->findAllBy(['id_cms' => $id_news,
+																												 'abon_ou_bib' => $abon_ou_bib,
+																												 'statut' => 1])) {
+			$note = array_sum(array_map(function($a){return $a->getNote();}, $all_avis)) / count($all_avis);
+		}
 
-		if ($news[0]["AVIS"] != 1)
-			return ' ';
+		$note = sprintf("%01.1f", floor($note * 2)/2);
+		$nombre = count($all_avis);
 
-		$sqlStmt = "Select * from cms_rank Where ID_CMS=$id_news";
-		$ret = $this->sql->fetchAll($sqlStmt);
+		$rank = Class_Article::find($id_news)->getRank();
 
-		$nb_avis = (int)$ret[0]["BIB_NOMBRE_AVIS"] + (int)$ret[0]["ABON_NOMBRE_AVIS"];
-		$label = ($nb_avis == 0) ? 'aucun avis' : ' avis ('.$nb_avis.')';
-		return '<div align="right"><a href="'.BASE_URL.'/cms/articleview/id/'.$id_news.'">&raquo;' . $label . '</a></div>';
+		if ($abon_ou_bib == static::$AVIS_BIBLIO) {
+			$rank
+				->setBibNombreAvis($nombre)
+				->setBibNote($note);
+		} else {
+			$rank
+				->setAbonNombreAvis($nombre)
+				->setAbonNote($note);
+		}
+
+		$rank->save();
 	}
-}
\ No newline at end of file
+}
+
+?>
\ No newline at end of file
diff --git a/library/Class/Batch.php b/library/Class/Batch.php
index 864b4938d8e88ba266a5afb29790010d5c4b22ec..43729e2926a8c315bde4bf608ac7277c8a123ea9 100644
--- a/library/Class/Batch.php
+++ b/library/Class/Batch.php
@@ -29,7 +29,8 @@ class Class_BatchLoader extends Storm_Model_Loader{
 						'COMMENT_REALLOCATION'=> new Class_Batch_AvisNotice(),
 						'INDEX_RESSOURCES_NUMERIQUES' => new Class_Batch_IndexRessourcesNumeriques(),
 						'AUTOCOMPLETE_RECORD_TITLE' => new Class_Batch_AutocompleteRecordTitle(),
-						'AUTOCOMPLETE_RECORD_AUTHOR' => new Class_Batch_AutocompleteRecordAuthor()
+						'AUTOCOMPLETE_RECORD_AUTHOR' => new Class_Batch_AutocompleteRecordAuthor(),
+						'BUILD_SITE_MAP' => new Class_Batch_BuildSiteMap()
 			]);
 	}
 
diff --git a/library/Class/Batch/BuildSiteMap.php b/library/Class/Batch/BuildSiteMap.php
new file mode 100644
index 0000000000000000000000000000000000000000..3f1558dfa8b888bcab7f64b8f20a5e0b2dd49be4
--- /dev/null
+++ b/library/Class/Batch/BuildSiteMap.php
@@ -0,0 +1,64 @@
+<?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 Class_Batch_BuildSiteMap extends Class_Batch_Abstract {
+  public function getLabel() {
+    return $this->_('Régénère le sitemap XML');
+  }
+
+	public function run() {
+
+		$sitemap = new Class_Sitemap();
+		$profiles = $this->sitemapProfiles();
+
+		if (empty($profiles)) {
+			Class_Sitemap::removeSitemap();
+			return;
+		}
+
+		foreach ($profiles as $profile) {
+			$sitemap->addUrl($profile['url'], $profile['priority']);
+		}
+
+		$sitemap->save();
+	}
+
+  protected function sitemapProfiles() {
+    $profiles = Class_Profil::findAll();
+    $sitemap_profiles = [];
+    foreach ($profiles as $profile) {
+      if (!$profile->isPublic())
+        continue;
+			
+			if (0 == (int) $profile->getCfgAccueilParam('sitemap'))
+				continue;
+
+      $url = ROOT_URL . BASE_URL . ($profile->getRewriteUrl() ? '/'.$profile->getRewriteUrl() : '?id_profil='.$profile->getId());
+
+      $priority = $profile->hasParentProfil() ? '0.5' : '0.8';
+      $priority = $profile->getId() == 1 ? '1.0' : $priority;
+      $sitemap_profiles[$profile->getId()] = ['url' => $url,
+                                              'priority' => $priority];
+    }
+    return $sitemap_profiles;
+  }
+}
diff --git a/library/Class/Bib.php b/library/Class/Bib.php
index 11357f4840092928b25df64979567f35977385c2..53f37020d44ceac39ad8bf2734c432871f640ed3 100644
--- a/library/Class/Bib.php
+++ b/library/Class/Bib.php
@@ -135,6 +135,8 @@ class BibLoader extends Storm_Model_Loader {
 
 
 class Class_Bib extends Storm_Model_Abstract {
+	use Trait_PermissionTargetable;
+
 	const V_INVISIBLE = 0;
 	const V_NODATA = 1;
 	const V_DATA = 2;
@@ -248,23 +250,24 @@ class Class_Bib extends Storm_Model_Abstract {
 
 	public function getAffZoneAsArray() {
 		if (!$aff_zone = ZendAfi_Filters_Serialize::unserialize($this->getAffZone()))
-			$aff_zone = array('profilID' => null,
+			$aff_zone = [];
+
+		return array_merge(['profilID' => null,
 												'libelle' => '',
 												'posX' => 0,
 												'posY' => 0,
-												'posPoint' => '');
-		return $aff_zone;
+												'posPoint' => ''],
+											 $aff_zone);
 	}
 
 
 	public function getUrl() {
 		$props = $this->getAffZoneAsArray();
-		if (array_isset("profilID", $props) and (1 < ($profilID = $props["profilID"]))) // mode moulins => adresse du profil précisé en config
-			return sprintf("%s?id_profil=%d",BASE_URL, $profilID);
-
-		return sprintf("%s/bib/bibview/id/%d",
-									 BASE_URL,
-									 $this->getId()); //mode calice => pointe vers la bib
+		$profil = Class_Profil::find($props["profilID"]);
+		$parts = $profil ? $profil->getUrlParts() : ['controller' => 'bib',
+																								 'action' => 'bibview',
+																								 'id' => $this->getId()];
+		return Class_Url::assemble($parts, null, true);
 	}
 
 
@@ -384,39 +387,6 @@ class Class_Bib extends Storm_Model_Abstract {
 	}
 
 
-	// Lire les bibs par une id_zone =============== OLD FONCTION =========
-
-	public function getAllBibByIdZone($id_zone=0)
-	{
-		try
-			{
-				$BibCSite = new BibCSite();
-				if($id_zone == 0)
-					{
-						$select = $BibCSite->getAdapter()->select()
-															 ->from('bib_c_site',array('ID_SITE'=>'ID_SITE','LIBELLE'=>'LIBELLE', 'ID_ZONE'=>'ID_ZONE','VILLE'=>'VILLE','MAIL'=>'MAIL','URL_WEB'=>'URL_WEB','TELEPHONE'=>'TELEPHONE','VISIBILITE'=>'VISIBILITE'))
-															 ->where('ID_ZONE >=?', 0)
-															 ->order('VILLE');
-						$stmt = $select->query();
-						$row = $stmt->fetchAll();
-						return $row;
-					}
-				else
-					{
-						$select = $BibCSite->getAdapter()->select()
-															 ->from('bib_c_site',array('ID_SITE'=>'ID_SITE','LIBELLE'=>'LIBELLE', 'ID_ZONE'=>'ID_ZONE','VILLE'=>'VILLE','MAIL'=>'MAIL','URL_WEB'=>'URL_WEB','TELEPHONE'=>'TELEPHONE','VISIBILITE'=>'VISIBILITE'))
-															 ->where('ID_ZONE=?', $id_zone)
-															 ->order('VILLE');
-						$stmt = $select->query();
-						$row = $stmt->fetchAll();
-						return $row;
-					}
-			}catch (Exception $e)
-				{
-					logErrorMessage('Class: Class_Zone; Function: getAllBibByIdZone' . NL . $e->getMessage());
-					return $this->_dataBaseError;
-				}
-	}
 
 	public function getBibById($id_bib)
 	{
@@ -650,5 +620,20 @@ class Class_Bib extends Storm_Model_Abstract {
 	public function getArticleCategoriesToJson() {
 		return $this->articlesToJSON(false);
 	}
+
+
+	/** @see Trait_PermissionTargetable */
+	public function getPermissionsEditUrl($view) {
+		return $view->url(['module' => 'admin',
+											 'controller' => 'bib',
+											 'action' => 'permissions',
+											 'id' => (int)$this->getId()], null, true);
+	}
+
+
+	/** @see Trait_PermissionTargetable */
+	public function getPermissionsChildren() {
+		return $this->getArticleCategories();
+	}
 }
 ?>
\ No newline at end of file
diff --git a/library/Class/CVSLink.php b/library/Class/CVSLink.php
index 8b4a54695691354d6784689d03cf06bbcc50c88c..124020e72f1cd4d4280a7b3a9e6221dd8508346a 100644
--- a/library/Class/CVSLink.php
+++ b/library/Class/CVSLink.php
@@ -128,23 +128,23 @@ class Class_CVSLink extends Class_WebService_Abstract {
       <adhid>".$loginCVS.
      "</adhid>
       <action>{$action}</action>
-    </header>";
+    </header>
+    <body>";
     if($action == "acces_site"){
-			$xml .= "<body>
-          <querystring><![CDATA[".urldecode($body['querystring'])."]]></querystring>
-          <affichage>{$body['affichage']}</affichage>
-        </body>";
+			$xml .= "
+        <querystring><![CDATA[".urldecode($body['querystring'])."]]></querystring>
+        <affichage>{$body['affichage']}</affichage>
+      ";
     } elseif($action == "search_document"){
-			$xml .= "<body>
-          <q><![CDATA[".$body['q']."]]></q>
-          <espace><![CDATA[".$body['espace']."]]></espace>
-          <classement><![CDATA[".$body['classement']."]]></classement>
-          <page><![CDATA[".$body['page']."]]></page>
-          <nombre_par_page><![CDATA[".$body['nombre_par_page']."]]></nombre_par_page>
-          $xml_user_infos
-        </body>";
+			$xml .= "
+        <q><![CDATA[".$body['q']."]]></q>
+        <espace><![CDATA[".$body['espace']."]]></espace>
+        <classement><![CDATA[".$body['classement']."]]></classement>
+        <page><![CDATA[".$body['page']."]]></page>
+        <nombre_par_page><![CDATA[".$body['nombre_par_page']."]]></nombre_par_page>
+      ";
     }
-		$xml .= "</albums>";
+		$xml .= "$xml_user_infos</body></albums>";
 
 		$xml = base64_encode($this->formatXML($xml));
 		$xml = strtr($xml,'+/','-_');
diff --git a/library/Class/Calendar.php b/library/Class/Calendar.php
index bbe855be38b1262b3ef347e36d85c1b982bf4f24..8d70b37114e5efe666f5f44eb18cf03ff455807c 100644
--- a/library/Class/Calendar.php
+++ b/library/Class/Calendar.php
@@ -69,6 +69,28 @@ class Class_Calendar {
 		return $this->_loadArticles([]);
 	}
 
+	public static function getAllNextEvents($id_module,$profil) {
+		$preferences	= $profil->getModuleAccueilPreferences($id_module, 'NEWS');
+
+		if (
+			array_key_exists('id_categorie', $preferences)
+			&& ('' != $preferences['id_categorie'])
+		) {
+			if ('Random' == $preferences['display_order']) {
+				$preferences['nb_aff'] = $preferences['nb_analyse'];
+			}
+		}
+
+		$preferences['event_end_after']=strftime('%Y-%m-%d', self::getTimeSource()->time());
+		$preferences['events_only'] = true;
+		$preferences['published'] = true;
+		$preferences['display_order'] = 'EventDebut';
+
+		$articles = Class_Article::getArticlesByPreferences($preferences);
+		$articles = Class_Article::filterByLocaleAndWorkflow($articles);
+
+		return $articles;
+	}
 
 	protected function _loadArticles($extra_prefs) {
 		if (('all' !== $this->param["SELECT_ID_CAT"]) and ($this->param['DISPLAY_CAT_SELECT']))
diff --git a/library/Class/CasTicket.php b/library/Class/CasTicket.php
index 74b72a12aa231d6cdebf6871e3d4742653e6d1fb..74c49a9db6e08e4046b5764444c5cedd7326ec14 100644
--- a/library/Class/CasTicket.php
+++ b/library/Class/CasTicket.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 Class_CasTicket {
 	//see http://www.jasig.org/cas/protocol#service-ticket-properties
@@ -36,8 +36,8 @@ class Class_CasTicket {
 
 	public function save() {
 		if ($user = Class_Users::getIdentity())
-			Zend_Registry::get('cache')->save((string)$user->getId(),
-																				$this->withoutPrefix($this->getTicketForCurrentUser()));
+			(new Storm_Cache())->save((string)$user->getId(),
+																$this->withoutPrefix($this->getTicketForCurrentUser()));
 	}
 
 
@@ -48,11 +48,11 @@ class Class_CasTicket {
 
 	public function clear() {
 		if ($ticket = $this->getTicketForCurrentUser())
-			Zend_Registry::get('cache')->remove($this->withoutPrefix($ticket));
+			(new Storm_Cache())->remove($this->withoutPrefix($ticket));
 	}
 
 	public function userForTicket($ticket) {
-		if ($id = (int)Zend_Registry::get('cache')->load($this->withoutPrefix($ticket)))
+		if ($id = (int)(new Storm_Cache())->load($this->withoutPrefix($ticket)))
 			return Class_Users::find($id);
 		return null;
 	}
diff --git a/library/Class/Catalogue.php b/library/Class/Catalogue.php
index d271c14c7caf696ca67f3babf906b6563d71ad0d..b442552b0a2cf8c8c0fb0b2bc44074fd2d5f103e 100644
--- a/library/Class/Catalogue.php
+++ b/library/Class/Catalogue.php
@@ -117,6 +117,17 @@ class CatalogueLoader extends Storm_Model_Loader {
 	}
 
 
+	public function noticesLinked($catalogue) {
+		if(!$catalogue)
+			return '';
+
+		if (!Class_NoticeDomain::getClesNoticesForDomain($catalogue->getId()))
+			return '';
+
+		return Class_MoteurRecherche::prepareFacetteDomainForOrConditions($catalogue->getId());
+	}
+
+
 	/**
 	 * @param $catalogue Class_Catalogue
 	 * @return string
@@ -312,9 +323,8 @@ class CatalogueLoader extends Storm_Model_Loader {
 			return $this->getRequetesPanier($preferences);
 
 		$against = $this->selectionFacettesForCatalogueRequestByPreferences($preferences);
-		if ($catalogue = $this->find($preferences['id_catalogue'])) {
+		if ($catalogue = Class_Catalogue::getLoader()->find($preferences['id_catalogue'])) {
 			$conditions = [$this->facetsClauseFor($catalogue, $against)];
-
 			$conditions []= $this->docTypeClauseFor($catalogue);
 			$conditions []= $this->yearClauseFor($catalogue);
 			$conditions []= $this->coteClauseFor($catalogue);
@@ -325,20 +335,23 @@ class CatalogueLoader extends Storm_Model_Loader {
 				: [];
 		}
 
-		if (isset($preferences['only_img']) && ($preferences['only_img'] == 1))
+		if ($catalogue && $catalogue->isEmpty())
+			return [];
+
+		if(isset($preferences['only_img']) && ($preferences['only_img'] == 1)
+			 && (($catalogue && Class_Catalogue::getLoader()->hasFilters($preferences['id_catalogue'])) || !$catalogue))
 			$conditions[] = "url_vignette > '' and url_vignette != 'NO'";
 
 		$join = (isset($preferences['avec_avis']) && ($preferences['avec_avis'] == 1))
 			?	' INNER JOIN notices_avis ON notices.clef_oeuvre=notices_avis.clef_oeuvre '
 			: '';
 
-		if ($where = implode(' and ', array_filter($conditions)))
-			$where = ' where ' . $where;
-
 		$order_by = $this->orderByForCatalogueRequestByPreferences($preferences);
 		$limite = $this->limitForCatalogueRequestByPreferences($preferences);
 
-		$sql = 'select %s from notices' . $join . $where;
+		$sql = 'select %s from notices' .
+			$join .
+			Class_MoteurRecherche::getConditionsForRequest(implode(' and ', array_filter($conditions)), $this->noticesLinked($catalogue));
 
 		return ['req_liste' => sprintf($sql, $fields ? implode(',', $fields) : '*')
 						. $order_by . $limite,
@@ -363,6 +376,7 @@ class CatalogueLoader extends Storm_Model_Loader {
 		return $against;
 	}
 
+
 	public function orderByForCatalogueRequestByPreferences($preferences) {
 		if(!array_key_exists('tri', $preferences)
 			 || ($preferences["tri"] == '0'
@@ -489,8 +503,8 @@ class CatalogueLoader extends Storm_Model_Loader {
 		return (new Storm_Cache())
 			->memoize([$preferences, __CLASS__, __FUNCTION__, $cache_vignette],
 								function() use ($preferences, $cache_vignette) {
-							     return Class_Catalogue::getLoader()->fetchAllNoticesByPreferences($preferences, $cache_vignette);
-						    });
+																																	return Class_Catalogue::getLoader()->fetchAllNoticesByPreferences($preferences, $cache_vignette);
+																																});
 	}
 
 
@@ -509,13 +523,57 @@ class CatalogueLoader extends Storm_Model_Loader {
 		return array_slice ($notices, 0, $preferences["nb_notices"]);
 	}
 
-}
 
+	public function hasFilters($id) {
+		if(!$catalogue = Class_Catalogue::find($id))
+			return false;
+
+		return !$catalogue->hasNoSettings();
+	}
+
+
+	public function saveThesaurus($catalogue) {
+		if ($thesaurus=Class_CodifThesaurus::findThesaurusForCatalogue($catalogue->getId()))
+			$catalogue->deleteThesaurusInFacette($thesaurus->getIdThesaurus());
+		else
+			if (!$thesaurus = $catalogue->saveThesauriParents()) return;
+
+		if($catalogue->hasDomaineParent()){
+			$parent=$catalogue->getParent();
+			$thesaurus_parent = $parent->saveThesauriParents();
+			if (!$thesaurus_parent)
+				return null;
+			$new_thesaurus_id=Class_CodifThesaurus::findNextThesaurusChildId($thesaurus_parent->getIdThesaurus());
+		}
+		else if (strlen($thesaurus->getIdThesaurus())>8) {
+			$new_thesaurus_id=Class_CodifThesaurus::findNextRacineCatalogue();}
+		else
+			return $thesaurus;
+
+		if ($thesaurus->getId() && ($new_thesaurus_id != $thesaurus->getIdThesaurus())) {
+			$thesaurus->setIdThesaurus($new_thesaurus_id);
+			$thesaurus->setLibelle($catalogue->getLibelle());
+			$thesaurus->save();
+
+			Class_Catalogue::updateAllThesaurusForCatalogueChildren($catalogue->getId(),$new_thesaurus_id);
+			return $thesaurus;
+		}
+
+		$thesaurus->setIdThesaurus($new_thesaurus_id);
+		$thesaurus->setLibelle($catalogue->getLibelle());
+		$thesaurus->save();
+
+		return $thesaurus;
+	}
+}
 
 
 
 class Class_Catalogue extends Storm_Model_Abstract {
-	use Trait_TreeNode, Trait_Translator;
+	use Trait_TreeNode, Trait_Translator, Trait_CustomFields;
+
+	const CODE_FACETTE = 'Q';
+
 
 	protected
 		$_table_name = 'catalogue',
@@ -549,9 +607,9 @@ class Class_Catalogue extends Storm_Model_Abstract {
 
 		$_belongs_to = [ 'domaine_parent' => ['model' => 'Class_Catalogue',
 																					'referenced_in' => 'parent_id'],
-										 'user' => ['model' => 'Class_Users',
-																	 'referenced_in' => 'id_user']
-			],
+										'user' => ['model' => 'Class_Users',
+															 'referenced_in' => 'id_user']
+		],
 
 		$_has_many = ['sous_domaines' => ['model' => 'Class_Catalogue',
 																			'role' => 'domaine_parent',
@@ -605,6 +663,10 @@ class Class_Catalogue extends Storm_Model_Abstract {
 	}
 
 
+	public function getFacette() {
+		return self::CODE_FACETTE . $this->getId();
+	}
+
 
 	public function getAllNoticeIdsForDomaine($nb_par_page,$numero_page) {
 		$preferences = $this->toArray();
@@ -643,14 +705,13 @@ class Class_Catalogue extends Storm_Model_Abstract {
 
 
 	public function updateNoticesWithFacette($nb_par_page,$page) {
-
 		if (!$ids=$this->getAllNoticeIdsForDomaine($nb_par_page,$page))
 			return false;
 
 		$sql = Zend_Registry::get('sql');
 		$thesaurus=Class_CodifThesaurus::findThesaurusForCatalogue($this->getId());
 		if (!$thesaurus)
-			$this->saveThesaurus();
+			$this->getLoader()->saveThesaurus($this);
 		if ($thesaurus) {
 			if ($page==0) $this->deleteThesaurusInFacette($thesaurus->getIdThesaurus());
 
@@ -681,18 +742,21 @@ class Class_Catalogue extends Storm_Model_Abstract {
 		$preferences = $this->toArray();
 		$preferences['nb_notices'] = 20;
 		$requetes = Class_Catalogue::getLoader()->getRequetes($preferences);
+
+		if(empty($requetes['req_liste']))
+			return [];
+
 		$ret["requete"] = $requetes["req_liste"];
 		$temps = time();
 
-		$ret["notices"] = $this->getNotices(null, null,
-																				['limitPage' => [1, $preferences['nb_notices']],
-																				 'order' => 'alpha_titre']);
-
+		$ret["notices"] = Class_Notice::findAllByRequeteRecherche($requetes['req_ids'],
+																															$preferences['nb_notices'],
+																															1);
 		$ret["temps_execution"] = time() - $temps;
 		$ret["nb_notices"] = fetchOne($requetes["req_comptage"]);
 
 		$req = $requetes["req_comptage"];
-		if (strpos($req,"where") > 0)
+		if (strpos($req,"Where") > 0)
 			$req.=" and ";
 		else
 			$req.=" where ";
@@ -732,25 +796,25 @@ class Class_Catalogue extends Storm_Model_Abstract {
 	}
 
 
-	public function buildCriteresRecherche($params) {
-			$type_doc = $this->getTypeDoc();
-			$filtres = ['B' => $this->getBibliotheque(),
-									'S' => $this->getSection(),
-									'G' => $this->getGenre(),
-									'L' => $this->getLangue(),
-									'Y' => $this->getAnnexe(),
-									'E' => $this->getEmplacement(),
-									'T' => $this->getTypeDoc(),
-									'A' => $this->getAuteur(),
-									'Z' => $this->getTags(),
-									'F' => $this->getInteret(),
-									'M' => $this->getMatiereAndSousVedettes(),
-									'D' => $this->getDewey(),
-									'P' => $this->getPcdm4(),
-									'H' => $this->getThesaurus()];
+	public function buildCriteresRecherche($params = []) {
+		$type_doc = $this->getTypeDoc();
+		$filtres = ['B' => $this->getBibliotheque(),
+								'S' => $this->getSection(),
+								'G' => $this->getGenre(),
+								'L' => $this->getLangue(),
+								'Y' => $this->getAnnexe(),
+								'E' => $this->getEmplacement(),
+								'T' => $this->getTypeDoc(),
+								'A' => $this->getAuteur(),
+								'Z' => $this->getTags(),
+								'F' => $this->getInteret(),
+								'M' => $this->getMatiereAndSousVedettes(),
+								'D' => $this->getDewey(),
+								'P' => $this->getPcdm4(),
+								'H' => $this->getThesaurus()];
 
-			$params['filtres'] = $this->transformArrayToUrlStringAndConcatenateMatiereDeweyPcdm4($filtres);
-			return array_filter($params);
+		$params['filtres'] = $this->transformArrayToUrlStringAndConcatenateMatiereDeweyPcdm4($filtres);
+		return array_filter($params);
 	}
 
 
@@ -924,7 +988,7 @@ class Class_Catalogue extends Storm_Model_Abstract {
 
 
 	public function afterSave(){
-		$this->saveThesaurus();
+		$this->getLoader()->saveThesaurus($this);
 	}
 
 
@@ -974,42 +1038,6 @@ class Class_Catalogue extends Storm_Model_Abstract {
 	}
 
 
-
-	public function saveThesaurus(){
-		if ($thesaurus=Class_CodifThesaurus::findThesaurusForCatalogue($this->getId()))
-			$this->deleteThesaurusInFacette($thesaurus->getIdThesaurus());
-		else
-			if (!$thesaurus = $this->saveThesauriParents()) return;
-
-		if($this->hasDomaineParent()){
-			$parent=$this->getParent();
-			$thesaurus_parent = $parent->saveThesauriParents();
-			if (!$thesaurus_parent)
-				return null;
-			$new_thesaurus_id=Class_CodifThesaurus::findNextThesaurusChildId($thesaurus_parent->getIdThesaurus());
-		}
-		else if (strlen($thesaurus->getIdThesaurus())>8) {
-			$new_thesaurus_id=Class_CodifThesaurus::findNextRacineCatalogue();}
-		else
-			return $thesaurus;
-
-		if ($thesaurus->getId() && ($new_thesaurus_id != $thesaurus->getIdThesaurus())) {
-			$thesaurus->setIdThesaurus($new_thesaurus_id);
-			$thesaurus->setLibelle($this->getLibelle());
-			$thesaurus->save();
-
-			Class_Catalogue::getLoader()->updateAllThesaurusForCatalogueChildren($this->getId(),$new_thesaurus_id);
-			return $thesaurus;
-		}
-
-		$thesaurus->setIdThesaurus($new_thesaurus_id);
-		$thesaurus->setLibelle($this->getLibelle());
-		$thesaurus->save();
-
-		return $thesaurus;
-	}
-
-
 	public function startsWith($haystack, $needle) {
 		return !strncmp($haystack, $needle, strlen($needle));
 	}
@@ -1074,8 +1102,8 @@ class Class_Catalogue extends Storm_Model_Abstract {
 		unset($attributes['id']);
 
 		return $copy
-		->updateAttributes($attributes)
-		->setLibelle('** Nouveau Domaine **');
+			->updateAttributes($attributes)
+			->setLibelle('** Nouveau Domaine **');
 	}
 
 
@@ -1111,6 +1139,36 @@ class Class_Catalogue extends Storm_Model_Abstract {
 																	'multipleSelection' => false]];
 
 	}
+
+
+	public function hasNoSettings() {
+		$attributs = $this->_attributes;
+		unset($attributs['libelle']);
+		unset($attributs['id']);
+		unset($attributs['id_catalogue']);
+		unset($attributs['id_user']);
+		unset($attributs['indexer']);
+		unset($attributs['URL_IMG']);
+		unset($attributs['parent_id']);
+		unset($attributs['description']);
+
+		if (empty($attributs))
+			return true;
+
+		foreach ($attributs as $key => $value) {
+			if (!$this->isAttributeEmpty($key))
+				return false;
+		}
+		return true;
+	}
+
+
+	public function isEmpty() {
+		if($this->hasNoSettings() && !Class_NoticeDomain::getClesNoticesForDomain($this->getId()))
+			return true;
+
+		return false;
+	}
 }
 
 
diff --git a/library/Class/CmsRank.php b/library/Class/CmsRank.php
index d1f8467ac54e28b30222540816bcac564f0d696d..72a6cb0d88097d784e4a9113ea80c7d03cba114e 100644
--- a/library/Class/CmsRank.php
+++ b/library/Class/CmsRank.php
@@ -16,29 +16,27 @@
  *
  * 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 Class_CmsRank extends Storm_Model_Abstract {
 	protected $_table_name = 'cms_rank';
-	protected $_belongs_to = array('article' => array('model' => 'Class_Article',
-			                                              'referenced_in' => 'id_cms'));
-	protected $_default_attribute_values = array(
+	protected $_table_primary = 'id_cms';
+
+	protected $_belongs_to = ['article' => ['model' => 'Class_Article',
+																					'referenced_in' => 'id_cms']];
+
+	protected $_default_attribute_values = [
 																					'abon_nombre_avis' => 0,
 																					'bib_nombre_avis' => 0,
 																					'abon_note' => 0,
-	                                        'bib_note' => 0);
+																					'bib_note' => 0];
 
-	public static function getLoader() {
-		return self::getLoaderFor(__CLASS__);
-	}
-
-	
 	public function getNbAvisTotal() {
 		return $this->getBibNombreAvis() + $this->getAbonNombreAvis();
 	}
 }
 
-		
+
 ?>
\ No newline at end of file
diff --git a/library/Class/Codification.php b/library/Class/Codification.php
index a55c3948bb32b02648aae4b93a545f6cb02cd6ed..d2863f31a4cd8090067e35841d8b90dd4bbe3571 100644
--- a/library/Class/Codification.php
+++ b/library/Class/Codification.php
@@ -38,7 +38,7 @@
  * N: année
  * O: notes
  * P: pcdm4
- * Q:
+ * Q: domain for pseudo record
  * R: résumé
  * S: section
  * T: type de doc
@@ -54,7 +54,7 @@
 class Class_Codification {
 	use Trait_Singleton, Trait_Translator;
 	const
-		CHAMPS = 'JAKEFCNMDGHPILOR8',
+		CHAMPS = 'JAKEFCNMDGHPILOR8Q',
 		CODE_COLLECTION = 'C',
 		CODE_EDITEUR = 'E',
 		CODE_IDENTIFIANT = 'I',
@@ -62,6 +62,7 @@ class Class_Codification {
 		CODE_COLLATION = 'K',
 		CODE_ANNEE = 'N',
 		CODE_NOTES = 'O',
+		CODE_PRIX = 'Q',
 		CODE_RESUME = 'R',
 		CODE_URL = '8',
 		CODE_NOUVEAUTE = '9',
@@ -178,7 +179,8 @@ class Class_Codification {
 			Class_CodifTags::CODE_FACETTE							=> [		$this->_("Tag"),							$this->_("Tag(s)")],
 			Class_Codification::CODE_URL							=> [		$this->_("Lien internet"),		$this->_("Liens internet")],
 			Class_Codification::CODE_AVAILABILITY     => [		$this->_("En rayon"),		      $this->_("En rayon")],
-			Class_Codification::CODE_NOUVEAUTE  			=> [		$this->_("Nouveauté"),			  $this->_("Nouveauté")]];
+  		Class_Codification::CODE_NOUVEAUTE  			=> [		$this->_("Nouveauté"),			  $this->_("Nouveauté")],
+		Class_Codification::CODE_PRIX  			=> [		$this->_("Prix"),			  $this->_("Prix")]];
 
 		$this->addThesauriToNomChamps();
 		$this->setFacetDisplayNames();
diff --git a/library/Class/CriteresRecherche.php b/library/Class/CriteresRecherche.php
index f2978a4bab75620486aea761343dba49e89832ce..6461e446836c62aceda7b8fb4da987c348d2e797 100644
--- a/library/Class/CriteresRecherche.php
+++ b/library/Class/CriteresRecherche.php
@@ -120,7 +120,6 @@ class Class_CriteresRecherche {
 	public function setParams($rech) {
 		$this->_params = $this->filterParams($rech);
 		return $this;
-
 	}
 
 
@@ -144,8 +143,7 @@ class Class_CriteresRecherche {
 		$facettes = explode('-', $this->getParam('facettes', ''));
 		$facettes []= $this->getParam('facette', '');
 
-		$profil = Class_Profil::getCurrentProfil();
-		if ($selection_bib = $profil->getIdSite())
+		if ($this->_profil && ($selection_bib = $this->_profil->getIdSite()))
 			$facettes []= 'B'.$selection_bib;
 
 		return  array_unique(array_filter($facettes,
@@ -155,6 +153,14 @@ class Class_CriteresRecherche {
 	}
 
 
+	public function hasEmptyDomain() {
+		if( !$catalogue = Class_Catalogue::find($this->getParam('id_catalogue')))
+			return false;
+		return $catalogue->isEmpty();
+
+	}
+
+
 	public function getGeoZone(){
 		return $this->getParam('geo_zone',false);
 	}
@@ -171,17 +177,15 @@ class Class_CriteresRecherche {
 
 
 	public function getFiltres() {
-		$profil = Class_Profil::getCurrentProfil();
-
 		$filtres = [
 			$this->selectionToArray('G', $this->getGenre()),
 		];
 
-		if (!$this->isRecherchePanier()) {
+		if (!$this->isRecherchePanier() && !$this->isRechercheCatalogueEmpty() && $this->_profil) {
 			$filtres = array_merge($filtres, [
-				$this->selectionToArray('T', $profil->getSelTypeDoc()),
-				$this->selectionToArray('Y', $profil->getSelAnnexe()),
-				$this->selectionToArray('S', $profil->getSelSection()),
+				$this->selectionToArray('T', $this->_profil->getSelTypeDoc()),
+				$this->selectionToArray('Y', $this->_profil->getSelAnnexe()),
+				$this->selectionToArray('S', $this->_profil->getSelSection()),
 			]);
 		}
 
@@ -283,7 +287,14 @@ class Class_CriteresRecherche {
 
 
 	public function visitCatalogue($visitor, $catalogue) {
-		$this->setParams($catalogue->buildCriteresRecherche($this->getCriteres()));
+		$criteres = $this->getCriteres();
+		$this->setParams($catalogue->buildCriteresRecherche($criteres));
+
+		$domain_settings = $catalogue->buildCriteresRecherche();
+		$domain_critere = (new Class_CriteresRecherche())->clearProfil();
+
+		$visitor->visitDomain($domain_critere->setParams($domain_settings));
+
 		if ($catalogue->getCoteDebut()||$catalogue->getCoteFin())
 			$visitor->visitCoteDebutFin($catalogue->getCoteDebut(),$catalogue->getCoteFin());
 
@@ -292,6 +303,26 @@ class Class_CriteresRecherche {
 
 		if ($catalogue->getAnneeDebut() || $catalogue->getAnneeFin())
 			$visitor->visitAnneeDebutFin($catalogue->getAnneeDebut(),$catalogue->getAnneeFin());
+
+		if($cles_notices = Class_NoticeDomain::getClesNoticesForDomain($catalogue->getId()))
+			$visitor->visitFacetteDomainForOrConditions($catalogue->getId());
+
+		if(isset($criteres['id_catalogue']) && !empty($criteres['id_catalogue'])) {
+			$visitor->visitFilterOnDomain((new Class_CriteresRecherche())
+																		->setParams($this->cleansCritereForFilterOnDomain($criteres, ['id_catalogue'])));
+		}
+	}
+
+
+	protected function cleansCritereForFilterOnDomain($criteres, $keys = []) {
+		foreach($keys as $key)
+			unset($criteres[$key]);
+		return $criteres;
+	}
+
+
+	protected function isRechercheCatalogueEmpty() {
+		return $this->isRechercheCatalogue() && !Class_Catalogue::hasFilters($this->getParam('id_catalogue'));
 	}
 
 
@@ -308,6 +339,11 @@ class Class_CriteresRecherche {
 	}
 
 
+	public function isSearchInBasket() {
+		return null != $this->getParam('id_panier');
+	}
+
+
 	public function visitPanierCatalogue($visitor) {
 		if (!$this->isRechercheCatalogueOuPanier())
 			return $this;
@@ -658,6 +694,12 @@ class Class_CriteresRecherche {
 	function updateRubrique($rubrique) {
 		return $this->_params['rubrique']=$rubrique;
 	}
+
+
+	public function clearProfil() {
+		$this->_profil = null;
+		return $this;
+	}
 }
 
 ?>
\ No newline at end of file
diff --git a/library/Class/CustomField.php b/library/Class/CustomField.php
index 2b4dacbdc5390d5f47e710020711fc954ef73037..f61844747fe2f75a7c37bd658ec61924814f2c8a 100644
--- a/library/Class/CustomField.php
+++ b/library/Class/CustomField.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 CustomFieldsLoader extends Storm_Model_Loader {
@@ -27,9 +27,9 @@ class CustomFieldsLoader extends Storm_Model_Loader {
 		foreach($custom_fields_meta as $custom_field_meta) {
 			$custom_fields = Class_CustomField::findAllBy(['meta_id' => $custom_field_meta->getId(),
 																										 'model' => $model]);
-			if(!array_filter($custom_fields)){
+			if (!array_filter($custom_fields))
 				$availables_meta[] = $custom_field_meta;
-			}
+
 		}
 
 		return $availables_meta;
@@ -37,7 +37,7 @@ class CustomFieldsLoader extends Storm_Model_Loader {
 
 
 	public function initializePrioritiesAndSave($fields) {
-		foreach($fields as $priority => $field) 
+		foreach($fields as $priority => $field)
 			$field->setPriority($priority + 1)->save();
 	}
 }
@@ -46,8 +46,8 @@ class CustomFieldsLoader extends Storm_Model_Loader {
 
 class Class_CustomField extends Storm_Model_Abstract {
 	use Trait_TreeViewableItem;
-	
-	protected 
+
+	protected
 		$_table_name = 'custom_field',
 		$_loader_class = 'CustomFieldsLoader',
 		$_belongs_to = ['meta' => ['model' => 'Class_CustomField_Meta',
@@ -91,13 +91,13 @@ class Class_CustomField extends Storm_Model_Abstract {
 		$this->getMeta()->save();
 	}
 
-	
+
 	public function setFieldType($value) {
 		$this->getMeta()->setFieldType($value);
 		return $this;
 	}
 
-	
+
 	public function getFieldType() {
 		return $this->getMeta()->getFieldType();
 	}
@@ -108,12 +108,12 @@ class Class_CustomField extends Storm_Model_Abstract {
 		return $this;
 	}
 
-	
+
 	public function getOptionsList() {
 		return $this->getMeta()->getOptionsList();
 	}
 
-	
+
 	public function getOptionsListAsArray() {
 		return $this->getMeta()->getOptionsListAsArray();
 	}
diff --git a/library/Class/CustomField/Model.php b/library/Class/CustomField/Model.php
index eabd2e0c9407dafb7269581d3ad139a8647f1f13..eaeb8bb9c9f7d49d92c4f32b95328873dbce2cc5 100644
--- a/library/Class/CustomField/Model.php
+++ b/library/Class/CustomField/Model.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
  */
 
 
@@ -24,10 +24,10 @@ class Class_CustomField_Model {
 	use Trait_TreeViewableCategorie;
 
 	const CLASS_PREFIX = 'Class_';
-	
+
 	protected static $_models = [];
 
-	protected $_configuration;
+	protected $_configuration, $_mapping;
 
 	public static function registerAll($array_of_models_configuration) {
 		static::$_models = [];
@@ -63,11 +63,12 @@ class Class_CustomField_Model {
 	}
 
 
+
 	public function __construct($configuration) {
 		$this->_configuration = $configuration;
 	}
 
-	
+
 	public function isNew() {
 		return false;
 	}
@@ -87,7 +88,7 @@ class Class_CustomField_Model {
 		return $this->_configuration->getEditUrl();
 	}
 
-	
+
 	public function getId() {
 		return $this->_configuration->getClassName();
 	}
@@ -97,6 +98,7 @@ class Class_CustomField_Model {
 		return $this->getFields();
 	}
 
+
 	public function getFields() {
 		return Class_CustomField::findAllBy(['model' => $this->getId(),
 																				 'order' => 'priority']);
@@ -104,23 +106,75 @@ class Class_CustomField_Model {
 
 
 	public function find($id) {
-		$loader = call_user_func([self::CLASS_PREFIX.$this->getId(), 'getLoader']);
+		$loader = $this->getCustomizedClassLoader();
 		if (!$instance = $loader->find($id))
 			$instance = $loader->newInstance();
 		return new Class_CustomField_ModelValues($instance, $this);
 	}
 
 
+	public function getCustomizedClassLoader() {
+		return call_user_func([self::CLASS_PREFIX.$this->getId(), 'getLoader']);
+	}
+
+
+	public function getFieldIdByName($name) {
+		$this->initMapping();
+		return isset($this->_mapping[$name])
+			? $this->_mapping[$name] : null;
+	}
+
+
+	public function addField($name, $type, $options='') {
+		$this->initMapping();
+		if (array_key_exists($name, $this->_mapping))
+			return;
+
+		$meta = Class_CustomField_Meta::newInstance(['label' => $name,
+																								 'field_type' => $type,
+																								 'options_list' => $options]);
+		$meta->save();
+
+		$field = Class_CustomField::newInstance(['meta_id' => $meta->getId(),
+																						 'priority' => 1,
+																						 'model' => $this->getId()]);
+		$field->save();
+		$this->_mapping[$name] = $field->getId();
+	}
+
+
+	public function findFirstByCustomFieldValue($name, $value) {
+		if (!$id = $this->getFieldIdByName($name))
+			return null;
+
+		if (!$value = Class_CustomField_Value::findFirstBy(['custom_field_id' => $id,
+																											 'value' => $value]))
+			return null;
+
+		return $this->getCustomizedClassLoader()->find($value->getModelId());
+	}
+
+
 	public function getSousCategories() {
 		return [];
 	}
+
+
+	protected function initMapping() {
+		if (isset($this->_mapping))
+			return ;
+		$this->_mapping=[];
+		foreach ($this->getFields() as $field) {
+			$this->_mapping[$field->getLabel()]=$field->getId();
+		}
+	}
 }
 
 
 
 class Class_CustomField_NullModel extends Class_CustomField_Model {
 	public function __construct() {
-		
+
 	}
 
 
diff --git a/library/Class/CustomField/ModelConfiguration/ArticleCategorie.php b/library/Class/CustomField/ModelConfiguration/ArticleCategorie.php
new file mode 100644
index 0000000000000000000000000000000000000000..d1d62ddcbafd0abda5ebc7502742a800d696d67d
--- /dev/null
+++ b/library/Class/CustomField/ModelConfiguration/ArticleCategorie.php
@@ -0,0 +1,33 @@
+<?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 Class_CustomField_ModelConfiguration_ArticleCategorie extends Class_CustomField_ModelConfiguration {
+    public function __construct() {
+        $this->_class_name = 'ArticleCategorie';
+        $this->_label = $this->_('ArticleCategorie');
+        $this->_edit_url = [
+            'module' => 'admin',
+            'controller' => 'cms',
+            'action' => 'edit'];
+    }
+}
+?>
\ No newline at end of file
diff --git a/library/Class/CustomField/ModelConfiguration/Catalogue.php b/library/Class/CustomField/ModelConfiguration/Catalogue.php
new file mode 100644
index 0000000000000000000000000000000000000000..cf2d2a6f796bc019aead32c52744296c1f1e44f2
--- /dev/null
+++ b/library/Class/CustomField/ModelConfiguration/Catalogue.php
@@ -0,0 +1,33 @@
+<?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 Class_CustomField_ModelConfiguration_Catalogue extends Class_CustomField_ModelConfiguration {
+    public function __construct() {
+        $this->_class_name = 'Catalogue';
+        $this->_label = $this->_('Catalogue');
+        $this->_edit_url = [
+            'module' => 'admin',
+            'controller' => 'cms',
+            'action' => 'edit'];
+    }
+}
+?>
\ No newline at end of file
diff --git a/library/Class/CustomField/ModelConfiguration/Sitotheque.php b/library/Class/CustomField/ModelConfiguration/Sitotheque.php
new file mode 100644
index 0000000000000000000000000000000000000000..3462bb1560fcca971c3cbde6d7203b7509848988
--- /dev/null
+++ b/library/Class/CustomField/ModelConfiguration/Sitotheque.php
@@ -0,0 +1,33 @@
+<?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 Class_CustomField_ModelConfiguration_Sitotheque extends Class_CustomField_ModelConfiguration {
+    public function __construct() {
+        $this->_class_name = 'Sitotheque';
+        $this->_label = $this->_('Sitotheque');
+        $this->_edit_url = [
+            'module' => 'admin',
+            'controller' => 'cms',
+            'action' => 'edit'];
+    }
+}
+?>
\ No newline at end of file
diff --git a/library/Class/CustomField/ModelConfiguration/SitothequeCategorie.php b/library/Class/CustomField/ModelConfiguration/SitothequeCategorie.php
new file mode 100644
index 0000000000000000000000000000000000000000..47b092e295703022f4c9892d290d9829ea203bfa
--- /dev/null
+++ b/library/Class/CustomField/ModelConfiguration/SitothequeCategorie.php
@@ -0,0 +1,33 @@
+<?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 Class_CustomField_ModelConfiguration_SitothequeCategorie  extends Class_CustomField_ModelConfiguration {
+    public function __construct() {
+        $this->_class_name = 'SitothequeCategorie';
+        $this->_label = $this->_('Catégorie de sitothèque');
+        $this->_edit_url = [
+            'module' => 'admin',
+            'controller' => 'sito',
+            'action' => 'edit'];
+    }
+}
+?>
\ No newline at end of file
diff --git a/library/Class/CustomField/ModelValues.php b/library/Class/CustomField/ModelValues.php
index a72d9080b1fcc758a9b38ae0324da27d33c5565b..8953e2c074091fe9ea94804f149c3e27105636cd 100644
--- a/library/Class/CustomField/ModelValues.php
+++ b/library/Class/CustomField/ModelValues.php
@@ -56,12 +56,30 @@ class Class_CustomField_ModelValues {
 
 
 	public function setFieldValue($custom_field_id, $value) {
-		$instance = $this->getFieldValues()[$custom_field_id];
+		$instance = $this->getFieldValue($custom_field_id);
 		$instance->setValue($value);
 		return $this;
 	}
 
 
+	public function setFieldValueByName($name, $value) {
+		if ($id = $this->_model->getFieldIdByName($name))
+			$this->setFieldValue($id, $value);
+	}
+
+
+	public function getFieldValue($custom_field_id) {
+		return $this->getFieldValues()[$custom_field_id];
+	}
+
+
+	public function getFieldValueByName($name) {
+		return ($id = $this->_model->getFieldIdByName($name))
+			? $this->getFieldValue($id)
+			: null;
+	}
+
+
 	public function getFieldValues() {
 		if (isset($this->_values))
 			return $this->_values;
diff --git a/library/Class/Exemplaire.php b/library/Class/Exemplaire.php
index cdafaf371c295f16560cdba1926e43fa9bcda1dd..0b3ea17c077747bb7b425a2eba4f127dd5b1169d 100644
--- a/library/Class/Exemplaire.php
+++ b/library/Class/Exemplaire.php
@@ -20,6 +20,7 @@
  */
 
 class Class_Exemplaire extends Storm_Model_Abstract {
+//	use Trait_CustomFields;
 	protected $_table_name = 'exemplaires';
 	protected $_table_primary = 'id';
 
@@ -137,7 +138,7 @@ class Class_Exemplaire extends Storm_Model_Abstract {
 
 
 	public function getCollectionPrincipale() {
-		return $this->getNotice()->getCollection(true);
+		return $this->getNotice()->getCollections(true);
 	}
 
 
diff --git a/library/Class/FRBR/Link.php b/library/Class/FRBR/Link.php
index 36648941dbd2120e2792d201c935e5811b32b981..bcaed308e59536f0fdc1e27dd9953cef05959dbe 100644
--- a/library/Class/FRBR/Link.php
+++ b/library/Class/FRBR/Link.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 FRBR_LinkLoader extends Storm_Model_Loader {
@@ -24,8 +24,10 @@ class FRBR_LinkLoader extends Storm_Model_Loader {
 	 * @param $key string
 	 * @return array
 	 */
-	public function getLinksForSource($key) {
-		return $this->getLinksFor('source', $key);
+	public function getRecordLinksForSource($key) {
+		return Class_FRBR_Link::findAllBy(['source_key' => $key,
+																			 'target_type' => Class_FRBR_Link::TYPE_NOTICE,
+																			 'order' => 'type_id']);
 	}
 
 
@@ -33,8 +35,10 @@ class FRBR_LinkLoader extends Storm_Model_Loader {
 	 * @param $key string
 	 * @return array
 	 */
-	public function getLinksForTarget($key) {
-		return $this->getLinksFor('target', $key);
+	public function getRecordLinksForTarget($key) {
+		return Class_FRBR_Link::findAllBy(['target_key' => $key,
+																			 'source_type' => Class_FRBR_Link::TYPE_NOTICE,
+																			 'order' => 'type_id']);
 	}
 
 
@@ -43,8 +47,75 @@ class FRBR_LinkLoader extends Storm_Model_Loader {
 	 * @param $value string
 	 */
 	public function getLinksFor($name, $value) {
-		return $this->findAllBy(['where' => $name . ' like \'%' . $value . '%\'',
-				                     'order' => 'type_id']);
+		return Class_FRBR_Link::findAllBy([$name .'_key' => $value,
+																			 'order' => 'type_id']);
+	}
+
+
+	public function findAllAlbumsFromNotice($notice) {
+		return array_merge(Class_FRBR_Link::findAllBy($this->_albumsFromNoticeFilter($notice)),
+											 Class_FRBR_Link::findAllBy($this->_recordsFromNoticeFilter($notice)));
+	}
+
+
+	public function countAlbumsFromNotice($notice) {
+		return Class_FRBR_Link::countBy($this->_albumsFromNoticeFilter($notice))
+			+ Class_FRBR_Link::countBy($this->_recordsFromNoticeFilter($notice));
+	}
+
+
+	protected function _albumsFromNoticeFilter($notice) {
+		return ['source_type' => Class_FRBR_Link::TYPE_NOTICE,
+						'target_type' => Class_FRBR_Link::TYPE_ALBUM,
+						'source_key' => $notice->getClefAlpha()];
+	}
+
+
+	protected function _recordsFromNoticeFilter($notice) {
+		return ['target_type' => Class_FRBR_Link::TYPE_NOTICE,
+						'source_type' => Class_FRBR_Link::TYPE_ALBUM,
+						'target_key' => $notice->getClefAlpha()];
+	}
+
+
+	/**
+	 * @param $album Class_Album
+	 * @return Class_FRBR_Link or null
+	 */
+	public function findFirstRecordLinkForAlbum($album) {
+		return Class_FRBR_Link::findFirstBy($this->_recordLinkFilterFromAlbum($album));
+	}
+
+
+	/**
+	 * @param $album Class_Album
+	 * @return Array Class_FRBR_Link
+	 */
+	public function findAllRecordLinksForAlbum($album) {
+		return array_merge(Class_FRBR_Link::findAllBy($this->_recordLinkFilterFromAlbum($album)),
+											 Class_FRBR_Link::findAllBy($this->_albumLinkFilterFromRecord($album)));
+	}
+
+
+	/**
+	 * @param $album Class_Album
+	 * @return Array
+	 */
+	public function _recordLinkFilterFromAlbum($album) {
+		return ['source_type' => Class_FRBR_Link::TYPE_NOTICE,
+						'target_type' => Class_FRBR_Link::TYPE_ALBUM,
+						'target_key' => $album->getId()];
+	}
+
+
+	/**
+	 * @param $album Class_Album
+	 * @return Array
+	 */
+	public function _albumLinkFilterFromRecord($album) {
+		return ['target_type' => Class_FRBR_Link::TYPE_NOTICE,
+						'source_type' => Class_FRBR_Link::TYPE_ALBUM,
+						'source_key' => $album->getId()];
 	}
 }
 
@@ -54,7 +125,8 @@ class Class_FRBR_Link extends Storm_Model_Abstract {
 
 	const TYPE_NOTICE = 'afi:notice';
 	const TYPE_EXTERNAL = 'afi:external';
-	
+	const TYPE_ALBUM = 'afi:album';
+
 	protected $_table_name = 'frbr_link';
 	protected $_belongs_to = ['type' => ['model' => 'Class_FRBR_LinkType',
 	                                     'referenced_in' => 'type_id']];
@@ -99,38 +171,57 @@ class Class_FRBR_Link extends Storm_Model_Abstract {
 
 
 	protected function _detectEntityTypeFromUrl($url, $callback) {
-		if (false === strpos($url, 'http:'))
+		if (!$request = $this->_route($url))
 			return;
 
-		$uri = Zend_Uri_Http::fromString($url);
-		$callback(($uri->getHost() == $_SERVER['HTTP_HOST']) ?
-			self::TYPE_NOTICE :
-			self::TYPE_EXTERNAL);
+		if ('opac' == $request->getModuleName()
+				&& 'recherche' == $request->getControllerName()
+				&& 'viewnotice' == $request->getActionName()) {
+			$callback(self::TYPE_NOTICE, $request->getParam('clef'));
+			;
+			return;
+		}
+
+		if ('opac' == $request->getModuleName()
+				&& 'bib-numerique' == $request->getControllerName()
+				&& 'notice' == $request->getActionName()) {
+			$callback(self::TYPE_ALBUM, $request->getParam('id'));
+			return;
+		}
+
+		$callback(self::TYPE_EXTERNAL, null);
 	}
 
 
 	protected function _detectSourceType() {
 		$this->_detectEntityTypeFromUrl(
-				$this->getSource(),
-				function ($type) {$this->setSourceType($type);});
+																		$this->getSource(),
+																		function ($type, $key) {
+																			$this->setSourceType($type)
+																					 ->setSourceKey($key);
+																		});
 	}
 
 
+
 	protected function _detectTargetType() {
 		$this->_detectEntityTypeFromUrl(
-				$this->getTarget(),
-				function ($type) {$this->setTargetType($type);});
+																		$this->getTarget(),
+																		function ($type, $key) {
+																			$this->setTargetType($type)
+																					 ->setTargetKey($key);
+																		});
 	}
 
 
-	/** @return Class_Notice */
-	public function getTargetNotice() {
+	/** @return Storm_Model_Abstract */
+	public function getTargetEntity() {
 		return $this->getEntityFor('target');
 	}
 
 
-	/** @return Class_Notice */
-	public function getSourceNotice() {
+	/** @return Storm_Model_Abstract */
+	public function getSourceEntity() {
 		return $this->getEntityFor('source');
 	}
 
@@ -139,16 +230,31 @@ class Class_FRBR_Link extends Storm_Model_Abstract {
 	 * @param $type string
 	 * @return Class_Notice
 	 */
-	public function getEntityFor($type) {
-		if (self::TYPE_EXTERNAL == $this->{'get'. ucfirst($type) . 'Type'}())
+	public function getEntityFor($link_end) {
+		$entity_type = $this->getTypeFor($link_end);
+		if (self::TYPE_EXTERNAL == $entity_type)
 			return;
 
-		$attribute = '_' . $type .'_entity';
+		$attribute = '_' . $link_end .'_entity';
 		if ($this->$attribute)
 			return $this->$attribute;
 
-		$this->$attribute = Class_Notice::findByUrl($this->{'get' . ucfirst($type)}());
-		return $this->$attribute;
+		$key = $this->getKeyFor($link_end);
+		if (self::TYPE_NOTICE == $entity_type)
+			return $this->$attribute = Class_Notice::getNoticeByClefAlpha($key);
+
+		if (self::TYPE_ALBUM == $entity_type)
+			return $this->$attribute = Class_Album::find($key);
+	}
+
+
+	public function getTypeFor($link_end) {
+		return $this->{'get'. ucfirst($link_end) . 'Type'}();
+	}
+
+
+	public function getKeyFor($link_end) {
+		return $this->{'get'. ucfirst($link_end) . 'Key'}();
 	}
 
 
@@ -169,14 +275,49 @@ class Class_FRBR_Link extends Storm_Model_Abstract {
 	 * @return string
 	 */
 	public function extractKeyFromUrl($url) {
-		try {		
-			// simule un routage standard
+		return ($request = $this->_route($url)) ?
+			$request->getParam('clef', '') : '';
+	}
+
+
+	/**
+	 * @param $type string self::TYPE_* const
+	 * @return Storm_Model_Abstract or null
+	 */
+	public function getEntityOfType($type) {
+		if ($this->getSourceType() == $type)
+			return $this->getSourceEntity();
+
+		if ($this->getTargetType() == $type)
+			return $this->getTargetEntity();
+
+		return null;
+	}
+
+
+	/**
+	 * @param $type string self::TYPE_* const
+	 * @return string
+	 */
+	public function getLinkLabelOfEntityType($type) {
+		if ($this->getSourceType() == $type)
+			return $this->getType()->getFromSource();
+
+		if ($this->getTargetType() == $type)
+			return $this->getType()->getFromTarget();
+
+		return null;
+	}
+
+
+	protected function _route($url) {
+		try {
 			$request = new Zend_Controller_Request_Http($url);
 			$router = new  ZendAfi_Controller_Router_RewriteWithoutBaseUrl();
 			$router->route($request);
-			return $request->getParam('clef', '');
+			return $request;
 		} catch(Zend_Uri_Exception $e) {
-			return '';
+			return null;
 		}
 	}
 }
diff --git a/library/Class/FRBR/LinkType.php b/library/Class/FRBR/LinkType.php
index 5a05a832d2776e327ca5990977e41948f3a88a48..58a5871f72eab48326e305830037adac3374344a 100644
--- a/library/Class/FRBR/LinkType.php
+++ b/library/Class/FRBR/LinkType.php
@@ -30,6 +30,17 @@ class Class_FRBR_LinkTypeLoader extends Storm_Model_Loader {
 		
 		return $list;
 	}
+
+	/** @return array */
+	public function getSourceTargetComboList() {
+		$list = [];
+		foreach ($this->findAllBy(['order' => 'libelle']) as $model) {
+			$list[$model->getId() . ':source'] = $model->getLibelle() . ' - ' . $model->getFromSource();
+			$list[$model->getId() . ':target'] = $model->getLibelle() . ' - ' . $model->getFromTarget();
+		}
+		return $list;
+	}
+
 }
 
 
diff --git a/library/Class/Import/Typo3.php b/library/Class/Import/Typo3.php
index 03b490b14f60b2b356b06ca57ce9b9eba904097f..e855a01705ca543f8c839b39e1a6b3ea4e6168c4 100644
--- a/library/Class/Import/Typo3.php
+++ b/library/Class/Import/Typo3.php
@@ -19,13 +19,18 @@
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
 class Class_Import_Typo3 {
+	use Trait_TimeSource;
   const DEFAULT_CAT_ID = 30000;
-	const BOKEH_IMG_URL="http://www.mediathequeouestprovence.fr/uploads/";
+	const BOKEH_IMG_URL = "http://www.mediathequeouestprovence.fr/uploads/";
 //	const BOKEH_IMG_URL="http://web.afi-sa.net/miop-test.net/userfiles/image/uploads/";
-	const BOKEH_ATTACHMENT_URL="http://www.mediathequeouestprovence.fr/fileadmin/fichiers/";
+	const BOKEH_ATTACHMENT_URL = "http://www.mediathequeouestprovence.fr/fileadmin/fichiers/";
 //	const BOKEH_ATTACHMENT_URL="http://web.afi-sa.net/miop-test.net/userfiles/file/";
-	const BOKEH_FILEADMIN_URL= "http://www.mediathequeouestprovence.fr/fileadmin/";
+	const BOKEH_FILEADMIN_URL = "http://www.mediathequeouestprovence.fr/fileadmin/";
 //	const BOKEH_FILEADMIN_URL= "http://web.afi-sa.net/miop_test.net/userfiles/image/";
+
+	const FOREIGN_UID_DATETIME = [867,36];
+	const UID_TYPO3_CF = 'uid_typo3';
+
 	protected
 		$user_mapper,
 		$unknown_koha_urls = [];
@@ -49,46 +54,90 @@ class Class_Import_Typo3 {
     $this->errors = [];
     $this->domaine_map = new DomaineMap();
 		$this->userMap = new UserMap();
+		$this->createCustomField();
   }
 
+
+	public function createCustomField() {
+		$to_customize = ['Class_Article', 'Class_ArticleCategorie',
+										 'Class_Catalogue', 'Class_Sitotheque', 'Class_SitothequeCategorie'];
+
+		foreach($to_customize as $class_name)
+			call_user_func_array([$class_name, 'addCustomField'],
+													 [self::UID_TYPO3_CF, Class_CustomField_Meta::TEXT_INPUT]);
+	}
+
+
 	public function setTypo3DB($t3db) {
-		$this->t3db=$t3db;
+		$this->t3db = $t3db;
 		return $this;
 	}
 
 
-  public function run($what = 'all') {
+  public function run($what='all', $last_update=null) {
 		$_SERVER['HTTP_HOST']='localhost';
+		$logger = Class_Import_Typo3_Logs::getInstance();
+
+		if ('update' == $what && !$last_update) {
+			$logger->addErrorRow("Mode mise à jour demandé sans date de référence");
+			return $logger->report();
+		}
+
+		if ($what == 'update') {
+			list($day, $month, $year, $hour, $minute) = split('[/ : -]', $last_update);
+
+			$last_update_timestamp = mktime($hour, $minute, 0, $month, $day, $year);
+
+			$logger->addLogRow("\n\n ******** Mise a jour uniquement");
+			$this->updateCategories($last_update_timestamp);
+
+			$logger->addLogRow("\n\n ******** Mise a jour Articles");
+			$this->updateArticles($last_update_timestamp);
+
+			$logger->addLogRow("\n\n ******** Mise a jour Articles Pages");
+			$this->updateArticlePages($last_update_timestamp);
+
+			$logger->addLogRow("\n\n ******** Détection des dossiers documentaires");
+      $this->updateCategoriesForDossiersDocumentaires();
+		}
 
     if ($what == 'all' || $what == 'users') {
-      Class_Import_Typo3_Logs::getInstance()->addLogRow("\n\n ******** Importing users");
+      $logger->addLogRow("\n\n ******** Importing users");
       $this->import_user();
     }
 
+		if ($what == 'docu') {
+			$logger->addLogRow("\n\n ******** Détection des dossiers documentaires");
+      $this->updateCategoriesForDossiersDocumentaires();
+		}
+
     if ($what == 'all' || $what == 'articles') {
-      Class_Import_Typo3_Logs::getInstance()->addLogRow("\n\n ******** Importing categories");
+      $logger->addLogRow("\n\n ******** Importing categories");
       $this->import_categories();
 
-      Class_Import_Typo3_Logs::getInstance()->addLogRow("\n\n ******** Importing articles pages");
+      $logger->addLogRow("\n\n ******** Importing articles pages");
       $this->importArticlesPages();
 
-      Class_Import_Typo3_Logs::getInstance()->addLogRow("\n\n ******** Importing articles");
+      $logger->addLogRow("\n\n ******** Importing articles");
       $this->importArticles();
 
-      Class_Import_Typo3_Logs::getInstance()->addLogRow("\n\n ******** Importing events");
+      $logger->addLogRow("\n\n ******** Importing events");
       $this->importCalendar();
 
-      Class_Import_Typo3_Logs::getInstance()->addLogRow("\n\n ******** Importing sites");
+      $logger->addLogRow("\n\n ******** Importing sites");
       $this->importSites();
+
+			$logger->addLogRow("\n\n ******** Détection des dossiers documentaires");
+      $this->updateCategoriesForDossiersDocumentaires();
     }
 
-		return Class_Import_Typo3_Logs::getInstance()->report();
+		return $logger->report();
   }
 
 
   public function import_user() {
+		$this->userMap->init();
     $t3users = $this->t3db->findAllUsers();
-
     foreach($t3users as $t3user) {
 			$this->userMap->newUser($t3user);
     }
@@ -97,6 +146,33 @@ class Class_Import_Typo3 {
   }
 
 
+	public function updateCategory($category,$t3_category)  {
+		$category->setLibelle($t3_category['title']);
+		$category->save();
+	}
+
+
+	public function updateCategories($update_date) {
+    $this->news_categories_map = new ArticleCategoriesMap('Non classé', $this->domaine_map);
+
+    $t3categories = $this->t3db->findAllNewsCatSince($update_date);
+
+		foreach ($t3categories as $t3category)  {
+			if ($category = ArticleCategoriesMap::getCategory($t3category['uid'])) {
+				$this->updateCategory($category,$t3category);
+				continue;
+			}
+
+			$this->news_categories_map->newCategory($t3category['title'],
+																							$t3category['uid'],
+																							$t3category['parent_category']);
+
+			$this->news_categories_map->fixParentCategory($t3category['uid'],
+																										$t3category['parent_category']);
+		}
+	}
+
+
   public function import_categories() {
 		Class_Article::deleteBy([]);
     Class_ArticleCategorie::deleteBy([]);
@@ -104,6 +180,7 @@ class Class_Import_Typo3 {
     Class_CodifThesaurus::deleteBy([]);
     Class_Sitotheque::deleteBy([]);
     Class_SitothequeCategorie::deleteBy([]);
+
     $this->reset_auto_increment();
 
     $this->cal_categories_map= new CalendarCategoriesMap('Agenda', $this->domaine_map);
@@ -124,43 +201,51 @@ class Class_Import_Typo3 {
 
   public function importSites() {
     $t3news = $this->t3db->findAllSites();
-
-    foreach($t3news as $new) {
+    foreach($t3news as $new)
 			$element = $this->traceUnknownKohaUrls(
-																	function() use ($new){
-																		return $this->createSito($new);
-																	},
-																 $new['uid']);
-
-		}
-
+																						 function() use ($new) {
+																							 return $this->createSito($new);
+																						 },
+																						 $new['uid']);
 	}
 
+
 	public function createSito($new) {
-      $date_creation = date("Y-m-d H:i:s", $new['crdate']);
-      $element = Class_Sitotheque::newInstance(['id_cat' => $this->sites_categories_map->find($new['category'])->getId(),
-                                                'date_maj' => $date_creation,
+      $date_maj = date("Y-m-d H:i:s", $new['crdate']);
+			$id_cat = $this->sites_categories_map->find($new['category'])->getId();
+			$url = $this->convertKohaToBokehUrl($this->format_url($new['ext_url']));
+			$tags = str_replace(', ', ';', $new['tx_danpextendnews_tags']);
+
+      $element = Class_Sitotheque::newInstance(['id_cat' => $id_cat,
+                                                'date_maj' => $date_maj,
                                                 'titre' => $new['title'],
-                                                'url' => $this->convertKohaToBokehUrl($this->format_url($new['ext_url'])),
-                                                'tags' => str_replace(', ', ';', $new['tx_danpextendnews_tags']),
+                                                'url' => $url,
+                                                'tags' => $tags,
                                                 'description' => $new['short']]);
 
-      if (!$element->save()) {
+			$element->setCustomField(self::UID_TYPO3_CF, $new['uid']);
+      if (!$element->saveWithCustomFields()) {
         $this->report['sitotheque_errors']++;
-        Class_Import_Typo3_Logs::getInstance()->addErrorRow("site with uid : " . $new['uid'] . ' (' . $new['title'] . ') - ' . implode(', ', $element->getErrors()));
+        Class_Import_Typo3_Logs::getInstance()
+					->addErrorRow("site with uid : " . $new['uid'] . ' (' . $new['title'] . ') - ' . implode(', ', $element->getErrors()));
         return $element;
       }
 
 			$foreign_uids = $this->t3db->findAllForeignUidForNewsCat($new['uid']);
 			$this->sites_categories_map->setDomainsFor($element, $foreign_uids);
 
-			if (in_array(43, array_map(function($row){return $row['uid_foreign'];}, $foreign_uids))) {
+			if ($this->isInTop5($foreign_uids))
 				$this->putSiteInTop5($element, $new);
-			}
+
 			return $element;
 	}
 
 
+	protected function isInTop5($foreign_uids) {
+		return in_array(43, array_map(function($item) { return $item['uid_foreign']; },
+																	$foreign_uids));
+	}
+
 
 	public function putSiteInTop5($element, $t3record) {
 		$parts = $element->getCategorie()->getPathParts();
@@ -176,14 +261,14 @@ class Class_Import_Typo3 {
 	}
 
 
-	public function manipulate_body($body,$image='') {
-		$body = $this->addImage($image).$body;
+	public function manipulate_body($body, $image='') {
 		$body = $this->wrapWithHtml($body);
+		$body = $this->addImage($image).$body;
 		$body = $this->translateLinksToAnchor($body);
 		$body = $this->fixImageLinks($body);
 		$body = $this->fixAttachmentsLinks($body);
 		$body = $this->fixFileAdminLinks($body);
-		return str_replace(["\r\n", "\n\r","\n","\r"],"<br />",$body);
+		return str_replace(["\r\n", "\n\r", "\n","\r"], "<br />", $body);
 	}
 
 
@@ -209,6 +294,7 @@ class Class_Import_Typo3 {
 		return $this->replaceUserfilesLinkPath($body,'fileadmin\/user_upload\/',self::BOKEH_FILEADMIN_URL);
 	}
 
+
 	protected function replaceUserfilesLinkPath($body,$source, $dest) {
 		$regs= '/(< *(a|img)[^>]*(href|src) *= *["\'])(http:\/\/[^"\']*\/)?'.$source.'([^"\']*)/i';
 		return preg_replace($regs, '$1'.$dest.'$5', $body);
@@ -216,11 +302,14 @@ class Class_Import_Typo3 {
 
 
 	protected function translateLinksToAnchor($content) {
-		while((false !== ($start = strpos($content, '<link '))) && (false !== ($end = strpos($content, '</link>')))) {
+		while((false !== ($start = strpos($content, '<link ')))
+					&& (false !== ($end = strpos($content, '</link>')))) {
 			if(false === ($link = substr($content, $start, $end - $start + 7)))
 				return $content;
+
 			$content = str_replace($link, $this->linkToAnchor($link), $content);
 		}
+
 		return $content;
 	}
 
@@ -245,8 +334,9 @@ class Class_Import_Typo3 {
 		return '<a href="' . $url . '">' . $this->getContentFromLink($link) . '</a>';
 	}
 
+
 	protected function convertKohaToBokehUrl($url) {
-		if (false === strpos($url,'http://koha.mediathequeouestprovence.fr/'))
+		if (false === strpos($url, 'http://koha.mediathequeouestprovence.fr/'))
 			return $url;
 
 		if (preg_match('/biblionumber=(\d+)$/', $url, $matches))
@@ -263,6 +353,7 @@ class Class_Import_Typo3 {
 		return $url;
 	}
 
+
 	protected function urlSearchAuthors($url, $search_expression) {
 		return '/miop-test.net/recherche/simple/rech_auteurs/'.$search_expression;
 	}
@@ -272,6 +363,7 @@ class Class_Import_Typo3 {
 		return '/miop-test.net/recherche/simple/expressionRecherche/'.$search_expression;
 	}
 
+
 	protected function urlViewNotice($url, $id_origine) {
 		if (!$exemplaire = Class_Exemplaire::findFirstBy(['id_origine' => $id_origine]))
 			return $url;
@@ -288,61 +380,175 @@ class Class_Import_Typo3 {
 
 
 	protected function wrapWithHtml($content) {
+		return ('<' === substr($content,0,1)) ?
+			$content : '<p>' . $content . '</p>';
+	}
 
-		if('<' === substr($content,0,1))
-			return $content;
 
-		return '<p>' . $content . '</p>';
+	public function getCatDossierDoc() {
+		return $this->createCategory("Nos dossiers documentaires");
 	}
 
 
-  public function importArticles() {
-    $t3news = $this->t3db->findAllArticles();
-    foreach($t3news as $new) {
-			$id_cat = $this->news_categories_map->find($new['category'])->getId();
-			$element = $this->traceUnknownKohaUrls(
-																	function() use ($new, $id_cat){
-																		return $this->createArticle($new, $id_cat);
-																	},
- 																  $new['uid']);
+	public function updateCategoriesForDossiersDocumentaires() {
+		$dossiers_doc_id = $this->getCatDossierDoc()->getId();
+		foreach ($this->t3db->findAllPagesWithPid(309) as $page)
+			$this->_addPageInDossiersDocumentaires($page, $dossiers_doc_id);
+	}
 
 
-			$this->news_categories_map->setDomainsFor($element,
-																								$this->t3db->findAllForeignUidForNewsCat($new['uid']));
-    }
+	protected function _addPageInDossiersDocumentaires($page, $dossiers_doc_id) {
+		if (!$category = $this->createCategory($page['title'], $dossiers_doc_id))
+			return;
+
+		foreach ($this->t3db->findAllContents($page['uid']) as $content)
+			$this->_addContentInDossiersDocumentaires($content, $category);
+	}
+
+
+	protected function _addContentInDossiersDocumentaires($content, $category) {
+		if (!$article = $this->_getArticle($content['uid'])) {
+			Class_Import_Typo3_Logs::getInstance()
+				->addErrorRow('Article with uid: ' . $content['uid'] . ' missing');
+			return;
+		}
+
+		$article->setCategorie($category)
+						->save();
+	}
+
+
+	protected function _getArticle($value) {
+		return Class_Article::findFirstByCustomFieldValue(self::UID_TYPO3_CF, $value);
+	}
+
+
+	public function updateArticles($last_import_date) {
+		foreach($this->t3db->findAllArticlesSince($last_import_date) as $new)
+			$this->_updateArticle($new);
+
+		Class_Import_Typo3_Logs::getInstance()->setArticlesLog();
+    return $this;
+	}
+
+
+	protected function _updateArticle($new) {
+		if ($new['deleted']) {
+			if ($article = $this->_getArticle($new['uid']))
+				$article->deleteWithCustomFields();
+			return;
+		}
+
+		$id_cat = ArticleCategoriesMap::getCategoryOrDefaultCategory($new['category'])
+			->getId();
+
+		$block = function() use ($new, $id_cat){
+			if ($article = $this->_getArticle($new['uid']))
+				return $this->updateArticle($article, $new, $id_cat);
+			return $this->createArticle($new, $id_cat);
+		};
+
+		$this->traceUnknownKohaUrls($block, $new['uid']);
+	}
+
+
+  public function importArticles() {
+    foreach($this->t3db->findAllArticles() as $new)
+			$this->_importArticle($new);
 
 		Class_Import_Typo3_Logs::getInstance()->setArticlesLog();
     return $this;
   }
 
 
-	public function createArticle($new,$id_cat) {
-		$date_creation = date("Y-m-d H:i:s", $new['crdate']);
-		$debut = $new['starttime'] ? date("Y-m-d", $new['starttime']) : '';
-		$fin = $new['endtime'] ? date("Y-m-d", $new['endtime']) : '';
+	protected function _importArticle($new) {
+		$id_cat = $this->news_categories_map->find($new['category'])->getId();
+		$element = $this->traceUnknownKohaUrls(
+																					 function() use ($new, $id_cat){
+																						 return $this->createArticle($new, $id_cat);
+																					 },
+																					 $new['uid']);
 
-		$element = Class_Article::newInstance(['date_creation' => $date_creation,
-																					 'debut' => $debut,
-																					 'fin' => $new['hidden'] ? null : $fin,
-																					 'id_user' => $this->userMap->find($new['cruser_id']),
-																					 'description' => $new['short']? $this->manipulate_body($new['short'],$new['image']): '',
-																					 'contenu' => $new['bodytext'] ? $this->manipulate_body($new['bodytext'],$new['image']) : '&nbsp;',
-																					 'id_cat' => $id_cat,
-																					 'status' => Class_Article::STATUS_VALIDATED,
-																					 'tags' => str_replace(', ', ';', $new['tx_danpextendnews_tags']),
-																					 'titre' => $new['title']]);
+
+		$this->news_categories_map->setDomainsFor($element,
+																							$this->t3db->findAllForeignUidForNewsCat($new['uid']));
+	}
 
 
-		if (!$element->save()) {
-			Class_Import_Typo3_Logs::getInstance()->incrementArticleRejected($element, [$new['uid'],
-																																									$new['title']]);
-			return $element;
+	public function setMiopDateEvent($article, $data) {
+		if ($this->existCategoriesAsForeign($data, self::FOREIGN_UID_DATETIME)) {
+			$converted_date = $this->formatDate($data['datetime']);
+			$article->setEventsDebut($converted_date)->setEventsFin($converted_date);
+		}
+
+		$this->changeCategory($article, $data, $this->getCatDossierDoc()->getId());
+		return $article;
+	}
 
+
+	public function existCategoriesAsForeign($data, $array_catid) {
+		foreach ($this->t3db->findAllForeignUidForNewsCat($data['uid']) as $domain)
+			if (in_array($domain['uid_foreign'], $array_catid))
+				return true;
+
+		return false;
+	}
+
+
+	public function changeCategory($article,$data,$id_cat) {
+		if ($this->existCategoriesAsForeign($data, [$id_cat]))
+			$article->setIdCat($this->news_categories_map->find($id_cat)->getId());
+	}
+
+
+	public function formatDate($date_str) {
+		return $date_str ? date("Y-m-d H:i", $date_str) : '';
+	}
+
+
+	public function updateArticle($article,$new,$id_cat) {
+		$date_creation = date("Y-m-d H:i:s", $new['crdate']);
+		$debut = $this->formatDate($new['starttime']);
+		$fin = $this->formatDate($new['endtime']);
+		$description = $new['short'] ?
+			$this->manipulate_body($new['short'], $new['image']) :
+			$this->addImage($new['image']);
+
+		$contenu = $new['bodytext'] ?
+			$this->manipulate_body($new['bodytext'], $new['image']) :
+			'&nbsp;';
+
+		$tags = str_replace(', ', ';', $new['tx_danpextendnews_tags']);
+
+		$article->updateAttributes(['date_creation' => $date_creation,
+																'debut' => $debut,
+																'fin' => $new['hidden'] ? null : $fin,
+																'id_user' => $this->userMap->find($new['cruser_id']),
+																'description' => $description,
+																'contenu' => $contenu,
+																'id_cat' => $id_cat,
+																'status' => Class_Article::STATUS_VALIDATED,
+																'tags' => $tags,
+																'titre' => $new['title']]);
+
+		$article = $this->setMiopDateEvent($article,$new);
+		$article->setCustomField('uid_typo3',$new['uid']);
+		if (!$article->saveWithCustomFields()) {
+			Class_Import_Typo3_Logs::getInstance()
+				->incrementArticleRejected($article, [$new['uid'], $new['title']]);
+			return $article;
 		}
+
 		Class_Import_Typo3_Logs::getInstance()->incrementArticlesSaved();
-		return $element;
+		return $article;
 	}
 
+
+	public function createArticle($new,$id_cat) {
+		return $this->updateArticle(new Class_Article(), $new, $id_cat);
+	}
+
+
   private function typo_event_to_date($date, $time) {
     $year = substr($date, 0, 4);
     $month = substr($date, 4, 2);
@@ -357,57 +563,94 @@ class Class_Import_Typo3 {
       $minutes = 59;
     }
 
-    return sprintf('%s-%s-%s %s:%s:%s', $year, $month, $day, $hour, $minutes, '00');
+    return sprintf('%s/%s/%s %s:%s:%s', $year, $month, $day, $hour, $minutes, '00');
   }
 
 
   public function importCalendar() {
-    $cal_news = $this->t3db->findAllCalendarEvents();
-    foreach($cal_news as $new) {
-			$this->traceUnknownKohaUrls( function() use ($new) {
-																														return $this->createCalendar($new);
-																													},
-																	 $new['uid']);
-		}
+    foreach($this->t3db->findAllCalendarEvents() as $new)
+			$this->traceUnknownKohaUrls(
+																	function() use ($new) {
+																		return $this->createCalendar($new);
+																	},
+																	$new['uid']);
     return $this;
   }
 
 
 	public function createCalendar($new) {
-
       $date_creation = date("Y-m-d H:i:s", $new['crdate']);
-      $debut = $new['starttime'] ? date("Y-m-d", $new['starttime']) : '';
-      $fin = $new['endtime'] ? date("Y-m-d", $new['endtime']) : '';
+			$debut = $this->formatDate($new['starttime']);
+			$fin = $this->formatDate($new['endtime']);
+
       $event_debut = $this->typo_event_to_date($new['start_date'], $new['start_time']);
       $event_fin = $this->typo_event_to_date($new['end_date'], $new['end_time']);
+			$contenu = $this->manipulate_body($new['description'] ?
+																				$new['description'] : $new['title'],
+																				$new['image']);
 
-      $element = Class_Article::newInstance(['debut' => $debut,
+			$id_cat = $this->cal_categories_map->find($new['category_id'])->getId();
+
+      $element = Class_Article::newInstance(['date_creation' => $date_creation,
+																						 'debut' => $debut,
                                              'fin' => $new['hidden'] ? null : $fin,
                                              'events_debut' => $event_debut,
                                              'events_fin' => $event_fin,
                                              'id_user' => $this->userMap->find($new['cruser_id']),
                                              'all_day' => $new['allday'],
                                              'description' => '',
-                                             'contenu' => $this->manipulate_body($new['description'] ? $new['description'] : $new['title'],$new['image']),
-                                             'id_cat' => $this->cal_categories_map->find($new['category_id'])->getId(),
+                                             'contenu' => $contenu,
+                                             'id_cat' => $id_cat,
                                              'status' => Class_Article::STATUS_VALIDATED,
                                              'tags' => '',
                                              'titre' => $new['title']]);
 
-      if (!$element->save()) {
+			$element->setCustomField(self::UID_TYPO3_CF,$new['uid']);
+      if (!$element->saveWithCustomFields()) {
         $this->report['calendar_errors']++;
-        Class_Import_Typo3_Logs::getInstance()->addErrorRow('calendar with uid: ' . $new['uid'] . '(' . $new['title'] . ') - ' . implode(', ', $element->getErrors()));
+        Class_Import_Typo3_Logs::getInstance()
+					->addErrorRow('calendar with uid: ' . $new['uid']
+												. '(' . $new['title'] . ') - '
+												. implode(', ', $element->getErrors()));
         return $element;
       }
 
 			$element->setDateCreation($date_creation)->save();
-
       $this->report['calendar_created']++;
-
-			$this->cal_categories_map->setDomainsFor($element,
-																							 $this->t3db->findAllForeignUidForCalendarEventCategory($new['uid']));
+			$this->cal_categories_map
+				->setDomainsFor($element,
+												$this->t3db->findAllForeignUidForCalendarEventCategory($new['uid']));
 			return $element;
+	}
+
 
+	public function createCategory($category_name, $id_cat_mere=0) {
+		if ($category = Class_ArticleCategorie::findFirstBy(['libelle' => $category_name]))
+			return $category;
+
+    $category = Class_ArticleCategorie::newInstance(['libelle' => $category_name,'id_cat_mere' =>$id_cat_mere]);
+
+		if ($category->save())
+			return $category;
+
+		return null;
+	}
+
+
+	public function updateArticlePages($update_date) {
+		$pages_cat = Class_ArticleCategorie::findFirstBy(['libelle' => 'Pages fixes']);
+		foreach($this->t3db->findAllContentsSince($update_date) as $new) {
+			$this->traceUnknownKohaUrls(
+																	function() use ($new, $pages_cat){
+																		if ($article = $this->_getArticle($new['uid']))
+																			return $this->updateArticlePage($article,$new,$pages_cat);
+																		return $this->createArticlePage($new, $pages_cat);
+																	},
+				$new['uid']);
+      $this->report['pages_created']++;
+    }
+
+    return $this;
 	}
 
 
@@ -428,40 +671,49 @@ class Class_Import_Typo3 {
     return $this;
   }
 
-
-	public function createArticlePage($new, $pages_cat) {
+	public function updateArticlePage($article,$new,$pages_cat) {
 		$date_creation = date("Y-m-d H:i:s", $new['crdate']);
-		$debut = $new['starttime'] ? date("Y-m-d", $new['starttime']) : '';
-		$fin = $new['endtime'] ? date("Y-m-d", $new['endtime']) : '';
-
+		$debut = $this->formatDate($new['starttime']);
+		$fin = $this->formatDate($new['endtime']);
+		$contenu = $new['bodytext'] ?
+			$this->manipulate_body($new['bodytext'], $new['image'])
+			:'';
 
-		$element = Class_Article::newInstance(['date_creation' => $date_creation,
+		$element = $article->updateAttributes(['date_creation' => $date_creation,
 																					 'debut' => $debut,
 																					 'fin' => $new['hidden'] ? null : $fin,
 																					 'id_user' => $this->userMap->find($new['cruser_id']),
 																					 'description' => '',
-																					 'contenu' => $new['bodytext'] ? $this->manipulate_body($new['bodytext'],$new['image']) :'',
+																					 'contenu' => $contenu,
 																					 'id_cat' => $pages_cat->getId(),
 																					 'status' => Class_Article::STATUS_VALIDATED,
 																					 'tags' => '',
 																					 'titre' => $new['header']]);
 
-		if (!$element->save()) {
+		$element->setCustomField(self::UID_TYPO3_CF,$new['uid']);
+		if (!$element->saveWithCustomFields()) {
 			$this->report['pages_errors']++;
-			Class_Import_Typo3_Logs::getInstance()->addErrorRow('pages with uid: ' . $new['uid'] . ' (' . $new['title'] . ') - ' . implode(', ', $element->getErrors()));
+			Class_Import_Typo3_Logs::getInstance()
+				->addErrorRow('pages with uid: ' . $new['uid']
+											. ' (' . $new['title'] . ') - '
+											. implode(', ', $element->getErrors()));
 		}
+
 		return $element;
 	}
 
 
-	public function traceUnknownKohaUrls($closure,$uid) {
+	public function createArticlePage($new, $pages_cat) {
+		return $this->updateArticlePage(new Class_Article(), $new, $pages_cat);
+	}
+
+
+	public function traceUnknownKohaUrls($closure, $uid) {
 		$this->unknown_koha_urls = [];
 		$element = $closure();
-		foreach($this->unknown_koha_urls as $url) {
-			Class_Import_Typo3_Logs::getInstance()->addUnknownUrl($element,
-																														$url,
-																														$uid);
-		}
+		foreach($this->unknown_koha_urls as $url)
+			Class_Import_Typo3_Logs::getInstance()->addUnknownUrl($element, $url, $uid);
+
 		return $element;
 	}
 
@@ -470,13 +722,12 @@ class Class_Import_Typo3 {
   public function format_url($url) {
     if (!$url = explode(' ', $url)[0])
       return '';
+
     $url = preg_replace(['/^http:\/\//', '/^\/\//'], '', trim(strtolower($url)));
     return (0 !== strpos('http', $url)) ? 'http://'.$url : $url;
   }
 
 
-
-
   public function reset_auto_increment() {
     Zend_Registry::get('sql')->execute('alter table cms_article AUTO_INCREMENT=1');
     Zend_Registry::get('sql')->execute('alter table cms_categorie AUTO_INCREMENT=1');
@@ -488,67 +739,57 @@ class Class_Import_Typo3 {
 }
 
 
-class UserMap {
-
 
+class UserMap {
 	protected $map = [];
-	protected $saved = 0;
-	protected $updated = 0;
-	protected $errors = [];
-	protected $t3users = 0;
 
-
-	public function __construct() {
+	public function init() {
 		$where = 'ROLE_LEVEL < ' . ZendAfi_Acl_AdminControllerRoles::ADMIN_PORTAIL; // exclude ADMIN.
 		$where .= ' AND ROLE_LEVEL <> ' . ZendAfi_Acl_AdminControllerRoles::ABONNE_SIGB; // exclude SIGB members.
-
     Class_Users::deleteBy(['where' => $where]);
 	}
 
 
-	public function updateUser($data, &$user) {
-		$user->setNom($data['realName']);
-		$user->setMail($data['email']);
+	public function updateUser($data, $user) {
+		$user->setNom($data['realName'])
+				 ->setMail($data['email'])
+				 ->setPassword('achanger');
 	}
 
 
 	public function newUser($data) {
-		Class_Import_Typo3_Logs::getInstance()->incrementT3Users();
-			$user;
-			$updated = 0;
-			if ($user = Class_Users::findFirstBy(['login'=> $data['username']])) {
-				$updated = 1;
-				$this->updateUser($data, $user);
-			}
-			else {
-				$user = Class_Users::newInstance(['nom' => $data['realName'],
-																					'mail' => $data['email'],
-																					'login' => $data['username'],
-																					'password' => 'achanger']);
-			}
+		$logs = Class_Import_Typo3_Logs::getInstance();
+		$logs->incrementT3Users();
 
-			if ($data['admin'] == '1') {
-				$user->beAdminPortail();
-			}
-			else {
-				$user->changeRoleTo(ZendAfi_Acl_AdminControllerRoles::MODO_PORTAIL);
-			}
+		$user = $this->_findOrCreate($data['username']);
+		$updated = !$user->isNew();
+		$this->updateUser($data, $user);
 
-			if (!$user->save())
-				return Class_Import_Typo3_Logs::getInstance()->incrementRejected($user, $data);
+		($data['admin'] == '1') ?
+			$user->beAdminPortail() :
+			$user->changeRoleTo(ZendAfi_Acl_AdminControllerRoles::MODO_PORTAIL);
 
-			$updated
-				? Class_Import_Typo3_Logs::getInstance()->incrementUpdated()
-				: Class_Import_Typo3_Logs::getInstance()->incrementSaved();
+		if (!$user->save()) {
+			$logs->incrementRejected($user, $data);
+			return;
+		}
 
-			$this->map[$data['uid']] = $user->getId();
+		$updated
+			? $logs->incrementUpdated()
+			: $logs->incrementSaved();
+
+		$this->map[$data['uid']] = $user->getId();
+	}
+
+
+	protected function _findOrCreate($login) {
+		return ($user = Class_Users::findFirstBy(['login' => $login])) ?
+			$user : Class_Users::newInstance(['login' => $login]);
 	}
 
 
 	public function find($uid) {
-    return  isset($this->map[$uid])
-      ? $this->map[$uid]
-      : 0;
+    return isset($this->map[$uid]) ? $this->map[$uid] : 0;
 	}
 }
 
@@ -584,12 +825,15 @@ abstract class CategoriesMap {
       $cat = $this->map[$t3id];
       return $cat->getId();
     }
+
     return  0;
   }
 
+
   public function newCategory($title, $t3uid, $t3pid) {
     $category = $this->_buildNewCategory($title, $this->getParentCatId($t3pid));
-    if(!$category->save())
+		$category->setCustomField(Class_Import_Typo3::UID_TYPO3_CF,$t3uid);
+    if(!$category->saveWithCustomFields())
 			return $this->addError($category, $title, $t3uid, $t3pid);
 
 		$this->incrementSaved();
@@ -607,6 +851,40 @@ abstract class CategoriesMap {
 		}
 	}
 
+
+	public static function getOrCreateDefaultCategory() {
+		if ($category = Class_ArticleCategorie::findFirstBy(['libelle' => 'Non classé']))
+			return $category;
+
+    $cat = Class_ArticleCategorie::newInstance(['libelle' => 'Non classé',
+																								'id_cat_mere' => 0]);
+		$cat->save();
+		return $cat;
+
+	}
+
+
+	public static function getCategory($uid) {
+		return Class_ArticleCategorie::findFirstByCustomFieldValue(Class_Import_Typo3::UID_TYPO3_CF, $uid);
+	}
+
+
+	public static function getCategoryOrDefaultCategory($uid) {
+		return ($category = self::getCategory($uid)) ?
+			$category : self::getOrCreateDefaultCategory();
+	}
+
+
+	public function fixParentCategory($t3uid, $t3pid) {
+		$category = self::getCategory($t3uid);
+		if ($category->getId() == $this->default_cat->getId())
+			return;
+
+		$category->setParentCategorie(self::getCategory($t3pid))
+						 ->save();
+	}
+
+
 	public function createDomainMap() {
 		foreach($this->map as $category)
 			$this->domaine_map->findOrCreateDomaine($category);
@@ -619,22 +897,22 @@ abstract class CategoriesMap {
 
 
   public function find($t3id) {
-    return  isset($this->map[$t3id])
+		return isset($this->map[$t3id])
       ? $this->map[$t3id]
       : $this->default_cat;
   }
 
 
 	public function setDomainsFor($model, $t3_domain_ids) {
-		$domains = [$this->domaine_map->findOrCreateDomaine($model->getCategorie())];
+		$domains = [];
 		foreach ($t3_domain_ids as $t3_domain) {
 			$category = $this->find($t3_domain['uid_foreign']);
-			$domains[] = $this->domaine_map->findOrCreateDomaine($category);
+			$domains[] = $this->domaine_map
+				->findOrCreateDomaine($category,$t3_domain['uid_foreign']);
 		}
 
-		if (isset($category)) {
+		if (isset($category))
 			$model->setCategorie($category);
-		}
 
 		$model->setDomaines($domains)->save();
 	}
@@ -645,7 +923,7 @@ abstract class CategoriesMap {
 class DomaineMap {
   protected $map = [];
 
-  public function findOrCreateDomaine($category) {
+  public function findOrCreateDomaine($category, $uid=0) {
     if (!$category)
       return null;
 
@@ -655,10 +933,14 @@ class DomaineMap {
       return $this->map[$path];
 
     $parent_domaine = $this->findOrCreateDomaine($category->getParentCategorie());
+
     $catalogue = Class_Catalogue::newInstance(['libelle' => $category->getLibelle(),
                                                'parent_id' => $parent_domaine ? $parent_domaine->getId() : 0]);
 
-		if (!$catalogue->save()) {
+		if (!$uid)
+			$catalogue->setCustomField(Class_Import_Typo3::UID_TYPO3_CF, $uid);
+
+		if (!$catalogue->saveWithCustomFields()) {
 			Class_Import_Typo3_Logs::getInstance()->incrementDomainsRejected($catalogue);
 			return;
 		}
@@ -669,19 +951,16 @@ class DomaineMap {
 }
 
 
-class ArticleCategoriesMap  extends CategoriesMap {
+class ArticleCategoriesMap extends CategoriesMap {
   protected function _buildNewCategory($libelle, $parent_id=0) {
-    $cat =  Class_ArticleCategorie::newInstance(['libelle' => $libelle,
-                                                 'id_cat_mere' => $parent_id]);
-    /* if ($double_cat = $this->findByPath($cat->getPath)) */
-    /*  return $double_cat; */
-    /* $this->path_map [] = $cat; */
-    return $cat;
+    return Class_ArticleCategorie::newInstance(['libelle' => $libelle,
+																								'id_cat_mere' => $parent_id]);
   }
 
 
 	protected function addError($category, $title, $t3uid, $t3pid) {
-		return Class_Import_Typo3_Logs::getInstance()->incrementArticleCategoriesRejected($category, $title, $t3uid, $t3pid);
+		return Class_Import_Typo3_Logs::getInstance()
+			->incrementArticleCategoriesRejected($category, $title, $t3uid, $t3pid);
 	}
 
 
@@ -691,7 +970,7 @@ class ArticleCategoriesMap  extends CategoriesMap {
 }
 
 
-class SiteCategoriesMap  extends CategoriesMap {
+class SiteCategoriesMap extends CategoriesMap {
   protected function _buildNewCategory($libelle, $parent_id=0) {
     return Class_SitothequeCategorie::newInstance(['libelle' => $libelle,
                                                    'id_cat_mere' => $parent_id]);
@@ -699,7 +978,8 @@ class SiteCategoriesMap  extends CategoriesMap {
 
 
 	protected function addError($category, $title, $t3uid, $t3pid) {
-		return Class_Import_Typo3_Logs::getInstance()->incrementSitoCategoriesRejected($category, $title, $t3uid, $t3pid);
+		return Class_Import_Typo3_Logs::getInstance()
+			->incrementSitoCategoriesRejected($category, $title, $t3uid, $t3pid);
 	}
 
 
@@ -708,7 +988,6 @@ class SiteCategoriesMap  extends CategoriesMap {
 	}
 
 
-
 	public function findOrCreateByPath($path) {
 		if ($path == Trait_TreeNode::$PATH_SEPARATOR)
 			return null;
@@ -716,14 +995,20 @@ class SiteCategoriesMap  extends CategoriesMap {
 		$parts = array_values(array_filter(explode(Trait_TreeNode::$PATH_SEPARATOR, $path)));
 
 		$category = null;
-		foreach($parts as $part) {
-			if (!$sub_category = Class_SitothequeCategorie::findFirstBy(['libelle' => $part,
-																																	 'id_cat_mere' => $category ? $category->getId() : 0])) {
-				$sub_category = Class_SitothequeCategorie::newInstance(['libelle' => $part,
-																														'id_cat_mere' => $category ? $category->getId() : 0]);
-				$sub_category->save();
-			}
-			$category = $sub_category;
+		foreach($parts as $part)
+			$category = $this->_findOrCreateUnder($part, $category);
+
+		return $category;
+	}
+
+
+	protected function _findOrCreateUnder($label, $parent) {
+		$attribs = ['libelle' => $label,
+								'id_cat_mere' => $parent ? $parent->getId() : 0];
+
+		if (!$category = Class_SitothequeCategorie::findFirstBy($attribs)) {
+			$category = Class_SitothequeCategorie::newInstance($attribs);
+			$category->save();
 		}
 
 		return $category;
@@ -746,8 +1031,6 @@ class Typo3DB {
 		$t3db,
 		$pages_titles;
 
-
-
 	public function __construct() {
     $this->t3db = new Class_Systeme_Sql('','','','');
 		$this
@@ -757,7 +1040,8 @@ class Typo3DB {
 																		['host' => 'localhost',
 																		 'username' => 'root',
 																		 'password' => 'root',
-																		 'dbname' =>  'miop_typo3']));
+//																		 'dbname' =>  'miop_typo3']));
+																		 'dbname' =>  'miopencoding']));
 	}
 
 
@@ -765,37 +1049,63 @@ class Typo3DB {
 		if (isset($this->pages_titles[$uid]))
 			return $this->pages_titles[$uid];
 
-		if (!$row = $this->t3db->fetchEnreg('select title from pages where uid='.$uid)) {
-			return $this->pages_titles[$uid] = '';
-		}
+		$title = ($row = $this->t3db->fetchEnreg('select title from pages where uid=' . $uid)) ?
+			$row['title'] : '';
 
-		return $this->pages_titles[$uid] = $row['title'];
+		return $this->pages_titles[$uid] = $title;
 	}
 
+
+	public function findAllPagesWithPid($pid) {
+		return $this->t3db->fetchAll('select * from pages where pid='.$pid);
+	}
+
+
 	public function findAllUsers() {
 		return $this->t3db->fetchAll('select * from be_users where uid > 1');
 	}
 
+
 	public function findAllNewsCat() {
 		return $this->t3db->fetchAll('select * from tt_news_cat where deleted=0 order by uid ASC');
 	}
 
+
+	public function findAllNewsCatSince($update_date) {
+		return $this->t3db->fetchAll('select * from tt_news_cat where deleted=0 and  tstamp >='.$update_date.' order by uid ASC');
+	}
+
+
 	public function findAllEventCat() {
 		return $this->t3db->fetchAll("select * from tx_cal_category where deleted=0 order by uid");
 	}
 
+
+	public function findAllEventCatSince($update_date) {
+		return $this->t3db->fetchAll("select * from tx_cal_category where deleted=0 and tstamp >=".$update_date." order by uid");
+	}
+
+
 	public function findAllSites() {
 		return $this->t3db->fetchAll("select * from tt_news where deleted=0 and hidden=0 and ext_url>'' order by uid ASC");
 	}
 
+
+	public function findAllArticlesSince($last_import_date) {
+		return $this->t3db->fetchAll("select * from tt_news where hidden=0 and ext_url='' and tstamp >= ".$last_import_date."  order by uid ASC");
+	}
+
+
 	public function findAllArticles() {
 		return $this->t3db->fetchAll("select * from tt_news where deleted=0 and hidden=0 and ext_url='' order by uid ASC");
 	}
 
+
 	public function findAllForeignUidForNewsCat($uid) {
 		return $this->t3db->fetchAll("select distinct uid_foreign from tt_news_cat_mm where uid_local=". $uid ." order by sorting");
 	}
 
+
 	public function findAllForeignUidForCalendarEventCategory($uid) {
 		return $this->t3db->fetchAll("select distinct uid_foreign from tx_cal_event_category_mm where uid_local=" . $uid . " order by sorting");
 	}
@@ -805,8 +1115,19 @@ class Typo3DB {
 		return $this->t3db->fetchAll("select * from tx_cal_event where deleted=0 and hidden=0 order by uid ASC");
 	}
 
-	public function findAllContents() {
-		return $this->t3db->fetchAll("select * from tt_content where deleted=0 and hidden=0 and header>'' and bodytext>'' and  (ctype='text' or ctype='textpic') order by uid ASC");
+
+	public function findAllContents($page_id=false) {
+		$pid_sql='';
+		if ($page_id)
+			$pid_sql='and pid='.$page_id;
+
+		return $this->t3db->fetchAll("select * from tt_content where deleted=0 and hidden=0 and header>'' and bodytext>'' and  (ctype='text' or ctype='textpic') ".$pid_sql." order by uid ASC");
 	}
+
+
+	public function findAllContentsSince($update_date) {
+		return $this->t3db->fetchAll("select * from tt_content where deleted=0 and tstamp>=".$update_date." and hidden=0 and header>'' and bodytext>'' and  (ctype='text' or ctype='textpic') order by uid ASC");
+	}
+
 }
 ?>
\ No newline at end of file
diff --git a/library/Class/Indexation.php b/library/Class/Indexation.php
index 6e3d3cb5bcca8414c4c0178302f58f34ccb36878..65a27098db2c272fc25a9020cbdb98a529d4c571 100644
--- a/library/Class/Indexation.php
+++ b/library/Class/Indexation.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
  */
 ////////////////////////////////////////////////////////////////////////
 // OPAC3 :       CALCUL DES INDEX
@@ -24,19 +24,27 @@
 
 
 class Class_Indexation {
+	protected static $_instance;
 
 	private $articles;							// Articles rejetes
 	private $inclu;									// Mots inclus
 	private $exclu;									// Mots vides
 	private $pluriel;								// Règles des pluriels
 	private $tableMaj;							// Table de transco pour majuscules
-	
+
+
+	public static function getInstance() {
+		if(!static::$_instance)
+			static::$_instance = new Class_Indexation();
+		return static::$_instance;
+	}
+
 	public function __construct() {
 		// Lire formes rejetées
 		$this->articles=array("L'","LE ","LA ","LES ","UN ","UNE ");
 		$this->inclu=array("AN","AS","OR","U2","AI","LU","XO","DO","RE","MI","FA","SI","AC","DC","XX","B","C","D","E","F","G","H","I","J","K","M","P","Q","R","S","T","V","W","X","Y","Z","L","YU","UT","LI","OC","PI","ZU","WU","TO","OZ","ZZ","XX");
 		$this->exclu = array("L","LE","LA","LES","UN","UNE","LES","DES","MES","TES","CES");
-		
+
 		// Pluriels
 		$this->pluriel=array(
 			array("AIL","AULX"),
@@ -86,9 +94,9 @@ class Class_Indexation {
 			array("*EU","*EUX"),
 			array("*AU","*AUX")
 			);
-	
+
 		// Init table ascii pour majuscules
-		$this->tableMaj = str_repeat( " ", 41 ) 
+		$this->tableMaj = str_repeat( " ", 41 )
 			. "*     0123456789       "
 			. "ABCDEFGHIJKLMNOPQRSTUVWXYZ      "
 			. "ABCDEFGHIJKLMNOPQRSTUVWXYZ      "
@@ -121,18 +129,18 @@ class Class_Indexation {
 		foreach($this->articles as $article) {
 			$lg = strlen($article);
 			if (strLeft($titre, $lg)==$article) {
-				$titre = strMid($titre,$lg,256); 
+				$titre = strMid($titre,$lg,256);
 				break;
 			}
 		}
 		$titre=$this->alphaMaj($titre);
 		return $titre;
 	}
-	
+
 	/** Rend une suite de mots complete pour les formes plurielles */
 	public function getExpressionRecherche($mot) {
 		$mot = trim($mot);
-		if (!$mot) 
+		if (!$mot)
 			return false;
 
 		// Pluriel
@@ -147,7 +155,7 @@ class Class_Indexation {
 		$ret = "(" . $m[0] . $etoile . " " . $m[1] . " " . $m[2] . ")";
 		return trim($ret);
 	}
-	
+
 	/** Transformation en majuscules */
 	public function alphaMaj($chaine) {
 		$chaine = utf8_decode($chaine);
@@ -160,10 +168,10 @@ class Class_Indexation {
 		}
 		return trim($new);
 	}
-	
+
 	/** Remet les articles entre parenthèses au début du titre */
 	public function setArticleDebut($titre) {
-		if (substr($titre, -1) != ')') 
+		if (substr($titre, -1) != ')')
 			return $titre;
 
 		foreach($this->articles as $article) {
@@ -173,7 +181,7 @@ class Class_Indexation {
 				$deb = substr($titre, -$lg);
 				$deb = str_replace("(", "", $article);
 				$deb = str_replace(")", "", $article);
-				if (substr($deb,-1) != "'") 
+				if (substr($deb,-1) != "'")
 					$deb .= " ";
 				$new = $deb.trim(substr($titre, 0, (strlen($titre) - $lg)));
 
@@ -182,7 +190,7 @@ class Class_Indexation {
 		}
 		return $titre;
 	}
-	
+
 	/** Decoupe une expression en mots en tenant compte des exclusions / inclusions */
 	public function getMots($chaine) {
 		$new = [];
@@ -202,12 +210,12 @@ class Class_Indexation {
 			// On garde le mot
 			$new[$index++] = $mot[$i];
 		}
-		return $new;	
+		return $new;
 	}
 
 	/** Rend une chaine de mots dedoublonnes et filtres */
 	public function getFulltext($data) {
-		if (gettype($data) != 'array') 
+		if (gettype($data) != 'array')
 			$data = array($data);
 
 		$new = '';
@@ -221,12 +229,12 @@ class Class_Indexation {
 		}
 		return trim($new);
 	}
-	
+
 	/**
 	 * Rend le mot au singulier et au pluriel
 	 */
 	public function getPluriel($mot) {
-		if (strToUpper($mot) != $mot) 
+		if (strToUpper($mot) != $mot)
 			$mot = $this->alphaMaj($mot);
 		if (!trim($mot))
 			return false;
@@ -250,7 +258,7 @@ class Class_Indexation {
 		// Si inchangé on ajoute le S
 		if ($singulier == $pluriel) {
 			if (strRight($mot,1)=="S") {
-				$pluriel = $singulier; 
+				$pluriel = $singulier;
 				$singulier = strLeft($singulier,strlen($singulier)-1);
 			} else {
 				$pluriel=$singulier."S";
@@ -259,7 +267,7 @@ class Class_Indexation {
 		}
 		return array($singulier,$pluriel);
 	}
-	
+
 	/** Compare deux expression et rend true si tous les mots correspondent */
 	public function compare_expression($texte1, $texte2) {
 		$mots_vides = ';THE;';
@@ -288,7 +296,7 @@ class Class_Indexation {
 
 
 	public function phonetix($sIn) {
-		if (strlen($sIn) < 4) 
+		if (strlen($sIn) < 4)
 			return false;
 
 
diff --git a/library/Class/Indexation/PseudoNotice.php b/library/Class/Indexation/PseudoNotice.php
index 4171837f3d5d93d0ddc23ab698f0c9805f24f367..ad5c63a1a79c6c16df735ec66c282f166370b21a 100644
--- a/library/Class/Indexation/PseudoNotice.php
+++ b/library/Class/Indexation/PseudoNotice.php
@@ -16,18 +16,12 @@
  *
  * 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 Class_Indexation_PseudoNotice {
-	const TYPE_CMS = 8;
-	const TYPE_RSS = 9;
-	const TYPE_SITO=10;
-
-	protected $_datas;
 	protected $_model;
-	protected $_indexation;
-
+	protected $_datas = [];
 	protected $_table;
 	protected $_id;
 	protected $_label;
@@ -36,6 +30,7 @@ class Class_Indexation_PseudoNotice {
 	protected $_exemplaire;
 	protected $_type_doc;
 
+
 	public static function index($model) {
 		if (null !== ($notice = $model->getNotice()))
 			$notice->delete();
@@ -43,11 +38,12 @@ class Class_Indexation_PseudoNotice {
 		return ($instance->isValid()) ? $instance->save() : false;
 	}
 
+
 	public static function newWith($type_doc, $datas) {
 		// types simples
-		$mapping = [self::TYPE_CMS => 'Cms', 
-								self::TYPE_RSS => 'Rss', 
-								self::TYPE_SITO=> 'Sito'];
+		$mapping = [Class_TypeDoc::ARTICLE => 'Cms',
+								Class_TypeDoc::RSS => 'Rss',
+								Class_TypeDoc::SITE=> 'Sito'];
 
 		if (array_key_exists($type_doc, $mapping)) {
 			$class_name = 'Class_Indexation_PseudoNotice_' . $mapping[$type_doc];
@@ -60,11 +56,11 @@ class Class_Indexation_PseudoNotice {
 		return new Class_Indexation_PseudoNotice_Null();
 	}
 
+
 	public function __construct($type_doc, $datas) {
 		$this->_type_doc = (int)$type_doc;
-		$this->_indexation = new Class_Indexation();
 		$this->_datas = array_change_key_case($datas, CASE_LOWER);
-		$this->_model = call_user_func_array([$this->_model_name, 'find'], 
+		$this->_model = call_user_func_array([$this->_model_name, 'find'],
 																				 [$this->_datas[$this->_id]]);
 	}
 
@@ -86,7 +82,6 @@ class Class_Indexation_PseudoNotice {
 
 	public function save() {
 		$this->_prepare();
-
 		$notice = Class_Notice::newInstance(['type_doc' => $this->_type_doc]);
 		if (!$notice->save())
 			return false;
@@ -108,7 +103,8 @@ class Class_Indexation_PseudoNotice {
 	}
 
 	protected function _prepare() {
-		$this->_datas = array_merge( ['id_bib' => Class_IntBib::findFirstBy(['order' => 'id_bib'])->getId(),
+		$intBib = Class_IntBib::findFirstBy(['order' => 'id_bib']);
+		$this->_datas = array_merge( ['id_bib' => $intBib ? $intBib->getId() : 0,
 																	'date_maj' => '',
 																	'auteur' => '',
 																	'matiere' => '',
@@ -132,13 +128,20 @@ class Class_Indexation_PseudoNotice {
 	}
 
 
-	protected function _getAuthorNames() {
+	public function getAuthorsNames() {
 		$names = [];
-		foreach($this->extractAuthors() as $author)
-			$names []= $author->getName();
-		return $names;
+		foreach($authors = $this->extractAuthors() as $author)
+			$names[] = $author->getName();
+
+		return implode('-', $names);
 	}
 
+
+	public function getEditorsNames() {
+		return implode('-', $this->extractEditors());
+	}
+
+
 	protected function _index() {
 		$unimarc = $this->_getUnimarc();
 		$data = [];
@@ -146,27 +149,22 @@ class Class_Indexation_PseudoNotice {
 		$data = $this->indexDeweyAndPCDM4($data);
 		$this->_notice->updateAttributes($data);
 
-		$authors = $this->_getAuthorNames();
-		
+		$authors = $this->getAuthorsNames();
+
+		$indexation = Class_Indexation::getInstance();
 		$this->_notice
-			->setMatieres($this->extractFullTextFromCodif($data, 
-																										'matiere', 
+			->setMatieres($this->extractFullTextFromCodif($data,
+																										'matiere',
 																										'Class_CodifMatiere'))
-			->setAlphaTitre($this->_indexation->codeAlphaTitre($this->_datas["titre"]))
-			->setClefAlpha($this->_indexation->getClefAlpha($this->_notice->getTypeDoc(), 
-																											$this->_datas["titre"], 
-																											'', 
-																											implode('-', $authors),
-																											'',
-																											$this->_datas["editeur"],
-																											$this->_datas["annee"]))
-			->setClefOeuvre($this->_indexation->getClefOeuvre($this->_datas["titre"],
-																												'', 
+			->setAlphaTitre($indexation->codeAlphaTitre($this->_datas["titre"]))
+			->setClefAlpha($this->_model->getAlphaKey())
+			->setClefOeuvre($indexation->getClefOeuvre($this->_datas["titre"],
+																												'',
 																												$this->_datas["auteur"],
 																												''))
-			->setTitres($this->_indexation->getfullText($this->extractTitles()))
-			->setAuteurs($this->_indexation->getfullText($authors))
-			->setAlphaAuteur($this->_indexation->alphaMaj(implode(' ', $authors)))
+			->setTitres($indexation->getfullText($this->extractTitles()))
+			->setAuteurs($indexation->getfullText($authors))
+			->setAlphaAuteur($indexation->alphaMaj(implode(' ', $this->extractAuthors())))
 			->setFacettes(implode(' ',$this->_getFacettes()))
 			->setAnnee($this->_datas["annee"])
 			->setQualite(3) // Qualite = pseudo_notice
@@ -174,17 +172,16 @@ class Class_Indexation_PseudoNotice {
 			->setUnimarc($unimarc)
 			->setDateMaj(date('Y-m-d H:i:s'));
 
+		if ($this->dataExist('editeur' , $this->_datas))
+			$this->_notice->setEditeur($indexation->getfullText($this->_datas["editeur"]));
 
-		if ($this->_dataExist("editeur")) 
-			$this->_notice->setEditeur($this->_indexation->getfullText($this->_datas["editeur"]));
-
-		if ($this->_dataExist("fichier") && !empty($this->_datas['fichier'])) {
+		if ($this->dataExist('fichier', $this->_datas)) {
 			$url_vignette = $this->_model->getThumbnailUrl();
 			$this->_notice->setUrlVignette($url_vignette);
 			$this->_notice->setUrlImage($url_vignette);
 		}
 
-		if ($this->_dataExist("url_image"))
+		if ($this->dataExist('url_image', $this->_datas))
 			$this->_notice->setUrlImage($this->_datas["url_image"]);
 
 
@@ -192,15 +189,15 @@ class Class_Indexation_PseudoNotice {
 
 		// exemplaire
 		$genre = '';
-		if ($this->_dataExist('genre')) {
+		if ($this->dataExist('genre', $this->_datas)) {
 			$genres = explode(';', $this->_datas['genre']);
 			$genre = $genres[0];
 		}
 
 		$cote = '';
-		if ($this->_dataExist("cote"))
-			$cote = $this->_datas["cote"];
-		
+		if ($this->dataExist('cote', $this->_datas))
+			$cote = $this->_datas['cote'];
+
 		$this->_exemplaire
 			->setGenre($genre)
 			->setCote($cote)
@@ -208,22 +205,32 @@ class Class_Indexation_PseudoNotice {
 	}
 
 
-	protected function extractAuthors() {
+	public function extractYear($model) {
+		if (!$this->dataExist('annee', $model->toArray()))
+			return '';
+		return $model->getAnnee();
+	}
+
+
+	public function extractAuthors() {
 		$authors = [];
-		if ($this->_dataExist('auteur'))
+		if ($this->dataExist('auteur', $this->_datas))
 			$authors[] = new Class_Notice_Author($this->_datas['auteur']);
 		return array_filter($authors);
 	}
 
 
-	protected function extractEditors() {
-		return array_filter([$this->_datas['editeur']]);
+	public function extractEditors() {
+		if (!$this->dataExist('editeur', $this->_datas))
+			return [];
+
+		return array_filter($this->_datas['editeur']);
 	}
 
 
 	protected function extractCollections() {
 	  $collections = [];
-		if ($this->_dataExist('collection'))
+		if ($this->dataExist('collection', $this->_datas))
 			$collections[] = $this->_datas['collection'];
 		return array_filter($collections);
 	}
@@ -235,26 +242,26 @@ class Class_Indexation_PseudoNotice {
 
 
 	protected function extractFullTextFromCodif($data, $field, $codif_class)   {
-		if (!$this->_dataExist($field)) 
+		if (!$this->dataExist($field, $this->_datas))
 			return '';
-      	 
+
 		$ids = array_filter(array_map('trim', explode(';', $this->_datas[$field])));
 		$fulltext = [];
 		foreach($ids as $id) {
 			if ($model = $codif_class::find($id))
 				$fulltext[] = $model->getLibelle();
 		}
-      
-		return $this->_indexation->getfullText(implode(' ', $fulltext));
+
+		return Class_Indexation::getInstance()->getfullText(implode(' ', $fulltext));
 	}
 
 
 	protected function indexDeweyAndPCDM4($data)	{
-		$pcdm4 = $this->extractFullTextFromCodif($data, 
-																						 'pcdm4', 
+		$pcdm4 = $this->extractFullTextFromCodif($data,
+																						 'pcdm4',
 																						 'Class_CodifPcdm4');
-		$dewey = $this->extractFullTextFromCodif($data, 
-																						 'dewey', 
+		$dewey = $this->extractFullTextFromCodif($data,
+																						 'dewey',
 																						 'Class_CodifDewey');
 
 		$data["dewey"] = implode(' ', array_filter([$pcdm4, $dewey]));
@@ -267,9 +274,9 @@ class Class_Indexation_PseudoNotice {
 			return $this->url_site;
 
 		$adresse = Class_CosmoVar::get('url_site');
-		if (strtolower(substr($adresse, 0, 7)) != 'http://') 
+		if (strtolower(substr($adresse, 0, 7)) != 'http://')
 			$adresse = 'http://' . $adresse;
-		if (substr($adresse, -1, 1) != '/') 
+		if (substr($adresse, -1, 1) != '/')
 			$adresse .= '/';
 		return $this->url_site = $adresse;
 	}
@@ -322,7 +329,7 @@ class Class_Indexation_PseudoNotice {
 	}
 
 	protected function _getCodeBarres() {
-		return str_repeat('0', (4 - strlen($this->_datas['id_bib']))) 
+		return str_repeat('0', (4 - strlen($this->_datas['id_bib'])))
 			. $this->_datas['id_bib'] . '-' . $this->_notice->getId();
 	}
 
@@ -330,8 +337,9 @@ class Class_Indexation_PseudoNotice {
 		return null != $this->_model;
 	}
 
-	protected function _dataExist($name) {
-		return array_key_exists($name, $this->_datas);
+
+	public function dataExist($name ,$datas = []) {
+		return array_key_exists($name, $datas) && ($datas[$name] != '');
 	}
 }
 
@@ -350,20 +358,22 @@ class Class_Indexation_PseudoNotice_Album extends Class_Indexation_PseudoNotice{
 		parent::_prepare();
 
 		if ($this->_datas['id_origine'])
-			$this->_datas['url'] = $this->getUrlSite() . 'bib-numerique/notice/ido/' 
+			$this->_datas['url'] = $this->getUrlSite() . 'bib-numerique/notice/ido/'
 				. $this->_datas['id_origine'];
 
 		if ($poster = $this->_model->getPoster())
 			$this->_datas['url_image'] = $poster;
 
-		$this->_datas['notes'] = $this->_model->getNotesForPseudoNotice();
+		$model = $this->_model;
+		$this->_datas['notes'] = $model->getNotesForPseudoNotice();
 	}
 
 
 	protected function _modelIdAcceptVisitor($visitor) {}
-	
+
+
 	/** @return array */
-	protected function extractAuthors() {
+	public function extractAuthors() {
 		$authors = parent::extractAuthors();
 
 		$this->_addAuthorArrayAsNoticeAuthor($this->_model->getAuthors(), $authors);
@@ -374,23 +384,27 @@ class Class_Indexation_PseudoNotice_Album extends Class_Indexation_PseudoNotice{
 		return array_unique($authors);
 	}
 
-	
+
 	protected function _addAuthorArrayAsNoticeAuthor($datas, &$authors) {
 		foreach($datas as $data)
-			$authors []= new Class_Notice_Author($data['a'], 
+			$authors []= new Class_Notice_Author($data['a'],
 																					 isset($data['4']) ? $data['4'] : '');
 	}
 
-	
-	protected function extractEditors() {
-		return array_unique(array_merge(parent::extractEditors(), 
+
+	public function extractEditors() {
+		$editors = parent::extractEditors();
+		if(empty($editors))
+			$editors = [];
+
+		return array_unique(array_merge($editors,
 																		$this->_model->getEditors()));
 	}
 
 
 	/** @return array */
 	protected function extractCollections() {
-	  return array_unique(array_merge(parent::extractCollections(), 
+	  return array_unique(array_merge(parent::extractCollections(),
 																		$this->_model->getCollections()));
 	}
 
@@ -418,7 +432,7 @@ class Class_Indexation_PseudoNotice_Album extends Class_Indexation_PseudoNotice{
 			->visitGenre($this->getValuesFromModel('genre'))
 			->visitPcdm4($this->getValuesFromModel('pcdm4'));
 
-		foreach ($this->_model->getRessources() as $ressource) 
+		foreach ($this->_model->getRessources() as $ressource)
 			$visitor->visitRessource($ressource);
 	}
 
diff --git a/library/Class/IntProfilDonnees.php b/library/Class/IntProfilDonnees.php
index 819fa1b8312d0c7dc1f7fb42832abdafd270f377..775b33a871bda7f0517650ca4cbdc735da9adda8 100644
--- a/library/Class/IntProfilDonnees.php
+++ b/library/Class/IntProfilDonnees.php
@@ -94,6 +94,45 @@ class Class_IntProfilDonnees extends Storm_Model_Abstract {
 	}
 
 
+	public static function forOrphee() {
+		return self::
+			newInstance(['libelle' => 'Unimarc Orphée',
+									 'accents' => self::ENCODING_ISO2709,
+									 'rejet_periodiques' => '0',
+									 'id_article_periodique' => self::SERIAL_FORMAT_ORPHEE,
+									 'type_fichier' => self::FT_RECORDS,
+									 'format' => self::FORMAT_UNIMARC,
+									 'attributs' =>
+									 [['type_doc' =>
+										 [[ 'code' => '0', 'label' => '', 			'zone_995' => '' ],
+											[ 'code' => '1', 'label' => 'am;na', 	'zone_995' => 'au;uu;iu;LIV;MS' ],
+											[ 'code' => '2', 'label' => 'as', 		'zone_995' => 'PER'],
+											[ 'code' => '3', 'label' => 'i;j', 		'zone_995' => 'jz;CD;LIVCD;LIVK7;K7'],
+											[ 'code' => '4', 'label' => 'g',			'zone_995' => 'gz;DIAPO;DVD;VHS;VHD;VD'],
+											[ 'code' => '5', 'label' => 'l;m', 		'zone_995' => 'lu;CDR'],
+											[ 'code' => '6', 'label' => '', 			'zone_995' => 'DOS' ],
+											[ 'code' => '7', 'label' => '', 			'zone_995' => '' ],
+											[ 'code' => '8', 'label' => '', 			'zone_995' => 'WEB;MF']
+										 ],
+										 'champ_code_barres' => 'a',
+										 'champ_cote' => 'f',
+										 'champ_type_doc' => 'c',
+										 'champ_genre' => '',
+										 'champ_section' => 'w',
+										 'champ_emplacement' => 'x',
+										 'champ_annexe' => 'g'
+										 ],
+										['zone' => '995',
+										 'champ' => '5',
+										 'format' => self::NOVELTY_DATE_FORMAT_VALUES,
+										 'jours' => '',
+										 'valeurs' => '1']
+									 ]
+									 ]
+			);
+	}
+
+
 	public static function forALOES() {
 		$type_doc = [['code' => '0', 'label' => '', 'zone_995' => ''],
 								 ['code' => '1', 'label' => 'am;na', 'zone_995' => 'LIV;MS;az'],
diff --git a/library/Class/Localisation.php b/library/Class/Localisation.php
index eaa7ceea89bfa59a0b9375bab982c7544653e532..d30f5302badd069e4a7afb698f229e26c964b978 100644
--- a/library/Class/Localisation.php
+++ b/library/Class/Localisation.php
@@ -20,6 +20,7 @@
  */
 
 class LocalisationLoader extends Storm_Model_Loader {
+	use Trait_Translator;
 
 	public function findAvailableLocalisation($localisations, $exemplaire) {
 		$available_localisations = [];
@@ -35,10 +36,38 @@ class LocalisationLoader extends Storm_Model_Loader {
 		krsort($available_localisations);
 		return array_shift($available_localisations);
 	}
+
+
+	public function getLocFromExemplaire($bib, $cote, $code_barres) {
+		if(!$localisations = $bib->getLocalisations())
+			return $this->localisationErrorAsArray();
+
+		$exemplaire = (!$code_barres)
+			? Class_Exemplaire::findFirstBy(['cote' => $cote,
+																			 'id_bib' => $bib->getId()])
+			: Class_Exemplaire::findFirstBy(['code_barres' => $code_barres,
+																			 'id_bib' => $bib->getId()]);
+
+		if(!$exemplaire)
+			return $this->localisationErrorAsArray();
+
+		$localisation = Class_Localisation::getLoader()->findAvailableLocalisation($localisations, $exemplaire);
+
+		if(!$localisation)
+			return $this->localisationErrorAsArray();
+
+		return $localisation->asArrayForJson();
+	}
+
+
+	protected function localisationErrorAsArray() {
+		return ['erreur'=> $this->_('Aucune donnée de localisation trouvée pour cet exemplaire')];
+	}
 }
 
 
 
+
 class Class_Localisation extends Storm_Model_Abstract {
 	use Trait_Translator;
 	use Trait_StaticFileSystem;
@@ -104,10 +133,11 @@ class Class_Localisation extends Storm_Model_Abstract {
 	}
 
 
-	protected function getImageLocalisationsPath()
-	{
+	protected function getImageLocalisationsPath()	{
 		return self::getFileSystem()->getcwd()."/userfiles/photobib/localisations/";
 	}
+
+
 	// ----------------------------------------------------------------
 	// Rend les caracteristiques de l'image pour un plan
 	// ----------------------------------------------------------------
@@ -127,37 +157,6 @@ class Class_Localisation extends Storm_Model_Abstract {
 	}
 
 
-	// ----------------------------------------------------------------
-	// Rend les données de localisation pour 1 exemplaire
-	// ----------------------------------------------------------------
-	public function getLocFromExemplaire($bib, $cote, $code_barres) {
-
-		if(!$localisations = $bib->getLocalisations())
-			return $this->localisationErrorAsArray();
-
-		$exemplaire = (!$code_barres)
-			? Class_Exemplaire::findFirstBy(['cote' => $cote,
-																			 'id_bib' => $bib->getId()])
-			: Class_Exemplaire::findFirstBy(['code_barres' => $code_barres,
-																			 'id_bib' => $bib->getId()]);
-
-		if(!$exemplaire)
-			return $this->localisationErrorAsArray();
-
-		$localisation = Class_Localisation::getLoader()->findAvailableLocalisation($localisations, $exemplaire);
-
-		if(!$localisation)
-			return $this->localisationErrorAsArray();
-
-		return $localisation->asArrayForJson();
-	}
-
-
-	protected function localisationErrorAsArray() {
-		return ['erreur'=> $this->_('Aucune donnée de localisation trouvée pour cet exemplaire')];
-	}
-
-
 	protected function getImageLocalisation() {
 		if(!$this->hasImage())
 			return '';
@@ -166,10 +165,12 @@ class Class_Localisation extends Storm_Model_Abstract {
 	}
 
 
-	protected function asArrayForJson() {
+	public function asArrayForJson() {
+		$url ='';
+
+		if ($image_plan = $this->getImagePlan())
+			$url = trim($image_plan['url']);
 
-		if (!$url=$this->getImagePlan()["url"])
-			$url ='';
 
 		return [
 						'url' =>  $url,
@@ -200,42 +201,36 @@ class Class_Localisation extends Storm_Model_Abstract {
 
 
 	protected function findLocationWithParamsFor($exemplaire) {
-		$exemplaire_datas = ['type_doc' => $exemplaire->getNotice()->getTypeDoc(),
-												 'genre' => $exemplaire->getGenre(),
-												 'annexe' => $exemplaire->getAnnexe(),
-												 'section' => $exemplaire->getSection(),
-												 'emplacement' => $exemplaire->getEmplacement()];
-
-		$matching_datas = ['type_doc' => $this->getTypeDoc(),
-											 'genre' => $this->getGenre(),
-											 'annexe' => $this->getAnnexe(),
-											 'section' => $this->getSection(),
-											 'emplacement' => $this->getEmplacement()];
-
-		if(!$matching_datas = array_filter($matching_datas)) {
-			return '';
-		}
-
-		$quality = count($matching_datas);
-
-		if(!array_filter(array_diff_assoc($matching_datas, $exemplaire_datas)))
-			return [$quality => $this];
-		return '';
+		$exemplaire_datas = array_filter(['type_doc' => $exemplaire->getNotice()->getTypeDoc(),
+																			'genre' => $exemplaire->getGenre(),
+																			'annexe' => $exemplaire->getAnnexe(),
+																			'section' => $exemplaire->getSection(),
+																			'emplacement' => $exemplaire->getEmplacement()]);
+
+		$matching_datas = array_filter(['type_doc' => $this->getTypeDoc(),
+																		'genre' => $this->getGenre(),
+																		'annexe' => $this->getAnnexe(),
+																		'section' => $this->getSection(),
+																		'emplacement' => $this->getEmplacement()]);
+
+		$quality = count(array_intersect_assoc($matching_datas, $exemplaire_datas));
+		$quality += $this->coteMatchingQuality($exemplaire);
+		return [$quality => $this];
 	}
 
 
-	protected function findLocalisationByCote($exemplaire) {
-		$start_cote_exemplaire = substr($exemplaire->getCote() ,0,strlen($this->getCoteDebut()));
-		$end_cote_exemplaire =substr($exemplaire->getCote() ,0,strlen($this->getCoteFin()));
+	protected function coteMatchingQuality($exemplaire) {
+		$start_cote_exemplaire = substr($exemplaire->getCote() ,0, strlen($this->getCoteDebut()));
+		$end_cote_exemplaire = substr($exemplaire->getCote() ,0, strlen($this->getCoteFin()));
 
 		if(!$this->hasCoteDebut() && !$this->hasCoteFin())
-			 return '';
+			 return 0;
 
-		if((!$this->hasCoteDebut() || ($this->hasCoteDebut() and $start_cote_exemplaire >= $this->getCoteDebut())) &&
-			 (!$this->hasCoteFin() || ($this->hasCoteFin() and $end_cote_exemplaire <= $this->getCoteFin())))
-			return ['1' => $this];
+		if ((!$this->hasCoteDebut() || ($this->hasCoteDebut() and $start_cote_exemplaire >= $this->getCoteDebut())) &&
+				(!$this->hasCoteFin() || ($this->hasCoteFin() and $end_cote_exemplaire <= $this->getCoteFin())))
+			return strlen($start_cote_exemplaire);
 
-		return '';
+		return 0;
 	}
 }
 ?>
\ No newline at end of file
diff --git a/library/Class/Migration/PatchHash.php b/library/Class/Migration/PatchHash.php
new file mode 100644
index 0000000000000000000000000000000000000000..ac81c1c56e1359efff21a9cb8bcd86c33c4feae5
--- /dev/null
+++ b/library/Class/Migration/PatchHash.php
@@ -0,0 +1,26 @@
+<?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 Class_Migration_PatchHash extends Storm_Model_Abstract{
+	protected $_table_name = 'patch_hash';
+}
+?>
\ No newline at end of file
diff --git a/library/Class/Migration/Patchs.php b/library/Class/Migration/Patchs.php
index 3bd0452e55f88faeb1e14a85a0aee0288041793c..21b0975f5a0ad489b5c586640778db7dcb0dc9fd 100644
--- a/library/Class/Migration/Patchs.php
+++ b/library/Class/Migration/Patchs.php
@@ -26,20 +26,22 @@ class Class_Migration_Patchs {
 	public $force = false;
 
 
-	function extensionForFile($file) {
-		return substr($file, -4);
+	public function extensionForFile($file) {
+		return substr($file, -3);
 	}
 
-	function setForce($force) {
+
+	public function setForce($force) {
 		$this->force=$force;
 		return $this;
 	}
 
 
-	function versionForFile($filename) {
+	public function versionForFile($filename) {
 		return  (int)preg_replace('/.*\/patch_(.*)\.(php|sql)/', '\1', $filename);
 	}
 
+
 	public function getLastPatchNumber() {
 		$ids=[];
 		foreach (self::getFileSystem()->glob(PATCH_PATH."patch_*") as $filename) {
@@ -51,13 +53,22 @@ class Class_Migration_Patchs {
 		return $ids[0];
 	}
 
+
 	public function getMatchingPatchFiles($patch_level) {
+		return $this->getPatchesFilteredBy(
+																			 function($version) use ($patch_level) {
+																				 return $version > (int)$patch_level;
+																			 });
+	}
+
+
+	public function getPatchesFilteredBy($closure) {
 		$scripts=[];
 		foreach (self::getFileSystem()->glob(PATCH_PATH."patch_*") as $filename) {
-			if (!in_array($this->extensionForFile($filename), ['.sql', '.php']))
+			if (!$this->isValidFile($filename))
 				continue;
-			$numero = $this->versionForFile($filename);
-			if ($numero > (int)$patch_level)
+
+			if ($closure($this->versionForFile($filename)))
 				$scripts[] = $filename;
 		}
 		sort($scripts);
@@ -71,79 +82,138 @@ class Class_Migration_Patchs {
 		$patch_level = Class_CosmoVar::find("patch_level");
 		$visitor->visitHeader($patch_level,$this->getLastPatchNumber());
 
-    // Recup des patches a executer
-		$ok=true;
+		$ok = true;
 		foreach($this->getMatchingPatchFiles($patch_level->getValeur()) as $script) {
 			$num_patch = $this->versionForFile($script);
 			$visitor->visitDisplayPatchLevel($num_patch);
 			$ok = $visitor->visitSqlSkip($num_patch) || $this->upgrade($script, $visitor);
 			if (!$ok && !$visitor->visitIsForceUpgrade())
 				break;
-			if ($ok) $patch_level->setValeur($num_patch)->save();
+
+			if ($ok)
+				$patch_level->setValeur($num_patch)->save();
 		}
+
 		if ($ok)
 			$visitor->visitDisplayDone();
+	}
+
 
+	public function upgrade($script, $visitor) {
+		if (!$this->isValidFile($script))
+			return false;
+
+		return call_user_func_array([$this, $this->methodForFile($script)],
+																[$script, $visitor]);
 	}
 
-	function upgrade($script,$visitor) {
-		switch($this->extensionForFile($script)) {
-		case '.php':
-			return $this->runPhpUpgrade($script,$visitor);break;
-		case '.sql':
-			return $this->runSqlUpgrade($script, $visitor);break;
-		}
-		return false;
+
+	protected function methodForFile($script) {
+		return 'run' . ucfirst($this->extensionForFile($script)) . 'Upgrade';
 	}
 
 
-	function runPhpUpgrade($script,$visitor) {
-		$e=Class_Systeme_Include::getInstance()->include_file($script);
-		if ($e instanceof Exception) {
-			$visitor->visitPhpError($e);
-			return false;
-		}
-		return true;
+	protected function isValidFile($filename) {
+		return in_array($this->extensionForFile($filename), ['sql', 'php']);
 	}
 
 
-	function runSqlUpgrade($script,$visitor) {
-		$data = self::getFileSystem()->file($script);
+	protected function runPhpUpgrade($script, $visitor) {
+		$closure = function() use ($script, $visitor) {
+			$e = Class_Systeme_Include::getInstance()->include_file($script);
+			if ($e instanceof Exception) {
+				$visitor->visitPhpError($e);
+				return false;
+			}
+			return true;
+		};
 
-		$instructions = [];
+		return $this->hashScriptAndDo($script, $closure);
+	}
+
+
+	protected function runSqlUpgrade($script, $visitor) {
+		$closure = function() use ($script, $visitor) {
+			$data = self::getFileSystem()->file($script);
+			foreach($this->_extractSqlInstructions($data) as $instruction) {
+				flush();
+				try {
+					Zend_Registry::get('sql')->execute(trim($instruction));
+				} catch(Exception $e)	{
+					$visitor->visitSqlException($e, $this->versionForFile($script));
+					return $this->force;
+				}
+			}
+
+			return true;
+		};
+
+		return $this->hashScriptAndDo($script, $closure);
+	}
+
+
+	protected function _extractSqlInstructions($data) {
 		$contents = implode(' ', $data);
 		if (false !== strpos($contents, 'CREATE FUNCTION'))
-			$instructions[] = $contents;
-		else {
-			$instruction = '';
-			foreach($data as $ligne) {
-				$ligne = trim($ligne);
-				if (!$ligne or substr($ligne,0,2)=="--") continue;
+			return [$contents];
 
-				$instruction.=$ligne.' ';
+		$instructions = [];
+		$instruction = '';
+		foreach($data as $ligne) {
+			$ligne = trim($ligne);
+			if (!$ligne or substr($ligne,0,2) == '--')
+				continue;
 
-				if(substr($ligne,-1)!=";")
-					continue;
+			$instruction .= $ligne . ' ';
 
-				$instructions[] = $instruction;
-				$instruction = '';
-			}
+			if(substr($ligne,-1) != ';')
+				continue;
+
+			$instructions[] = $instruction;
+			$instruction = '';
 		}
 
+		return $instructions;
+	}
 
-		foreach($instructions as $instruction) {
-			flush();
 
-			try {
-				$sql = Zend_Registry::get('sql');
-				$sql->execute(trim($instruction));
+	public function hashScriptAndDo($script, $closure) {
+		$hash = $this->_hashOf($script);
+		if ($this->_hashExists($hash))
+			return true;
 
-			} catch(Exception $e)	{
-				$visitor->visitSqlException($e, $this->versionForFile($script));
-				return $this->force;
-			}
-		}
+		if (!$closure())
+			return false;
+
+		$this->_registerHash($hash);
 		return true;
 	}
+
+
+	protected function _hashOf($file) {
+		return self::getFileSystem()->sha1_file($file);
+	}
+
+
+	protected function _hashExists($hash) {
+		if (!$this->_isHashSystemEnabled())
+			return false;
+
+		return 0 < Class_Migration_PatchHash::countBy(['value' => $hash]);
+	}
+
+
+	protected function _registerHash($hash) {
+		if (!$this->_isHashSystemEnabled())
+			return;
+
+		Class_Migration_PatchHash::newInstance(['value' => $hash])
+			->save();
+	}
+
+	protected function _isHashSystemEnabled() {
+		$adapter = Zend_Registry::get('sql');
+		return (1 === $adapter->query("SHOW TABLES LIKE '" . Class_Migration_PatchHash::getClassVar('_table_name') . "'"));
+	}
 }
-?>
\ No newline at end of file
+?>
diff --git a/library/Class/MoteurRecherche.php b/library/Class/MoteurRecherche.php
index ae55b79258555cae72acf319d8e07cdf424549d3..c4ae068c61c482c35f43f1dfd38d320f35c5607d 100644
--- a/library/Class/MoteurRecherche.php
+++ b/library/Class/MoteurRecherche.php
@@ -23,16 +23,30 @@ class Class_MoteurRecherche {
 
 	private $ix;																			// Classe d'indexation
 	private $limite_facettes = ' limit 15000';					// limite pour le calcul des facettes
-	protected $all_facettes;
-	protected $_notices_not_shown = false;
-	protected $fil_ariane;
-	protected $rubriques;
+	protected $all_facettes,
+		$_notices_not_shown = false,
+		$fil_ariane,
+		$rubriques,
+		$_domain_conditions = null,
+		$conditions = '',
+		$_or_conditions = '',
+		$_filter_domain_conditions = '';
 
 	public function __construct()	{
 		$this->ix = new Class_Indexation();
 	}
 
 
+	public function getConditions() {
+		return $this->conditions;
+	}
+
+
+	public function getorConditions() {
+		return $this->_or_conditions;
+	}
+
+
 	public function createReqForIsbn($expression) {
 		if (!$this->_isISBN($expression))
 			return false;
@@ -105,16 +119,27 @@ class Class_MoteurRecherche {
 			$this->select_notices = 'id_notice, '
 				. 'MATCH(alpha_titre) ' . $against_titre . ' as rel1, '
 				. 'MATCH(alpha_auteur) ' . $against_titre . ' as rel2 '
-				. 'from notices ';
+				. 'from notices';
 			$this->order_by = ' order by (rel1 * 1.5) + (rel2) desc';
 		}
 	}
 
 
 	public function setCondition($condition) {
+		if(!$condition)
+			return $this;
+
 		if ($this->conditions != '')
 			$this->conditions .= ' and ';
+
 		$this->conditions .= $condition;
+		return $this;
+	}
+
+
+	protected function setOrConditions($conditions) {
+		$this->_or_conditions = $conditions;
+
 	}
 
 
@@ -156,18 +181,39 @@ class Class_MoteurRecherche {
 	}
 
 
-	public function visitClesNotices($cles_notices) {
+	protected function prepareRecordsKeys($records_keys) {
 		$keys = [];
-		foreach($cles_notices as $notice) {
-			if (!trim($notice))
+		foreach($records_keys as $record) {
+			if (!trim($record))
 				continue;
-			$keys[] = "'" . $notice . "'";
+			$keys[] = "'" . $record . "'";
 		}
+		return $keys;
+	}
 
+
+	public function visitClesNotices($cles_notices) {
+		$keys = $this->prepareRecordsKeys($cles_notices);
 		$this->setCondition("notices.clef_alpha in(" . implode(', ', $keys) . ")");
 	}
 
 
+	public function visitFacetteDomainForOrConditions($domain_id) {
+		$this->setOrConditions(self::prepareFacetteDomainForOrConditions($domain_id));
+	}
+
+
+	public function prepareFacetteDomainForOrConditions($domain_id) {
+		if(!$domain_id)
+			return '';
+
+		if(!$facette = Class_Catalogue::find($domain_id)->getFacette())
+			return '';
+
+		return 'MATCH(facettes) AGAINST(\'' . trim($facette) . '\' IN BOOLEAN MODE)';
+	}
+
+
 	public function visitMatiere($matiere_id) {
 		if (!$matiere = Class_Matiere::getLoader()->find($matiere_id))
 			continue;
@@ -183,12 +229,13 @@ class Class_MoteurRecherche {
 
 
 	public function visitNouveaute($nouveaute) {
-		$time = $this->getTimeSource()->mktime(0, 0, 0,
-																					 date('m') - $nouveaute,
-																					 date('d'),
-																					 date('Y'));
+		$now = $this->getTimeSource()->time();
+		$novel_time = mktime(0, 0, 0,
+												 date('m', $now) - $nouveaute,
+												 date('d', $now),
+												 date('Y', $now));
 
-		$this->setCondition("date_creation >'" . date('Y-m-d', $time) . "'");
+		$this->setCondition("date_creation >'" . date('Y-m-d', $novel_time) . "'");
 	}
 
 
@@ -231,22 +278,103 @@ class Class_MoteurRecherche {
 	}
 
 
-	public function lancerRecherche($criteres_recherche) {
-		$ret = ['nb_mots' => null,
-						'statut' => '',
-						'nombre' => 0];
+	public static function getConditionsForRequest($ands = '', $ors = '', $filter = '') {
+		if(!$ands && !$ors)
+			return '';
+
+		if(!$ors && !$filter)
+			return ' Where ' . $ands;
+
+		if(!$ors && $filter)
+			return ' Where (' . $ands . ')' . $filter;
+
+		if(!$ands && !$filter)
+			return ' Where ' . $ors;
+
+		if(!$ands && $filter)
+			return ' Where (' . $ors . ')' . $filter;
+
+		if(!$filter)
+			return ' Where (' . $ands . ')'.' or '. $ors;
+
+		return ' Where ((' . $ands . ')'.' or '. $ors . ')' . $filter;
+	}
+
+
+	public function visitDomain($search_settings) {
+		$search_engine = (new Class_MoteurRecherche());
+			$search_engine
+			->visitSearchSettings($search_settings);
+
+		if(is_array($search_engine->buildWherePartQuery()))
+			return;
+
+		$this->_domain_conditions = $search_engine->getConditions();
+	}
 
+
+	public function visitFilterOnDomain($search_settings) {
+		$search_engine = (new Class_MoteurRecherche())
+			->visitSearchSettings($search_settings);
+
+		if(is_array($search_engine->buildWherePartQuery()))
+			return;
+
+		$this->setFilterDomainConditions(self::getConditionsForRequest($search_engine->getConditions(),
+																																	 $search_engine->getOrConditions()));
+	}
+
+
+	public function setFilterDomainConditions($request) {
+		$this->_filter_domain_conditions = $this->filterDomainConditionAsOr($request);
+	}
+
+
+	protected function filterDomainConditionAsOr($request) {
+		return str_replace('Where', 'and', $request);
+	}
+
+
+	public function visitSearchSettings($criteres_recherche) {
 		$this->criteres_recherche = $criteres_recherche;
 		$this->conditions = '';
 		$this->all_facettes = '';
-		$this->select_notices = 'id_notice from notices ';
+		$this->select_notices = 'id_notice from notices';
 		$this->nb_mots = 0;
-
 		$tri = $criteres_recherche->getTri();
 		$this->order_by = ($tri > '' && $tri !== '*') ? ' order by ' . $tri : '';
 
 		$criteres_recherche->acceptVisitor($this);
 
+		return $this;
+	}
+
+
+	public function buildWherePartQuery() {
+		if(!is_null($this->_domain_conditions)) {
+			$this->setCondition($this->_domain_conditions);
+			return self::getConditionsForRequest($this->conditions, $this->_or_conditions, $this->_filter_domain_conditions);
+		}
+
+		if ($this->all_facettes)
+			$this->setCondition("MATCH(facettes) AGAINST('" . trim($this->all_facettes)."' IN BOOLEAN MODE)");
+
+		if($this->criteres_recherche->hasEmptyDomain() && !$this->criteres_recherche->isSearchInBasket()){
+			return ['statut' => 'erreur',
+							'erreur' => $this->_('Domaine non paramétré')];
+		}
+
+		return self::getConditionsForRequest($this->conditions, $this->_or_conditions, $this->_filter_domain_conditions);
+	}
+
+
+	public function lancerRecherche($criteres_recherche) {
+		$ret = ['nb_mots' => null,
+						'statut' => '',
+						'nombre' => 0];
+
+		$this->visitSearchSettings($criteres_recherche);
+
 		if (!empty($this->fil_ariane))
 			$ret['fil_ariane'] = $this->fil_ariane;
 
@@ -259,18 +387,21 @@ class Class_MoteurRecherche {
 				return $ret;
 		}
 
-		if ($this->all_facettes) {
-			$this->setCondition("MATCH(facettes) AGAINST('" . trim($this->all_facettes)."' IN BOOLEAN MODE)");
-		}
-
-		$where = '';
-		if ($this->conditions)
-			$where = 'Where ' . $this->conditions;
+		if(is_array($where = $this->buildWherePartQuery()))
+			return $where;
 
 		// Finalisation des requetes
-		$req_notices = 'Select ' . $this->select_notices . $where . $this->order_by;
-		$req_comptage = 'Select count(*) from notices ' . $where;
-		$req_facettes = 'Select id_notice, type_doc, facettes from notices ' . $where . $this->limite_facettes;
+		$req_notices = 'Select ' .
+			$this->select_notices .
+			$where.
+			$this->order_by;
+
+		$req_comptage = 'Select count(*) from notices' .
+			$where;
+
+		$req_facettes = 'Select id_notice, type_doc, facettes from notices' .
+			$where.
+			$this->limite_facettes;
 
 		// Lancer les requetes
 		$nb = fetchOne($req_comptage);
@@ -278,6 +409,7 @@ class Class_MoteurRecherche {
 		if(!$nb && ($this->criteres_recherche->getPertinence() || $this->nb_mots <=1)) {
 			$ret["statut"] = 'erreur';
 			$ret["erreur"] = $this->_('Aucun résultat trouvé');
+			$ret['req_liste']=$req_notices;
 			$this->addStatEchec(2, $criteres_recherche->getCriteres());
 			return $ret;
 		}
@@ -294,6 +426,7 @@ class Class_MoteurRecherche {
 		$ret['req_liste'] = $req_notices;
 		$ret['req_facettes'] = $req_facettes;
 		$ret['nb_mots'] = $this->nb_mots;
+
 		return $ret;
 	}
 
@@ -313,7 +446,9 @@ class Class_MoteurRecherche {
 		else
 			$rubriques_tmp = $this->getRubriquesGuidees($indice);
 
+
 		$this->all_facettes .= ' +'.$indice.'*';
+
 		$this->fil_ariane = $this->getFilAriane($fil,$indice);
 
     // Tableau des rubriques
diff --git a/library/Class/MultiUpload.php b/library/Class/MultiUpload.php
index 873935f039858e6411941edd14ef5277bfa05711..2d9f3d22520a56fa95b706ad9bd0fa44728e079c 100644
--- a/library/Class/MultiUpload.php
+++ b/library/Class/MultiUpload.php
@@ -22,9 +22,6 @@ class Class_MultiUpload {
 	/** @var Zend_Controller_Request_Http */
 	protected $_request;
 
-	/** @var int */
-	protected $_sizeLimit = 10485760;
-
 	/** @var Class_MultiUpload_HandlerFactory */
 	protected $_handlerFactory;
 
@@ -165,9 +162,6 @@ class Class_MultiUpload {
 	 * @return bool
 	 */
 	public function handleUpload($uploadDirectory, $filename_prefix) {
-		if (!$this->_isPhpSettingsCompatible())
-			return false;
-
 		if (!$this->_setHandler())
 			return false;
 
@@ -176,10 +170,8 @@ class Class_MultiUpload {
 			return false;
 		}
 
-		if ($size > $this->_sizeLimit) {
-			$this->_error = 'Fichier trop volumineux';
+		if (!$this->_isPhpSettingsCompatible($size))
 			return false;
-		}
 
 		$filename = $filename_prefix . '_' . $this->_handler->getName();
 
@@ -227,17 +219,16 @@ class Class_MultiUpload {
 	/**
 	 * @return bool
 	 */
-	protected function _isPhpSettingsCompatible() {
+	protected function _isPhpSettingsCompatible($size) {
 		$settingsReader = $this->getSettingsReader();
 		$postSize		= $this->_toBytes($settingsReader->get('post_max_size'));
 		$uploadSize	= $this->_toBytes($settingsReader->get('upload_max_filesize'));
 
 		if (
-			($postSize < $this->_sizeLimit )
-			|| ($uploadSize < $this->_sizeLimit)
+			($postSize < $size )
+			|| ($uploadSize < $size)
 		) {
-			$size = max(array(1, $this->_sizeLimit / 1024 / 1024)) . 'M';
-			$this->_error = 'Paramétrage du serveur : monter le post_max_size et le upload_max_filesize à ' . $size;
+			$this->_error = 'Fichier trop volumineux. Paramétrage du serveur : monter le post_max_size et le upload_max_filesize à ' . $size;
 			return false;
 		}
 
diff --git a/library/Class/Notice.php b/library/Class/Notice.php
index f08728f2008c0eca512f07f3f7a25716532ee857..6a392cc76e8fb4b4623f2791a531c9f4b21f9c25 100644
--- a/library/Class/Notice.php
+++ b/library/Class/Notice.php
@@ -104,7 +104,7 @@ class NoticeLoader extends Storm_Model_Loader {
 	}
 
 	public function findByUrl($url) {
-		$frbr_link = new Class_FRBR_Link;
+		$frbr_link = new Class_FRBR_Link();
 		if (!$clef_alpha = $frbr_link->extractKeyFromUrl($url))
 			return;
 
@@ -135,7 +135,10 @@ class Class_Notice extends Storm_Model_Abstract {
 	protected $_notice_unimarc;
 	protected $_has_many = ['exemplaires' => ['model' => 'Class_Exemplaire',
 																						'role' => 'notice',
-																						'dependents' => 'delete']];
+																						'dependents' => 'delete'],
+													'notice_domain' => ['model' => 'Class_NoticeDomain',
+																							'role' => 'domain',
+																							'dependents' => 'delete']];
 
 	protected
 		$_titre_principal,
@@ -480,6 +483,22 @@ class Class_Notice extends Storm_Model_Abstract {
 	}
 
 
+	public function getAlphaKey() {
+		return $this->getClefAlpha();
+	}
+
+
+	public function updateFacette($facettes) {
+		$this->setFacettes(Class_Notice_Facettes::mergeFacettes($this->getFacettes(), $facettes));
+		return $this;
+	}
+
+
+	public function deleteFacettes($facettes) {
+		$this->setFacettes(Class_Notice_Facettes::removeFacettes($this->getFacettes(), $facettes));
+		return $this;
+	}
+
 
 	/**
 	 * Rend la structure notice pour affichage liste
@@ -506,7 +525,7 @@ class Class_Notice extends Storm_Model_Abstract {
 					case "A": $notice["A"] = $this->getAuteurPrincipal(); break;
 					case "E": $notice["E"] = $this->getEditeur(); break;
 					case "F": $notice["F"] = $this->getCentreInteret(); break;
-					case "C": $notice["C"] = $this->getCollection(true); break;
+					case "C": $notice["C"] = $this->getCollections(true); break;
 					case "O": $notice["O"] = $this->getNotes(); break;
 					case "R": $notice["R"] = $this->getResume(); break;
 					case "U": $notice["U"] = $unimarc; break;
@@ -617,12 +636,13 @@ class Class_Notice extends Storm_Model_Abstract {
 			case Class_CodifAuteur::CODE_FACETTE: return  $this->getAuteursUnimarc(false, true);
 			case Class_Codification::CODE_TITRE: return  $this->getZonesTitre();
 			case Class_Codification::CODE_EDITEUR: return  $this->getEditeur();
-			case Class_Codification::CODE_COLLECTION: return  $this->getCollection(true);
+			case Class_Codification::CODE_COLLECTION: return  $this->getCollections(true);
 			case Class_Codification::CODE_NOTES: return  $this->getNotes();
 			case Class_Codification::CODE_COLLATION: return  $this->getCollation();
 			case Class_Codification::CODE_RESUME: return  $this->getResume();
 			case Class_Codification::CODE_URL: return  $this->getUrls();
 			case Class_Codification::CODE_ANNEE: return  $this->getAnnee();
+			case Class_Codification::CODE_PRIX: return  $this->getPrix();
 			case Class_Codification::CODE_IDENTIFIANT: return  (new Class_Isbn($this->getIsbnOrEan()))->getAll()['isbn10'];
 			case Class_Codification::CODE_NOUVEAUTE: return $this->isNouveaute() ? $this->_('Oui'): $this->_('Non');
 		}
@@ -635,9 +655,19 @@ class Class_Notice extends Storm_Model_Abstract {
 		return (new Class_Notice_Urls($this))->asArray();
 	}
 
+	public function getPrix() {
+		return $this->get_subfield("010", "d");
+	}
+
+	public function hasTome() {
+		if ($data = $this->get_subfield("461", "t")
+				&& strlen($this->getClefChapeau())>1)
+			return true;
+		return false;
+	}
 
 	public function getNoticesMemeSeries() {
-		if (strlen($this->getClefChapeau())<1)
+		if (!$this->hasTome())
 			return [];
 
 		return $this->getLoader()->getAllNoticesByClefChapeau($this->getClefChapeau());
@@ -739,12 +769,13 @@ class Class_Notice extends Storm_Model_Abstract {
 
 	public function getTitreChapeau() {
 		$titre = '';
-		if ($titres = $this->get_subfield('200', 'a'))
-			$titre = trim($titres[0]);
-		// On cherche le chapeau et le n°
 		if ($data = $this->get_subfield("461", "t")) {
 			return $this->filtreTitre($data[0]);
 		}
+
+		if ($titres = $this->get_subfield('200', 'a'))
+			$titre = trim($titres[0]);
+		// On cherche le chapeau et le n°
 		return $this->filtreTitre($titre);
 
 	}
@@ -909,7 +940,7 @@ class Class_Notice extends Storm_Model_Abstract {
 
 	private function filtreTitre($titre) {
 		$titre = str_replace([BR, '[', ']', '<', '>', '  ', chr(136)],
-												 ["#BR#", ' ', '', ' ', ' ', ' '],
+												 ["#BR# ", ' ', '', ' ', ' ', ' '],
 												 $titre);
 
 		if (substr($titre, 0, 1) == '?') {
@@ -1165,7 +1196,7 @@ class Class_Notice extends Storm_Model_Abstract {
 	}
 
 
-	public function getCollection($principale=false) {
+	public function getCollections($principale=false) {
 		if (!$data = $this->get_subfield(225, "a")) $data = $this->get_subfield(410, "t");
 		if(!$data) $data = [];
 
@@ -1467,12 +1498,12 @@ class Class_Notice extends Storm_Model_Abstract {
 
 
 	public function getLinksAsSource() {
-		return Class_FRBR_Link::getLinksForSource($this->getClefAlpha());
+		return Class_FRBR_Link::getRecordLinksForSource($this->getClefAlpha());
 	}
 
 
 	public function getLinksAsTarget() {
-		return Class_FRBR_Link::getLinksForTarget($this->getClefAlpha());
+		return Class_FRBR_Link::getRecordLinksForTarget($this->getClefAlpha());
 	}
 
 
@@ -1514,7 +1545,7 @@ class Class_Notice extends Storm_Model_Abstract {
 		$enreg .= $this->getTitrePrincipal().$sep;
 		$enreg .= $this->getAuteurPrincipal().$sep;
 		$enreg .= $this->getEditeur().$sep;
-		$enreg .= implode(',', $this->getCollection()).$sep;
+		$enreg .= implode(',', $this->getCollections()).$sep;
 		$enreg .= $this->getAnnee().chr(10);
 
 		return $enreg;
diff --git a/library/Class/Notice/ClefAlpha.php b/library/Class/Notice/ClefAlpha.php
index 4f8669665be09d448849ecd1fedf979a1f047f08..995f63bd33a8230a2d5a4801dc6969c9394985c7 100644
--- a/library/Class/Notice/ClefAlpha.php
+++ b/library/Class/Notice/ClefAlpha.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 Class_Notice_ClefAlpha {
@@ -31,8 +31,63 @@ class Class_Notice_ClefAlpha {
 
 	protected $_clef;
 
-	public function __construct($clef) {
-		$this->_clef =  explode(self::SEPARATOR, $clef);
+	public function __construct($model_or_clef) {
+		if (is_string($model_or_clef))
+			$this->_clef = explode(self::SEPARATOR, $model_or_clef);
+		else
+			$this->extractDatasFromModel($model_or_clef);
+	}
+
+	public function extractDatasFromModel($model) {
+		$model->acceptClefAlphaVisitor($this);
+		$this->_clef = explode(self::SEPARATOR, (new Class_Indexation())->getClefAlpha($this->getTypeDoc(),
+																																									 $this->getClefTitre(),
+																																									 $this->getClefComplementTitre(),
+																																									 $this->getClefAuteur(),
+																																									 $this->getTome(),
+																																									 $this->getClefEditeur(),
+																																									 $this->getClefAnnee()));
+	}
+
+
+	public function visitTypeDocId($type_doc_id) {
+		$this->_clef[self::POS_TYPE_DOC] = $type_doc_id;
+		return $this;
+	}
+
+
+	public function visitTitre($titre) {
+		$this->_clef[self::POS_TITRE] = $titre;
+		return $this;
+	}
+
+
+	public function visitComplementTitre($titre) {
+		$this->_clef[self::POS_COMPLEMENT_TITRE] = $titre;
+		return $this;
+	}
+
+
+	public function visitAuteur($auteur) {
+		$this->_clef[self::POS_AUTEUR] = $auteur;
+		return $this;
+	}
+
+
+	public function visitEditeur($editeur) {
+		$this->_clef[self::POS_EDITEUR] = $editeur;
+		return $this;
+	}
+
+
+	public function visitAnnee($annee) {
+		$this->_clef[self::POS_ANNEE] = $annee;
+		return $this;
+	}
+
+
+	public function keyString() {
+		return implode(self::SEPARATOR, $this->_clef);
 	}
 
 
@@ -42,32 +97,32 @@ class Class_Notice_ClefAlpha {
 
 
 	public function getClefComplementTitre() {
-		return $this->_clef[self::POS_COMPLEMENT_TITRE];
+		return isset($this->_clef[self::POS_COMPLEMENT_TITRE]) ? $this->_clef[self::POS_COMPLEMENT_TITRE] : '';
 	}
 
 
 	public function getClefEditeur() {
-		return $this->_clef[self::POS_EDITEUR];
+		return isset($this->_clef[self::POS_EDITEUR]) ? $this->_clef[self::POS_EDITEUR] : '';
 	}
 
 
-	public function getTypeDoc() {
-		return isset($this->_clef[self::POS_TYPE_DOC]) ? $this->_clef[self::POS_TYPE_DOC] : '';
+	public function getClefAnnee() {
+		return isset($this->_clef[self::POS_ANNEE]) ? $this->_clef[self::POS_ANNEE] : '';
 	}
 
 
-	public function getTome() {
-		return isset($this->_clef[self::POS_TOME]) ? $this->_clef[self::POS_TOME] : '';
+	public function getClefAuteur() {
+		return isset($this->_clef[self::POS_AUTEUR]) ? $this->_clef[self::POS_AUTEUR] : '';
 	}
 
 
-	public function likeKeyWith($positions) {
-		$cle = [];
-		for($i=0; $i <=6; $i++) {
-			$cle []= in_array($i, $positions) ? $this->_clef[$i] : '%';
-		}
+	public function getTypeDoc() {
+		return isset($this->_clef[self::POS_TYPE_DOC]) ? $this->_clef[self::POS_TYPE_DOC] : '';
+	}
 
-		return implode(self::SEPARATOR, $cle);
+
+	public function getTome() {
+		return isset($this->_clef[self::POS_TOME]) ? $this->_clef[self::POS_TOME] : '';
 	}
 
 
@@ -87,27 +142,9 @@ class Class_Notice_ClefAlpha {
 			if ($score > $max_score)
 				$current_notice = $notice;
 		}
-		
-		return $current_notice;
-	}
-
-
-	public function getUniqueNoticeWithParams($search_params) {
-		$current_params = [];
-		foreach($search_params as $key => $value) {
-			$current_params[$key] = $value;
-			$notices = Class_Notice::findAllBy($current_params);
-
-			if (!$notices)
-				return null;
-			
-			if (count($notices) === 1)
-				return $notices[0];
-		}
 
-		return null;
+		return $current_notice;
 	}
-	
 }
 
 ?>
\ No newline at end of file
diff --git a/library/Class/Notice/Facettes.php b/library/Class/Notice/Facettes.php
new file mode 100644
index 0000000000000000000000000000000000000000..26ffded8d73d7ce4b4b8f3fe8e2c3e963d9bb168
--- /dev/null
+++ b/library/Class/Notice/Facettes.php
@@ -0,0 +1,33 @@
+<?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 Class_Notice_Facettes {
+	public static function mergeFacettes($old_facettes, $facettes) {
+		return implode(' ', array_filter(array_unique(array_merge(explode(' ', $old_facettes), explode(' ', $facettes)))));
+	}
+
+
+	public static function removeFacettes($old_facettes, $facettes) {
+		return implode(' ', array_filter(array_diff(explode(' ', $old_facettes), explode(' ', $facettes))));
+	}
+}
+?>
\ No newline at end of file
diff --git a/library/Class/Notice/NavigationRecherche.php b/library/Class/Notice/NavigationRecherche.php
index 856814478ed30c27b0baa3b68e1ba9810ff95025..4233108cf7f9b2abaf023fe69553301e185c9ff8 100644
--- a/library/Class/Notice/NavigationRecherche.php
+++ b/library/Class/Notice/NavigationRecherche.php
@@ -74,7 +74,7 @@ class Class_Notice_NavigationRecherche {
 				&& isset($result['req_liste'])) {
 			$this->_all_notice_ids = Class_Notice::getNoticeIdsByRequeteRecherche($result['req_liste']);
 		}
-		return $this->_all_notice_ids;
+		return isset($this->_all_notice_ids) ? $this->_all_notice_ids : [];
 	}
 
 
diff --git a/library/Class/Notice/TracksReader.php b/library/Class/Notice/TracksReader.php
index b2ebff633e72ddb00b6c28e4de1b596fb9cf73bf..3e2042330d47ec7024c1f733d323315bb43a136c 100644
--- a/library/Class/Notice/TracksReader.php
+++ b/library/Class/Notice/TracksReader.php
@@ -67,14 +67,19 @@ class Class_Notice_TracksReader {
 
 class Class_Notice_TracksReader_Local extends Class_Notice_TracksReader {
 	public function getMapping($datas) {
-		foreach($datas as $data)
-			if ($this->_record->decoupe_bloc_champ($data)[0]['code'] == 't')
-				return ['t' => 'titre',
-								'd' => 'duree',
-								'a' => 'auteur_nom',
-								'b' => 'auteur_prenom',
-								'v' => 'volume',
-								'3' => 'url_ecoute'];
+		foreach($datas as $data) {
+			$subfields = $this->_record->decoupe_bloc_champ($data);
+			foreach ($subfields as $subfield) {
+				if ($subfield['code'] == 't') {
+					return ['t' => 'titre',
+									'd' => 'duree',
+									'a' => 'auteur_nom',
+									'b' => 'auteur_prenom',
+									'v' => 'volume',
+									'3' => 'url_ecoute'];
+				}
+			}
+		}
 
 		return ['a' => 'titre',
 						'd' => 'duree',
diff --git a/library/Class/NoticeDomain.php b/library/Class/NoticeDomain.php
new file mode 100644
index 0000000000000000000000000000000000000000..b3b5bebabb0215b03ffd0fc50ca3a1e3b3cc6002
--- /dev/null
+++ b/library/Class/NoticeDomain.php
@@ -0,0 +1,165 @@
+<?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 NoticeDomainLoader extends Storm_Model_Loader {
+	public function getDomainsForRecordAlphaKey($alpha_key) {
+		$notice_domains = Class_NoticeDomain::findAllBy(['record_alpha_key' => $alpha_key,
+																										 'domain_id not' => 0 ]);
+		$all_domain_ids = [];
+		foreach($notice_domains as $notice_domain) {
+			$all_domain_ids[] = $notice_domain->getDomainId();
+		}
+
+		$all_domain_ids =  array_filter(array_unique($all_domain_ids));
+
+		$domains = [];
+		foreach ($all_domain_ids as $id) {
+			$domains[] = Class_Catalogue::find($id);
+		}
+		return $domains;
+
+	}
+
+	public function getClesNoticesForDomain($domain_id) {
+		$alpha_keys = [];
+		foreach(Class_noticeDomain::getRecordsForDomain($domain_id) as $record) {
+			$alpha_keys[] = $record->getAlphaKey();
+		}
+
+		return array_filter($alpha_keys);
+	}
+
+
+	public function getRecordsFromCart($id_cart) {
+		$notice_domains = Class_NoticeDomain::findAllBy(['panier_id' => $id_cart]);
+		$notices = [];
+		foreach($notice_domains as $notice_domain) {
+			$notices[] = Class_Notice::findFirstBy(['clef_alpha' => $notice_domain->getRecordAlphaKey()]);
+		}
+
+		return array_filter($notices);
+	}
+
+
+	public function getDomainsFromCart($id_cart) {
+		$notice_domains = Class_NoticeDomain::findAllBy(['panier_id' => $id_cart]);
+		$domains = [];
+		foreach($notice_domains as $notice_domain) {
+			$domains[] = $notice_domain->getDomain();
+		}
+
+		return array_filter($domains);
+	}
+
+
+	public function getRecordsForDomain($domain_id) {
+		$records_domains = Class_NoticeDomain::findAllBy(['domain_id' => $domain_id]);
+		$records = [];
+		foreach($records_domains as $record_domain)
+			$records[] = Class_Notice::findFirstBy(['clef_alpha' => $record_domain->getRecordAlphaKey()]);
+
+		return array_filter($records);
+	}
+
+
+	public function getRecordsForCart($cart_id) {
+		$records_domains = Class_NoticeDomain::findAllBy(['panier_id' => $cart_id]);
+		$records = [];
+		foreach($records_domains as $record_domain)
+			$records[] = Class_Notice::findFirstBy(['clef_alpha' => $record_domain->getRecordAlphaKey()]);
+
+		return array_filter($records);
+	}
+
+
+	public function updateRecordsFacette($domain_id) {
+		if($catalogue = Class_Catalogue::find($domain_id)) {
+			foreach(Class_NoticeDomain::getRecordsForDomain($domain_id) as $record)
+				$record->updateFacette($catalogue->getFacette())->save();
+		}
+	}
+
+
+	public function deleteByCart($id_cart, $attributs = []) {
+		$notices_domains = Class_NoticeDomain::findAllBy(array_merge(['panier_id' => $id_cart],
+																																 $attributs));
+
+		foreach ($notices_domains as $notice_domain) {
+			//Class_NoticeDomain::deleteRecordsFacetteForCart($notice_domain->getDomainId(), $notice_domain->getPanierId());
+			$notice_domain->delete();
+		}
+	}
+
+
+	public function deleteByDomain($domain) {
+		$notices_domains = Class_NoticeDomain::findAllBy(['domain_id' => $domain->getId()]);
+
+		foreach ($notices_domains as $notice_domain) {
+			//Class_NoticeDomain::deleteRecordsFacetteForDomain($notice_domain->getDomainId());
+			$notice_domain->delete();
+		}
+	}
+}
+
+
+class Class_NoticeDomain extends Storm_Model_Abstract {
+	protected
+		$_table_name = 'notice_domain',
+		$_table_primary = 'ID',
+		$_loader_class = 'NoticeDomainLoader',
+		$_belongs_to = ['domain' => ['model' => 'Class_Catalogue',
+																 'referenced_in' => 'domain_id'],
+										'panier_notice' => ['model' => 'Class_PanierNotice',
+																				'referenced_in' => 'panier_id']],
+
+		$_default_attribute_values = ['panier_id' => 0,
+																	'record_alpha_key' => ''];
+
+
+	public function save() {
+		if(Class_NoticeDomain::getLoader()->findFirstBy(['domain_id' => $this->getDomainId(),
+																										 'record_alpha_key' => $this->getRecordAlphaKey(),
+																										 'panier_id' => $this->getPanierId()]))
+			return true;
+
+		return parent::save();
+	}
+
+
+	public function beforeDelete() {
+		if(!$record = $this->getNotice())
+			return;
+		if(!$domain = $this->getDomain())
+			return;
+		$record->deleteFacettes($domain->getFacette())->save();
+	}
+
+
+	public function afterDelete() {
+		Class_NoticeDomain::getLoader()->updateRecordsFacette($this->getDomainId());
+	}
+
+
+	public function getNotice() {
+		return Class_Notice::findFirstBy(['clef_alpha' => $this->getRecordAlphaKey()]);
+	}
+}
+?>
\ No newline at end of file
diff --git a/library/Class/Onglet.php b/library/Class/Onglet.php
index 2d157ffaf8af19c0147a35fa71e522bcf96a78bb..02a2a4934ad82cff56c06d746286d97a7cf50511 100644
--- a/library/Class/Onglet.php
+++ b/library/Class/Onglet.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
  */
 
 
@@ -91,10 +91,9 @@ class Class_Onglet {
 
 
 	function checkIfHasRessourcesNumeriques($notice) {
-		if(null !== $exemplaire = Class_Exemplaire::findFirstBy(['id_notice' => $notice->getId()]))
-			return ($exemplaire->getAlbum() != null);
-		return false;
-
+		return (null !== ($exemplaire = Class_Exemplaire::findFirstBy(['id_notice' => $notice->getId()]))
+						&& null !== $exemplaire->getAlbum())
+			|| 0 < Class_FRBR_Link::countAlbumsFromNotice($notice);
 	}
 
 
@@ -103,10 +102,10 @@ class Class_Onglet {
     $targetLinks = $notice->getLinksAsTarget();
 
 		return !(0 == count($sourceLinks) and 0 == count($targetLinks));
-	
+
 	}
 
-	
+
 	function setDisplayMode($display) {
 		$this->display=$display;
 		return $this;
diff --git a/library/Class/PanierNotice.php b/library/Class/PanierNotice.php
index 9a9fa0b8c80811b0f1a0270f9c5ec865a7e239b4..13dfa076395a18777006ab188189e51323dcffe0 100644
--- a/library/Class/PanierNotice.php
+++ b/library/Class/PanierNotice.php
@@ -50,17 +50,27 @@ class PanierNoticeLoader extends Storm_Model_Loader {
 	public function findAllWithCatalogue() {
 		$this->user = Class_Users::getIdentity();
 
-		return array_unique(array_map(
-													function($association) {return $association->getPanierNotice();},
-														Class_PanierNoticeCatalogue::findAll()));
+		return array_filter(
+												array_unique(
+																		 array_map(
+																							 function($association) {return $association->getPanierNotice();},
+																							 Class_PanierNoticeCatalogue::findAll())));
+	}
+
 
+	public function indexAll() {
+		Class_NoticeDomain::deleteBy(['panier_id not' => 0]);
+		$cart_to_index = Class_PanierNotice::findAllWithCatalogue();
+		foreach($cart_to_index as $cart) {
+			$cart->index();
+		}
 	}
 }
 
 
 
 class Class_PanierNotice extends Storm_Model_Abstract {
-	use Trait_Translator;
+	use Trait_Translator, Trait_Indexable;
 
 	const NOTICES_SEPARATOR = ';';
 
@@ -88,10 +98,38 @@ class Class_PanierNotice extends Storm_Model_Abstract {
 	}
 
 
+	public function index() {
+		foreach($this->getNoticesAsArray() as $notice) {
+			$this->indexIntoDomain($notice);
+		}
+
+		return $this;
+	}
+
+
+	protected function indexIntoDomain( $notice ) {
+		if(!$catalogues = $this->getCatalogues())
+			return Class_NoticeDomain::deleteByCart($this->getId());
+
+		foreach($catalogues as $domain) {
+			$params = ['domain_id' => $domain->getId(),
+								 'panier_id' => $this->getId(),
+								 'record_alpha_key' => $notice->getClefAlpha()];
+
+			if($delete = Class_NoticeDomain::findFirstBy($params))
+				$delete->delete();
+
+			Class_NoticeDomain::newInstance($params)->save();
+			Class_NoticeDomain::updateRecordsFacette($domain->getId());
+		}
+	}
+
+
 	public static function newPanierForUser($user) {
 		return (new Class_PanierNotice())->initializeForUser($user);
 	}
 
+
 	public function initializeForUser($user){
 		$panier = (new Class_PanierNotice())
 			->setLibelle('Nouveau panier')
@@ -124,14 +162,16 @@ class Class_PanierNotice extends Storm_Model_Abstract {
 
 
 	public function getClesNotices() {
-		$cles = array();
+		$cles = [];
 
 		$notices_field = $this->_get('notices');
-		if (empty($notices_field)) return $cles;
+		if(empty($notices_field))
+			return $cles;
+
 		$exploded_cles = explode(self::NOTICES_SEPARATOR, $notices_field);
-		foreach($exploded_cles as $cle) {
-			if (empty($cle) || !trim($cle)) continue;
 
+		foreach($exploded_cles as $cle) {
+			if(empty($cle) || !trim($cle)) continue;
 			$cles []= $cle;
 		}
 
@@ -165,17 +205,17 @@ class Class_PanierNotice extends Storm_Model_Abstract {
 		$notices = $this->getNoticesAsArray();
 		if (!$only_vignettes) return $notices;
 
-		return array_filter($notices, function($notice) {return $notice->hasVignette();});
+		return array_values(array_filter($notices, function($notice) {return $notice->hasVignette();}));
 	}
 
 
 	public function numberOfNotices() {
-		return count($this->getClesNotices());
+		return count($this->getExistingClesNotices());
 	}
 
 
 	public function numberOfLostNotices() {
-		return $this->numberOfNotices() - count($this->getNoticesAsArray());
+		return count($this->getClesNotices()) - $this->numberOfNotices();
 	}
 
 
@@ -211,9 +251,41 @@ class Class_PanierNotice extends Storm_Model_Abstract {
 
 
 	public function beforeSave() {
-		$user=Class_Users::getIdentity();
+		$user = Class_Users::getIdentity();
+
 		if ($user && $user->getIdabon())
 			$this->setIdabon($user->getIdabon());
+
+		if($this->hasChange())
+			$this->setDateMaj(date('Y-m-d'));
+
+		$this->unindex();
+	}
+
+
+	public function beforeDelete() {
+		return Class_NoticeDomain::deleteByCart($this->getId());
+	}
+
+
+	public function unindex() {
+		if(!$records_from_cart = $this->getNoticesAsArray()) {
+			return Class_NoticeDomain::deleteByCart($this->getId());
+		}
+
+		if(!$domains_from_cart = $this->getCatalogues())
+			return Class_NoticeDomain::deleteByCart($this->getId());
+
+		$records_from_db = Class_NoticeDomain::getRecordsFromCart($this->getId());
+
+		foreach(array_diff($records_from_db, $records_from_cart) as $record)
+			Class_NoticeDomain::deleteByCart($this->getId(), ['record_alpha_key' => $record->getAlphaKey()]);
+
+		$domains_from_db = Class_NoticeDomain::getDomainsFromCart($this->getId());
+		foreach(array_diff($domains_from_db, $domains_from_cart) as $domain)
+			Class_NoticeDomain::deleteByCart($this->getId(), ['domain_id' => $domain->getId()]);
+
+		return $this;
 	}
 
 
@@ -291,9 +363,9 @@ class Class_PanierNotice extends Storm_Model_Abstract {
 	static function getPaniersForCombo() {
 		$user = Class_Users::getIdentity();
 
-		$paniers = array_unique(array_merge($user->getPaniers(),
-																				Class_PanierNotice::findAllWithCatalogue(),
-																				Class_PanierNotice::findAllBelongsToAdmin()));
+		$paniers = array_filter(array_unique(array_merge($user->getPaniers(),
+																										 Class_PanierNotice::findAllWithCatalogue(),
+																										 Class_PanierNotice::findAllBelongsToAdmin())));
 
 		static::sortPaniersByLibelle($paniers);
 
@@ -354,7 +426,7 @@ class Class_PanierNotice extends Storm_Model_Abstract {
 			$owner_id = $owner->getId();
 
 			if (!isset($users_paniers[$owner_id]))
-				$users_paniers[$owner_id] = ['id' => $owner_id,
+				$users_paniers[$owner_id] = ['id' => 'cart-owner-id-' . $owner_id,
 																		 'label' => $owner->getNomComplet(),
 																		 'categories' => [],
 																		 'items' => [],
diff --git a/library/Class/Permission.php b/library/Class/Permission.php
new file mode 100644
index 0000000000000000000000000000000000000000..de6b1de497c0eab297f4607f3780ce14160922d9
--- /dev/null
+++ b/library/Class/Permission.php
@@ -0,0 +1,110 @@
+<?php
+/**
+ * Copyright (c) 2012, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * AFI-OPAC 2.0 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).
+ *
+ * AFI-OPAC 2.0 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 AFI-OPAC 2.0; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+class PermissionLoader extends Storm_Model_Loader {
+	const MODULE_ARTICLE = 'ARTICLE';
+	const DYNAMIC_PREFIX = 'DYNAMIC_';
+	const WORKFLOW_TYPE = 'Nouveaux statuts autorisés';
+
+	public function getCmsPermissions() {
+		$params = ['module' => static::MODULE_ARTICLE,
+							 'order' => ['type', 'sorting']];
+
+		if (!Class_AdminVar::isWorkflowEnabled())
+			$params['type not'] = static::WORKFLOW_TYPE;
+
+		return Class_Permission::findAllBy($params);
+	}
+
+
+	public function getWorkflow($id) {
+		$mapping = [Class_Article::STATUS_VALIDATION_PENDING => 'PENDING',
+								Class_Article::STATUS_VALIDATED => 'VALIDATED',
+								Class_Article::STATUS_ARCHIVED => 'ARCHIVED',
+								Class_Article::STATUS_REFUSED => 'REFUSED'];
+
+		return (5 < $id) ?
+			Class_Permission::findDynamicWorkflow($id) :
+			Class_Permission::findFirstBy(['code' => $mapping[$id],
+																		 'module' => static::MODULE_ARTICLE,
+																		 'type' => static::WORKFLOW_TYPE]);
+	}
+
+
+	public function findDynamicWorkflow($id) {
+		return Class_Permission::findFirstBy(['code' => static::DYNAMIC_PREFIX . $id,
+																					'module' => static::MODULE_ARTICLE,
+																					'type' => static::WORKFLOW_TYPE]);
+	}
+
+
+	public function newDynamicWorkflow($id) {
+		return Class_Permission::newInstance(['code' => static::DYNAMIC_PREFIX . $id,
+																					'module' => static::MODULE_ARTICLE,
+																					'type' => static::WORKFLOW_TYPE]);
+	}
+
+
+	public function createArticle() {
+		return Class_Permission::findFirstBy(['module' => static::MODULE_ARTICLE,
+																					'code' => 'ARTICLE']);
+	}
+
+
+	public function createArticleCategory() {
+		return Class_Permission::findFirstBy(['module' => static::MODULE_ARTICLE,
+																					'code' => 'CATEGORY']);
+	}
+
+
+	public function cleanDynamicWorkflow($valid_ids=[]) {
+		$params = ['module' => static::MODULE_ARTICLE];
+		if (!empty($valid_ids))
+			$params['id not'] = $valid_ids;
+
+		$to_delete = Class_Permission::findAllBy($params);
+		foreach ($to_delete as $permission)
+			if (static::DYNAMIC_PREFIX == substr($permission->getCode(),
+																					 0, strlen(static::DYNAMIC_PREFIX)))
+				$permission->delete();
+	}
+}
+
+
+class Class_Permission extends Storm_Model_Abstract {
+	protected $_table_name = 'permission';
+	protected $_loader_class = 'PermissionLoader';
+
+	protected $_has_many = ['group_permissions' =>
+													['model' => 'Class_UserGroup_Permission',
+													 'role' => 'permission',
+													 'dependents' => 'delete']];
+
+
+	public function permitTo($group, $model) {
+		return Class_UserGroup_Permission::permit($this, $group, $model);
+	}
+
+
+	public function denyTo($group, $model) {
+		Class_UserGroup_Permission::deny($this, $group, $model);
+	}
+}
\ No newline at end of file
diff --git a/library/Class/Pret.php b/library/Class/Pret.php
index 8429c49a87c7df68502960c73eaddbfebf9d094a..55547cf96b4dc8564702e61fa491c82160ad4c24 100644
--- a/library/Class/Pret.php
+++ b/library/Class/Pret.php
@@ -16,12 +16,12 @@
  *
  * 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 Class_Pret extends Storm_Model_Abstract {
 	protected $_table_name = 'prets';
-	protected $_table_primary = 'id_pret'; 
+	protected $_table_primary = 'id_pret';
 
 	protected $_belongs_to = array('bib' => array('model' => 'Class_Bib',
 																								'referenced_in' => 'id_site'));
@@ -32,8 +32,8 @@ class Class_Pret extends Storm_Model_Abstract {
 
 
 	public function getExemplaire() {
-		return Class_Exemplaire::getLoader()->findFirstBy(array('id_bib' => $this->getIdSite(),
-																														'code_barres' => $this->getCodeBarres()));
+		return Class_Exemplaire::findFirstBy(['id_bib' => $this->getIdSite(),
+																					'code_barres' => $this->getCodeBarres()]);$item;
 	}
 }
 
diff --git a/library/Class/Profil.php b/library/Class/Profil.php
index 967955ec9da752bd4d6c4268ffe7a5c99a61c804..c854c5c7b57dcb04fce0733e9b09e24c6af7cf5b 100644
--- a/library/Class/Profil.php
+++ b/library/Class/Profil.php
@@ -194,7 +194,8 @@ class Class_Profil extends Storm_Model_Abstract {
 				['cfg_site' => '',
 				 'cfg_accueil' => ZendAfi_Filters_Serialize::serialize([ 'page_css' => null,
 																																 'use_parent_css' => true,
-																																 'modules' => []]),
+                                                                 'modules' => [],
+                                                                 'sitemap' => 1]),
 				 'cfg_menus' => ZendAfi_Filters_Serialize::serialize(['H' => ['libelle' => 'Menu horizontal',
 																															'picto' => 'vide.gif',
 																															'menus' => []],
@@ -211,8 +212,8 @@ class Class_Profil extends Storm_Model_Abstract {
 																																								 'plan' => 1,
 																																								 'resa' => 1,
 																																								 'dispo' => 1,
-																																								 'date_retour' => 0],
-																															 'en_pret' => 'En prêt']),
+																																								 'date_retour' => 0,
+																																								 'en_pret' => Class_WebService_SIGB_Exemplaire::DISPO_EN_PRET]]),
 				 'hauteur_banniere' => 100,
 				 'mail_site' => '',
 				 'mail_suggestion_achat' => '',
@@ -518,6 +519,21 @@ class Class_Profil extends Storm_Model_Abstract {
 	}
 
 
+	public function getUrlParts() {
+		if (!$this->hasRewriteUrl())
+			return ['id_profil' => $this->getId()];
+
+		if (!$this->hasParentProfil())
+			return ['module' => $this->getRewriteUrl()];
+
+		if (!$this->getParentProfil()->hasRewriteUrl())
+			return ['module' => $this->getRewriteUrl()];
+
+		return ['module' => $this->getParentProfil()->getRewriteUrl(),
+						'controller' => $this->getRewriteUrl()];
+	}
+
+
 	/**
 	 * @param int $start_id
 	 * @return int
@@ -548,7 +564,6 @@ class Class_Profil extends Storm_Model_Abstract {
 			:	['type_module' => $type_module,
 				 'preferences' => Class_Systeme_ModulesAccueil::getInstance()->getValeursParDefaut($type_module)];
 
-
 		if ($this->isModulePreferencesSharedBetweenProfils($module, $type_module))
 			return $this->getModuleAccueilConfigByType($type_module, self::DIV_BANNIERE);
 
@@ -963,6 +978,18 @@ class Class_Profil extends Storm_Model_Abstract {
 	}
 
 
+	/**
+	 * @param string $param
+	 * @param mixed $value
+	 * @return Class_Profil
+	 */
+	public function setCfgAccueilParam($param, $value) {
+		$cfg_accueil = $this->getCfgAccueilAsArray();
+		$cfg_accueil[$param] = $value;
+		return $this->setCfgAccueil(ZendAfi_Filters_Serialize::serialize($cfg_accueil));
+	}
+
+
 	/**
 	 * @param string $param
 	 * @return bool
@@ -1185,7 +1212,22 @@ class Class_Profil extends Storm_Model_Abstract {
 	 * @return bool
 	 */
 	public function isPublic() {
-		return (int)$this->getAccessLevel() === -1;
+		$access_level = (int)$this->getAccessLevel();
+		if($this->hasParentProfil() && $access_level === -1)
+			return $this->getParentProfil()->isPublic();
+		return $access_level === -1;
+	}
+
+
+	public function getAccessLevel() {
+		if($access_level = parent::getAccessLevel())
+			return $access_level;
+
+		if(!$parent_profil = $this->getParentProfil())
+			return $access_level;
+
+		return $parent_profil->getAccessLevel();
+
 	}
 
 
@@ -1471,6 +1513,7 @@ class Class_Profil extends Storm_Model_Abstract {
 		$attributes = $this->_attributes;
 		unset($attributes['id']);
 		unset($attributes['id_profil']);
+		unset($attributes['rewrite_url']);
 
 		return $copy
 			->updateAttributes($attributes)
@@ -1889,23 +1932,47 @@ class Class_Profil extends Storm_Model_Abstract {
 
 
 	public function getModuleTitle($id_module) {
-		if(! $cfg_accueil = $this->getCfgAccueilAsArray())
+		if(!array_key_exists('titre', ($pref_module = $this->getModulePref($id_module))))
 			return '';
 
-		if(! array_key_exists($id_module, $cfg_accueil['modules']))
-			 return '';
+		return $pref_module['titre'];
+	}
 
-		$module = $cfg_accueil['modules'][$id_module];
 
-		if(! array_key_exists('preferences', $module))
-			return '';
+	public function getModulePref($id_module) {
+		if(! array_key_exists('preferences', ($module = $this->getLocalModuleAccueilConfig($id_module))))
+			return [];
 
-		$pref_module =  $module['preferences'];
+		return $module['preferences'];
 
-		if(!array_key_exists('titre', $pref_module))
-			 return '';
+	}
 
-		return $pref_module['titre'];
+
+	public function getModules() {
+		if(!$cfg_accueil = $this->getCfgAccueilAsArray())
+			return [];
+
+		if(! array_key_exists('modules', $cfg_accueil))
+			return [];
+
+		return $cfg_accueil['modules'];
+	}
+
+
+	public function getModuleIdFromCol($id, $type_module, $colonne) {
+		$modules = $this->getModules();
+		if(!$two_cols_pref = $this->getModulePref($id))
+			return '';
+
+		$keys = [];
+		foreach($modules as $key => $module) {
+			if(		$module['type_module'] == $type_module
+				 && $module['parent_id'] == $id
+				 && $key >= 1000
+				 && $key == $two_cols_pref['col_' . $colonne . '_module_id'])
+				return $key;
+		}
+		return '';
 	}
 }
 ?>
diff --git a/library/Class/Reservation.php b/library/Class/Reservation.php
index 4f72f78104463e66fd2ab40f0989aeb912a956d5..2e75a6ed7b1f316e3dc098d2cfb7835c8c7accb4 100644
--- a/library/Class/Reservation.php
+++ b/library/Class/Reservation.php
@@ -16,15 +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 Class_Reservation extends Storm_Model_Abstract {
 	protected $_table_name = 'reservations';
-	protected $_table_primary = 'id_resa'; 
+	protected $_table_primary = 'id_resa';
 
-	protected $_belongs_to = array('bib' => array('model' => 'Class_Bib',
-																								'referenced_in' => 'id_site'));
+	protected $_belongs_to = ['bib' => ['model' => 'Class_Bib',
+																			'referenced_in' => 'id_site']];
 
 	protected $_notice;
 
@@ -33,7 +33,8 @@ class Class_Reservation extends Storm_Model_Abstract {
 		if (isset($this->_notice))
 			return $this->_notice;
 
-		if ($exemplaire = Class_Exemplaire::getLoader()->findFirstBy(array('id_origine' => $this->getIdNoticeOrigine())))
+		if ($exemplaire = Class_Exemplaire::findFirstBy(['id_origine' => $this->getIdNoticeOrigine(),
+																										 'order' => 'zone995 desc']))
 			return $this->_notice = $exemplaire->getNotice();
 
 		return null;
@@ -41,8 +42,8 @@ class Class_Reservation extends Storm_Model_Abstract {
 
 
 	public function getRang() {
-		return 1 + $this->getLoader()->countBy(array('ID_NOTICE_ORIGINE' => $this->getIdNoticeOrigine(),
-																								 'where' => sprintf('DATE_RESA<"%s"', $this->getDateResa())));
+		return 1 + Class_Reservation::getLoader()->countBy(['ID_NOTICE_ORIGINE' => $this->getIdNoticeOrigine(),
+																												'where' => sprintf('DATE_RESA<"%s"', $this->getDateResa())]);
 	}
 
 
@@ -54,5 +55,4 @@ class Class_Reservation extends Storm_Model_Abstract {
 		return "Réservé";
 	}
 }
-
 ?>
\ No newline at end of file
diff --git a/library/Class/Rss.php b/library/Class/Rss.php
index f1fa19c40d52a8bd7e1bc04b9635985e29195537..afbaddc8270d5e382abee6f23a7da14f2c359cf2 100644
--- a/library/Class/Rss.php
+++ b/library/Class/Rss.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
  */
 require_once dirname(__FILE__)."/CompositeBuilder.php";
 
@@ -106,6 +106,8 @@ class RssLoader extends Storm_Model_Loader {
 
 
 class Class_Rss extends Storm_Model_Abstract {
+	use Trait_Indexable;
+
 	protected $_loader_class = 'RssLoader';
 	protected $_table_name = 'rss_flux';
 	protected $_table_primary = 'ID_RSS';
@@ -586,6 +588,12 @@ class Class_Rss extends Storm_Model_Abstract {
 			return ($html);
 		}
 	}
+
+
+	public function acceptClefAlphaVisitor($visitor) {
+		$visitor->visitTitre($this->getTitre())
+						->visitTypeDocId(Class_TypeDoc::RSS);
+	}
 }
 
 ?>
diff --git a/library/Class/ScriptLoader.php b/library/Class/ScriptLoader.php
index 42e4dffd8fa1ea172a91cd5989dcb3373844a673..cb2218304fb4cf5d51c206bb03107eff46e91653 100644
--- a/library/Class/ScriptLoader.php
+++ b/library/Class/ScriptLoader.php
@@ -36,7 +36,8 @@ class Class_ScriptLoader {
 		$_amber_ready_scripts,
 		$_jquery_ready_scripts,
 		$_jquery_ready_mode = self::MODE_JQUERY_READY,
-		$_version_pergame_hash;
+		$_version_pergame_hash,
+		$_metas = [];
 
 	/**
 	 * @return ScriptLoader
@@ -730,6 +731,27 @@ class Class_ScriptLoader {
 			->addAdminScript('codemirror-4.4/keymap/emacs')
 			->addStyleSheet(URL_ADMIN_JS . 'codemirror-4.4/lib/codemirror');
 	}
+
+
+	public function addRecordMeta($record) {
+		$this->_metas[] = '<meta property="og:title" content="' .  htmlentities($record->getTitrePrincipal()) . ' - ' . $record->getAuteurPrincipal() . '" />';
+		$this->_metas[] = '<meta property="og:description" content="' .  htmlentities($record->getResume()) . '" />';
+		$this->_metas[] = '<meta property="og:image" content="' . $record->fetchUrlImage() . '" />';
+		return $this;
+	}
+
+
+	public function addCmsMeta($article) {
+		$this->_metas[] = '<meta property="og:title" content="' .  htmlentities($article->getTitre()) . '" />';
+		$this->_metas[] = '<meta property="og:description" content="' .  htmlentities($article->getSummary()) . '" />';
+		return $this;
+	}
+
+
+	public function loadMeta() {
+		echo implode('',$this->_metas);
+		return $this;
+	}
 }
 
 ?>
\ No newline at end of file
diff --git a/library/Class/Sitemap.php b/library/Class/Sitemap.php
new file mode 100644
index 0000000000000000000000000000000000000000..8d6f452c05c1ba9e9145b881b149010ebf04c535
--- /dev/null
+++ b/library/Class/Sitemap.php
@@ -0,0 +1,77 @@
+<?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 Class_Sitemap {
+	use Trait_StaticFileSystem;
+
+	protected $xml;
+	protected $urls;
+
+	public static function sitemap_exists() {
+		return static::getFileSystem()->file_exists(static::getSitemapPath());
+	}
+
+	public static function getSitemap() {
+		return static::getFileSystem()->file_get_contents(static::getSitemapPath());
+	}
+
+	public static function removeSitemap() {
+		return static::getFileSystem()->unlink(static::getSitemapPath());
+	}
+
+	public static function getSitemapPath() {
+		return PATH_TEMP . 'sitemap.xml';
+	}
+
+	public function __construct() {
+		$this->xml = new DOMDocument('1.0', 'UTF-8');
+    $this->xml->formatOutput = true;
+		$this->urls = [];
+	}
+
+	public function addUrl($url, $priority) {
+		$this->urls[] = ['url' => $url, 'priority' => $priority];
+	}
+
+	public function save() {
+    $xmlns = $this->xml->createAttribute('xmlns');
+    $xmlns->value = 'http://www.sitemaps.org/schemas/sitemap/0.9';
+
+    $urlset = $this->xml->createElement('urlset');
+    $urlset->appendChild($xmlns);
+
+    foreach ($this->urls as $u) {
+      $url = $this->xml->createElement('url');
+      $loc = $this->xml->createElement('loc', $u['url']);
+      $priority = $this->xml->createElement('priority', $u['priority']);
+      $changefreq = $this->xml->createElement('changefreq', 'weekly');
+      $url->appendChild($loc);
+      $url->appendChild($priority);
+      $url->appendChild($changefreq);
+
+      $urlset->appendChild($url);
+    }
+
+    $this->xml->appendChild($urlset);
+
+		$handle = static::getFileSystem()->fopen(static::getSitemapPath(), 'w');
+		static::getFileSystem()->fwrite($handle, $this->xml->saveXML());
+	}
+}
diff --git a/library/Class/Sitotheque.php b/library/Class/Sitotheque.php
index 8d43456ce377ab906fd818ebf0c5c8eb5a66d42f..2dae95d5f2915995671a52cb087cc470a8ea8f18 100644
--- a/library/Class/Sitotheque.php
+++ b/library/Class/Sitotheque.php
@@ -20,18 +20,6 @@
  */
 require_once dirname(__FILE__)."/CompositeBuilder.php";
 
-class SitoCategorie extends Zend_Db_Table_Abstract {
-	protected static $_instance;
-	protected $_name = 'sito_categorie';
-
-	public static function getInstance() {
-		if (!isset(self::$_instance))
-			self::$_instance = new self();
-		return self::$_instance;
-	}
-}
-
-
 class Class_SitothequeModelCategorie extends ItemCategory {
 	public function getLabel(){
 		return $this->libelle;
@@ -97,13 +85,23 @@ class SitothequeLoader extends Storm_Model_Loader {
 
 		return $filtered;
 	}
-}
 
 
+	public function indexAll() {
+		$page= 1;
+		while($sito_to_index = Class_Sitotheque::findAllBy(['limitPage' => [$page, 500]])) {
+			foreach($sito_to_index as $sito)
+				$sito->index();
+			Class_Sitotheque::clearCache();
+			$page++;
+		}
+	}
+}
 
 
 class Class_Sitotheque extends Storm_Model_Abstract {
-	use Trait_Translator, Trait_TreeViewableItem, Trait_HasManyDomaines;
+	use Trait_Translator, Trait_TreeViewableItem, Trait_HasManyDomaines, Trait_Indexable, Trait_CustomFields;
+
 
 	protected $_loader_class = 'SitothequeLoader';
 	protected $_table_name = 'sito_url';
@@ -125,6 +123,12 @@ class Class_Sitotheque extends Storm_Model_Abstract {
 
 	private $_dataBaseError = "Problème d'accès à la base de données";
 
+
+	public function beforeSave() {
+		$this->unindex();
+	}
+
+
 	public function getIdZone() {
 		if (!$this->hasZone())
 			return 0;
@@ -146,11 +150,6 @@ class Class_Sitotheque extends Storm_Model_Abstract {
 	}
 
 
-	public function index() {
-		return Class_Indexation_PseudoNotice::index($this);
-	}
-
-
 	public function getTypeDocId() {
 		return Class_TypeDoc::SITE;
 	}
@@ -190,26 +189,6 @@ class Class_Sitotheque extends Storm_Model_Abstract {
 	}
 
 
-	public function getAllSousCategorie($id_cat_mere = "all")
-	{
-		try{
-			if($id_cat_mere == "all") { return $fetch = SitoCategorie::getInstance()->fetchAll(); }
-			else
-				{
-					$select = SitoCategorie::getInstance()>getAdapter()->select()
-						->from('sito_categorie',array('ID_CAT'=>'ID_CAT', 'ID_CAT_MERE'=>'ID_CAT_MERE','LIBELLE'=>'LIBELLE','ID_SITE'=>'ID_SITE'))
-						->where('ID_CAT_MERE=?', $id_cat_mere);
-					$stmt = $select->query();
-					$row = $stmt->fetchAll();
-					return $row;
-				}
-		}catch (Exception $e){
-			logErrorMessage('Class: Class_Sitotheque; Function: getAllSousCategorie' . NL . $e->getMessage());
-			return $this->_dataBaseError;
-		}
-	}
-
-
 	public function validate()	{
 		$this->check($this->getTitre(), $this->_("Vous devez compléter le champ 'Titre'"));
 
@@ -249,5 +228,9 @@ class Class_Sitotheque extends Storm_Model_Abstract {
 	}
 
 
+	public function acceptClefAlphaVisitor($visitor) {
+		$visitor->visitTitre($this->getTitre())
+						->visitTypeDocId($this->getTypeDocId());
+	}
 }
 ?>
\ No newline at end of file
diff --git a/library/Class/SitothequeCategorie.php b/library/Class/SitothequeCategorie.php
index 322cf4026bd3deb42b3503826f2dea923265a716..0fcf189d8fc6e969593f8f4ca69485bddfeeef75 100644
--- a/library/Class/SitothequeCategorie.php
+++ b/library/Class/SitothequeCategorie.php
@@ -20,7 +20,7 @@
  */
 class Class_SitothequeCategorie extends Storm_Model_Abstract {
  	use Trait_TreeViewableCategorie, Trait_Translator, Trait_TreeNode;
-
+	use Trait_CustomFields;
 	protected $_table_name = 'sito_categorie';
 	protected $_table_primary = 'ID_CAT';
 	protected $_belongs_to = ['parent_categorie' => ['model' => 'Class_SitothequeCategorie',
diff --git a/library/Class/Systeme/ModulesAccueil/ConteneurDeuxColonnes.php b/library/Class/Systeme/ModulesAccueil/ConteneurDeuxColonnes.php
index 62faf5a2d0eab508df3af66b34817882e6e24774..107cb95a03745ced359177afe5ae38494eec2792 100644
--- a/library/Class/Systeme/ModulesAccueil/ConteneurDeuxColonnes.php
+++ b/library/Class/Systeme/ModulesAccueil/ConteneurDeuxColonnes.php
@@ -51,12 +51,9 @@ class Class_Systeme_ModulesAccueil_ConteneurDeuxColonnes extends Class_Systeme_M
 		$type_module = $preferences[$type_key];
 		$profil = Class_Profil::getCurrentProfil();
 
-		$id_module = $profil->createNewModuleAccueilId(1000);
+		$id_module = self::getOrCreateModuleForCol($profil, $parent_id, $type_module, $colonne);
 
-		$modules_accueil = new Class_Systeme_ModulesAccueil();
-		$preferences = $modules_accueil->getValeursParDefaut($type_module);
-
-		$config = ['preferences' => $preferences,
+		$config = ['preferences' => self::getOrCreateModulePref($profil, $id_module, $type_module),
 							 'type_module' => $type_module,
 							 'parent_id' => $parent_id];
 
@@ -72,5 +69,19 @@ class Class_Systeme_ModulesAccueil_ConteneurDeuxColonnes extends Class_Systeme_M
 
 		return $id_module;
 	}
+
+
+	public static function getOrCreateModuleForCol($profil, $id, $type_module, $colonne) {
+		if(!$id = $profil->getModuleIdFromCol($id, $type_module, $colonne))
+			return $profil->createNewModuleAccueilId(1000);
+		return $id;
+	}
+
+
+	public static function getOrCreateModulePref($profil, $id_module, $type_module) {
+		if(!$pref = $profil->getModulePref($id_module))
+			return (new Class_Systeme_ModulesAccueil())->getValeursParDefaut($type_module);
+		return $pref;
+	}
 }
 ?>
\ No newline at end of file
diff --git a/library/Class/Systeme/ModulesAccueil/DomainBrowser.php b/library/Class/Systeme/ModulesAccueil/DomainBrowser.php
index d5048065b2f3c93429dc3a9c97de710e5fdbb1d4..c1d4e173d8a88d785b4ab9eaf25272bda697784e 100644
--- a/library/Class/Systeme/ModulesAccueil/DomainBrowser.php
+++ b/library/Class/Systeme/ModulesAccueil/DomainBrowser.php
@@ -16,12 +16,12 @@
  *
  * 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 Class_Systeme_ModulesAccueil_DomainBrowser extends Class_Systeme_ModulesAccueil_Null{
-	
+
 	protected $_group = Class_Systeme_ModulesAccueil::GROUP_RECH;
 
 	protected $_libelle = 'Domaines';
@@ -34,6 +34,7 @@ class Class_Systeme_ModulesAccueil_DomainBrowser extends Class_Systeme_ModulesAc
 	protected $_popupHeight = 700;
 
 	protected $_defaultValues = ['titre' => 'Domaines',
+															 'allow_breadcrumb' => 0,
 															 'root_domain_id' => 0,
 															 'display_mode' => Class_Systeme_ModulesAppli::LISTE_FORMAT_ACCORDEON];
 
diff --git a/library/Class/Systeme/ModulesAccueil/Sitotheque.php b/library/Class/Systeme/ModulesAccueil/Sitotheque.php
index d8f54fb9c236d15c393e6159ac6d2dec6fc49382..943e06fd591cf2f5383c918e0c582e0b6565eba1 100644
--- a/library/Class/Systeme/ModulesAccueil/Sitotheque.php
+++ b/library/Class/Systeme/ModulesAccueil/Sitotheque.php
@@ -16,12 +16,12 @@
  *
  * 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 Class_Systeme_ModulesAccueil_Sitotheque extends Class_Systeme_ModulesAccueil_Null{
 	/** @var string */
 	protected $_group = Class_Systeme_ModulesAccueil::GROUP_INFO;
-	
+
 	/** @var string */
 	protected $_libelle = 'Sitothèque';
 
@@ -44,7 +44,8 @@ class Class_Systeme_ModulesAccueil_Sitotheque extends Class_Systeme_ModulesAccue
 		'id_categorie' => '', // Liste d'id_categorie séparés par des tirets
 		'id_items' => '', // Liste d'id_site séparés par des tirets
 		'nb_aff' => '2', // Nombre à afficher
-		'group_by_categorie' => false //grouper les sites par categorie sous forme de menu
+		'group_by_categorie' => false, //grouper les sites par categorie sous forme de menu,
+		'display_order' => 'Random' //ordre d'affichage: Random ou Selection
 	);
 }
 ?>
\ No newline at end of file
diff --git a/library/Class/Systeme/ModulesMenu/Profil.php b/library/Class/Systeme/ModulesMenu/Profil.php
index 2c62fde404dc979ccd8918976062c0594641aace..6bb005ce0df01b4763dd623b01089580f56ad87d 100644
--- a/library/Class/Systeme/ModulesMenu/Profil.php
+++ b/library/Class/Systeme/ModulesMenu/Profil.php
@@ -48,17 +48,7 @@ class Class_Systeme_ModulesMenu_Profil extends Class_Systeme_ModulesMenu_Null{
 		if (!$profil = Class_Profil::find($preferences['clef_profil']))
 			return [];
 
-		if (!$profil->hasRewriteUrl())
-			return ['id_profil' => $profil->getId()];
-
-		if (!$profil->hasParentProfil())
-			return ['module' => $profil->getRewriteUrl()];
-
-		if (!$profil->getParentProfil()->hasRewriteUrl())
-			return ['module' => $profil->getRewriteUrl()];
-
-		return ['module' => $profil->getParentProfil()->getRewriteUrl(),
-						'controller' => $profil->getRewriteUrl()];
+		return $profil->getUrlParts();
 	}
 }
 ?>
\ No newline at end of file
diff --git a/library/Class/Systeme/PergameService.php b/library/Class/Systeme/PergameService.php
index 33c2dabf0fd8b36641c7c23874bcb2ce4cfc7ee5..710f8c982bb27e6a508807ba49dca59daa02b27b 100644
--- a/library/Class/Systeme/PergameService.php
+++ b/library/Class/Systeme/PergameService.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 Class_Systeme_PergameService {
@@ -58,7 +58,7 @@ class Class_Systeme_PergameService {
 		$nb_resas=fetchOne("select count(*) from reservations where IDABON='".$this->user->IDABON."' and ORDREABON=".$this->user->ORDREABON);
 		return $nb_resas;
 	}
-	
+
 
 	public function getPrets() {
 		$data=fetchAll("select * from prets where IDABON='".$this->user->IDABON."' and ORDREABON=".$this->user->ORDREABON." and EN_COURS=1");
@@ -80,31 +80,26 @@ class Class_Systeme_PergameService {
 
 
 	public function getDisponibilite($ex) {
-		// terme en pret
-		$tmp=Class_Profil::getCurrentProfil()->getCfgNoticeAsArray();
-		$libelle_en_pret="emprunté";
-		if (array_isset("en_pret", $tmp["exemplaires"]))
-			$libelle_en_pret=$tmp["exemplaires"]["en_pret"];
-		
 		// verif pret
 		$code_barres=$ex["code_barres"];
-		$prets = fetchAll("select * from prets where code_barres='$code_barres' and EN_COURS=1");
+		$prets = Class_Pret::findAllBy(['code_barres' => $code_barres,
+																		'en_cours' => 1]);
 
 		// Activité
 		$ex["dispo"]=$ex["activite"];
 		$ex["reservable"]=true;
 		if ($prets)
 		{
-			$ex["dispo"]=$libelle_en_pret;
+			$ex["dispo"] = Class_Profil::getCurrentProfil()->getCfgNoticeAsArray()['exemplaires']['en_pret'];
 			$ex["date_retour"] = strftime('%d/%m/%y' ,strtotime($prets[0]['DATE_RETOUR']));
-		}	
+		}
 		else
 		{
 			$regles=$this->getReglesReservation($ex["id_bib"]);
 			if($regles["Autoriser_docs_disponibles"]==1) $ex["reservable"]=true;
 			else $ex["reservable"]=false;
 		}
-		
+
 		// nombre de réservations
 		$nb_resas=fetchOne("select count(*) from reservations where ID_NOTICE_ORIGINE=".intval($ex["id_origine"]));
 		if($nb_resas)
@@ -152,7 +147,7 @@ class Class_Systeme_PergameService {
 			return $notice;
 		}
 
-		return ($model = Class_Notice::find($id_notice)) 
+		return ($model = Class_Notice::find($id_notice))
 			? $model->getNotice('JA') : [];
 	}
 
@@ -166,26 +161,26 @@ class Class_Systeme_PergameService {
 		}
 
 		foreach($ids as $id) {
-			if ($inSql > '') 
+			if ($inSql > '')
 				$inSql.=",";
 			$inSql.=$id["id_notice"];
 		}
 
 		$id_notice = fetchOne("select id_notice from notices where id_notice in($inSql) and type_doc=$support");
 
-		return ($model = Class_Notice::find($id_notice)) 
+		return ($model = Class_Notice::find($id_notice))
 			? $model->getNotice('JA') : [];
 	}
 
 
 	public function reserverExemplairePergame($id_bib, $exemplaire, $code_annexe) {
-		if (!$this->user || !$this->user->ID_USER) 
+		if (!$this->user || !$this->user->ID_USER)
 			return ['erreur' => 'Vous devez être connecté pour réserver un document'];
 
-		if(!$this->user->IDABON) 
+		if(!$this->user->IDABON)
 			return ['erreur' => 'Vous devez être connecté en tant qu\'abonné pour réserver un document'];
-		
-		if (!$exemplaire) 
+
+		if (!$exemplaire)
 			return ['erreur' => 'Une erreur s\'est produite lors de la lecture de la notice.'];
 
 		$notice = $exemplaire->getNotice();
@@ -202,22 +197,22 @@ class Class_Systeme_PergameService {
 			'idabon' => $id_abon,
 			'ordreabon'=> $ordre_abon]);
 
-		if ($resa) 
+		if ($resa)
 			return ['erreur' => 'Vous avez déjà réservé ce document le ' . formatDate($resa->getDateResa(), 1)];
 
 		$regles = $this->getReglesReservation($exemplaire->getIdBib());
 
 		// controle quota par carte
 		$nb = Class_Reservation::countBy(['idabon' => $id_abon]);
-		if ($nb >= $regles['Max_par_carte']) 
+		if ($nb >= $regles['Max_par_carte'])
 			return ['erreur' => 'La réservation est impossible car vous avez atteint le nombre maximum de réservations sur votre carte.'];
-		
+
 		// controle quota par document
 		$nb = Class_Reservation::countBy(['id_notice_origine' => $id_origine]);
 		if ($nb >= $regles['Max_par_document'])
 			return ['erreur' => 'La réservation est impossible car le nombre maximum de réservations pour ce document a été atteint (' . $regles["Max_par_document"] . ').'];
-		
-		if (!$id_bib) 
+
+		if (!$id_bib)
 			$id_bib = 1;
 
 		$time_source = self::getTimeSource();
@@ -241,7 +236,7 @@ class Class_Systeme_PergameService {
 
 	 public function supprimerReservation($id_reservation)  {
 		 $resa = fetchEnreg("select * from reservations where ID_RESA=$id_reservation");
-		 if(!$resa) 
+		 if(!$resa)
 			 return false;
 		 sqlExecute("delete from reservations where ID_RESA=$id_reservation");
 
@@ -269,7 +264,7 @@ class Class_Systeme_PergameService {
 		 $nbProlong = (int)$pret["NB_PROLONGATIONS"];
 		 $nbProlong += 1;
 		 $dateRetour = $pret["DATE_RETOUR"];
-		 if($nbProlong > $regles["Nombre_max_par_document"]) 
+		 if($nbProlong > $regles["Nombre_max_par_document"])
 			 return ['statut' => 0,
 			         'erreur' => 'Le prêt n\'a pas pu être prolongé car il a atteint le nombre de prolongations autorisé.' . $complement_msg];
 
@@ -277,7 +272,7 @@ class Class_Systeme_PergameService {
 		 $anteriorite_max = (int)$regles['Anteriorite_max_en_jours'];
 		 if ($anteriorite_max) {
 			 $ecart = ecartDates($dateJour, $dateRetour);
-			 if ($ecart>$anteriorite_max) 
+			 if ($ecart>$anteriorite_max)
 				 return ['statut' => 0,
 				         'erreur' => 'Le prêt n\'a pas pu être prolongé car il a un retard trop important.' . $complement_msg];
 		 }
@@ -285,14 +280,14 @@ class Class_Systeme_PergameService {
 		 // Controle si le doc est réservé
 		 if ($regles['Interdire_si_reservation'] == 1) {
 			 $controle = fetchOne("Select Count(*) From reservations Where ID_NOTICE_ORIGINE=".$pret["ID_NOTICE_ORIGINE"]);
-			 if($controle > 0) 
+			 if($controle > 0)
 				 return ['statut' => 0,
 				         'erreur' => 'Le prêt n\'a pas pu être prolongé car il est réservé.' . $complement_msg];
 		 }
 
 		 // On prolonge
 		 $newDate = ajouterJours($pret["DATE_RETOUR"], $regles['Duree_en_jours']);
-		 while($newDate<=$dateJour) 
+		 while($newDate<=$dateJour)
 			 $newDate = ajouterJours($newDate,$regles['Duree_en_jours']);
 		 $tempsProlong=(int)$regles['Duree_en_jours'];
 
@@ -312,7 +307,7 @@ class Class_Systeme_PergameService {
 
 	 private function ecrireTransaction($type_mvt, $enreg) {
 		 $data = '';
-		 foreach($enreg as $item) 
+		 foreach($enreg as $item)
 			 $data .= $item . '|';
 		 Class_Transaction::newInstance(['type_mvt' => $type_mvt, 'data' => $data])
 			 ->save();
@@ -320,13 +315,13 @@ class Class_Systeme_PergameService {
 
 
 	public function getReglesReservation($id_bib) {
-		if (!$id_bib) 
+		if (!$id_bib)
 			return false;
 		$bib = Class_IntBib::find($id_bib);
 		$data = $bib->getCommParamsAsArray();
-		if (!isset($data["Max_par_carte"])) 
+		if (!isset($data["Max_par_carte"]))
 			$data["Max_par_carte"] = 3;
-		if (!isset($data["Max_par_document"])) 
+		if (!isset($data["Max_par_document"]))
 			$data["Max_par_document"] = 3;
 		return $data;
 	}
diff --git a/library/Class/Testing/FileSystem.php b/library/Class/Testing/FileSystem.php
index b36fbee3a46e5d47c0f2171a02eb1cf9454bb60c..370eeee0b111562102d84363f05d11dc326185b9 100644
--- a/library/Class/Testing/FileSystem.php
+++ b/library/Class/Testing/FileSystem.php
@@ -26,7 +26,7 @@ class Class_Testing_FileSystem {
 		 'filesize', 'fclose', 'ftell', 'fread', 'feof',
 		 'getcwd', 'file_exists', 'scandir', 'is_dir',
 		 'opendir', 'readdir', 'closedir', 'mkdir','glob','file', 'fwrite','rename',
-		 'getimagesize'
+		 'getimagesize', 'file_get_contents', 'sha1_file'
 		];
 
 
diff --git a/library/Class/TypeDoc.php b/library/Class/TypeDoc.php
index 416faddc9ce1f1bfdb9e0864a1568889e6103662..a88a9bc3574b22543c11be52f1cc9f0ba0661724 100644
--- a/library/Class/TypeDoc.php
+++ b/library/Class/TypeDoc.php
@@ -16,12 +16,12 @@
  *
  * 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 TypeDocLoader extends Storm_Model_Loader  {
 	protected $_all_instances;
-	
+
 	public function __construct() {
 		$this->findAll();
 	}
@@ -37,7 +37,7 @@ class TypeDocLoader extends Storm_Model_Loader  {
 
 		if (!empty($attributes))
 			$instance->updateAttributes($attributes);
-		
+
 		return $instance;
 	}
 
@@ -65,21 +65,24 @@ class TypeDocLoader extends Storm_Model_Loader  {
 		return $this;
 	}
 
+
 	public function findAllBy($select) {
 		return $this->findAll();
 	}
 
-	public function findAll($select = NULL) {
+
+	public function findAll($select=null) {
 		if (isset($this->_all_instances))
 			return $this->_all_instances;
-	
-		$types_docs = Class_CosmoVar::getLoader()->find('types_docs');
-		$lines = explode("\r\n", $types_docs->getListe());
-		$instances = array();
-
-		foreach ($lines as $line) {
-			$instance = $this->unserialize($line);
-			$instances [$instance->getId()]= $instance;
+
+		if ($types_docs = Class_CosmoVar::find('types_docs')) {
+			$lines = array_filter(explode("\r\n", $types_docs->getListe()));
+			$instances = [];
+
+			foreach ($lines as $line) {
+				$instance = $this->unserialize($line);
+				$instances[$instance->getId()] = $instance;
+			}
 		}
 
 		$default_types = Class_TypeDoc::getDefaultTypeDocs();
@@ -87,7 +90,7 @@ class TypeDocLoader extends Storm_Model_Loader  {
 			if (!isset($instances[$id]))
 				$instances[$id] = Class_TypeDoc::newWithLabel($label)->setId($id);
 		}
-		
+
 		return $this->_all_instances = $instances;
 	}
 
@@ -128,7 +131,7 @@ class TypeDocLoader extends Storm_Model_Loader  {
 		return		$this->_saveSerialized($serialized);
 	}
 
-	
+
 	public function delete($model) {
 		$serialized = array();
 
@@ -176,7 +179,7 @@ class Class_TypeDoc extends Storm_Model_Abstract {
 	const CODE_FACETTE = 'T';
 
 	protected $_loader_class = 'TypeDocLoader';
-	protected $_belongs_to = ['codif_type_doc' => 
+	protected $_belongs_to = ['codif_type_doc' =>
 														[	'model' => 'Class_CodifTypeDoc',
 															'referenced_in' => 'id' ]];
 	const LIVRE = 1;
@@ -200,7 +203,7 @@ class Class_TypeDoc extends Storm_Model_Abstract {
 	const NUMERIQUEPREMIUM = 110;
 	const CYBERLIBRIS = 111;
 
-	
+
 	public static function getDefaultTypeDocs() {
 		return [self::LIVRE => 'Livres',
 						self::PERIODIQUE => 'Périodiques',
@@ -228,7 +231,7 @@ class Class_TypeDoc extends Storm_Model_Abstract {
 	/**
 	 * @param String label
 	 * @return Class_Type_Doc
-	 */	
+	 */
 	public static function newWithLabel($label) {
 		$instance = new self();
 		return $instance->setLabel($label);
@@ -279,7 +282,7 @@ class Class_TypeDoc extends Storm_Model_Abstract {
 	}
 
 	public function isRessourceNumerique() {
-		return ($this->getId() >= Class_TypeDoc::LIVRE_NUM) 
+		return ($this->getId() >= Class_TypeDoc::LIVRE_NUM)
 			&& (!in_array($this->getId(),[static::ARTICLE, static::RSS, static::SITE]));
 	}
 
@@ -295,7 +298,7 @@ class Class_TypeDoc extends Storm_Model_Abstract {
 		return $codif;
 	}
 
-	
+
 	public function getLibelleFamille() {
 		return $this->getCodifTypeDoc()->getLibelle();
 	}
diff --git a/library/Class/UserGroup.php b/library/Class/UserGroup.php
index a3cc138a47092023a7bc9fa6cdde435e44ed99fa..9410da4f403690f60f03ae3dfd1c9518fa96d685 100644
--- a/library/Class/UserGroup.php
+++ b/library/Class/UserGroup.php
@@ -23,14 +23,23 @@ class Class_UserGroup extends Storm_Model_Abstract {
 	use Trait_TreeViewableItem, Trait_HasManyDomaines;
 	protected $_table_name = 'user_groups';
 	protected $_has_many = ['user_group_memberships' => [ 'model' => 'Class_UserGroupMembership',
-																												'role' => 'user_group' ],
+																												'role' => 'user_group'],
 
 													'users' => ['through' => 'user_group_memberships',
 																			'unique' => true,
-																			'dependents' => 'delete']];
+																			'dependents' => 'delete'],
+
+													'user_group_permissions' => ['model' => 'Class_UserGroup_Permission',
+																											 'role' => 'group',
+																											 'dependents' => 'delete'],
+
+													'permissions' => ['through' => 'user_group_permissions',
+																						'unique' => true]];
 
 	protected $_belongs_to = ['categorie' => ['model' => 'Class_UserGroupCategorie',
-																						'referenced_in' => 'id_cat']];
+																						'referenced_in' => 'id_cat'],
+														'library' => ['model' => 'Class_Bib',
+																					'referenced_in' => 'id_bib']];
 
 	// Les droits sont utilisés comme puissance de 2 (ce sont des masques) pour l'attribut rights_token
 	const RIGHT_SUIVRE_FORMATION = 0;
@@ -58,6 +67,7 @@ class Class_UserGroup extends Storm_Model_Abstract {
 	const RIGHT_USER_CONFIG_FRONT = 17;
 	const RIGHT_USER_PUBLICATION_DIRECTE = 18;
 	const RIGHT_USER_DOMAINES_TOTAL_ACCESS = 19;
+	const RIGHT_USER_ACCES_ARTICLES = 23;
 
 	// Type de groupe
 	const TYPE_MANUAL = 0;
@@ -88,12 +98,14 @@ class Class_UserGroup extends Storm_Model_Abstract {
 		self::RIGHT_USER_NOTICES_LIEES => 'Catalogue: accès notices liées',
 		self::RIGHT_USER_SIGB_USER_READ => 'Administration: accès en lecture aux fiches adhérents SIGB',
 		self::RIGHT_USER_CONFIG_FRONT => 'Administration: accès à la configuration des boîtes et modules depuis l\'interface publique',
-		self::RIGHT_USER_PUBLICATION_DIRECTE => 'Articles: autorise la validation d\'un article par son auteur'
+		self::RIGHT_USER_PUBLICATION_DIRECTE => 'Articles: autorise la validation d\'un article par son auteur',
+		self::RIGHT_USER_ACCES_ARTICLES => 'Articles: accès articles'
 	];
 
 
 	protected $_default_attribute_values = ['rights_token' => 0,
-																					'group_type' => 0];
+																					'group_type' => 0,
+																					'id_bib' => 0];
 
 	public static function getLoader() {
 		return self::getLoaderFor(__CLASS__);
@@ -140,10 +152,13 @@ class Class_UserGroup extends Storm_Model_Abstract {
 	 * @return array
 	 */
 	public function getUsers() {
-		if ($this->isManual())
-			return parent::_get('users');
-		return Class_Users::findAllBy(['role_level' => $this->getRoleLevel(),
-																	 'limit' => 50]);
+		return $this->isManual() ?
+			parent::_get('users') : $this->getDynamicUsers();
+	}
+
+
+	public function getDynamicUsers() {
+		return $this->getDynamicUsersPage(0, 50);
 	}
 
 
@@ -151,9 +166,22 @@ class Class_UserGroup extends Storm_Model_Abstract {
 	 * @return int
 	 */
 	public function numberOfUsers() {
-		if ($this->isManual())
-			return parent::_numberOf('users');
-		return Class_Users::countBy(['role_level' => $this->getRoleLevel()]);
+		return $this->isManual() ?
+			parent::_numberOf('users') : $this->numberOfDynamicUsers();
+	}
+
+
+	public function numberOfDynamicUsers() {
+		return $this->isDynamic() ?
+			Class_Users::countBy($this->_getDynamicParams()) : 0;
+	}
+
+
+	protected function _getDynamicParams() {
+		$params = ['role_level' => $this->getRoleLevel()];
+		if ($library = $this->getLibrary())
+			$params['id_site'] = $library->getId();
+		return $params;
 	}
 
 
@@ -192,6 +220,21 @@ class Class_UserGroup extends Storm_Model_Abstract {
 	}
 
 
+	/**
+	 * @param array $permissions
+	 */
+	public function setPermissions($permissions) {
+		$dependents = [];
+		foreach ($permissions as $permission) {
+			if (!$model = Class_Permission::find($permission))
+				continue;
+			$dependents[] = $model;
+		}
+
+		return parent::setPermissions($dependents);
+	}
+
+
 	/**
 	 * Ajoute les droits pour le Zend_Form::populate
 	 * @return array
@@ -199,6 +242,8 @@ class Class_UserGroup extends Storm_Model_Abstract {
 	public function toArray() {
 		$attributes = parent::toArray();
 		$attributes['rights'] = $this->getRights();
+		$attributes['permissions'] = array_map(function($item) { return $item->getId(); },
+																					 $this->getPermissions());
 		return $attributes;
 	}
 
@@ -219,6 +264,14 @@ class Class_UserGroup extends Storm_Model_Abstract {
 	}
 
 
+	/**
+	 * @return bool
+	 */
+	public function hasRightAccesArticles() {
+		return $this->hasRight(self::RIGHT_USER_ACCES_ARTICLES, $this->getRights());
+	}
+
+
 	/** @return Class_UserGroup */
 	public function addRightDirigerFormation() {
 		return $this->addRight(self::RIGHT_DIRIGER_FORMATION);
@@ -331,13 +384,95 @@ class Class_UserGroup extends Storm_Model_Abstract {
 
 
 	public function getUsersPage($page, $items_by_page) {
+		if ($this->isDynamic())
+			return $this->getDynamicUsersPage($page, $items_by_page);
+
 		$users=[];
 		$members = Class_UserGroupMembership::findAllBy(['user_group_id' => $this->getId(),
-																										 'limitPage' => [$page,$items_by_page]]);
+																										 'limitPage' => [$page, $items_by_page]]);
 		foreach ($members as $member) {
 			$users[]=$member->getUser();
 		}
 		return array_filter($users);
 	}
+
+
+	public function getDynamicUsersPage($page, $items_by_page) {
+		if (!$this->isDynamic())
+			return [];
+
+		$params = $this->_getDynamicParams();
+		$params['limitPage'] = [$page, $items_by_page];
+		return Class_Users::findAllBy($params);
+	}
+
+
+	/**
+	 * @param Class_Permission $permission
+	 * @param Trait_TreeNode $model
+	 * @return boolean
+	 */
+	public function hasPermissionOn($permission, $model) {
+		return $this->hasLocalPermissionOn($permission, $model)
+			|| $this->hasParentPermissionOn($permission, $model);
+	}
+
+
+	public function hasLocalPermissionOn($permission, $model) {
+		return Class_UserGroup_Permission::isPermitted($permission, $this, $model);
+	}
+
+
+	public function hasParentPermissionOn($permission, $model) {
+		if ($parent = $model->getPermissionsParent())
+			return $this->hasPermissionOn($permission, $parent);
+
+		return ($defaults_holder = $model->getDefaultPermissionsHolder()) ?
+			$this->hasLocalPermissionOn($permission, $defaults_holder) : false;
+	}
+
+
+	public function hasAnyPermissionOn($model, $permissions) {
+		foreach($permissions as $permission)
+			if ($this->hasPermissionOn($permission, $model))
+				return true;
+		return false;
+	}
+
+
+	public function getParentWithPermissionOn($permission, $model) {
+		if ($parent = $model->getPermissionsParent())
+			return ($this->hasLocalPermissionOn($permission, $parent)) ?
+				$parent : $this->getParentWithPermissionOn($permission, $parent);
+
+		if (!$defaults_holder = $model->getDefaultPermissionsHolder())
+			return null;
+
+		return $this->hasLocalPermissionOn($permission, $defaults_holder) ?
+			$defaults_holder : null;
+	}
+
+
+	public function hasAnyPermissionUnder($model, $permissions) {
+		return $this->hasAnyLocalPermissionUnder($model, $permissions)
+			|| $this->hasAnyChildrenPermissionUnder($model, $permissions);
+	}
+
+
+	public function hasAnyLocalPermissionUnder($model, $permissions) {
+		return Class_UserGroup_Permission::hasAny($permissions, $this, $model);
+	}
+
+
+	public function hasAnyChildrenPermissionUnder($model, $permissions) {
+		if (!$children = $model->getChildren())
+			return false;
+
+		foreach($children as $child)
+			if ($this->hasAnyPermissionUnder($child, $permissions))
+				return true;
+		return false;
+	}
 }
+
 ?>
\ No newline at end of file
diff --git a/library/Class/UserGroup/Permission.php b/library/Class/UserGroup/Permission.php
new file mode 100644
index 0000000000000000000000000000000000000000..953e4bae707146ac98ac996bf4975a2278040f3a
--- /dev/null
+++ b/library/Class/UserGroup/Permission.php
@@ -0,0 +1,106 @@
+<?php
+/**
+ * Copyright (c) 2012-2014, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * AFI-OPAC 2.0 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).
+ *
+ * AFI-OPAC 2.0 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 AFI-OPAC 2.0; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+class UserGroup_PermissionLoader extends Storm_Model_Loader {
+	public function permit($permission, $group, $model) {
+		if(!($permission && $group && $model))
+			return null;
+
+		$params = ['permission' => $permission,
+							 'group' => $group,
+							 'id_model' => $model->getId(),
+							 'model_class' => $model->getClassName()];
+
+		$group_permission = Class_UserGroup_Permission::newInstance($params);
+		$group_permission->save();
+		return $group_permission;
+	}
+
+
+	public function deny($permission, $group, $model) {
+		if(!($permission && $group && $model))
+			return;
+
+		$params = ['id_permission' => $permission->getId(),
+							 'id_group' => $group->getId(),
+							 'id_model' => $model->getId(),
+							 'model_class' => $model->getClassName()];
+
+		if ($existing = Class_UserGroup_Permission::findFirstBy($params))
+			$existing->delete();
+	}
+
+
+	public function hasAny($permissions, $group, $model) {
+		if(!($permissions && $group && $model))
+			return false;
+
+		$id_permissions = array_map(function($perm) { return $perm->getId(); },
+																$permissions);
+
+		return 0 < Class_UserGroup_Permission::countBy(['id_group' => $group->getId(),
+																										'id_permission' => $id_permissions,
+																										'id_model' => $model->getId(),
+																										'model_class' => $model->getClassName()]);
+	}
+
+
+	public function isPermitted($permission, $group, $model) {
+		return $permission && $group && $model
+			&& 0 < Class_UserGroup_Permission::countBy(['id_group' => $group->getId(),
+																									'id_permission' => $permission->getId(),
+																									'id_model' => $model->getId(),
+																									'model_class' => $model->getClassName()]);
+	}
+
+
+	public function findFor($permission, $group, $model) {
+		if (!($permission && $group && $model))
+			return;
+
+		return Class_UserGroup_Permission::findFirstBy(['id_group' => $group->getId(),
+																										'id_permission' => $permission->getId(),
+																										'id_model' => $model->getId(),
+																										'model_class' => $model->getClassName()]);
+	}
+
+
+	public function clean($model, $ids_to_keep) {
+		$params = ['id_model' => $model->getId(),
+							 'model_class' => $model->getClassName()];
+		if(!empty($ids_to_keep))
+			$params['id not'] = $ids_to_keep;
+
+		Class_UserGroup_Permission::deleteBy($params);
+	}
+}
+
+
+class Class_UserGroup_Permission extends Storm_Model_Abstract{
+	protected $_table_name = 'user_group_permission';
+	protected $_loader_class = 'UserGroup_PermissionLoader';
+
+	protected $_belongs_to = ['group' => ['model' => 'Class_UserGroup',
+																				'referenced_in' => 'id_group'],
+														'permission' => ['model' => 'Class_Permission',
+																						 'referenced_in' => 'id_permission']];
+}
+?>
\ No newline at end of file
diff --git a/library/Class/Users.php b/library/Class/Users.php
index e41ab42e8bb4d9c9e1539383e18bb2b85149df56..c708fa9a40f4e29684a8b984a3887c0e43dcfb32 100644
--- a/library/Class/Users.php
+++ b/library/Class/Users.php
@@ -20,7 +20,7 @@
  */
 
 class UsersLoader extends Storm_Model_Loader {
-	public function getNewslettersReceivers($id_newsletter,$recipient_size) {
+ 	public function getNewslettersReceivers($id_newsletter,$recipient_size) {
 		$req = "select bib_admin_users.* from bib_admin_users join newsletters_users on bib_admin_users.id_user = newsletters_users.user_id where newsletter_id = ".$id_newsletter." and newsletters_users.send is false limit ".$recipient_size;
 		$users = Class_Users::findAll($req);
 		return $users;
@@ -398,6 +398,14 @@ class Class_Users extends Storm_Model_Abstract {
 	}
 
 
+	/**
+	 * @return bool
+	 */
+	public function isRoleMoreThanModoPortail() {
+		return $this->getRoleLevel() > ZendAfi_Acl_AdminControllerRoles::MODO_PORTAIL;
+	}
+
+
 	/**
 	 * @return bool
 	 */
@@ -406,6 +414,11 @@ class Class_Users extends Storm_Model_Abstract {
 	}
 
 
+	public function isRoleLibraryLimited() {
+		return ZendAfi_Acl_AdminControllerRoles::ADMIN_BIB >= $this->getRoleLevel();
+	}
+
+
 
 	/**
 	 * @param mode string : 'is_contact_mail' | 'is_contact_sms' | 'is_contact_letter'
@@ -444,16 +457,27 @@ class Class_Users extends Storm_Model_Abstract {
 		if ($this->getRoleLevel() < ZendAfi_Acl_AdminControllerRoles::MODO_BIB)
 			return false;
 
-		if ($this->getRoleLevel() > ZendAfi_Acl_AdminControllerRoles::ADMIN_BIB)
+
+		$hasPermission = ($article->hasCategorie()) ?
+			$this->hasAnyPermissionOn($article->getCategorie(),
+																[Class_Permission::createArticle(),
+																 Class_Permission::createArticleCategory()]) :
+			true;
+
+		if ($this->getRoleLevel() > ZendAfi_Acl_AdminControllerRoles::ADMIN_BIB
+				&& $hasPermission)
 			return true;
 
 		if (!$article->hasCategorie())
 			return false;
 
-		if ($this->hasRightConfigFront() && !$article->getCategorie()->getIdSite())
+		if ($this->hasRightConfigFront()
+				&& !$article->getCategorie()->getIdSite()
+				&& $permission)
 			return true;
 
-		return ($this->getIdSite() == $article->getCategorie()->getIdSite());
+		return $this->getIdSite() == $article->getCategorie()->getIdSite()
+			&& $hasPermission;
 	}
 
 
@@ -563,8 +587,19 @@ class Class_Users extends Storm_Model_Abstract {
 	 */
 	public function getUserGroups() {
 		return array_merge(parent::_get('user_groups'),
-											 Class_UserGroup::findAllBy(['role_level' => $this->getRoleLevel(),
-																									 'group_type' => Class_UserGroup::TYPE_DYNAMIC]));
+											 $this->getDynamicUserGroups());
+	}
+
+
+	/** @return array */
+	protected function getDynamicUserGroups() {
+		$params = ['role_level' => $this->getRoleLevel(),
+							 'group_type' => Class_UserGroup::TYPE_DYNAMIC];
+
+		if ($library = $this->getBib())
+			$params['id_bib'] = [0, $library->getId()];
+
+		return Class_UserGroup::findAllBy($params);
 	}
 
 
@@ -579,10 +614,9 @@ class Class_Users extends Storm_Model_Abstract {
 	 * @return array
 	 */
 	public function getLastAvis() {
-		return Class_AvisNotice::getLoader()->findAllBy(array(
-																													'id_user' => $this->getId(),
-																													'order' => 'date_avis desc',
-																													'limit' =>  10));
+		return Class_AvisNotice::findAllBy(['id_user' => $this->getId(),
+																				'order' => 'date_avis desc',
+																				'limit' =>  10]);
 	}
 
 
@@ -601,11 +635,6 @@ class Class_Users extends Storm_Model_Abstract {
 	}
 
 
-	public function beModoBib() {
-		return $this->changeRoleTo(ZendAfi_Acl_AdminControllerRoles::MODO_BIB);
-	}
-
-
 	public function hasRightToAccess($access_name) {
 		return in_array($access_name, $this->getRights());
 	}
@@ -733,9 +762,7 @@ class Class_Users extends Storm_Model_Abstract {
 		return null;
 	}
 
-	//------------------------------------------------------------------------------------------------------
-	// Enreg user
-	//------------------------------------------------------------------------------------------------------
+
 	public function getUser($id_user)
 	{
 		if (!$id_user) return null;
@@ -744,9 +771,6 @@ class Class_Users extends Storm_Model_Abstract {
 	}
 
 
-	//------------------------------------------------------------------------------------------------------
-	// Liste de users pour 1 zone ou une bib
-	//------------------------------------------------------------------------------------------------------
 	public function getUsers($id_zone,$id_site,$role_level,$recherche,$page)
 	{
 		if($id_site and $id_site !="ALL")
@@ -789,9 +813,6 @@ class Class_Users extends Storm_Model_Abstract {
 	}
 
 
-	//------------------------------------------------------------------------------------------------------
-	// Ecriture user
-	//------------------------------------------------------------------------------------------------------
 	public function updatePseudo($data_user, $pseudo) {
 		$user = $this->getLoader()->find($data_user->ID_USER);
 		if ($user == null) return false;
@@ -801,9 +822,7 @@ class Class_Users extends Storm_Model_Abstract {
 			->save();
 	}
 
-	//------------------------------------------------------------------------------------------------------
-	// Vérification pour eviter les doublons de login
-	//------------------------------------------------------------------------------------------------------
+
 	public function ifLoginExist($login) {
 		$login = (trim($login));
 
@@ -856,17 +875,12 @@ class Class_Users extends Storm_Model_Abstract {
 	}
 
 
-	//------------------------------------------------------------------------------------------------------
-	// Supprime un utilisateur
-	//------------------------------------------------------------------------------------------------------
 	public function deleteUser($id_user)
 	{
 		sqlExecute("delete from bib_admin_users where ID_USER=$id_user");
 	}
 
-	//------------------------------------------------------------------------------------------------------
-	// Enregistrement d'une demande d'inscription
-	//------------------------------------------------------------------------------------------------------
+
 	public function registerUser($data)	{
 		// Test champ valid
 		extract($data);
@@ -917,18 +931,13 @@ class Class_Users extends Storm_Model_Abstract {
 	}
 
 
-	//------------------------------------------------------------------------------------------------------
-	// Liste des inscriptions en attente
-	//------------------------------------------------------------------------------------------------------
 	public function getUsersNonValid()
 	{
 		$users = fetchAll("Select * from bib_admin_users_non_valid order by DATE DESC");
 		return($users);
 	}
 
-	//------------------------------------------------------------------------------------------------------
-	// Mot de passe oublié
-	//------------------------------------------------------------------------------------------------------
+
 	function lostpass($login) {
 		if(!trim($login))
 			return array('error' => 1);
@@ -957,9 +966,7 @@ class Class_Users extends Storm_Model_Abstract {
 			: ['message_mail' => $this->_("Un mail vient de vous être envoyé avec vos paramètres de connexion.")];
 	}
 
-	//------------------------------------------------------------------------------------------------------
-	// Nom abonné
-	//------------------------------------------------------------------------------------------------------
+
 	public function getNomAff($id_user = null, $complet = false) {
 		if ($id_user != null) {
 			if (null === $user = $this->getLoader()->find($id_user))
@@ -1062,9 +1069,6 @@ class Class_Users extends Storm_Model_Abstract {
 	}
 
 
-	//------------------------------------------------------------------------------------------------------
-	// Fiche abonné sigb
-	//------------------------------------------------------------------------------------------------------
 	public function getFicheSigb($user = null) {
 		if ($user === null)
 			$user = $this; // compatibilité
@@ -1244,6 +1248,16 @@ class Class_Users extends Storm_Model_Abstract {
 	}
 
 
+	public function beModoBib() {
+		return $this->changeRoleTo(ZendAfi_Acl_AdminControllerRoles::MODO_BIB);
+	}
+
+
+	public function beModoPortail() {
+		return $this->changeRoleTo(ZendAfi_Acl_AdminControllerRoles::MODO_PORTAIL);
+	}
+
+
 	/**
 	 * return Class_Users
 	 */
@@ -1300,10 +1314,9 @@ class Class_Users extends Storm_Model_Abstract {
 	public function getEditablePaniers() {
 		$user_paniers  = $this->getPaniers();
 		if ($this->canAccessBackend())
-			$user_paniers = array_unique(array_merge($user_paniers,
-																							 array_filter(Class_PanierNotice::findAllWithCatalogue())));
+			$user_paniers = array_filter(array_unique(array_merge($user_paniers,
+																														Class_PanierNotice::findAllWithCatalogue())));
 		Class_PanierNotice::sortPaniersByLibelle($user_paniers);
-
 		return $user_paniers;
 	}
 
@@ -1397,5 +1410,63 @@ class Class_Users extends Storm_Model_Abstract {
 		return 0 < Class_NewsletterSubscription::countBy(['user_id' => $this->getId(),
 																											'newsletter_id' => $newsletter->getId()]);
 	}
+
+
+	public function getLoginOrFullName() {
+		return ($full_name = $this->getNomComplet())
+			? $full_name
+			: $this->getLogin();
+	}
+
+
+	public function hasPermissionOn($permission, $model) {
+		$closure = function($group) use ($permission, $model) {
+			return $group->hasPermissionOn($permission, $model);
+		};
+
+		return $this->_proxyPermissionOn($closure);
+	}
+
+
+	public function hasParentPermissionOn($permission, $model) {
+		$closure = function($group) use ($permission, $model) {
+			return $group->hasParentPermissionOn($permission, $model);
+		};
+
+		return $this->_proxyPermissionOn($closure);
+	}
+
+
+	protected function _proxyPermissionOn($closure) {
+		if ($this->isRoleMoreThanModoPortail())
+			return true;
+
+		foreach($this->getUserGroups() as $group)
+			if ($closure($group))
+				return true;
+		return false;
+	}
+
+
+	public function hasAnyPermissionUnder($model, $permissions) {
+		if ($this->isRoleMoreThanModoPortail())
+			return true;
+
+		foreach($this->getUserGroups() as $group)
+			if ($group->hasAnyPermissionUnder($model, $permissions))
+				return true;
+		return false;
+	}
+
+
+	public function hasAnyPermissionOn($model, $permissions) {
+		if ($this->isRoleMoreThanModoPortail())
+			return true;
+
+		foreach($this->getUserGroups() as $group)
+			if ($group->hasAnyPermissionOn($model, $permissions))
+				return true;
+		return false;
+	}
 }
 ?>
\ No newline at end of file
diff --git a/library/Class/WebService/BibNumerique/Cyberlibris.php b/library/Class/WebService/BibNumerique/Cyberlibris.php
index 09b868e7568b280db5589a7e65894cd429c13478..4fdbd26eeb792bcae576a25a602ca68094970ddd 100644
--- a/library/Class/WebService/BibNumerique/Cyberlibris.php
+++ b/library/Class/WebService/BibNumerique/Cyberlibris.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
  */
 
 
@@ -32,7 +32,7 @@ class Class_WebService_BibNumerique_Cyberlibris extends Class_WebService_BibNume
 			->deleteBy(['where' => 'url_origine="'.self::BASE_URL.'" and id_origine not in (\'' . implode("', '", $this->getHarvestedIds()) . '\')']);
 	}
 
-	
+
 	protected function _importRessources($ressources) {
 		$harvestedIds = [];
 		$this->_albums = [];
@@ -49,7 +49,7 @@ class Class_WebService_BibNumerique_Cyberlibris extends Class_WebService_BibNume
 
 	protected function loadPage($page_number = 1) {
 		$reader = (new Class_WebService_BibNumerique_Cyberlibris_LivresNumeriquesReader());
-		$reader->parse();
+		$reader->parse('');
 		return $reader;
 	}
 
diff --git a/library/Class/WebService/BibNumerique/Cyberlibris/LivresNumeriquesReader.php b/library/Class/WebService/BibNumerique/Cyberlibris/LivresNumeriquesReader.php
index 1b0cbd4da0083965b9758520215e74d147d7f20b..ff989966eb93f804fc92722c9f23d7be4bfcf85c 100644
--- a/library/Class/WebService/BibNumerique/Cyberlibris/LivresNumeriquesReader.php
+++ b/library/Class/WebService/BibNumerique/Cyberlibris/LivresNumeriquesReader.php
@@ -36,7 +36,7 @@ class Class_WebService_BibNumerique_Cyberlibris_LivresNumeriquesReader extends C
 	}
 
 
-	public function parse() {
+	public function parse($xml) {
 		$this->_total_count = $this->_page_number = 0;
 		$this->_page_size = 1;
 		$this->livres = [];
diff --git a/library/Class/WebService/Lastfm.php b/library/Class/WebService/Lastfm.php
index c8fc54fbf2d87aaf517540fc4cc30744d1349504..c7e40df2104b988073ecb5b30ff4d3d27f98ba4e 100644
--- a/library/Class/WebService/Lastfm.php
+++ b/library/Class/WebService/Lastfm.php
@@ -219,43 +219,46 @@ class Class_WebService_Lastfm  extends Class_WebService_Abstract {
 		$url=str_replace(" ","+",$url);
 
 		$data = self::getHttpClient()->open_url($url);
-		
-		// Bloc des albums
-		$pos=strPos($data,'<ul class="albums',0);
-		if(!$pos) return false;
-		$posfin=strPos($data,'</ul>"',$pos);
-		$data=substr($data,$pos,($posfin-$pos));
-		
-		while(true)
-		{
-			$pos=strPos($data,'<div class="resContainer">');
-			if(!$pos) break;
-			$index=count($albums);
-			// Vignette
-			$pos=strPos($data,'src=',$pos)+5;
-			$posfin=strPos($data,'"',$pos);
-			$albums[$index]["vignette"]=substr($data,$pos,($posfin-$pos));
-			// Titre
-			$posfin=strPos($data,'</a>',$posfin);
-			for($pos=$posfin; $data[$pos] != ">"; $pos--);
-			$pos=$pos+1;
-			$albums[$index]["titre"]=trim(substr($data, $pos,($posfin-$pos)));
-			// date et nbre de pistes
-			$pos=strpos($data,'<p class="label">',$posfin);
-			if($pos)
-			{
-				$posfin=strpos($data,'</p>',$pos);
-				$bloc=substr($data,$pos,($posfin-$pos));
-				$bloc=str_replace("Released","Sortie le",$bloc);
-				$bloc=str_replace("track","titre",$bloc);
-				$elem=explode('<br />',$bloc);
-				for($i=0; $i < count($elem); $i++) $elem[$i]=trim(strip_tags($elem[$i]));
-				$albums[$index]["infos"]=$elem;
+		$dom = new Zend_Dom_Query($data);
+		$elements = $dom->queryXpath('//ul[contains(@class,"albums")]/li');
+		$albums = [];
+		foreach ($elements as $element) {
+			$album = [];
+			$dom = $dom->setDocumentHtml($element->ownerDocument->saveHtml($element));
+
+			$titles = $dom->query('.album-item-details a.link-reference h3');
+			foreach ($titles as $title) {
+				if (isset($title)) {
+					$album['titre'] = $title->textContent;
+				}
 			}
-			// Eliminer le bloc
-			$data = substr($data,$posfin);
+
+			$covers = $dom->query('a.album-item-cover img');
+			foreach ($covers as $cover) {
+				if (isset($cover) && isset($cover->attributes)) {
+					$album['vignette'] = $cover->attributes->getNamedItem('src')->value;
+				}
+			}
+
+			$release_dates = $dom->query('.album-item-details time');
+			foreach ($release_dates as $release_date) {
+				if (isset($release_date) && isset($release_date->attributes)) {
+					$date = $release_date->attributes->getNamedItem('datetime')->value;
+					$date = strftime('%d/%m/%Y', strtotime($date));
+					$album['infos'][] = "Sortie le $date";
+				}
+			}
+
+			$tracks_counts = $dom->query('.album-item-details span[itemprop="numTracks"]');
+			foreach ($tracks_counts as $tracks_count) {
+				if (isset($tracks_count)) {
+					$album['infos'][] = $tracks_count->textContent . " titres";
+				}
+			}
+
+			$albums[] = $album;
 		}
-		//tracedebug($albums,true);
+
 		return $albums;
 	}
 
diff --git a/library/Class/WebService/SIGB/Carthame/RecordResponseReader.php b/library/Class/WebService/SIGB/Carthame/RecordResponseReader.php
index fafc5fdd15ff4e0c1ff6d360a8241846d68747ca..51aa5b1fd3a5cc74922131aecda6e198e503836a 100644
--- a/library/Class/WebService/SIGB/Carthame/RecordResponseReader.php
+++ b/library/Class/WebService/SIGB/Carthame/RecordResponseReader.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 Class_WebService_SIGB_Carthame_RecordResponseReader {
 	const STATUT_DISPO = 1;
@@ -73,10 +73,11 @@ class Class_WebService_SIGB_Carthame_RecordResponseReader {
 
 	public function endSFo($data) {
 		$data = (int)$data;
-		$this->_current_exemplaire->setReservable(in_array($data, [self::STATUT_RESERVE, 
+		$this->_current_exemplaire->setReservable(in_array($data, [self::STATUT_DISPO,
+																															 self::STATUT_RESERVE,
 																															 self::STATUT_PRETE]));
-		
-		(self::STATUT_DISPO === $data) 
+
+		(self::STATUT_DISPO === $data)
 			? $this->_current_exemplaire->setDisponibiliteLibre()
 			: $this->_current_exemplaire->setDisponibiliteIndisponible();
 	}
diff --git a/library/Class/WebService/SIGB/Emprunteur.php b/library/Class/WebService/SIGB/Emprunteur.php
index 2b38b4808bc1abc7e705f8de6058ed50212cf08f..48fe7286a048278ec6ecb20eebbe20595fb1fcdf 100644
--- a/library/Class/WebService/SIGB/Emprunteur.php
+++ b/library/Class/WebService/SIGB/Emprunteur.php
@@ -16,11 +16,11 @@
  *
  * 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 Class_WebService_SIGB_Emprunteur {
-	protected 
+	protected
 		$_id,
 		$_name,
 		$_emprunts,
@@ -44,7 +44,7 @@ class Class_WebService_SIGB_Emprunteur {
 	  $_date_naissance,
 	  $_is_contact_email= 0,
 	  $_is_contact_sms= 0;
-	
+
 
 	public function __sleep() {
 		$this->getEmprunts();
@@ -551,7 +551,7 @@ class Class_WebService_SIGB_Emprunteur {
 	public function getEndDate() {
 		return $this->_end_date;
 	}
-		
+
 
 	/**
 	 * @param Class_WebService_SIGB_AbstractService $service
@@ -615,7 +615,7 @@ class Class_WebService_SIGB_Emprunteur {
 		return $this;
 	}
 
-	
+
 	/**
 	 * @return boolean
 	 */
diff --git a/library/Class/WebService/SIGB/Exemplaire.php b/library/Class/WebService/SIGB/Exemplaire.php
index 749d489f9228f479e25b8562526bab51a3a5bebd..82383398640a3f5cd7e5ed1353f0d74efbdfafc1 100644
--- a/library/Class/WebService/SIGB/Exemplaire.php
+++ b/library/Class/WebService/SIGB/Exemplaire.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 Class_WebService_SIGB_Exemplaire {
@@ -99,16 +99,16 @@ class Class_WebService_SIGB_Exemplaire {
 	public function getExemplaireOPAC() {
 		if (isset($this->_exemplaire_opac))
 			return $this->_exemplaire_opac;
-		
+
 		if ($no_notice = $this->getNoNotice())
 			$params = array('id_origine' => $no_notice);
-		
+
 		if ($this->code_barre)
 			$params = array('code_barres' => $this->code_barre);
 
 		if (!isset($params))
 			return null;
-		
+
 		return $this->_exemplaire_opac = Class_Exemplaire::getLoader()->findFirstBy($params);
 	}
 
@@ -182,7 +182,7 @@ class Class_WebService_SIGB_Exemplaire {
 		return $this->bibliotheque;
 	}
 
- 
+
 	public function getAuteur(){
 		if (!$this->auteur  and ($notice = $this->getNoticeOPAC()))
 			$this->auteur = $notice->getAuteurPrincipal();
@@ -294,13 +294,7 @@ class Class_WebService_SIGB_Exemplaire {
 
 
 	public function getLibelleDispoEnPret() {
-		if (!$tmp = Class_Profil::getCurrentProfil()->getCfgNoticeAsArray())
-			return self::DISPO_EN_PRET;
-
-		if (array_isset("en_pret", $tmp["exemplaires"]))
-			 return $tmp["exemplaires"]["en_pret"];
-
-		return self::DISPO_EN_PRET;
+		return Class_Profil::getCurrentProfil()->getCfgNoticeAsArray()['exemplaires']['en_pret'];
 	}
 
 
@@ -367,11 +361,11 @@ class Class_WebService_SIGB_Exemplaire {
 		return $this;
 	}
 
-	
+
 	public function getEdition() {
 		return $this->edition;
 	}
-	
+
 
 	public function getDisponibiliteLabel() {
 		return $this->_disponibiliteLabel;
diff --git a/library/Class/WebService/SIGB/Pergame/Service.php b/library/Class/WebService/SIGB/Pergame/Service.php
index 14053507de7f350098e6f1d6ea821baf7885f349..40fadf007a975a19b0076983c912a8c4e6e47e7a 100644
--- a/library/Class/WebService/SIGB/Pergame/Service.php
+++ b/library/Class/WebService/SIGB/Pergame/Service.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 Class_WebService_SIGB_Pergame_Service extends Class_WebService_SIGB_AbstractService {
@@ -63,16 +63,15 @@ class Class_WebService_SIGB_Pergame_Service extends Class_WebService_SIGB_Abstra
 	}
 
 
-	public function getEmpruntsOf($emprunteur)
-	{
-		$params = Class_IntBib::getLoader()->find($this->_id_bib)->getCommParamsAsArray();
+	public function getEmpruntsOf($emprunteur) {
+		$params = Class_IntBib::find($this->_id_bib)->getCommParamsAsArray();
 		$renouvelable = isset($params['Autoriser_prolongations']) ? $params['Autoriser_prolongations'] : false;
-		
-		$user = Class_Users::getLoader()->find($emprunteur->getId());
-		$prets = Class_Pret::getLoader()->findAllBy(array('IDABON' => $user->getIdabon(),
-																											'ORDREABON' => $user->getOrdreabon(),
-																											'EN_COURS' => 1));
-		$emprunts = array();
+
+		$user = Class_Users::find($emprunteur->getId());
+		$prets = Class_Pret::findAllBy(['IDABON' => $user->getIdabon(),
+																		'ORDREABON' => $user->getOrdreabon(),
+																		'EN_COURS' => 1]);
+		$emprunts = [];
 		foreach($prets as $pret)
 		{
 			$emprunts []= Class_WebService_SIGB_Emprunt::newInstanceWithEmptyExemplaire()
@@ -90,7 +89,7 @@ class Class_WebService_SIGB_Pergame_Service extends Class_WebService_SIGB_Abstra
 		$user = Class_Users::find($emprunteur->getId());
 		$reservations_db = Class_Reservation::findAllBy(['IDABON' => $user->getIdabon(),
 																										 'ORDREABON' => $user->getOrdreabon()]);
-		$reservations = array();
+		$reservations = [];
 		foreach($reservations_db as $reservation) {
 			$bib = Class_Bib::find($reservation->getIdSite());
 
@@ -107,8 +106,8 @@ class Class_WebService_SIGB_Pergame_Service extends Class_WebService_SIGB_Abstra
 
 
 	public function reserverExemplaire($user, $exemplaire, $code_annexe) {
-		return $this->getLegacyService()->reserverExemplairePergame($this->_id_bib, 
-																												 $exemplaire, 
+		return $this->getLegacyService()->reserverExemplairePergame($this->_id_bib,
+																												 $exemplaire,
 																												 $code_annexe);
 	}
 
@@ -122,7 +121,7 @@ class Class_WebService_SIGB_Pergame_Service extends Class_WebService_SIGB_Abstra
 		return $this->getLegacyService()->prolongerPret($pret_id);
 	}
 
-	
+
 	public function getNotice($id){
 		if (!$exemplaire = Class_Exemplaire::getLoader()->findFirstBy(array('id_origine' => $id,
 																																				'id_bib' => $this->_id_bib)))
diff --git a/library/Class/WebService/SIGB/Reservation.php b/library/Class/WebService/SIGB/Reservation.php
index ff8d5be190169d14fc782e9c4abbc6c752174230..049bfbfe3ae932fe888be296145c36cfc76e56a3 100644
--- a/library/Class/WebService/SIGB/Reservation.php
+++ b/library/Class/WebService/SIGB/Reservation.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 Class_WebService_SIGB_Reservation extends Class_WebService_SIGB_ExemplaireOperation {
@@ -24,6 +24,7 @@ class Class_WebService_SIGB_Reservation extends Class_WebService_SIGB_Exemplaire
 	protected $etat;
 	protected $pickup_location_label;
 
+
 	public function getRang() {
 		if (!isset($this->rang)) $this->rang=1;
 		return $this->rang;
@@ -32,7 +33,7 @@ class Class_WebService_SIGB_Reservation extends Class_WebService_SIGB_Exemplaire
 
 	public function setRang($rang) {
 		$this->rang = (int)$rang;
-		if ($this->rang == 0) 
+		if ($this->rang == 0)
 			$this->rang = 1;
 		return $this;
 	}
@@ -48,14 +49,17 @@ class Class_WebService_SIGB_Reservation extends Class_WebService_SIGB_Exemplaire
 		return $this->etat;
 	}
 
+
 	public function setPickupLocationLabel($pickup_location_label) {
 		$this->pickup_location_label = $pickup_location_label;
 	}
 
+
 	public function getPickupLocationLabel() {
 		return $this->pickup_location_label;
 	}
 
+
 	public function onParseAttributes() {
 		$this->setRang($this->getAttribute('Rang'));
 		$this->setEtat($this->getAttribute('Etat'));
@@ -63,15 +67,14 @@ class Class_WebService_SIGB_Reservation extends Class_WebService_SIGB_Exemplaire
 		if (!$code_annexe = $this->getAttribute('Lieu'))
 			return;
 
-		if ($annexe = Class_CodifAnnexe::getLoader()->findFirstBy(array('code' => $code_annexe)))
-				$this->setBibliotheque($annexe->getLibelle());
+		if ($annexe = Class_CodifAnnexe::findFirstBy(['code' => $code_annexe]))
+			$this->setBibliotheque($annexe->getLibelle());
 	}
-	
+
 
 	/** @codeCoverageIgnore */
 	public function __toString(){
 		return parent::__toString().", Rang:".$this->getRang();
 	}
 }
-
-?>
\ No newline at end of file
+?>
diff --git a/library/Trait/CustomFields.php b/library/Trait/CustomFields.php
new file mode 100644
index 0000000000000000000000000000000000000000..fb029e77a91279d6654431e94d4aa860193facee
--- /dev/null
+++ b/library/Trait/CustomFields.php
@@ -0,0 +1,86 @@
+<?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
+ */
+
+trait Trait_CustomFields {
+	protected $custom_fields=[],
+		$custom_fields_meta;
+
+	public static function findFirstByCustomFieldValue($name, $value) {
+		$model = Class_CustomField_Model::getModel(static::getModelName());
+		return $model->findFirstByCustomFieldValue($name, $value);
+	}
+
+
+	public static function addCustomField($name, $type, $options='') {
+		Class_CustomField_Model::getModel(static::getModelName())
+			->addField($name, $type, $options);
+	}
+
+
+	public static function getModelName() {
+		return Class_CustomField_Model::sanitizeName(static::getLoader()->getModel());
+	}
+
+
+	public function getCustomField($name) {
+		if (isset($this->custom_fields[$name]))
+			return $this->custom_fields[$name];
+		if ($this->isNew())
+			return '';
+		$model_values = Class_CustomField_Model::getModel($this->getModelName())->find($this->getId());
+
+		if ($value = $model_values->getFieldValueByName($name))
+			return $value->getValue();
+		return '';
+	}
+
+
+	public function setCustomField($field,$value) {
+		$this->custom_fields[$field]=$value;
+		return $this;
+	}
+
+
+	public function saveWithCustomFields() {
+		if (!$this->save())
+			return false;
+
+		if (!Class_CustomField_Model::isRegistered($this->getModelName()))
+			return true;
+
+		$model_values = Class_CustomField_Model::getModel($this->getModelName())->find($this->getId());
+
+		foreach ($this->custom_fields as $key => $value)
+			$model_values->setFieldValueByName($key, $value);
+
+		$model_values->save();
+		return true;
+	}
+
+
+	public function deleteWithCustomFields() {
+		if (Class_CustomField_Model::isRegistered($this->getModelName())
+				&& $model_values = Class_CustomField_Model::getModel($this->getModelName())->find($this->getId()))
+			$model_values->deleteValues();
+		$this->delete();
+	}
+}
+?>
\ No newline at end of file
diff --git a/library/Trait/Indexable.php b/library/Trait/Indexable.php
new file mode 100644
index 0000000000000000000000000000000000000000..8120e7b975c0a0c06e01503a5474336aed631d9e
--- /dev/null
+++ b/library/Trait/Indexable.php
@@ -0,0 +1,79 @@
+<?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
+ */
+
+
+trait Trait_Indexable {
+	public function index() {
+		if (!$this->hasToBeIndexed())
+			return $this;
+		Class_Indexation_PseudoNotice::index($this);
+		$this->indexIntoDomain();
+	}
+
+
+	protected function indexIntoDomain() {
+		if(!$domains_ids = $this->getDomaineIdsAsArray())
+			 return $this;
+
+		if(!$alpha_key = $this->getAlphaKey()) {
+			return $this;
+		}
+
+		Class_NoticeDomain::deleteBy(['record_alpha_key' => $alpha_key]);
+
+		foreach($domains_ids as $domain_id) {
+			Class_NoticeDomain::newInstance(['domain_id' => $domain_id,
+																			 'record_alpha_key' => $alpha_key])
+				->save();
+
+			Class_NoticeDomain::updateRecordsFacette($domain_id);
+		}
+
+		return $this;
+	}
+
+
+	public function hasToBeIndexed() {
+		return true;
+	}
+
+
+	public function unindex() {
+		$alpha_key = $this->getAlphaKey();
+
+		$domains = $this->getDomaines();
+		if (empty($domains)) {
+			Class_NoticeDomain::deleteBy(['record_alpha_key' => $alpha_key,
+																		'panier_id' => 0]);
+		}
+
+		$domains_in_db = Class_NoticeDomain::getDomainsForRecordAlphaKey($alpha_key);
+
+		foreach(array_diff($domains_in_db, $domains) as $domain)
+			Class_NoticeDomain::deleteBy(['domain_id' => $domain->getId()]);
+	}
+
+
+	public function getAlphaKey() {
+		return (new Class_Notice_ClefAlpha($this))->keyString();
+	}
+}
+?>
\ No newline at end of file
diff --git a/library/Trait/PermissionTargetable.php b/library/Trait/PermissionTargetable.php
new file mode 100644
index 0000000000000000000000000000000000000000..b8dc2cc6069e5911f98bab1a679cbd015ab9971f
--- /dev/null
+++ b/library/Trait/PermissionTargetable.php
@@ -0,0 +1,34 @@
+<?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
+ */
+
+trait Trait_PermissionTargetable {
+	abstract public function getPermissionsEditUrl($view);
+
+	public function getDefaultPermissionsHolder() {
+		return null;
+	}
+
+	public function getPermissionsParent() {
+		return null;
+	}
+
+	abstract public function getPermissionsChildren();
+}
\ No newline at end of file
diff --git a/library/ZendAfi/Acl/AdminControllerRoles.php b/library/ZendAfi/Acl/AdminControllerRoles.php
index 3b207e4c1b6f74e3c88620485a65c931d30fa8d7..986303a13403d86775a75585142b61e3a2504c90 100644
--- a/library/ZendAfi/Acl/AdminControllerRoles.php
+++ b/library/ZendAfi/Acl/AdminControllerRoles.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
  */
 //////////////////////////////////////////////////////////////////////////////////////////
 // OPAC3 : DEFINITION DES ROLES
@@ -33,7 +33,7 @@ class ZendAfi_Acl_AdminControllerRoles extends Zend_Acl {
 	const MODO_PORTAIL = 5;
 	const ADMIN_PORTAIL = 6;
 	const SUPER_ADMIN = 7;
-	
+
 	protected static $listeRole = [
 		'0' => ['abrege' => 'invite','libelle' => 'invité'],   													// 0 - Invité - Par default axx contenu.
 		//	'1' => array('abrege' => 'abonne','libelle' => 'abonné'),															// 1 - Abonne (registered) - Utilisateur du site (fonctionnalité non pergame reservation par mail, etc...)
@@ -54,6 +54,7 @@ class ZendAfi_Acl_AdminControllerRoles extends Zend_Acl {
 		$this->add(new Zend_Acl_Resource('agenda'));
 		$this->add(new Zend_Acl_Resource('auth'));
 		$this->add(new Zend_Acl_Resource('cms'));
+		$this->add(new Zend_Acl_Resource('cms-category'));
 		$this->add(new Zend_Acl_Resource('data'));
 		$this->add(new Zend_Acl_Resource('error'));
 		$this->add(new Zend_Acl_Resource('modo'));
@@ -65,7 +66,7 @@ class ZendAfi_Acl_AdminControllerRoles extends Zend_Acl {
 		$this->add(new Zend_Acl_Resource('menus'));
 		$this->add(new Zend_Acl_Resource('catalogue'));
 		$this->add(new Zend_Acl_Resource('accueil'));
-		
+
 		// Ressources reprise en OPAC 3
 		$this->add(new Zend_Acl_Resource('index'));
 		$this->add(new Zend_Acl_Resource('zone'));
@@ -93,8 +94,9 @@ class ZendAfi_Acl_AdminControllerRoles extends Zend_Acl {
 
 		//Access Rules
 		$this->allow('invite','auth');
-		
-		$this->allow('modo_bib','cms');
+
+ 		$this->allow('modo_bib','cms');
+		$this->allow('modo_bib','cms-category');
 		$this->allow('modo_bib','ajax');
 		$this->allow('modo_bib','modo');
 		$this->allow('modo_bib','panier');
@@ -141,18 +143,18 @@ class ZendAfi_Acl_AdminControllerRoles extends Zend_Acl {
  	{
  		return self::$listeRole[$role_level]["libelle"];
 	}
-	
+
 	public static function getNomRole($role_level)
  	{
  		return self::$listeRole[$role_level]["abrege"];
 	}
-  
+
 	//----------------------------------------------------------------------------------
 	// Rend la combo des roles
 	//----------------------------------------------------------------------------------
 	public static function rendCombo($selected,$user_role_level,$tous=false)
 	{
-		
+
 		$html[]='<select name="role">';
 		if($tous==true)
 			{
@@ -171,5 +173,5 @@ class ZendAfi_Acl_AdminControllerRoles extends Zend_Acl {
 	{
 		return (self::$listeRole[$level_role]["abrege"]);
 	}
-	
+
 }
\ No newline at end of file
diff --git a/library/ZendAfi/Controller/Action.php b/library/ZendAfi/Controller/Action.php
index 9d2ddfda91141885949b1a21df8e58d2798768fd..a6536c98eb8c000a58ffeb188979f9c3790e7b3a 100644
--- a/library/ZendAfi/Controller/Action.php
+++ b/library/ZendAfi/Controller/Action.php
@@ -102,7 +102,7 @@ class ZendAfi_Controller_Action extends Zend_Controller_Action {
 			$this->_helper->notify($this->_definitions->successfulDeleteMessage($model));
 		}
 		$this->_redirectToIndex();
-		$this->_definitions->doAfterDelete();
+		$this->_definitions->doAfterDelete($model);
 	}
 
 
@@ -127,13 +127,21 @@ class ZendAfi_Controller_Action extends Zend_Controller_Action {
 			$this->_redirectToIndex();
 			return;
 		}
+
+		if (!$this->_canEdit($model)) {
+			$this->_helper->notify($this->view->_('Vous n\'avez pas la permission "%s"',
+																						$this->_getEditActionTitle($model)));
+			$this->_redirectToIndex();
+			return;
+		}
+
 		$this->view->titre = $this->_getEditActionTitle($model);
 		$this->_addModelToView($model);
 
 		if ($this->_setupFormAndSave($model)) {
 			$this->_helper->notify($this->_definitions->successfulSaveMessage($model));
 			$this->_redirectToEdit($model);
-			$this->_definitions->doAfterEdit();
+			$this->_definitions->doAfterEdit($model);
 		}
 
 		$this->_postEditAction($model);
@@ -141,6 +149,13 @@ class ZendAfi_Controller_Action extends Zend_Controller_Action {
 
 
 	public function addAction() {
+		if (!$this->_canAdd()) {
+			$this->_helper->notify($this->view->_('Vous n\'avez pas la permission "%s"',
+																						$this->_definitions->addActionTitle()));
+			$this->_redirectToIndex();
+			return;
+		}
+
 		$this->view->titre = $this->_definitions->addActionTitle();
 		$model = $this->_definitions->newModel();
 		$this->_updateNewModel($model);
@@ -159,6 +174,15 @@ class ZendAfi_Controller_Action extends Zend_Controller_Action {
 	}
 
 
+	protected function _canAdd() {
+		return true;
+	}
+
+
+	protected function _canEdit($model) {
+		return true;
+	}
+
 
 	protected function _getCustomFieldModelValues($model) {
 		return Class_CustomField_Model::getModel($this->_definitions->getModelClass())
@@ -198,7 +222,13 @@ class ZendAfi_Controller_Action extends Zend_Controller_Action {
 
 
 	protected function _redirectToEdit($model) {
-		$this->_redirectClose('/admin/'.$this->_request->getControllerName().'/edit/id/'.$model->getId());
+		$this->_redirectClose($this->_getEditUrl($model));
+	}
+
+
+	protected function _getEditUrl($model) {
+		return sprintf('/admin/%s/edit/id/%s',
+									 $this->_request->getControllerName(), $model->getId());
 	}
 
 
diff --git a/library/ZendAfi/Controller/Action/Helper/GroupPermissions.php b/library/ZendAfi/Controller/Action/Helper/GroupPermissions.php
new file mode 100644
index 0000000000000000000000000000000000000000..fcd8fb4a3b2096fde38b9b848c690b49e0319a7f
--- /dev/null
+++ b/library/ZendAfi/Controller/Action/Helper/GroupPermissions.php
@@ -0,0 +1,47 @@
+<?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_GroupPermissions extends Zend_Controller_Action_Helper_Abstract {
+
+	public function groupPermissions($model, $permissions) {
+		$ids = [];
+		foreach($permissions as $perm) {
+			$parts = explode('-', $perm);
+			if ((2 != count($parts))
+					|| (!$group = Class_UserGroup::find($parts[0]))
+					|| (!$permission = Class_Permission::find($parts[1])))
+				continue;
+
+			if (!$group_permission = Class_UserGroup_Permission::findFor($permission, $group, $model))
+				$group_permission = Class_UserGroup_Permission::permit($permission, $group, $model);
+			$ids[] = $group_permission->getId();
+		}
+
+		Class_UserGroup_Permission::clean($model, $ids);
+	}
+
+
+	public function direct($model, $permissions) {
+		$this->groupPermissions($model, $permissions);
+	}
+}
+?>
\ No newline at end of file
diff --git a/library/ZendAfi/Controller/Action/RessourceDefinitions.php b/library/ZendAfi/Controller/Action/RessourceDefinitions.php
index 426b9627af78761f6385edfe74ccf7155e35808d..56e3adaac01fe2612167b1e60e26da07abf8de96 100644
--- a/library/ZendAfi/Controller/Action/RessourceDefinitions.php
+++ b/library/ZendAfi/Controller/Action/RessourceDefinitions.php
@@ -57,14 +57,14 @@ class ZendAfi_Controller_Action_RessourceDefinitions {
 	}
 
 
-	public function doAfterEdit() {
+	public function doAfterEdit($model) {
 		if (isset($this->_definitions['after_edit']))
-			$this->_definitions['after_edit']();
+			$this->_definitions['after_edit']($model);
 	}
 
-	public function doAfterDelete() {
+	public function doAfterDelete($model) {
 		if (isset($this->_definitions['after_delete']))
-			$this->_definitions['after_delete']();
+			$this->_definitions['after_delete']($model);
 	}
 
 
diff --git a/library/ZendAfi/Controller/Plugin/AdminAuth.php b/library/ZendAfi/Controller/Plugin/AdminAuth.php
index 19753e8d23cc6000467fc957b93236dcf0cf79e3..23b522f18961a664ea2f831ffd0be4418b921cc6 100644
--- a/library/ZendAfi/Controller/Plugin/AdminAuth.php
+++ b/library/ZendAfi/Controller/Plugin/AdminAuth.php
@@ -66,7 +66,7 @@ class ZendAfi_Controller_Plugin_AdminAuth extends Zend_Controller_Plugin_Abstrac
 			if ((!$user = Class_Users::getIdentity()) && ($controller == "abonne" && $action !== "authenticate")) {
 				$request->setParam('redirect', Class_Url::absolute());
 				$controller = 'auth';
-				$action = 'login';
+				$action = ($request->getParam('render') == 'popup') ? 'popup-login' : 'login';
 			}
 		}
 
diff --git a/library/ZendAfi/Controller/Plugin/DefineURLs.php b/library/ZendAfi/Controller/Plugin/DefineURLs.php
index 8243a73063c83d4a546ba1c44708043d7431e7e4..ed6387cb21aec751e59824b8be8facb12a2304a8 100644
--- a/library/ZendAfi/Controller/Plugin/DefineURLs.php
+++ b/library/ZendAfi/Controller/Plugin/DefineURLs.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
  */
 //////////////////////////////////////////////////////////////////////////////////////////
 // OPAC3 :	Activation du profil et du skin
@@ -33,7 +33,7 @@ class ZendAfi_Controller_Plugin_DefineURLs extends Zend_Controller_Plugin_Abstra
 		$module = $this->getModuleNameForProfilAndRequest($profil, $request);
 		$profil->setSkin($request->getParam('skin',$profil->getSkin()));
 		$this->setUpSkin($module, $profil);
-		
+
 
 		if ($module=="admin")
 			$this->setUpBibZoneFilters($request);
@@ -46,29 +46,26 @@ class ZendAfi_Controller_Plugin_DefineURLs extends Zend_Controller_Plugin_Abstra
 
 
 	public function findProfilTelephoneId() {
-		if (!$profil = Class_Profil::getLoader()->findFirstBy(array('BROWSER' => 'telephone'))) 
+		if (!$profil = Class_Profil::getLoader()->findFirstBy(array('BROWSER' => 'telephone')))
 			return 0;
 		return $profil->getId();
 	}
 
-	
-	public function selectProfilFromRequest($request) {
-		$requested_module = $request->getModuleName();
 
-		// Initialisation du profil
-		$id_profil = ($requested_module === 'admin') ? 0 : (int)$request->getParam('id_profil');
-		
-		if ($id_profil <= 0 && ($this->_session->id_profil) && ($requested_module !== 'telephone'))
+	protected function selectProfilFromRequest($request) {
+		$id_profil = $this->getIdProfilFromRequest($request);
+
+		if ($id_profil <= 0 && ($this->_session->id_profil) && ($request->getModuleName() !== 'telephone'))
 				$id_profil = intval($this->_session->id_profil);
-		
+
 		if ($id_profil <= 0 && $this->shouldSelectTelephone($request))
 				$id_profil = $this->findProfilTelephoneId();
 
 		if ($id_profil <= 0)
 			$id_profil = 1;
-			
-		if (!$profil = Class_Profil::getLoader()->find($id_profil))
-			$profil = Class_Profil::getLoader()->findFirstBy(array('order' => 'id_profil'));
+
+		if (!$profil = Class_Profil::find($id_profil))
+			$profil = Class_Profil::findFirstBy(['order' => 'id_profil']);
 
 		$this->_session->id_profil = $profil->getId();
 
@@ -76,30 +73,45 @@ class ZendAfi_Controller_Plugin_DefineURLs extends Zend_Controller_Plugin_Abstra
 	}
 
 
-	public function memorizeLastProfil() {
+	protected function getIdProfilFromRequest($request) {
+		if($request->getModuleName() === 'admin')
+			return 0;
+
+		if (!$profil = Class_Profil::findFirstBy(['rewrite_url' => $request->getControllerName()]))
+			return (int)$request->getParam('id_profil', 0);
+
+		return $profil->getId();
+	}
+
+
+	protected function memorizeLastProfil() {
 		$this->_session->previous_id_profil = isset($this->_session->id_profil) ? $this->_session->id_profil : 1;
 		return $this;
 	}
 
 
-	public function getModuleNameForProfilAndRequest($profil, $request) {
+	protected function getModuleNameForProfilAndRequest($profil, $request) {
 		$module = $requested_module = $request->getModuleName();
 		if (('telephone' == $profil->getBrowser()) &&  ($requested_module != 'admin'))
 			$module = 'telephone';
 
 		if ($requested_module == 'telephone' && $profil->getBrowser() == 'opac')
 			$module = 'opac';
-			
+
 		/**
 		 * Si l'ouverture du profil nécessite un niveau d'accès et que
 		 * le niveau requis est trop faible, on redirige sur la page de login
-		 */		
+		 */
 		if (!$profil->isPublic()) {
 			$auth = ZendAfi_Auth::getInstance();
 			if (!$auth->hasIdentity() or	$auth->getIdentity()->ROLE_LEVEL < $profil->getAccessLevel()) {
+        if ($module != 'admin' && !$auth->hasIdentity()) {
+          $redirect = '?redirect=' . urlencode('/index/index/id_profil/' . $profil->getId());
+          return $this->getResponse()->setRedirect(BASE_URL . '/auth/login/id_profil/1' . $redirect);
+        }
+
 				$request->setControllerName('auth');
 				$request->setActionName('login');
-				$module = 'admin';
 			}
 		}
 
@@ -108,7 +120,7 @@ class ZendAfi_Controller_Plugin_DefineURLs extends Zend_Controller_Plugin_Abstra
 	}
 
 
-	public function setUpSkin($module, $profil) {
+	protected function setUpSkin($module, $profil) {
 		$skindir = $profil->getPathTheme();
 		$url_skin = BASE_URL . $skindir;
 
@@ -116,10 +128,10 @@ class ZendAfi_Controller_Plugin_DefineURLs extends Zend_Controller_Plugin_Abstra
 	}
 
 
-	public function setUpBibZoneFilters($request) {
+	protected function setUpBibZoneFilters($request) {
 		if (!array_key_exists('admin', $_SESSION))
-			$_SESSION["admin"] = array("filtre_localisation" => array("id_zone" => 'ALL',
-																																"id_bib" => 'ALL'));
+			$_SESSION['admin'] = ['filtre_localisation' => ['id_zone' => 'ALL',
+																											'id_bib' => 'ALL']];
 			$session=$_SESSION["admin"]["filtre_localisation"];
 
 
diff --git a/library/ZendAfi/Controller/Plugin/XHProfile.php b/library/ZendAfi/Controller/Plugin/XHProfile.php
index d38de3b2dfd32efd9be01fdeb5e6f345b7b30892..516a5f58082aa22c69b31646f28c09f590058ea0 100644
--- a/library/ZendAfi/Controller/Plugin/XHProfile.php
+++ b/library/ZendAfi/Controller/Plugin/XHProfile.php
@@ -16,11 +16,11 @@
  *
  * 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_Controller_Plugin_XHProfile extends Zend_Controller_Plugin_Abstract {
-	protected 
+	protected
 		$_enabled = false;
 
 	public function setRootUrl($url) {
@@ -50,7 +50,7 @@ class ZendAfi_Controller_Plugin_XHProfile extends Zend_Controller_Plugin_Abstrac
 		xhprof_enable();
 	}
 
- 
+
 	public function postDispatch(Zend_Controller_Request_Abstract $request) {
 		if (!$this->_enabled)
 			return;
@@ -59,13 +59,13 @@ class ZendAfi_Controller_Plugin_XHProfile extends Zend_Controller_Plugin_Abstrac
 
 
 		$xhprof_data = xhprof_disable();
-		require_once ROOT_PATH."xhprof/xhprof_lib/utils/xhprof_lib.php";
-		require_once ROOT_PATH."xhprof/xhprof_lib/utils/xhprof_runs.php";
+		require_once "xhprof/xhprof_lib/utils/xhprof_lib.php";
+		require_once "xhprof/xhprof_lib/utils/xhprof_runs.php";
 		$xhprof_runs = new XHProfRuns_Default();
 		$run_id = $xhprof_runs->save_run($xhprof_data, "xhprof_testing");
 
 		$this->_response
-			->setRedirect($view->absoluteUrl("/xhprof/xhprof_html/index.php?run={$run_id}&source=xhprof_testing\n"))
+			->setRedirect(BASE_URL."/xhprof/xhprof_html/index.php?run={$run_id}&source=xhprof_testing\n")
 			->clearBody();
 	}
 }
diff --git a/library/ZendAfi/Form.php b/library/ZendAfi/Form.php
index 2d3ab584fe855dd34ebba3e8e568aa33c8b0e22c..d19c0d3d1af3c1f2a0a4f4307b8cfec99953f6fb 100644
--- a/library/ZendAfi/Form.php
+++ b/library/ZendAfi/Form.php
@@ -32,7 +32,6 @@ class ZendAfi_Form extends Zend_Form {
 
 
 	public static function newWith($datas = [], $custom_form = null) {
-
 		return (new static())
 			->populate($datas)
 			->setCustomForm($custom_form);
diff --git a/library/ZendAfi/Form/Admin/CmsCategory.php b/library/ZendAfi/Form/Admin/CmsCategory.php
new file mode 100644
index 0000000000000000000000000000000000000000..73bf5b1694f5c0abfd62dac65431eb92c25b005c
--- /dev/null
+++ b/library/ZendAfi/Form/Admin/CmsCategory.php
@@ -0,0 +1,74 @@
+<?php
+/**
+ * Copyright (c) 2012, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * AFI-OPAC 2.0 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).
+ *
+ * AFI-OPAC 2.0 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 AFI-OPAC 2.0; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+class ZendAfi_Form_Admin_CmsCategory extends ZendAfi_Form {
+	public static function newWith($data=[], $custom_form=null) {
+		$names = ['libelle'];
+		$form = new self();
+		$form
+			->setAttrib('data-backurl',
+									$form->getView()->url(['module' => 'admin',
+																				 'controller' => 'cms',
+																				 'action' => 'index'],
+																				null, true))
+			->addElement('text', 'libelle',
+									 ['label' =>  $form->_('Libellé'),
+										'required' => true,
+										'allowEmpty' => false]);
+
+		if (isset($data['id_cat_mere']))
+			$form->setAttrib('data-backurl' ,
+											 $form->getView()->url(['module' => 'admin',
+																							'controller' => 'cms',
+																							'action' => 'index',
+																							'id_cat' => $data['id_cat_mere']],
+																						 null, true));
+
+		if (isset($data['id_cat'])) {
+			$identity = Class_Users::getIdentity();
+			$filter = function($category) use ($identity) {
+				return $identity->hasPermissionOn(Class_Permission::createArticleCategory(),
+																					$category);
+			};
+
+			$form
+				->addElement('comboParentCategory', 'id_cat',
+										 ['label' =>  $form->_('Catégorie parente'),
+											'categorie' => $data['id_cat'],
+											'categories_filter' => $filter,
+											'disable_none' => !$identity->isRoleMoreThanModoPortail()])
+				->setAttrib('data-backurl' ,
+										$form->getView()->url(['module' => 'admin',
+																					 'controller' => 'cms',
+																					 'action' => 'index',
+																					 'id_cat' => $data['id_cat']],
+																					null, true));
+			$names[] = 'id_cat';
+		}
+
+		$form
+			->addDisplayGroup($names, 'catégorie', ['legend' => $form->_('Catégorie')])
+			->populate($data);
+		return $form;
+	}
+}
+
+?>
\ No newline at end of file
diff --git a/library/ZendAfi/Form/Admin/News.php b/library/ZendAfi/Form/Admin/News.php
index 945c873202d6301d06a594964a0fca86a9f00938..c238bbde7cd9c25b855312c10b41767ca774845c 100644
--- a/library/ZendAfi/Form/Admin/News.php
+++ b/library/ZendAfi/Form/Admin/News.php
@@ -25,98 +25,107 @@ class ZendAfi_Form_Admin_News extends ZendAfi_Form {
 		$form = parent::newWith($datas, $custom_form);
 		$form
 			->addAuthorFullName(isset($datas['id_user']) ? $datas['id_user'] : 0)
-			->addWorkflow(isset($datas['status']) ? $datas['status'] : '' )
+			->addWorkflow(isset($datas['status']) ? $datas['status'] : '',
+										isset($datas['id_cat']) ? $datas['id_cat'] : '')
 			->addPublication($datas['debut'], $datas['fin'])
 			->addAgenda($datas['all_day'], $datas['events_debut'], $datas['events_fin']);
 
 		return $form;
 	}
 
-	 public function init() {
-		 parent::init();
-
-		 $this
-			 ->setAttrib('id','form')
-			 ->addElement('text', 'titre', ['label' => $this->_('Titre'),
-																			'size' => 50,
-																			'maxlength' => Class_Article::TITLE_MAX_LENGTH,
-																			'order' => 1])
-			 ->addElement('checkbox', 'cacher_titre', ['label' => $this->_('Titre caché'),
-																								 'order' => 2])
-			 ->addElement('text', 'auteur', ['label' => $this->_('Auteur'),
-																			 'disabled' => 'disabled',
-																			 'order' => 3])
-			 ->addElement('comboCategories', 'id_cat', ['label' => $this->_('Catégorie'),
-																									'order' => 4])
-
-			 ->addElement('dateRangePicker',
-										'publication_range',
-										['label' => $this->_('Publication'),
-										 'start' => ['name' => 'debut',
-																 'allowEmpty' => false],
-										 'end' => ['name' => 'fin'],
-										 'order' => 7])
-
-			 ->addElement('dateRangePicker',
-										'event_range',
-										['label' => $this->_('Agenda'),
-										 'start' => ['name' => 'events_debut',
-																 'allowEmpty' => false,
-																 'dateOnly' => false,
-																 'toggleAllDay' => 'all_day'],
-										 'end' => ['name' => 'events_fin',
-															 'dateOnly' => false,
-															 'toggleAllDay' => 'all_day'],
-										 'order' => 8])
-
-			 ->addElement('checkbox', 'all_day',
-										['label' => $this->_("L'évenement dure toute la journée"),
-										 'order' => 11])
-
-			 ->addElement('select', 'id_lieu', ['label' => $this->_('Lieu'),
-																					'multiOptions' => $this->getLocations(),
-																					'order' => 12])
-			 ->addElement('ckeditor', 'contenu')
-			 ->addElement('ckeditor', 'description')
-			 ->addElement('textarea', 'tags',
-										['label' => $this->_('Tags') . '<br><span style="font-weight:normal;font-size:80%">'
-										 . $this->_('Entrez la liste des mots-clefs et expressions qui caractérisent votre article séparés par ;') . '</span>',
-										 'rows' => 4,
-										 'cols' => 51])
-			 ->addElement('checkbox', 'avis',
-										['label' => $this->_('Autoriser les commentaires d\'internautes (Mode blog) ?')])
-			 ->addElement('checkbox', 'indexation',
-										['label' => $this->_('Indexer l\'article dans le catalogue ?')])
-
-			 ->addDisplayGroup([
-													'titre',
-													'cacher_titre',
-													'auteur',
-													'id_cat',
-													'publication_range',
-													'event_range',
-													'all_day',
-													'id_lieu'
-													],
-												 'publication',
-												 ['legend' => $this->_('Publication')])
-			 ->addDisplayGroup(['contenu'],
-												 'article',
-												 ['legend' => $this->_('Article')])
-			 ->addDisplayGroup(['description'],
-												 'article_for_widget',
-												 ['legend' => $this->_('Résumé pour l\'affichage dans les boîtes')])
-
-			 ->addDisplayGroup(['tags',
-													'avis',
-													'indexation'],
-												 'options',
-												 ['legend' => $this->_('Options')])
-
-			 ->addDomainIndexation();
-
-		 $this->tags->getDecorator('label')->setOption('escape', false);
-	 }
+	public function init() {
+		parent::init();
+		$identity = Class_Users::getIdentity();
+		$filter = function($category) use ($identity) {
+			return $identity->hasAnyPermissionOn($category,
+																					 [Class_Permission::createArticle(),
+																						Class_Permission::createArticleCategory()]);
+		};
+
+		$this
+			->setAttrib('id','form')
+			->addElement('text', 'titre', ['label' => $this->_('Titre'),
+																		 'size' => 50,
+																		 'maxlength' => Class_Article::TITLE_MAX_LENGTH,
+																		 'order' => 1])
+			->addElement('checkbox', 'cacher_titre', ['label' => $this->_('Titre caché'),
+																								'order' => 2])
+			->addElement('text', 'auteur', ['label' => $this->_('Auteur'),
+																			'disabled' => 'disabled',
+																			'order' => 3])
+			->addElement('comboCategories', 'id_cat',
+									 ['label' => $this->_('Catégorie'),
+										'categories_filter' => $filter,
+										'order' => 4])
+
+			->addElement('dateRangePicker',
+									 'publication_range',
+									 ['label' => $this->_('Publication'),
+										'start' => ['name' => 'debut',
+																'allowEmpty' => false],
+										'end' => ['name' => 'fin'],
+										'order' => 7])
+
+			->addElement('dateRangePicker',
+									 'event_range',
+									 ['label' => $this->_('Agenda'),
+										'start' => ['name' => 'events_debut',
+																'allowEmpty' => false,
+																'dateOnly' => false,
+																'toggleAllDay' => 'all_day'],
+										'end' => ['name' => 'events_fin',
+															'dateOnly' => false,
+															'toggleAllDay' => 'all_day'],
+										'order' => 8])
+
+			->addElement('checkbox', 'all_day',
+									 ['label' => $this->_("L'évenement dure toute la journée"),
+										'order' => 11])
+
+			->addElement('select', 'id_lieu', ['label' => $this->_('Lieu'),
+																				 'multiOptions' => $this->getLocations(),
+																				 'order' => 12])
+			->addElement('ckeditor', 'contenu')
+			->addElement('ckeditor', 'description')
+			->addElement('textarea', 'tags',
+									 ['label' => $this->_('Tags') . '<br><span style="font-weight:normal;font-size:80%">'
+										. $this->_('Entrez la liste des mots-clefs et expressions qui caractérisent votre article séparés par ;') . '</span>',
+										'rows' => 4,
+										'cols' => 51])
+			->addElement('checkbox', 'avis',
+									 ['label' => $this->_('Autoriser les commentaires d\'internautes (Mode blog) ?')])
+			->addElement('checkbox', 'indexation',
+									 ['label' => $this->_('Indexer l\'article dans le catalogue ?')])
+
+			->addDisplayGroup([
+												 'titre',
+												 'cacher_titre',
+												 'auteur',
+												 'id_cat',
+												 'publication_range',
+												 'event_range',
+												 'all_day',
+												 'id_lieu'
+												 ],
+												'publication',
+												['legend' => $this->_('Publication')])
+			->addDisplayGroup(['contenu'],
+												'article',
+												['legend' => $this->_('Article')])
+			->addDisplayGroup(['description'],
+												'article_for_widget',
+												['legend' => $this->_('Résumé pour l\'affichage dans les boîtes')])
+
+			->addDisplayGroup(['tags',
+												 'avis',
+												 'indexation'],
+												'options',
+												['legend' => $this->_('Options')])
+
+			->addDomainIndexation();
+
+		$this->tags->getDecorator('label')->setOption('escape', false);
+	}
 
 
 	protected function addAuthorFullName($id_user) {
@@ -138,7 +147,7 @@ class ZendAfi_Form_Admin_News extends ZendAfi_Form {
 	}
 
 
-	protected function addWorkflow($status) {
+	protected function addWorkflow($status, $id_cat) {
 		if(!Class_AdminVar::isWorkFlowEnabled())
 			return $this;
 
@@ -156,12 +165,29 @@ class ZendAfi_Form_Admin_News extends ZendAfi_Form {
 											 'rows' => 5,
 											 'style' => 'display:none',
 											 'placeholder' => $this->_('Message d\'explication du refus.')]);
-		$this->getElement('refus_message')->addDecorator('JqueryReady', ['script' => $this->getRejectCommentScript()]);
+		$this->getElement('refus_message')
+				 ->addDecorator('JqueryReady', ['script' => $this->getRejectCommentScript()]);
 
 		$status = $this->getElement('status');
-		$user = Class_Users::getLoader()->getIdentity();
-		if ($user->isRedacteur() &&  !$user->hasRightToAccess(Class_UserGroup::RIGHT_USER_PUBLICATION_DIRECTE))
-			$status->setOptions(['disable' => [Class_Article::STATUS_VALIDATED]]);
+		$user = Class_Users::getIdentity();
+		$category = Class_ArticleCategorie::find($id_cat);
+		$disabled = [];
+		if ($user->isRedacteur()
+				&& !$user->hasRightToAccess(Class_UserGroup::RIGHT_USER_PUBLICATION_DIRECTE)
+				&& !$user->hasPermissionOn(Class_Permission::getWorkflow(Class_Article::STATUS_VALIDATED),
+																	 $category))
+			$disabled[] = Class_Article::STATUS_VALIDATED;
+
+		foreach(array_keys($status->getMultiOptions()) as $k) {
+			if (1 == $k) // first status is always permitted
+				continue;
+
+			if (!$id_cat || !$user->hasPermissionOn(Class_Permission::getWorkflow($k),
+																							$category))
+				$disabled[] = $k;
+		}
+
+		$status->setOptions(['disable' => $disabled]);
 
 		return $this->updateDisplayGroup('publication', ['status', 'refus_message']);
 	}
diff --git a/library/ZendAfi/Form/Admin/UserGroup.php b/library/ZendAfi/Form/Admin/UserGroup.php
index e1c7cf62d53dfec0ffdee3137ea69dedd212a300..9b38d306a28f2db604d2fbc8444fa8e058aef70b 100644
--- a/library/ZendAfi/Form/Admin/UserGroup.php
+++ b/library/ZendAfi/Form/Admin/UserGroup.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
  */
 
 
@@ -40,13 +40,16 @@ class ZendAfi_Form_Admin_UserGroup extends ZendAfi_Form {
 									 ['label' => $this->_('Rôle'),
 										'multiOptions' => ZendAfi_Acl_AdminControllerRoles::getListeRolesWithoutSuperAdmin()] )
 
-			->addDisplayGroup(['libelle', 'group_type'], 
-												'usergroup', 
+			->addElement('comboLibraries', 'id_bib',
+									 ['label' => $this->_('Bibliothèque')])
+
+			->addDisplayGroup(['libelle', 'group_type'],
+												'usergroup',
 												['legend' => $this->_('Groupe')])
 
-			->addDisplayGroup(['role_level'], 
-												'dynamic_filter', 
-												['legend' => $this->_('Filtre')]);		
+			->addDisplayGroup(['role_level', 'id_bib'],
+												'dynamic_filter',
+												['legend' => $this->_('Filtre')]);
 
 		$this->displayGroupFiltreVisibleOnlyOnDynamicGroup();
 
@@ -58,18 +61,17 @@ class ZendAfi_Form_Admin_UserGroup extends ZendAfi_Form {
 			->addElement('multiCheckbox', 'rights', ['label' => '', 'multiOptions' => $rights])
 			->addDisplayGroup(['rights'], 'rights_group', ['legend' => $this->_('Droits')]);
 
-
 		if (Class_AdminVar::isMultimediaEnabled()) {
 			$this->addRequiredTextNamed('max_day')
 				->setLabel('Par jour *')
 				->setValue(0)
 				->setValidators(['Digits']);
-			
+
 			$this->addRequiredTextNamed('max_week')
 				->setLabel('Par semaine *')
 				->setValue(0)
 				->setValidators([
-					'Digits', 
+					'Digits',
 					new ZendAfi_Validate_FieldsGreater(['max_day' => 'Par jour'], true)]);
 
 			$this->addRequiredTextNamed('max_month')
diff --git a/library/ZendAfi/Form/Album.php b/library/ZendAfi/Form/Album.php
index c6d68562242bd059ffe6b46e35c11499eb638f85..c3d7f4f6adc6996e213a71711f8bd133a8bfcf57 100644
--- a/library/ZendAfi/Form/Album.php
+++ b/library/ZendAfi/Form/Album.php
@@ -16,13 +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_Form_Album extends ZendAfi_Form {
+	const RIGHT_PUBLIC_DOMAIN_KEY = 1;
+	const RIGHT_OTHER_KEY = 2;
 	const RIGHT_PUBLIC_DOMAIN = 'Domaine public';
 
-	public static function newWith($album) {
+	public static function newWith($album = [], $custom_form = null) {
 		$form = new self();
 
 		$form
@@ -34,7 +36,8 @@ class ZendAfi_Form_Album extends ZendAfi_Form {
 			->addVignetteFor($album)
 			->addFileFor($album)
 			->addAffichageFor($album)
-			->addDisplayGroup(['titre', 
+			->addFrbrLinkFor($album)
+			->addDisplayGroup(['titre',
 												 'sous_titre',
 												 'cat_id',
 												 'type_doc_id',
@@ -43,17 +46,17 @@ class ZendAfi_Form_Album extends ZendAfi_Form {
 												 'pdf',
 												 'authors',
 												 'editors',
-												 'collections'], 
-				                 'album', 
+												 'collections'],
+				                 'album',
 				                 ['legend' => $form->_('Album')])
 
 			->addDisplayGroup(['description'],
-												 'album_desc', 
+												 'album_desc',
 				                 ['legend' => $form->_('Description')])
 
 			->addDisplayGroup(['droits',
 					               'droits_precision',
-												 'annee', 
+												 'annee',
 												 'cote',
 												 'provenance',
 												 'distributor',
@@ -65,14 +68,18 @@ class ZendAfi_Form_Album extends ZendAfi_Form {
 												 'dewey',
 												 'genre',
 												 'tags'],
-												 'album_metadata', 
+												 'album_metadata',
 				                 ['legend' => $form->_('Metadonnées')])
 
 			->addDisplayGroup(['bibliotheques',
 												 'annexes',
 												 'sections'],
 												'album_display',
-												['legend' => $form->_('Affichage')]);
+												['legend' => $form->_('Affichage')])
+
+			->addDisplayGroup(['frbr_multi'],
+												'frbr',
+												['legend' => $form->_('FRBR')]);
 
 		return $form;
 	}
@@ -117,25 +124,26 @@ class ZendAfi_Form_Album extends ZendAfi_Form {
 	public function init() {
 		parent::init();
 		Class_ScriptLoader::getInstance()
-				->addInlineScript('function toggleAlbumRights() {
+			->addInlineScript('function toggleAlbumRights() {
 													   var target = $("#droits_precision").parent().parent();
-													   (2 == $("input[name=\'droits\']:checked").val()) ?
+													   (' . self::RIGHT_OTHER_KEY . ' == $("input[name=\'droits\']:checked").val()) ?
 													     target.show() :
 													     target.hide();
 													 }')
-				->addJQueryReady('$("input[name=\'droits\']").change(toggleAlbumRights);
+			->addJQueryReady('$("input[name=\'droits\']").change(toggleAlbumRights);
 													toggleAlbumRights();');
-						
+
 		$this
 			->setAttrib('id', 'album')
 			->setAttrib('enctype', self::ENCTYPE_MULTIPART)
 			->addElement('text', 'titre', ['label'			=> $this->_('Titre *'),
-																		 'size'				=> 75,	
+																		 'style'				=> 'width:440px;',
+																		 'size'				=> 75,
 																		 'required'		=> true,
 																		 'allowEmpty'	=> false])
 
 			->addElement('text', 'sous_titre', ['label'			=> $this->_('Sous-titre'),
-																					'size'				=> 75])
+																					'style'				=> 'width:440px;'])
 
 			->addElement('select', 'cat_id', ['label' => $this->_('Catégorie'),
 																				'multiOptions' => Class_AlbumCategorie::getAllLibelles()])
@@ -144,30 +152,32 @@ class ZendAfi_Form_Album extends ZendAfi_Form {
 
 			->addElement('ckeditor', 'description')
 
-			->addElement('text', 'annee', ['label' => $this->_("Année d'édition"), 
-																		 'size' => 4, 
+			->addElement('text', 'annee', ['label' => $this->_("Année d'édition"),
+																		 'size' => 4,
 																		 'maxlength' => 4])
 
-			->addElement('text', 'cote', ['label' => $this->_('Cote'), 
+			->addElement('text', 'cote', ['label' => $this->_('Cote'),
 																		'size' => 20])
 
-			->addElement('text', 'provenance', ['label' => $this->_('Provenance'), 
+			->addElement('text', 'provenance', ['label' => $this->_('Provenance'),
+																					'style'	=> 'width:440px;',
 																					'size' => 75])
 
-			->addElement('text', 'duration', ['label' => $this->_('Durée totale'), 
+			->addElement('text', 'duration', ['label' => $this->_('Durée totale'),
 																				'size' => 8])
 
-			->addElement('select', 'id_langue', ['label' => $this->_('Langue'), 
+			->addElement('select', 'id_langue', ['label' => $this->_('Langue'),
 																					 'multioptions' => Class_CodifLangue::allByIdLibelle()])
 
-			->addElement('select', 'type_doc_id', ['label' => $this->_('Type de document'), 
+			->addElement('select', 'type_doc_id', ['label' => $this->_('Type de document'),
 					                                   'multioptions' => Class_TypeDoc::allByIdLabelForAlbum()])
 
 			->addElement('cochesSuggestion', 'nature_doc', ['label' => $this->_('Nature de document'),
 																											'name' => 'nature_doc',
 																											'rubrique' => 'nature_doc'])
 
-			->addElement('text', 'distributor', ['label' => $this->_('Distributeur'), 
+			->addElement('text', 'distributor', ['label' => $this->_('Distributeur'),
+																					 'style'				=> 'width:440px;',
 																					 'size' => 75])
 
 
@@ -189,9 +199,10 @@ class ZendAfi_Form_Album extends ZendAfi_Form {
 																								 'rubrique' => 'genre'])
 
 			->addElement('textarea', 'tags', ['label' => $this->_('Tags'),
+																			  'style' => 'width:440px;',
 					                              'rows' => 2])
 
-			->addElement('multiInput', 'authors', 
+			->addElement('multiInput', 'authors',
 									 ['label' => $this->_('Auteurs'),
 										'fields' => [['name' => 'author', 'label' => $this->_('Nom')],
 																 ['name' => 'fonction', 'label' => $this->_('Fonction'),
@@ -199,17 +210,34 @@ class ZendAfi_Form_Album extends ZendAfi_Form {
 										'values' => ['author' => [], 'fonction' => []],
 										'deleteMessage' => 'cet auteur'])
 
-			->addElement('multiInput', 'editors', 
+			->addElement('multiInput', 'editors',
 									 ['label' => $this->_('Editeurs'),
 										'fields' => [['name' => 'editor', 'label' => $this->_('Nom')]	],
-									 'values' => ['editor' => []],
-									 'deleteMessage' => ' this editor'])
+										'values' => ['editor' => []],
+										'deleteMessage' => ' this editor'])
 
-			->addElement('multiInput', 'collections', 
+			->addElement('multiInput', 'collections',
 									 ['label' => $this->_('Collections'),
 										'fields' => [['name' => 'collection', 'label' => $this->_('Nom')]	],
 										'values' => ['collection' => []],
-										'deleteMessage' => ' this collection']);
+										'deleteMessage' => ' this collection'])
+
+			->addElement('multiInput', 'frbr_multi',
+									 ['label' => $this->_(''),
+										'fields' => [['name' => 'frbr_type',
+																	'label' => $this->_('Type de lien'),
+																	'attribs' => ['style' => 'width:180px;'],
+																	'type' => 'select',
+																	'options' => ['' => $this->_('Aucun')] + Class_FRBR_LinkType::getSourceTargetComboList()],
+																 ['name' => 'frbr_url',
+																	'label' => $this->_('Permalien de la notice'),
+																	'attribs' => ['style' => 'width:450px;']]],
+										'values' => ['frbr_type' => [], 'frbr_url' => []],
+										'deleteMessage' => 'ce lien FRBR']);
+
+		$this->frbr_multi
+			->setValidators([new ZendAfi_Validate_MultiAllOrNothing($this->frbr_multi)])
+			->setFieldsValidators(['frbr_url' => [new ZendAfi_Validate_LocalRecordUrl()]]);
 	}
 
 
@@ -219,13 +247,13 @@ class ZendAfi_Form_Album extends ZendAfi_Form {
 	 */
 	public function addVignetteFor($album) {
 		$vignette_element = new ZendAfi_Form_Element_Image(
-				'fichier',
-				['label'			=> 'Vignette<br/><em style="font-size:80%;font-weight:normal">(jpg, gif, png)</em>',
-					'escape'    => false,
-					'basePath'	=> $album->getBasePath(),
-					'baseUrl'		=> $album->getBaseUrl(),
-					'thumbnailUrl' => $album->getThumbnailUrl(),
-					'actionUrl'	=> $this->getView()->url(['action' => 'album-delete-vignette'])]);
+																											 'fichier',
+																											 ['label'			=> 'Vignette<br/><em style="font-size:80%;font-weight:normal">(jpg, gif, png)</em>',
+																												'escape'    => false,
+																												'basePath'	=> $album->getBasePath(),
+																												'baseUrl'		=> $album->getBaseUrl(),
+																												'thumbnailUrl' => $album->getThumbnailUrl(),
+																												'actionUrl'	=> $this->getView()->url(['action' => 'album-delete-vignette'])]);
 
 		$vignette_element
 			->getDecorator('label')
@@ -241,11 +269,11 @@ class ZendAfi_Form_Album extends ZendAfi_Form {
 	 */
 	public function addFileFor($album) {
 		return $this->addElement(new ZendAfi_Form_Element_File('pdf',
-				[ 'label'			=> 'Album PDF',
-					'escape'    => false,
-					'basePath'	=> $album->getBasePath(),
-					'baseUrl'		=> $album->getBaseUrl(),
-					'actionUrl'	=> $this->getView()->url(['action' => 'album-delete-pdf'])]));
+																													 [ 'label'			=> 'Album PDF',
+																														'escape'    => false,
+																														'basePath'	=> $album->getBasePath(),
+																														'baseUrl'		=> $album->getBaseUrl(),
+																														'actionUrl'	=> $this->getView()->url(['action' => 'album-delete-pdf'])]));
 	}
 
 
@@ -256,30 +284,30 @@ class ZendAfi_Form_Album extends ZendAfi_Form {
 	public function addRightsFor($album) {
 		$current = (self::RIGHT_PUBLIC_DOMAIN == $album->getDroits() || $album->isNew()) ? 1: 2;
 		$current_precision = (self::RIGHT_PUBLIC_DOMAIN != $album->getDroits()) ?
-				$album->getDroits(): '';
+			$album->getDroits(): '';
 
 		return $this
-				->addElement('radio', 'droits',
-					['label' => 'Droits',
-						'value' => $current,
-						'separator' => '',
-						'multiOptions' => [1 => self::RIGHT_PUBLIC_DOMAIN,
-							                 2 => 'Autre, Précisez']])
-
-				->addElement('text', 'droits_precision',
-										 ['label' => '', 
-											'value' => $current_precision, 
-											'size' => 75]);
+			->addElement('radio', 'droits',
+									 ['label' => 'Droits',
+										'value' => $current,
+										'separator' => '',
+										'multiOptions' => [self::RIGHT_PUBLIC_DOMAIN_KEY => self::RIGHT_PUBLIC_DOMAIN,
+																			 self::RIGHT_OTHER_KEY => 'Autre, Précisez']])
+
+			->addElement('text', 'droits_precision',
+									 ['label' => '',
+										'value' => $current_precision,
+										'style'	=> 'width:440px;']);
 	}
-	
+
 	public function addAffichageFor($album) {
 		return $this
 			->addElement('cochesSuggestion', 'bibliotheques',
-														 ['label' => 'Bibliotheques',
-															'name' => 'bibliotheques',
-															'rubrique' => 'bibliotheque',
-															'value' => $album->getBibliotheques(),
-															'selected_all_means_nothing' => false])
+									 ['label' => 'Bibliotheques',
+										'name' => 'bibliotheques',
+										'rubrique' => 'bibliotheque',
+										'value' => $album->getBibliotheques(),
+										'selected_all_means_nothing' => false])
 			->addElement('cochesSuggestion', 'annexes', ['label' => 'Sites',
 																									 'name' => 'annexes',
 																									 'rubrique' => 'annexe',
@@ -294,6 +322,35 @@ class ZendAfi_Form_Album extends ZendAfi_Form {
 
 
 	}
+
+
+	public function addFrbrLinkFor($album) {
+		$descs = $values = ['frbr_type' => [], 'frbr_url' => []];
+		foreach (Class_FRBR_Link::findAllRecordLinksForAlbum($album) as $link) {
+			$record_type = (Class_FRBR_Link::TYPE_NOTICE == $link->getSourceType()) ? 'Source' : 'Target';
+			$album_type = 'Source' == $record_type ? 'target' : 'source';
+
+			$values['frbr_url'][] = $link->{'get' . $record_type}();
+			$values['frbr_type'][] = $link->getType()->getId() . ':' . $album_type;
+			$descs['frbr_type'][] = '';
+			$descs['frbr_url'][] = $this->getView()->frbrLabel($link, $record_type);
+		}
+		$this->frbr_multi
+			->setValues($values)
+			->setDescs($descs);
+
+		return $this;
+	}
+
+
+	public function isPublicDomain() {
+		return self::RIGHT_PUBLIC_DOMAIN == $this->droits->getValue();
+	}
+
+
+	public function getPublicDomain() {
+		return self::RIGHT_PUBLIC_DOMAIN;
+	}
 }
 
 ?>
\ No newline at end of file
diff --git a/library/ZendAfi/Form/Album/Ressource.php b/library/ZendAfi/Form/Album/Ressource.php
index a67e6eba2d1cb9c18860365d4484e570236caf9d..cba3053e1aecfed43bc869d27c05fbf40a04795a 100644
--- a/library/ZendAfi/Form/Album/Ressource.php
+++ b/library/ZendAfi/Form/Album/Ressource.php
@@ -16,21 +16,19 @@
  *
  * 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_Form_Album_Ressource extends ZendAfi_Form {
-	use Trait_Translator;
-
 	/**
 	 * @param $model Class_AlbumRessource
 	 * @return ZendAfi_Form_Album_Ressource
 	 */
-	public static function newWith($model) {
+	public static function newWith($datas = [], $custom_form = null) {
 		$form = new self();
-
+		$model = $datas;
 		$form
-			->populate($model->toArray())
+			->populate($datas->toArray())
 			->addAuthorsFor($model)
 			->addDurationFor($model)
 			->addFileFor($model)
@@ -44,7 +42,7 @@ class ZendAfi_Form_Album_Ressource extends ZendAfi_Form {
 												'ressource',
 												['legend' => 'Media'])
 
-			->addDisplayGroup(['authors'], 
+			->addDisplayGroup(['authors'],
 												'authors_editors',
 												['legend' => $form->_('Auteurs')])
 
@@ -57,11 +55,11 @@ class ZendAfi_Form_Album_Ressource extends ZendAfi_Form {
 			  and !$album->isLivreNumerique())
 			$form->removeElement('folio');
 
-		
+
 		return $form;
 	}
 
-		
+
 	public function init() {
 		parent::init();
 		$this
@@ -77,9 +75,9 @@ class ZendAfi_Form_Album_Ressource extends ZendAfi_Form {
 					 'separator' => '',
 					 'multioptions' => [1 => 'Image', 2 => 'Autre fichier', 3 => 'Média en ligne'],
 					 'value' => 1])
-				
+
 			->addElement('url', 'url', ['label' => 'Url *', 'size' => '80'])
-				
+
 			->addElement('url', 'link_to', ['label' => 'Lien vers', 'size' => '80'])
 
 			->addElement('ckeditor', 'description')
@@ -87,10 +85,10 @@ class ZendAfi_Form_Album_Ressource extends ZendAfi_Form {
 			->addElement('listeSuggestion', 'matiere', ['label' => 'Matières / sujets',
 																									'name' => 'matiere',
 					                                        'rubrique' => 'matiere'])
-			->addElement('text', 'duration', ['label' => 'Durée', 
+			->addElement('text', 'duration', ['label' => 'Durée',
 																				'size' => 8])
 
-			->addElement('multiInput', 'authors', 
+			->addElement('multiInput', 'authors',
 									 ['label' => 'Auteurs',
 										'fields' => [['name' => 'author', 'label' => 'Nom'],
 																 ['name' => 'fonction', 'label' => 'Fonction',
@@ -150,7 +148,7 @@ class ZendAfi_Form_Album_Ressource extends ZendAfi_Form {
 		return $this->addElement($element);
 	}
 
-		
+
 
 	public function addJs() {
 		Class_ScriptLoader::getInstance()
diff --git a/library/ZendAfi/Form/Configuration/FormationsWidget.php b/library/ZendAfi/Form/Configuration/FormationsWidget.php
index cf5950347b4f51719a36c7bbd261dccfa9cef7d5..d28c0d01e513534195a377f2eb718a577c164654 100644
--- a/library/ZendAfi/Form/Configuration/FormationsWidget.php
+++ b/library/ZendAfi/Form/Configuration/FormationsWidget.php
@@ -23,9 +23,12 @@
 class ZendAfi_Form_Configuration_FormationsWidget extends ZendAfi_Form {
 	public static function newWith($module_settings = [], $custom_form = null) {
 		$form = new self();
+
+		$selected_formations = array_key_exists('selected_formations', $module_settings) ? $module_settings['selected_formations'] : '';
+
 		$form
 			->populate($module_settings)
-			->addToWith('formations_selector', $module_settings['selected_formations'])
+			->addToWith('formations_selector', $selected_formations)
 			->addDisplayGroup(['titre',
 												 'boite'],
 												'Base',
diff --git a/library/ZendAfi/Form/Decorator/ComboCategories.php b/library/ZendAfi/Form/Decorator/ComboCategories.php
index e6b875521f3c3b6bf5150ee7f0dbc32409ff01d8..1503e2537e107e8c5ec1275354cc507058fcad08 100644
--- a/library/ZendAfi/Form/Decorator/ComboCategories.php
+++ b/library/ZendAfi/Form/Decorator/ComboCategories.php
@@ -23,7 +23,9 @@
 class ZendAfi_Form_Decorator_ComboCategories extends Zend_Form_Decorator_Abstract {
 	public function render($content) {
 		return $content
-			.$this->_element->getView()->comboCategories($this->_element->getCategory());
+			.$this->_element->getView()
+											->comboCategories($this->_element->getCategory(),
+																				$this->_element->categories_filter);
 	}
 }
 ?>
\ No newline at end of file
diff --git a/library/ZendAfi/Form/Decorator/ComboLibraries.php b/library/ZendAfi/Form/Decorator/ComboLibraries.php
new file mode 100644
index 0000000000000000000000000000000000000000..1659012dc0dd8c02a851fc7a2854eecdd2b165f8
--- /dev/null
+++ b/library/ZendAfi/Form/Decorator/ComboLibraries.php
@@ -0,0 +1,30 @@
+<?php
+/**
+ * Copyright (c) 2012-2014, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * AFI-OPAC 2.0 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).
+ *
+ * AFI-OPAC 2.0 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 AFI-OPAC 2.0; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class ZendAfi_Form_Decorator_ComboLibraries extends Zend_Form_Decorator_Abstract {
+	public function render($content) {
+		return $content
+			.$this->_element->getView()
+											->comboLibraries($this->_element->getLibrary());
+	}
+}
+?>
\ No newline at end of file
diff --git a/library/ZendAfi/Form/Decorator/ComboParentCategory.php b/library/ZendAfi/Form/Decorator/ComboParentCategory.php
new file mode 100644
index 0000000000000000000000000000000000000000..f5c1df571d6a24d35dd6db55d6273d440dea2631
--- /dev/null
+++ b/library/ZendAfi/Form/Decorator/ComboParentCategory.php
@@ -0,0 +1,32 @@
+<?php
+/**
+ * Copyright (c) 2012-2014, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * AFI-OPAC 2.0 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).
+ *
+ * AFI-OPAC 2.0 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 AFI-OPAC 2.0; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class ZendAfi_Form_Decorator_ComboParentCategory extends Zend_Form_Decorator_Abstract {
+	public function render($content) {
+		return $content
+			. $this->_element->getView()
+											 ->comboParentCategorie($this->_element->getChildCategory(),
+																							$this->_element->categories_filter,
+																							$this->_element->disable_none);
+	}
+}
+?>
\ No newline at end of file
diff --git a/library/ZendAfi/Form/Decorator/MultiInput.php b/library/ZendAfi/Form/Decorator/MultiInput.php
index 4512a28827ec718cb2d4904e198ddbe90afd4e13..79f31e9d24e4438e1035c47b6d3faf2778e05818 100644
--- a/library/ZendAfi/Form/Decorator/MultiInput.php
+++ b/library/ZendAfi/Form/Decorator/MultiInput.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 ZendAfi_Form_Decorator_MultiInput extends Zend_Form_Decorator_Abstract {
 	/**
@@ -32,8 +32,10 @@ class ZendAfi_Form_Decorator_MultiInput extends Zend_Form_Decorator_Abstract {
 		return $content
 			. ' <div id="multi_inputs_' . $this->_element->getId() . '"></div>
 <script type="text/javascript">$("#multi_inputs_' . $this->_element->getId()  .'").multi_inputs({
-fields:' . json_encode($this->_element->getFields()) . ', 
+fields:' . json_encode($this->_element->getFields()) . ',
 values:' . json_encode($this->_element->getValues()) . ',
+line_errors:' . json_encode($this->_element->getLineErrors()) . ',
+descs:' . json_encode($this->_element->getDescs()) . ',
 "delete_message":'. json_encode($this->_element->getDeleteMessage()) . '});
 </script>';
 	}
diff --git a/library/ZendAfi/Form/Element/ComboLibraries.php b/library/ZendAfi/Form/Element/ComboLibraries.php
new file mode 100644
index 0000000000000000000000000000000000000000..5bf7510b0900cacfa5608c06d5f879cb39182618
--- /dev/null
+++ b/library/ZendAfi/Form/Element/ComboLibraries.php
@@ -0,0 +1,41 @@
+<?php
+/**
+ * Copyright (c) 2012-2014, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * AFI-OPAC 2.0 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).
+ *
+ * AFI-OPAC 2.0 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 AFI-OPAC 2.0; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class ZendAfi_Form_Element_ComboLibraries extends Zend_Form_Element {
+	public function __construct($spec, $options = null) {
+		parent::__construct($spec, $options);
+		$decorators = $this->_decorators;
+		$this->_decorators =
+			['ComboLibraries' => new ZendAfi_Form_Decorator_ComboLibraries()];
+
+		foreach ($decorators as $name => $value)
+			$this->_decorators[$name] = $value;
+
+		$this->removeDecorator('ViewHelper');
+	}
+
+
+	public function getLibrary() {
+		return Class_Bib::find($this->getValue());
+	}
+}
+?>
\ No newline at end of file
diff --git a/library/ZendAfi/Form/Element/ComboParentCategory.php b/library/ZendAfi/Form/Element/ComboParentCategory.php
new file mode 100644
index 0000000000000000000000000000000000000000..b83b33a72e7509e0fcabb4c21b57ca1c73059f4f
--- /dev/null
+++ b/library/ZendAfi/Form/Element/ComboParentCategory.php
@@ -0,0 +1,45 @@
+<?php
+/**
+ * Copyright (c) 2012-2014, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * AFI-OPAC 2.0 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).
+ *
+ * AFI-OPAC 2.0 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 AFI-OPAC 2.0; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class ZendAfi_Form_Element_ComboParentCategory extends Zend_Form_Element {
+	public function __construct($spec, $options = null) {
+		parent::__construct($spec, $options);
+		$decorators = $this->_decorators;
+		$this->_decorators = ['ComboCategories' => new ZendAfi_Form_Decorator_ComboParentCategory()];
+
+		foreach ($decorators as $name => $value) {
+			$this->_decorators[$name] = $value;
+		}
+
+		$this->removeDecorator('ViewHelper');
+	}
+
+
+	public function getCategory() {
+		return Class_ArticleCategorie::find($this->getValue());
+	}
+
+	public function getChildCategory() {
+		return Class_ArticleCategorie::find($this->categorie);
+	}
+}
+?>
\ No newline at end of file
diff --git a/library/ZendAfi/Form/Element/MultiInput.php b/library/ZendAfi/Form/Element/MultiInput.php
index 6b1a4babdb218e16ebbd99e3de4bb9d128c0ee6b..1bf5eff5218216935efed0c413955c94b297533b 100644
--- a/library/ZendAfi/Form/Element/MultiInput.php
+++ b/library/ZendAfi/Form/Element/MultiInput.php
@@ -16,13 +16,17 @@
  *
  * 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_Form_Element_MultiInput extends Zend_Form_Element {
 	protected $_fields = [];
 	protected $_values = [];
 	protected $_deleteMessage = '';
+	protected $_fields_validators = [];
+	protected $_line_errors = [];
+	protected $_descs = [];
+
 
 	public function __construct($spec, $options=null) {
 		parent::__construct($spec, $options);
@@ -34,7 +38,7 @@ class ZendAfi_Form_Element_MultiInput extends Zend_Form_Element {
 		$this->removeDecorator('ViewHelper');
 	}
 
-	
+
 	/**
 	 * element is composite, receive its values in datas array
 	 * @return boolean
@@ -42,7 +46,49 @@ class ZendAfi_Form_Element_MultiInput extends Zend_Form_Element {
 	public function isValid($value, $datas=null) {
 		foreach ($this->getFields() as $field)
 			$this->_values[$field['name']] = (isset($datas[$field['name']])) ? $datas[$field['name']] : [];
-		return true;
+
+		if (!$result = parent::isValid($value, $datas))
+			return false;
+
+		return $this->validateFields($datas);
+	}
+
+
+	protected function validateFields($datas) {
+		$valid = true;
+		foreach($this->getFieldsValidators() as $field_name => $validators)
+			$valid = $valid && $this->_validateField($field_name, $validators, $datas);
+
+		return $valid;
+	}
+
+
+	protected function _validateField($name, $validators, $datas) {
+		$valid = true;
+		foreach($this->_values[$name] as $k => $value)
+			$valid = $valid && $this->_validateValue($value, $k, $validators, $datas);
+
+		return $valid;
+	}
+
+
+	protected function _validateValue($value, $value_index, $validators, $datas) {
+		$valid = true;
+		foreach($validators as $validator) {
+			if ($validator->isValid($value, $datas))
+				continue;
+
+			$valid = false;
+			$this->_collectValidationMessagesFor($value_index, $validator);
+		}
+
+		return $valid;
+	}
+
+
+	protected function _collectValidationMessagesFor($line, $validator) {
+		foreach($validator->getMessages() as $message)
+			$this->addLineError($line, $message);
 	}
 
 
@@ -84,4 +130,39 @@ class ZendAfi_Form_Element_MultiInput extends Zend_Form_Element {
 	public function getDeleteMessage() {
 		return $this->_deleteMessage;
 	}
+
+
+	public function setFieldsValidators($validators) {
+		$this->_fields_validators = $validators;
+		return $this;
+	}
+
+
+	public function getFieldsValidators() {
+		return $this->_fields_validators;
+	}
+
+
+	public function addLineError($line, $message) {
+		if (!isset($this->_line_errors[$line]))
+			$this->_line_errors[$line] = [];
+		$this->_line_errors[$line][] = $message;
+		return $this;
+	}
+
+
+	public function getLineErrors() {
+		return $this->_line_errors;
+	}
+
+
+	public function setDescs($descs) {
+		$this->_descs = $descs;
+		return $this;
+	}
+
+
+	public function getDescs() {
+		return $this->_descs;
+	}
 }
\ No newline at end of file
diff --git a/library/ZendAfi/Form/FRBR/Link.php b/library/ZendAfi/Form/FRBR/Link.php
index a33da6a0382c93e74989238752425da46d2bf382..67f2203ed6172a337aa5eb7acf948dbc42b14a07 100644
--- a/library/ZendAfi/Form/FRBR/Link.php
+++ b/library/ZendAfi/Form/FRBR/Link.php
@@ -16,18 +16,16 @@
  *
  * 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_Form_FRBR_Link extends ZendAfi_Form {
-	use Trait_Translator;
-
 	public function init() {
 		parent::init();
 		$this
 			->setAttrib('id', 'frbr_link')
 			->setAttrib('class', 'zend_form')
-			
+
 			->addElement('frbrType', 'type_id', ['label' => $this->_('Type').' *'])
 			->addElement('text', 'source', ['label' => $this->_('URL Objet A') . ' *', 'size' => 80])
 			->addElement('text', 'target', ['label' => $this->_('URL Objet B') . ' *', 'size' => 80])
diff --git a/library/ZendAfi/Form/FRBR/LinkType.php b/library/ZendAfi/Form/FRBR/LinkType.php
index ac5c860925a94165aba091d5651c02abf6c30cbb..8500b3d9fe6f5cc1214ab9d83bc74eecea858f34 100644
--- a/library/ZendAfi/Form/FRBR/LinkType.php
+++ b/library/ZendAfi/Form/FRBR/LinkType.php
@@ -16,18 +16,16 @@
  *
  * 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_Form_FRBR_LinkType extends ZendAfi_Form {
-	use Trait_Translator;
-
 	public function init() {
 		parent::init();
 		$this
 			->setAttrib('id', 'frbr_linktype')
 			->setAttrib('class', 'zend_form')
-			
+
 			->addElement('text', 'libelle', ['label' => $this->_('Nom').' *', 'size' => 80])
 			->addElement('text', 'from_source', ['label' => $this->_('Libellé de l\'objet A vers l\'objet B') . ' *',
 					                                 'size' => 80])
diff --git a/library/ZendAfi/Validate/LocalRecordUrl.php b/library/ZendAfi/Validate/LocalRecordUrl.php
index d6c508439f9786da633bb822237c279fffbccdf8..a74ee14ecb330b087be226ab873899fb55491ab7 100644
--- a/library/ZendAfi/Validate/LocalRecordUrl.php
+++ b/library/ZendAfi/Validate/LocalRecordUrl.php
@@ -16,17 +16,20 @@
  *
  * 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_Validate_LocalRecordUrl extends Zend_Validate_Abstract {
-	const INVALID_URL = 'invalidUrl';	
+	const INVALID_URL = 'invalidUrl';
 	protected $_messageTemplates = array(self::INVALID_URL   => "'%value%' ne correspond pas à un permalien de notice");
 	protected $_record;
 
 
 	public function isValid($value) {
+		if ('' == trim($value))
+			return true;
+
 		$this->_setValue((string)$value);
 
 		if ($record = Class_Notice::findByUrl($value)) {
diff --git a/library/ZendAfi/Validate/MultiAllOrNothing.php b/library/ZendAfi/Validate/MultiAllOrNothing.php
new file mode 100644
index 0000000000000000000000000000000000000000..96fb79037b451e75b6c6729e7190ae81f5b9509f
--- /dev/null
+++ b/library/ZendAfi/Validate/MultiAllOrNothing.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_Validate_MultiAllOrNothing extends Zend_Validate_Abstract {
+	use Trait_Translator;
+
+	protected $_multi_input;
+	protected $_fields_count = 0;
+	protected $_lines = [];
+
+	public function __construct($multi_input) {
+		$this->_multi_input = $multi_input;
+	}
+
+
+	public function isValid($value, $context=null) {
+		$values = $this->_multi_input->getValues();
+		$this->_fields_count = count(array_keys($values));
+		$this->_organizeByLines($values);
+
+		$result = true;
+		foreach($this->_lines as $k => $line)
+			$result = $result && $this->_validateLineAt($line, $k);
+
+		return $result;
+	}
+
+
+	protected function _validateLineAt($line, $index) {
+		$count = count($line);
+		if (0 < $count && $this->_fields_count != $count) {
+			$this->_multi_input
+				->addLineError($index, $this->_("Un champ de cette ligne ne peut être vide lorsqu'un des autres champs est renseigné"));
+				return false;
+		}
+
+		return true;
+	}
+
+
+	protected function _organizeByLines($values) {
+		foreach($values as $field_name => $field_values)
+			$this->_dispatchValuesInLines($field_values);
+	}
+
+
+	protected function _dispatchValuesInLines($values) {
+		foreach($values as $k => $value) {
+			$this->_ensureLineAt($k);
+			$this->_insertNonEmptyValueAt($value, $k);
+		}
+	}
+
+
+	protected function _ensureLineAt($index) {
+		if (!isset($this->_lines[$index]))
+			$this->_lines[$index] = [];
+	}
+
+
+	protected function _insertNonEmptyValueAt($value, $index) {
+		if ('' != trim($value))
+			$this->_lines[$index][] = $value;
+	}
+}
+?>
\ No newline at end of file
diff --git a/library/ZendAfi/Validate/RequiredWith.php b/library/ZendAfi/Validate/RequiredWith.php
new file mode 100644
index 0000000000000000000000000000000000000000..9a1769e073fd3093022fd89bad3d6ec6f3946a9b
--- /dev/null
+++ b/library/ZendAfi/Validate/RequiredWith.php
@@ -0,0 +1,77 @@
+<?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_Validate_RequiredWith extends Zend_Validate_Abstract {
+ 	const NOT_ALONE = 'notAlone';
+
+	protected $_messageVariables = [
+		'fieldToCompare' => '_field_to_compare_label',
+	];
+
+	protected $_messageTemplates = array(
+		self::NOT_ALONE => "Ce champ ne peut être vide lorsqu'un de ces autres champs est renseigné : '%fieldToCompare%'",
+	);
+
+	/** @var string */
+	protected $_field_to_compare_label;
+
+	protected $_others = [];
+
+
+	/** @param $others array of Zend_Form_Element */
+	public function __construct($others = []) {
+		$this->_others = $others;
+
+		$labels = [];
+		foreach ($others as $other)
+			$labels[] = $other->getLabel();
+		$this->_field_to_compare_label = implode(', ', $labels);
+	}
+
+
+	public function isValid($value, $context=null) {
+		if (empty($this->_others)
+				|| !$this->_othersHasValues($context))
+			return true;
+
+		if (is_string($value))
+			$value = trim($value);
+
+		if (!empty($value))
+			return true;
+
+		$this->_error(self::NOT_ALONE);
+		return false;
+	}
+
+
+	protected function _othersHasValues($context) {
+		if (!$context)
+			return false;
+
+		foreach($this->_others as $element)
+			if (isset($context[$element->getName()]) && '' != $context[$element->getName()])
+				return true;
+		return false;
+	}
+}
+?>
\ No newline at end of file
diff --git a/library/ZendAfi/Validate/WorkflowVar.php b/library/ZendAfi/Validate/WorkflowVar.php
new file mode 100644
index 0000000000000000000000000000000000000000..0ed28d8bd23f93ea7a2a27c3d41c0609b2c8a8c8
--- /dev/null
+++ b/library/ZendAfi/Validate/WorkflowVar.php
@@ -0,0 +1,131 @@
+<?php
+/**
+ * Copyright (c) 2012-2014, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * AFI-OPAC 2.0 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).
+ *
+ * AFI-OPAC 2.0 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 AFI-OPAC 2.0; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class ZendAfi_Validate_WorkflowVar extends Zend_Validate_Abstract{
+	const INVALID_JSON = 'invalidJson';
+	const JSON_NOT_ARRAY = 'jsonNotArray';
+	const INVALID_STATUS = 'invalidStatus';
+	const RESERVED_STATUS = 'reservedStatus';
+	const USED_STATUS = 'usedStatus';
+
+	protected $_messageTemplates =
+		[self::INVALID_JSON => 'La valeur saisie n\'est pas un JSON valide.',
+		 self::JSON_NOT_ARRAY => 'Le JSON saisi n\'est pas un tableau de statuts',
+		 self::INVALID_STATUS => 'Tous les statuts doivent avoir un attribut "id" et un attribut "label"',
+		 self::RESERVED_STATUS => 'Le statut %status_id% est réservé par Bokeh',
+		 self::USED_STATUS => 'Le statut %status_id% est utilisé par un article et ne peut être supprimé'];
+
+	protected $_messageVariables = ['status_id' => '_status_id'];
+
+	protected $_old_value;
+	protected $_status_id;
+
+	public function __construct($old_value) {
+		$this->_old_value = $old_value;
+	}
+
+
+	public function isValid($value) {
+		// simple vers simple -> pas de validation
+		if ($this->_wasSimpleWorkflow()
+				&& $this->isSimpleWorkflow($value))
+			return true;
+
+		// dynamic vers simple -> les dynamics ne doivent pas être utilisés
+		if ($this->isSimpleWorkflow($value)) {
+			$this->validateStatusesUsage([]);
+			$errors = $this->getErrors();
+			return empty($errors);
+		}
+
+		// * vers dynamics -> les dynamics doivent être valides
+		if (!$statuses = json_decode($value)) {
+			$this->_error(self::INVALID_JSON);
+			return false;
+		}
+
+		if (!is_array($statuses)) {
+			$this->_error(self::JSON_NOT_ARRAY);
+			return false;
+		}
+
+		$invalid_status = false;
+		foreach($statuses as $status) {
+			if (!$invalid_status
+					&& (!isset($status->id) || !isset($status->label))) {
+				$this->_error(self::INVALID_STATUS);
+				$invalid_status = true;
+				continue;
+			}
+
+			if (5 >= $status->id) {
+				$this->_status_id = $status->id;
+				$this->_error(self::RESERVED_STATUS);
+			}
+		}
+
+		$errors = $this->getErrors();
+		if (!empty($errors))
+			return false;
+
+		// dynamics vers dynamics -> on ne doit pas supprimer d'anciens utilisés
+		if (!$this->_wasSimpleWorkflow())
+			$this->validateStatusesUsage($statuses);
+
+		$errors = $this->getErrors();
+		return empty($errors);
+	}
+
+
+	protected function validateStatusesUsage($new_statuses) {
+		$previous_statuses = json_decode($this->_old_value);
+		$id_extractor = function($item) {
+			return $item->id;
+		};
+
+		$previous_ids = array_map($id_extractor, $previous_statuses);
+		$new_ids = array_map($id_extractor, $new_statuses);
+
+		$deleted_ids = array_diff($previous_ids, $new_ids);
+		if (empty($deleted_ids))
+			return;
+
+		foreach($deleted_ids as $deleted_id) {
+			if (0 < Class_Article::countBy(['status' => $deleted_id])) {
+				$this->_status_id = $deleted_id;
+				$this->_error(self::USED_STATUS);
+			}
+		}
+	}
+
+
+	protected function _wasSimpleWorkflow() {
+		return !isset($this->_old_value)
+			|| $this->isSimpleWorkflow($this->_old_value);
+	}
+
+
+	protected function isSimpleWorkflow($value) {
+		return in_array($value, ['', '0', '1']);
+	}
+}
+?>
\ No newline at end of file
diff --git a/library/ZendAfi/View/Helper/Accueil/Kiosque.php b/library/ZendAfi/View/Helper/Accueil/Kiosque.php
index e1286506ce8f0cc16146659ea193bd0d607fe705..1d0cc702607ee74732e56f211be31c0514ce745b 100644
--- a/library/ZendAfi/View/Helper/Accueil/Kiosque.php
+++ b/library/ZendAfi/View/Helper/Accueil/Kiosque.php
@@ -262,7 +262,7 @@ class ZendAfi_View_Helper_Accueil_Kiosque extends ZendAfi_View_Helper_Accueil_Ba
 		$this->_nombre_notices_par_page = $this->preferences['nb_notices'];
 		$this->preferences['nb_notices'] = 0;
 		$this->preferences['nb_analyse'] = 0;
-		$requetes = Class_Catalogue::getLoader()->getRequetes($this->preferences, ['id_notice']);
+		$requetes = Class_Catalogue::getRequetes($this->preferences, ['id_notice']);
 
 		$nombre_total_notices = 0;
 		$notices = [];
@@ -274,6 +274,9 @@ class ZendAfi_View_Helper_Accueil_Kiosque extends ZendAfi_View_Helper_Accueil_Ba
 																												 $page);
 	  }
 
+		if(!$notices)
+			return $this->view->tag('p',$this->view->_('Aucun résultat'));
+
 		$html = $this->renderNoticesKiosqueHtml($notices);
 		if (!$this->isModeChrono())
 			$html = $this->getPagerHtml($page,
diff --git a/library/ZendAfi/View/Helper/Accueil/News.php b/library/ZendAfi/View/Helper/Accueil/News.php
index bbe01516d718f8c43665ee3a60215936ff7b4cde..01734fa0c58183f689b55952512742587d96f539 100644
--- a/library/ZendAfi/View/Helper/Accueil/News.php
+++ b/library/ZendAfi/View/Helper/Accueil/News.php
@@ -70,11 +70,18 @@ class ZendAfi_View_Helper_Accueil_News extends ZendAfi_View_Helper_Accueil_Base
 
 
 	protected function addNewsAddButton($articles) {
+		$category = $this->getFirstArticleCategorie($articles);
+		if (!$category
+				|| !Class_Users::getIdentity()
+				->hasAnyPermissionOn($category, [Class_Permission::createArticle(),
+																				 Class_Permission::createArticleCategory()]))
+			return '';
+
 		return $this->view->tagAnchor($this->view->url(['module' => 'admin',
 																										'controller' => 'cms',
 																										'action' => 'add',
 																										'id_module' => $this->id_module,
-																										'id_cat' => $this->getFirstArticleCategorie($articles)]),
+																										'id_cat' => $category->getId()]),
 																  $this->view->tagImg(URL_ADMIN_IMG.'ico/add.gif',['title' => $this->view->_('Ajouter un nouvel article')]),
 																	['data-popup' => 'true',
 																	 'class' => 'newsadd']);
@@ -85,8 +92,8 @@ class ZendAfi_View_Helper_Accueil_News extends ZendAfi_View_Helper_Accueil_Base
 		if (!$articles || !is_array($articles)) return '';
 
 		foreach($articles as $article) {
-			if($id_cat = $article->getIdCat())
-				return $id_cat;
+			if($category = $article->getCategorie())
+				return $category;
 		}
 	}
 
diff --git a/library/ZendAfi/View/Helper/Accueil/Sito.php b/library/ZendAfi/View/Helper/Accueil/Sito.php
index 12d97eb6dae6fe6238093b5b1178905fdee1d034..d464440a47955463750f933dd0a592c5dd6702d2 100644
--- a/library/ZendAfi/View/Helper/Accueil/Sito.php
+++ b/library/ZendAfi/View/Helper/Accueil/Sito.php
@@ -34,35 +34,40 @@ class ZendAfi_View_Helper_Accueil_Sito extends ZendAfi_View_Helper_Accueil_Base
 	public function getHtml()	{
 		extract($this->preferences);
 		$contenu = '';
+		$nb_aff = $this->preferences['nb_aff'];
+
 		if ($this->isTypeAffichageSelection())	{
 			$sites = Class_Sitotheque::getSitesFromIdsAndCategories(
-																															explode('-', $id_items),
-																															explode('-', $id_categorie));
+																															explode('-', $this->preferences['id_items']),
+																															explode('-', $this->preferences['id_categorie']));
 
-			shuffle($sites);
-			$contenu.=$this->renderSitesSlice($sites,$nb_aff);
-			$titre= sprintf('<a href="%s" title="%s">%s</a>',
+			if ($this->preferences['display_order'] == 'Random')
+				shuffle($sites);
+
+			if ($this->preferences['display_order'] == 'Selection')
+				$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);
 		}
 
 		if ($this->isTypeAffichagePlusRecents() && $nb_aff > 0) {
-			$last_sito = Class_Sitotheque::getLoader()->findAllBy(array('limit' => 50));
-			shuffle($last_sito);
+			$sites = Class_Sitotheque::findAllBy(['limit' => 50]);
+			shuffle($sites);
 
 			if(!$titre)
 				$titre = $this->translate()->_("Derniers sites ajoutés");
 
-			$titre= sprintf('<a href="%s" title="%s">%s</a>',
+			$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);
-			$contenu.=$this->renderSitesSlice($last_sito,$nb_aff);
 		}
 
-		$this->titre=$titre;
-		$this->contenu = $contenu;
+		$this->titre = $titre;
+		$this->contenu = $this->renderSitesSlice($sites, $nb_aff);
 		return $this->getHtmlArray();
 	}
 
@@ -103,8 +108,7 @@ class ZendAfi_View_Helper_Accueil_Sito extends ZendAfi_View_Helper_Accueil_Base
 		$sites = array_slice($sites, 0, $nb_aff);
 
 		if (!$this->isGroupByCategorie())
-			return $this->renderSites($sites,
-																'<div style="clear:both;width:100%;background:transparent url('.URL_IMG.'box/menu/separ.gif) repeat-x scroll center bottom;margin-bottom:5px">&nbsp;</div>');
+			return $this->renderSites($sites);
 
 		$categories = $this->groupSitesByCategorie($sites);
 		$htmls = array();
@@ -118,12 +122,8 @@ class ZendAfi_View_Helper_Accueil_Sito extends ZendAfi_View_Helper_Accueil_Base
 	}
 
 
-	protected function renderSites($sites, $separator = '') {
-		$htmls = array();
-		foreach($sites as $site)
-			$htmls []= $this->renderSite($site);
-
-		return implode($separator, $htmls);
+	protected function renderSites($sites) {
+		return implode('', array_map([$this, 'renderSite'], $sites));
 	}
 
 
diff --git a/library/ZendAfi/View/Helper/Admin/FrbrLabel.php b/library/ZendAfi/View/Helper/Admin/FrbrLabel.php
new file mode 100644
index 0000000000000000000000000000000000000000..eef1295fb4986edbfc5630d8797bd4f105470ab4
--- /dev/null
+++ b/library/ZendAfi/View/Helper/Admin/FrbrLabel.php
@@ -0,0 +1,90 @@
+<?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_Admin_FrbrLabel extends Zend_View_Helper_Abstract {
+	public function frbrLabel($link, $link_end) {
+		return ZendAfi_View_Helper_Admin_FrbrLabelRenderer::newFor($link,
+																															 $link_end,
+																															 $this->view)
+			->render();
+	}
+}
+
+
+class ZendAfi_View_Helper_Admin_FrbrLabelRenderer {
+	protected $entity, $url, $view;
+
+	public static function newFor($link, $link_end, $view) {
+		$class_name = 'Renderer';
+		$type = $link->getTypeFor($link_end);
+		$entity = $link->getEntityFor($link_end);
+		if ($entity && Class_FRBR_Link::TYPE_NOTICE == $type)
+			$class_name = 'RecordRenderer';
+
+		if ($entity && Class_FRBR_Link::TYPE_ALBUM == $type)
+			$class_name = 'AlbumRenderer';
+
+		$full_class_name = 'ZendAfi_View_Helper_Admin_FrbrLabel' . $class_name;
+		return new $full_class_name($entity, $link->$link_end, $view);
+	}
+
+
+	public function __construct($entity, $url, $view) {
+		$this->entity = $entity;
+		$this->url = $url;
+		$this->view = $view;
+	}
+
+
+	public function render() {
+		return (40 < mb_strlen($this->url)) ?
+			mb_substr($this->url, 0, 40) . '...' : $this->url;
+	}
+
+
+	protected function _renderTitleAuthor($title, $author) {
+		return $this->view
+			->tag('a', $title,
+						['href' => $this->url,
+						 'onclick' => 'window.open(this.href);return false;'])
+			. '<br>' . $author;
+	}
+}
+
+
+class ZendAfi_View_Helper_Admin_FrbrLabelRecordRenderer
+	extends ZendAfi_View_Helper_Admin_FrbrLabelRenderer{
+	public function render() {
+		return $this->_renderTitleAuthor($this->entity->getTitrePrincipal(),
+																		 $this->entity->getAuteurPrincipal());
+	}
+}
+
+
+class ZendAfi_View_Helper_Admin_FrbrLabelAlbumRenderer
+	extends ZendAfi_View_Helper_Admin_FrbrLabelRenderer{
+	public function render() {
+		return $this->_renderTitleAuthor($this->entity->getTitre(),
+																		 $this->entity->getMainAuthor());
+	}
+}
+?>
diff --git a/library/ZendAfi/View/Helper/Admin/GroupsPermissions.php b/library/ZendAfi/View/Helper/Admin/GroupsPermissions.php
new file mode 100644
index 0000000000000000000000000000000000000000..52f9973e8e745945b3c192c83a66b62ccd65d751
--- /dev/null
+++ b/library/ZendAfi/View/Helper/Admin/GroupsPermissions.php
@@ -0,0 +1,158 @@
+<?php
+/**
+ * Copyright (c) 2012-2014, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * AFI-OPAC 2.0 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).
+ *
+ * AFI-OPAC 2.0 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 AFI-OPAC 2.0; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class ZendAfi_View_Helper_Admin_GroupsPermissions extends Zend_View_Helper_Abstract {
+	use Trait_Translator;
+
+	public function groupsPermissions($model, $permissions, $action) {
+		if (!$groups = $this->permissionsGroups())
+			return $this->tag('div',
+												$this->_('Aucun groupe utilisateur ne dispose du droit "Articles: accès articles"'),
+												['class' => 'error']);
+
+		return $this
+			->tag('form',
+						$this->tag('table',
+											 $this->tag('thead',
+																	$this->permissionsTypesHeaders($permissions)
+																	. $this->permissionsHeaders($permissions))
+											 . $this->tag('tbody',
+																		$this->permissionsRows($model, $permissions, $groups)),
+											 ['style' => 'border-collapse:collapse;margin-bottom:15px;'])
+						. $this->view->bouton('type=V',
+																	'texte=' . $this->_('Valider les permissions'),
+																	'largeur=220px',
+																	'id=perms' . $model->getId(),
+																	'javascript=setFlagMaj(false);'),
+						['action' => $action,
+						 'method' => 'post']);
+	}
+
+
+	protected function permissionsTypesHeaders($permissions) {
+		$types = [];
+		$current = '';
+		foreach($permissions as $permission) {
+			$type = $permission->getType();
+			if ($current != $type) {
+				$types[$type] = 0;
+				$current = $type;
+			}
+			$types[$type]++;
+		}
+
+		if (1 >= count($types))
+			return '';
+
+		$html = $this->tag('th', '');
+		foreach($types as $label => $count)
+			$html .= $this->tag('th', $label,
+													['style' => 'text-align:center;border:1px solid;',
+													 'colspan' => $count,
+													 'scope' => 'colgroup']);
+		return $this->tag('tr', $html);
+	}
+
+
+	protected function permissionsHeaders($permissions) {
+		$html = $this->tag('th', 'Groupes utilisateurs',
+											 ['style' => 'text-align:center;border:1px solid;padding:2px;vertical-align:top;']);
+
+		foreach($permissions as $permission)
+			$html .= $this->tag('th', $permission->getDescription(),
+													['style' => 'text-align:center;border:1px solid;padding:2px;vertical-align:top;',
+													 'scope' => 'col']);
+		return $this->tag('tr', $html);
+	}
+
+
+	protected function permissionsGroups() {
+		$groups_by_path = [];
+		foreach(Class_UserGroup::findAll() as $group) {
+			if (!$group->hasRightAccesArticles())
+				continue;
+
+			$parts = ($group->hasCategorie())  ? $group->getCategorie()->getPathParts() : [];
+			$parts[] = $group->getLibelle();
+			$groups_by_path[implode(' > ', $parts)] = $group;
+		}
+		ksort($groups_by_path);
+		return $groups_by_path;
+	}
+
+
+	protected function permissionsRows($model, $permissions, $groups) {
+		$html = '';
+		foreach($groups as $path => $group)
+			$html .= $this->permissionsRowFor($path, $group, $model, $permissions);
+
+		return $html;
+	}
+
+
+	protected function permissionsRowFor($path, $group, $model, $permissions) {
+		return $this->tag('tr',
+											$this->tag('th', $path,
+																 ['style' => 'border:1px solid;',
+																	'scope' => 'row'])
+											. $this->permissionsFor($group, $model, $permissions));
+	}
+
+
+	protected function permissionsFor($group, $model, $permissions) {
+		$html = '';
+		foreach($permissions as $permission)
+			$html .= $this->permissionFor($group, $model, $permission);
+		return $html;
+	}
+
+
+	protected function permissionFor($group, $model, $permission) {
+		$attribs = ['type' => 'checkbox',
+								'name' => 'perms[]',
+								'value' => $group->getId() . '-' . $permission->getId()];
+
+		$is_local = $group->hasLocalPermissionOn($permission, $model);
+		$parent = $group->getParentWithPermissionOn($permission, $model);
+		if ($is_local || $parent)
+			$attribs['checked'] = 'checked';
+		if ($parent)
+			$attribs['disabled'] = 'disabled';
+		$html = $this->tag('input', null, $attribs);
+		if ($parent)
+			$html .= $this->tag('br')
+				. $this->tag('a', 'via ' . $parent->getLibelle(),
+										 ['href' => $parent->getPermissionsEditUrl($this->view)]);
+		$style = ['text-align:center',
+							'vertical-align:middle',
+							'border:1px solid',
+							'background-color:' . (isset($attribs['checked']) ? 'lightgreen' : 'lightgrey')];
+
+		return $this->tag('td', $html, ['style' => implode(';', $style)]);
+	}
+
+
+	protected function tag() {
+		return call_user_func_array([$this->view, 'tag'], func_get_args());
+	}
+}
+?>
\ No newline at end of file
diff --git a/library/ZendAfi/View/Helper/Admin/HelpLink.php b/library/ZendAfi/View/Helper/Admin/HelpLink.php
index 763a4829805d484fd8bddf0b2ffac50edb21a45f..2d8887c10276c21ab1c7affae54ef8fea8991eef 100644
--- a/library/ZendAfi/View/Helper/Admin/HelpLink.php
+++ b/library/ZendAfi/View/Helper/Admin/HelpLink.php
@@ -98,12 +98,8 @@ class ZendAfi_View_Helper_Admin_HelpLinkArdans
 	protected $_pattern = 'https://akm.ardans.fr/AFI2/invite/listerFiche.do?idFiche=%s';
 
 	protected $_mapping =
-		['profil' => [ 'index' => 3612,
-									 'menusindex' => 3618,
-									 'accueil' => 3614,
-									 'proprietes' => 3894],
-
-		 'catalogue' => [ 'index' => 3613 ],
+		['profil' => ['accueil' => 3614,
+			      'proprietes' => 3894],
 
 		 'formation' => [ 'index' => 4477 ],
 
@@ -135,7 +131,14 @@ class ZendAfi_View_Helper_Admin_HelpLinkBokehWiki
 
 	protected $_pattern = 'http://wiki.bokeh-library-portal.org/index.php/%s';
 	protected $_mapping =
-		['modules' => ['recherche_viewnotice' => 'Affichage_d%27une_notice']];
+		[
+                'modules' => ['recherche_viewnotice' => 'Affichage_d%27une_notice'],
+                'catalogue' => ['index' => 'Gestions_des_domaines'],
+                'profil' => [
+                             'index' => 'Configurer_un_profil',
+                             'menusindex' => 'Configurer_un_menu',
+                            ]
+                ];
 }
 
-?>
\ No newline at end of file
+?>
diff --git a/library/ZendAfi/View/Helper/Admin/UserGroupMemberShip.php b/library/ZendAfi/View/Helper/Admin/UserGroupMemberShip.php
index 7145d15ed2268b06e3986aa3a47c336892381b79..8f17a23379053a9707df49e1bb70a92e48c6d42c 100644
--- a/library/ZendAfi/View/Helper/Admin/UserGroupMemberShip.php
+++ b/library/ZendAfi/View/Helper/Admin/UserGroupMemberShip.php
@@ -16,34 +16,26 @@
  *
  * 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_Admin_UserGroupMemberShip extends ZendAfi_View_Helper_Admin_SubscribeUsers {
 	protected $group;
 
-
 	public function userGroupMemberShip($id_group, $search, $page = 0, $items_by_page = 20) {
-		if(!$group = Class_UserGroup::find($id_group))
+		if (!$group = Class_UserGroup::find($id_group))
 			return '';
-		
+
 		$this->group = $group;
-		
-		$items= [];
-		
-		if($group->isManual()){
-			$this->_total = Class_UserGroupMembership::countBy(['user_group_id' => $group->getId()]);
-			$items = $group->getUsersPage($page, $items_by_page);
-		}else {
-			$this->_total = Class_Users::countBy(['role_level' => $group->getRoleLevel()]);
-			$items = Class_Users::findAllBy(['role_level' => $group->getRoleLevel(),
-																			 'limitPage' => [$page, $items_by_page]]);
-		}
-		
+
+		$items = [];
+		$this->_total = $group->numberOfUsers();
+		$items = $group->getUsersPage($page, $items_by_page);
+
 		$this->_paginator = new Zend_Paginator(new Zend_Paginator_Adapter_Null($this->_total));
 		$this->_paginator->setItemCountPerPage($items_by_page);
 		$this->_paginator->setCurrentPageNumber($page);
 		$this->_paginator->setView($this->view);
-		
+
 		return $this
 			->setUsers($items)
 			->setSearch($search)
@@ -72,9 +64,9 @@ class ZendAfi_View_Helper_Admin_UserGroupMemberShip extends ZendAfi_View_Helper_
 		return $this->view->_('Ajouter les utilisateurs sélectionnés');
 	}
 
-	
+
 	protected function _getPagination() {
-		return $this->view->tag('div', $this->_paginator->render(), 
+		return $this->view->tag('div', $this->_paginator->render(),
 														['style' => 'text-align:center']);
 	}
 
diff --git a/library/ZendAfi/View/Helper/Article/RenderTitleOnlyCalendar.php b/library/ZendAfi/View/Helper/Article/RenderTitleOnlyCalendar.php
index 94a9472d66730fc9f5e0bf1f403d1a9bb2526674..9083597ab7e28ae4867c0d5b14122240a2082e4b 100644
--- a/library/ZendAfi/View/Helper/Article/RenderTitleOnlyCalendar.php
+++ b/library/ZendAfi/View/Helper/Article/RenderTitleOnlyCalendar.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 ZendAfi_View_Helper_Article_RenderTitleOnlyCalendar extends ZendAfi_View_Helper_Article_RenderAbstract{
 	public function article_RenderTitleOnlyCalendar($article) {
@@ -24,13 +24,13 @@ class ZendAfi_View_Helper_Article_RenderTitleOnlyCalendar extends ZendAfi_View_H
 	}
 
 	public function renderContent($article) {
-		return 
+		return
 			'<li>'.
 			$this->view->tagEditArticle($article).
 			$this->renderDraftStatus($article).
+			$this->renderCalendarEventTitle($article).
 			$this->renderCalendarEventDate($article).
 			$this->renderCalendarEventInfo($article).
-			$this->renderCalendarEventTitle($article).
 			'</li>';
 	}
 
diff --git a/library/ZendAfi/View/Helper/AvisCms.php b/library/ZendAfi/View/Helper/AvisCms.php
index 92123b55a149a1dd1ed606f9ebdb061b7d2183f9..3bb676fa561aa838b79e9bdba3a5cc3066377396 100644
--- a/library/ZendAfi/View/Helper/AvisCms.php
+++ b/library/ZendAfi/View/Helper/AvisCms.php
@@ -16,39 +16,40 @@
  *
  * 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_AvisCms extends Zend_View_Helper_HtmlElement {
 	public function avisCms($article) {
 		return $this->rendHtmlBlockAvis($article);
 	}
 
-	
-	function rendHtmlBlockAvis($article)	{
-		$info_bib = $this->rendInfoCmsAvis($article, 1);
-		$info_abo = $this->rendInfoCmsAvis($article, 0);
 
+	function rendHtmlBlockAvis($article)	{
 		$nb_avis = 0;
-		if ($rank = $article->getRank())
-			$nb_avis = $rank->getNbAvisTotal();
+		$rank = $article->getRank();
+		$nb_avis = $rank->getNbAvisTotal();
 
 		$txt_nb_avis = ($nb_avis == 0) ?"&nbsp;Aucun avis" : "&nbsp;Avis (".$nb_avis.")";
 		$id_news = $article->getId();
 		$html = '<div class="avis_show_avis" onclick="showCmsAvis('.$id_news.', this)"><img src="'.URL_IMG.'bouton/plus_carre.gif" style="float:left" border="0" alt="Voir les avis" title="Voir les avis" id="plus"/> '.$txt_nb_avis.' </div>';
 		$html.='<div id="avis_'.$id_news.'" style="display:none;padding-left:5px;">';
 		$html.='<br />';
-		$html.='<a href="#" onclick="javascript:fonction_abonne(\''.Class_Users::currentUserId().'\',\'/opac/abonne/cmsavis?id='.$id_news.'\')">&raquo; Donner ou modifier votre avis</a>';
+		$html.= $this->view->tagAnchor(['controller' => 'abonne', 'action' => 'cmsavis', 'id' => $id_news],
+																	 '&raquo; '.$this->view->_('Donner ou modifier votre avis'),
+																	 ['data-popup' => 'true']);
 		$html.='<ul class="notice_info">';
-		$html.='<li>'.$info_bib["NOTE"].' <a href="#" onclick="showAvis('.$id_news.',\'bib\');return false;">Avis de bibliothécaires</a> '.$info_bib["AVIS"].'</li>';
-		$html.='<li>'.$info_abo["NOTE"].' <a href="#" onclick="showAvis('.$id_news.',\'abo\');return false;">Avis de lecteurs du portail</a> '.$info_abo["AVIS"].'</li>';
+		if ($rank->getBibNombreAvis())
+				$html.='<li>'.$this->view->noteImg($rank->getBibNote()).' <a href="#" onclick="showAvis('.$id_news.',\'bib\');return false;">Avis de bibliothécaires</a> '.$this->formatNoteLabel($rank->getBibNombreAvis()).'</li>';
+		if ($rank->getAbonNombreAvis())
+				$html.='<li>'.$this->view->noteImg($rank->getAbonNote()).' <a href="#" onclick="showAvis('.$id_news.',\'abo\');return false;">Avis de lecteurs du portail</a> '.$this->formatNoteLabel($rank->getAbonNombreAvis()).'</li>';
 		$html.='</ul>';
-        
+
 		$view = (getVar('MODO_AVIS_BIBLIO') == 1) ? 1 : "";
-        
+
 		$liste_avis = $this->getCmsAvisBiblio($article, $view);
 
 		$style = (count($liste_avis) == 0) ? "none" : "block";
-		
+
 		// Tableau bib
 		$html.='<div id="bib_'.$id_news.'" style="display:'.$style.'"><table cellpadding="0" cellspacing="0" border="0" style="width:100%">';
 		$html.='<tr><td class="avis_from">Avis des bibliothécaires</td></tr>';
@@ -56,7 +57,7 @@ class ZendAfi_View_Helper_AvisCms extends Zend_View_Helper_HtmlElement {
 		$html.='</table></div>';
 
 		$view = (getVar('MODO_AVIS') == 1) ? 1 : "";
-        
+
 		// Tableau Abonne
 		$liste_avis_abo = $this->getCmsAvisAbo($article, $view);
 
@@ -65,46 +66,22 @@ class ZendAfi_View_Helper_AvisCms extends Zend_View_Helper_HtmlElement {
 		$html.='<div id="abo_'.$id_news.'" style="display:'.$style.'"><table cellpadding="0" cellspacing="0" border="0" style="width:100%">';
 		$html.='<tr><td class="avis_from">Avis des lecteurs du portail</td></tr>';
 		$html.='<tr><td>'.$this->rendHTMLCmsAvis($liste_avis_abo,0).'</td></tr>';
-        
+
 		$html.='</table></div>';
 		$html.='</div>';
 		$html.='<div style="width:100%;background:transparent url('.URL_IMG.'box/menu/separ.gif) repeat-x scroll center bottom">&nbsp;</div>';
-        
+
 		return($html);
 	}
 
-
-	public function rendInfoCmsAvis($article, $abon_ou_bib) {
-		if (!$rank = $article->getRank())
-			return array('NOTE' => 0, 'AVIS' => 0, 'ABON_NOMBRE_AVIS' => 0, 'BIB_NOMBRE_AVIS' => 0);
-
-		$abon_nb_avis = $rank->getAbonNombreAvis();
-		$bib_nb_avis = $rank->getBibNombreAvis();
-		
-		if($abon_ou_bib == 0)	{
-				if ($abon_nb_avis == 0 || $abon_nb_avis == null) $nb_eva = "(aucune évaluation)";
-				elseif($abon_nb_avis == 1) $nb_eva = "(1 évaluation)";
-				elseif($abon_nb_avis > 1) $nb_eva = "(".$abon_nb_avis." évaluations)";
-            
-			$note = $rank->getAbonNote();
-		}	else {
-				if ($bib_nb_avis == 0 || $bib_nb_avis == null) $nb_eva = "(aucune évaluation)";
-				elseif($bib_nb_avis == 1) $nb_eva = "(1 évaluation)";
-				elseif($bib_nb_avis > 1) $nb_eva = "(".$bib_nb_avis." évaluations)";
-
-			$note = $rank->getBibNote();
-		}
-
-		$note_r = str_replace('.','-',$note);
-		$note_r = str_replace('-0','',$note_r);
-		if ($note_r == '') $note_r = "0";
-		$img = '<img src="'.URL_ADMIN_IMG.'stars/stars-'.$note_r.'.gif"  alt="note:'.$note.'" border="0"/>';
-		$info["NOTE"] = $img;
-
-		$info["AVIS"] = $nb_eva;
-		return($info);
+	public function formatNoteLabel($note) {
+		return $this->view->_plural($note,
+																'(aucune évaluation)',
+																'(%d évaluation)',
+																'(%d évaluations)',
+																$note);
 	}
-    
+
 
 	function getCmsAvisBiblio($article, $statut = "")	{
 		return $this->getCmsAvis($article, Trait_Avis::$AVIS_BIBLIO, $statut);
@@ -126,11 +103,11 @@ class ZendAfi_View_Helper_AvisCms extends Zend_View_Helper_HtmlElement {
 		return Class_Avis::getLoader()->findAllBy($params);
 	}
 
-    
+
 	// type =0 -> rend bulle blanche pour abonne
 	// type =1 -> rend bulle bleue pour bibliothecaire
 	function rendHTMLCmsAvis($avis_array, $type=0)	{
-		if(!is_array($avis_array))	
+		if(!is_array($avis_array))
 			return '';
 
 		$html = array();
@@ -190,7 +167,7 @@ class ZendAfi_View_Helper_AvisCms extends Zend_View_Helper_HtmlElement {
 			$html[]='</table>';
 		}
 
-		if(is_array($html)) 
+		if(is_array($html))
 			return(implode("", $html));
 
 		return('<div style="padding-left:7px">&nbsp;Aucun avis pour le moment.</div>');
diff --git a/library/ZendAfi/View/Helper/Bouton.php b/library/ZendAfi/View/Helper/Bouton.php
index fdbb6a3447ea7f212625223a90f4b8dea4ded613..8126c1ab9d1415cb4a9389777bdfa7e215b14dea 100644
--- a/library/ZendAfi/View/Helper/Bouton.php
+++ b/library/ZendAfi/View/Helper/Bouton.php
@@ -16,13 +16,13 @@
  *
  * 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
  */
 //////////////////////////////////////////////////////////////////////////////////////////
 // OPAC3 :	Bouton avec picto et texte
 //////////////////////////////////////////////////////////////////////////////////////////
 class ZendAfi_View_Helper_Bouton extends ZendAfi_View_Helper_BaseHelper {
-	protected 
+	protected
 		$_render_popup = false;
 
 
@@ -37,7 +37,7 @@ class ZendAfi_View_Helper_Bouton extends ZendAfi_View_Helper_BaseHelper {
 	function bouton( )
 	{
 		for( $i=0; $i < func_num_args(); $i++) $args[] = func_get_arg($i);
-		
+
 		$id = '';
 		$picto='';
 		$texte='';
@@ -68,10 +68,11 @@ class ZendAfi_View_Helper_Bouton extends ZendAfi_View_Helper_BaseHelper {
 					if( $attrib[1]=="V")
 					{
 						if(!$id) $id="975";
-						$picto=URL_ADMIN_IMG . '/ico/coche_verte.gif';
-						$texte=$this->translate()->_("Valider");
-						if(!$largeur) 
-							$largeur="120px";	
+						$picto = URL_ADMIN_IMG . '/ico/coche_verte.gif';
+						if (!$texte)
+							$texte = $this->translate()->_('Valider');
+						if(!$largeur)
+							$largeur="120px";
 
 						$onclick = 'var form=$(this).parents(\'form\'); if (!form.size()) form=$(this).parents(\'table\').prevAll(\'form\');';
 						if ($this->_render_popup)
@@ -111,11 +112,11 @@ class ZendAfi_View_Helper_Bouton extends ZendAfi_View_Helper_BaseHelper {
 		$html[]=		'</tr>';
 		$html[]=	'</table>';
 		$html[]=	'</a>';
-		
+
 
 		if($id_toolbar > 0 ) $html[]='</td>';
 		$html[]='</div>';
-		
+
 		return implode("", $html);
 	}
 }
\ No newline at end of file
diff --git a/library/ZendAfi/View/Helper/CkEditor.php b/library/ZendAfi/View/Helper/CkEditor.php
index df0f9fa7a0568af6855af98cda0c60bae0e6067f..35b02fee9dcb21577c12342a8bbe7eb69c5b61a4 100644
--- a/library/ZendAfi/View/Helper/CkEditor.php
+++ b/library/ZendAfi/View/Helper/CkEditor.php
@@ -69,6 +69,7 @@ class ZendAfi_View_Helper_CkEditor extends ZendAfi_View_Helper_BaseHelper
 				'classes' => '*',
 			],
 			'*' => [
+				'attributes' => 'id',
 				'styles' => '*'
 			]
 		];
diff --git a/library/ZendAfi/View/Helper/ComboCategories.php b/library/ZendAfi/View/Helper/ComboCategories.php
index 0cb9ed336bde6404176510aa1d0b56c0b64656ec..4bd00ed32ff94d55e170263cc61eae8332960515 100644
--- a/library/ZendAfi/View/Helper/ComboCategories.php
+++ b/library/ZendAfi/View/Helper/ComboCategories.php
@@ -20,19 +20,22 @@
  */
 
 class ZendAfi_View_Helper_ComboCategories extends ZendAfi_View_Helper_BaseHelper {
-	public function comboCategories($category) {
+	protected $_categories_filter;
+
+	public function comboCategories($category, $categories_filter=null) {
+		$this->_categories_filter = $categories_filter;
+
 		if (Class_Users::isCurrentUserCanAccessAllBibs())
 			$bibs = Class_Bib::findAllWithPortail();
 		else {
 			$bib = $category->getBib();
 			if (0 == $bib->getId())
 				$bib = Class_Bib::getPortail();
-			$bibs = array($bib);
+			$bibs = [$bib];
 		}
 
-		return sprintf('<select name="id_cat" id="id_cat">%s</select>',
-									 $this->_getAllBibCategories($bibs, $category)
-		);
+		return $this->view->tag('select', $this->_getAllBibCategories($bibs, $category),
+														['name' => 'id_cat', 'id' => 'id_cat']);
 	}
 
 
@@ -62,15 +65,21 @@ class ZendAfi_View_Helper_ComboCategories extends ZendAfi_View_Helper_BaseHelper
 
 
 	protected function _getLeveledCategoryOption($category, $origin, $level = 0) {
-		$prefix = str_repeat('&nbsp;&nbsp;', $level*2);
-		$html = '<option value="' . $category->getId() . '"'
-							. (($category->getId() == $origin->getId()) ? ' selected="selected"' : '') . '>'
-							. $prefix . $category->getLibelle() . '</option>';
+		$html = '';
 
-		foreach ($category->getSousCategories() as $subCategory) {
-			$html .= $this->_getLeveledCategoryOption($subCategory, $origin, $level+1);
+		if ((!$filter = $this->_categories_filter) || $filter($category)) {
+			$label = str_repeat('&nbsp;&nbsp;', $level*2) . $category->getLibelle();
+			$attrs = ['value' => $category->getId()];
+			if ($category->getId() == $origin->getId())
+				$attrs['selected'] = 'selected';
+
+			$html .= $this->view->tag('option', $label, $attrs);
+			$level++;
 		}
 
+		foreach ($category->getSousCategories() as $subCategory)
+			$html .= $this->_getLeveledCategoryOption($subCategory, $origin, $level);
+
 		return $html;
 	}
 
diff --git a/library/ZendAfi/View/Helper/ComboLibraries.php b/library/ZendAfi/View/Helper/ComboLibraries.php
new file mode 100644
index 0000000000000000000000000000000000000000..3cfb5b53d2ed90498440207292449b134d045f85
--- /dev/null
+++ b/library/ZendAfi/View/Helper/ComboLibraries.php
@@ -0,0 +1,67 @@
+<?php
+/**
+ * Copyright (c) 2012, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * AFI-OPAC 2.0 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).
+ *
+ * AFI-OPAC 2.0 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 AFI-OPAC 2.0; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+class ZendAfi_View_Helper_ComboLibraries extends Zend_View_Helper_HtmlElement {
+	use Trait_Translator;
+
+	public function comboLibraries($current) {
+		return $this->view->tag('select', $this->_getLibraries($current),
+														['name' => 'id_bib', 'id' => 'id_bib']);
+	}
+
+
+	protected function _getLibraries($current) {
+		$all_attribs = ['value' => 0];
+		if (!$current)
+			$all_attribs['selected'] = 'selected';
+
+		$html = $this->view->tag('option',
+														 '**' . $this->_('Toutes') . '**',
+														 $all_attribs);
+
+		$zones = Class_Zone::findAllBy(['order' => 'libelle']);
+		foreach ($zones as $zone)
+			$html .= $this->_getZone($zone, $current);
+		return $html;
+	}
+
+
+	protected function _getZone($zone, $current) {
+		return $this->view->tag('optgroup',
+														$this->_getZoneLibraries($zone, $current),
+														['label' => $zone->getLibelle()]);
+	}
+
+
+	protected function _getZoneLibraries($zone, $current) {
+		$html = '';
+		if (!$libraries = $zone->getBibs())
+			return $html;
+
+		foreach ($libraries as $library) {
+			$attribs = $current && $library->getId() == $current->getId()
+				? ['selected' => 'selected'] : [];
+			$attribs['value'] = $library->getId();
+			$html .= $this->view->tag('option', $library->getLibelle(), $attribs);
+		}
+		return $html;
+	}
+}
\ No newline at end of file
diff --git a/library/ZendAfi/View/Helper/ComboParentCategorie.php b/library/ZendAfi/View/Helper/ComboParentCategorie.php
index d96af7fa52bc8d4fa2a7b0474bd9a93810ddf765..ac7592e07b81c7970cc215d1f6ec634c2012bf43 100644
--- a/library/ZendAfi/View/Helper/ComboParentCategorie.php
+++ b/library/ZendAfi/View/Helper/ComboParentCategorie.php
@@ -16,23 +16,30 @@
  *
  * 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_ComboParentCategorie extends ZendAfi_View_Helper_ComboCategories {
-	public function comboParentCategorie($category) {
+	protected $_filter;
+
+	public function comboParentCategorie($category, $filter=null, $disable_none=null) {
+		$this->_filter = $filter;
+
 		if (Class_Users::isCurrentUserCanAccessAllBibs())
 			$bibs = Class_Bib::findAllWithPortail();
 		else {
 			$bib = $category->getBib();
 			if (0 == $bib->getId())
 				$bib = $this->_bib;
-			$bibs = array($bib);
+			$bibs = [$bib];
 		}
 
-		return sprintf('<select name="id_cat_mere" id="id_cat_mere"><option value="0">Aucune</option>%s</select>', 
-									 $this->_getAllBibCategories($bibs, $category)
-		);
+		$none = $disable_none ?
+			'' : $this->view->tag('option', 'Aucune', ['value' => 0]);
+
+		return $this->view->tag('select',
+														$none . $this->_getAllBibCategories($bibs, $category),
+														['name' => 'id_cat_mere', 'id' => 'id_cat_mere']);
 	}
 
 
@@ -46,18 +53,22 @@ class ZendAfi_View_Helper_ComboParentCategorie extends ZendAfi_View_Helper_Combo
 	protected function _getLeveledCategoryOption($category, $origin, $level = 0) {
 		// on exclut la categorie courante => impossible de se déplacer sous
 		// soi-même ou un de ses descendants
-		if ($category->getId() == $origin->getId()) {
+		if ($category->getId() == $origin->getId())
 			return '';
-		}
 
-		$prefix = str_repeat('&nbsp;&nbsp;', $level);
-		$html = '<option value="' . $category->getId() . '"'
-							. (($category->getId() == $origin->getIdCatMere()) ? ' selected="selected"' : '') . '>'
-							. $prefix . $category->getLibelle() . '</option>';
+		$html = '';
+		if ((!$filter = $this->_filter) || $filter($category)) {
+			$label = str_repeat('&nbsp;&nbsp;', $level) . $category->getLibelle();
+			$attrs = ['value' => $category->getId()];
+			if ($category->getId() == $origin->getIdCatMere())
+				$attrs['selected'] = 'selected';
 
-		foreach ($category->getSousCategories() as $subCategory) {
-			$html .= $this->_getLeveledCategoryOption($subCategory, $origin, $level+1);
-		}
+			$html .= $this->view->tag('option', $label, $attrs);
+			$level++;
+		 }
+
+		foreach ($category->getSousCategories() as $subCategory)
+			$html .= $this->_getLeveledCategoryOption($subCategory, $origin, $level);
 
 		return $html;
 	}
diff --git a/library/ZendAfi/View/Helper/DatePicker.php b/library/ZendAfi/View/Helper/DatePicker.php
index d8299b9adfa7d698f38951f1985e00c3018263c0..458914b37d9386adcd5a0a852a7595d417568f01 100644
--- a/library/ZendAfi/View/Helper/DatePicker.php
+++ b/library/ZendAfi/View/Helper/DatePicker.php
@@ -78,7 +78,7 @@ $("#' . $allDaySwitch . '").click(function() {
 		}
 
 		return $this->view->formText($name,
-																 $this->formatDate($varDate, 'dd/MM/YYYY'. ($dateOnly ? '' : ' HH:mm')),
+																 $this->formatDate($varDate, 'dd/MM/yyyy'. ($dateOnly ? '' : ' HH:mm')),
 																 ['id' => $name,
 																	'maxlength' => $dateOnly ? 10 : 16]);
 	}
diff --git a/library/ZendAfi/View/Helper/FormRadioButtons.php b/library/ZendAfi/View/Helper/FormRadioButtons.php
index 0b90e5059ab0b6b1df60d7508830e2f9a5a465fc..8bad085513bc27ea3a13cf5b97175b2655ece4a7 100644
--- a/library/ZendAfi/View/Helper/FormRadioButtons.php
+++ b/library/ZendAfi/View/Helper/FormRadioButtons.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
  */
 //////////////////////////////////////////////////////////////////////////////////////////////////////
 // OPAC3 - Build radio buttons
@@ -24,8 +24,8 @@
 
 
 /* Exemple d'utilisation:
-   echo $this->formRadioButtons("display_order", 
- 														 $this->preferences["display_order"], 
+   echo $this->formRadioButtons("display_order",
+ 														 $this->preferences["display_order"],
 														 array(
 																	 "selection" => "Par ordre de sélection",
 																	 "creation_date_desc" => "Par date de création (plus récent en premier)",
@@ -38,12 +38,12 @@ class ZendAfi_View_Helper_FormRadioButtons extends ZendAfi_View_Helper_BaseHelpe
 		$html = '';
 
 		$all_options = array_keys($buttons_description_dict);
-		if (! in_array($option_value, $all_options)) 
+		if (! in_array($option_value, $all_options))
 			$option_value = $all_options[0];
 
 		foreach ($buttons_description_dict as $value => $label){
 			$checked = ($value == $option_value) ? "checked='checked'" : "";
-			$html .= '<input type="radio" class="'.$option_name.'"name="'.$option_name.'" value="'.$value.'" '.$checked.' >'.$label.'<br/>';
+			$html .= '<input type="radio" class="'.$option_name.'" name="'.$option_name.'" value="'.$value.'" '.$checked.' >'.$label.'<br/>';
 		}
 
 		return $html;
diff --git a/library/ZendAfi/View/Helper/Frbr.php b/library/ZendAfi/View/Helper/Frbr.php
index 1f9ea47ef5adaea1eccc45c04244e9c12c28d99e..716977d3b49722ff2ce60278770e8a28ad8b7610 100644
--- a/library/ZendAfi/View/Helper/Frbr.php
+++ b/library/ZendAfi/View/Helper/Frbr.php
@@ -11,133 +11,138 @@
  *
  * 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
+ * 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 
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301	 USA
  */
 
 class ZendAfi_View_Helper_Frbr extends Zend_View_Helper_HtmlElement{
-   protected $linksRenderer;
-   const NO_RESULT_MESSAGE = 'Aucun lien n\'a été trouvé';
-  /**
-   * Retourne les notices liées
-   *
-   * @param $model Class_Notice
-   * @return string
-   */
-  public function frbr($model) {
-    $this->linksRenderer = $this->getLinksRenderer();
-    $sourceLinks = $model->getLinksAsSource();
-    $targetLinks = $model->getLinksAsTarget();
-
-    if (0 == count($sourceLinks) and 0 == count($targetLinks)){
-      $noResultMessage = $this->linksRenderer->returnNoResultMessage();
-      return $noresultmessage;
-      }
- 
-    
-    $html = '';
-    foreach ($this->_getLinksBySourceTypes($sourceLinks) as $label => $links)
-      $html .= $this->_getTargetTypeLinks($label, $links);
-    
-    foreach ($this->_getLinksByTargetTypes($targetLinks) as $label => $links)
-      $html .= $this->_getSourceTypeLinks($label, $links);
-
-    if ('' == $html){
-      $noResultMessage = $this->linksRenderer->returnNoResultMessage();
-      return $noResultMessage;
-    }
-
-    return $html;}
-    
-  
-
-    protected function _getLinksBySourceTypes($links) {
-      return $this->_getLinksByType($links, function ($link) {
-	  return $link->getTypeLabelFromSource();
-	});
-    }
-
-
-    protected function _getLinksByTargetTypes($links) {
-      return $this->_getLinksByType($links, function ($link) {
-	  return $link->getTypeLabelFromTarget();
-	});
-    }
-
-
-    protected function _getLinksByType($links, $callback) {
-      $byTypes = [];
-      foreach ($links as $link) {
-	$typeLabel = $callback($link);
-	if (!array_key_exists($typeLabel, $byTypes))
-	  $byTypes[$typeLabel] = [];
-	
-	$byTypes[$typeLabel][] = $link;
-      }
-      return $byTypes;
-    }
-
-
-    protected function _getTargetTypeLinks($label, $links) {
-      return $this->_getTypeLinks($label, $links, function($link) {
-	  return $link->getTargetNotice();
-	});
-    }
-    
-
-    protected function _getSourceTypeLinks($label, $links) {
-      return $this->_getTypeLinks($label, $links, function($link) {
-	  return $link->getSourceNotice();
-	});
-    }
-
-
-    protected function _getTypeLinks($label, $links, $callback) {
-      $html = '';
-      if (!$links)
-	return $html;
-      
-      $html .= $this->linksRenderer->renderType($label);
-      $notices = [];
-      foreach ($links as $link) {
-	if ($model = $callback($link))
-	  $notices[] = $model;
-      }
-
-      if (empty($notices))
-	return '';
-      
-      $html .= $this->linksRenderer->render($notices, $this->view);
-      
-      return $html;
-    }
-
-
-    public function getLinksRenderer(){
-      return new FrbrNoticesOpacRenderer();
-    }
+	protected $linksRenderer;
+	const NO_RESULT_MESSAGE = 'Aucun lien n\'a été trouvé';
+	/**
+	 * Retourne les notices liées
+	 *
+	 * @param $model Class_Notice
+	 * @return string
+	 */
+	public function frbr($model) {
+		$this->linksRenderer = $this->getLinksRenderer();
+		$sourceLinks = $model->getLinksAsSource();
+		$targetLinks = $model->getLinksAsTarget();
+
+		if (0 == count($sourceLinks) and 0 == count($targetLinks)){
+			$noResultMessage = $this->linksRenderer->returnNoResultMessage();
+			return $noresultmessage;
+		}
+
+
+		$html = '';
+		foreach ($this->_getLinksBySourceTypes($sourceLinks) as $label => $links)
+			$html .= $this->_getTargetTypeLinks($label, $links);
+
+		foreach ($this->_getLinksByTargetTypes($targetLinks) as $label => $links)
+			$html .= $this->_getSourceTypeLinks($label, $links);
+
+		if ('' == $html){
+			$noResultMessage = $this->linksRenderer->returnNoResultMessage();
+			return $noResultMessage;
+		}
+
+		return $html;
+	}
+
+
+
+	protected function _getLinksBySourceTypes($links) {
+		return $this->_getLinksByType($links, function ($link) {
+																															return $link->getTypeLabelFromSource();
+																														});
+	}
+
+
+	protected function _getLinksByTargetTypes($links) {
+		return $this->_getLinksByType($links, function ($link) {
+																															return $link->getTypeLabelFromTarget();
+																														});
+	}
+
+
+	protected function _getLinksByType($links, $callback) {
+		$byTypes = [];
+		foreach ($links as $link) {
+			$typeLabel = $callback($link);
+			if (!array_key_exists($typeLabel, $byTypes))
+				$byTypes[$typeLabel] = [];
+
+			$byTypes[$typeLabel][] = $link;
+		}
+		return $byTypes;
+	}
+
+
+	protected function _getTargetTypeLinks($label, $links) {
+		return $this->_getTypeLinks(
+																$label, $links,
+																function($link) {
+																	return $link->getTargetEntity();
+																});
+	}
+
+
+	protected function _getSourceTypeLinks($label, $links) {
+		return $this->_getTypeLinks(
+																$label, $links,
+																function($link) {
+																	return $link->getSourceEntity();
+																});
+	}
+
+
+	protected function _getTypeLinks($label, $links, $callback) {
+		$html = '';
+		if (!$links)
+			return $html;
+
+		$html .= $this->linksRenderer->renderType($label);
+		$notices = [];
+		foreach ($links as $link) {
+			if ($model = $callback($link))
+				$notices[] = $model;
+		}
+
+		if (empty($notices))
+			return '';
+
+		$html .= $this->linksRenderer->render($notices, $this->view);
+
+		return $html;
+	}
+
+
+	public function getLinksRenderer(){
+		return new FrbrNoticesOpacRenderer();
+	}
 }
 
 
 
 class FrbrNoticesOpacRenderer {
-  public function render($notices, $view){
+	public function render($notices, $view){
     $noticeHtml = new Class_NoticeHtml();
     return $noticeHtml->getListeNotices($notices, $view);
-  }
+	}
+
 
+	public function renderType($type) {
+		return '<div class="notice_info_titre">' . $type . '</div>';
+	}
 
-  public function renderType($type) {
-    return '<div class="notice_info_titre">' . $type . '</div>';
-  } 
-  
 
-  public function returnNoResultMessage() {
-    return 'Aucun lien n\'a été trouvé';
-  }
-} 
+	public function returnNoResultMessage() {
+		return 'Aucun lien n\'a été trouvé';
+	}
+}
 ?>
diff --git a/library/ZendAfi/View/Helper/ModelActions.php b/library/ZendAfi/View/Helper/ModelActions.php
new file mode 100644
index 0000000000000000000000000000000000000000..ca91c5852dbf30139d6138ba09c8a4d250536b00
--- /dev/null
+++ b/library/ZendAfi/View/Helper/ModelActions.php
@@ -0,0 +1,107 @@
+<?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_ModelActions extends Zend_View_Helper_Abstract {
+	public function modelActions($model, $actions, $inline=false) {
+		if (!$model || !$actions)
+			return '';
+
+		$html = '';
+		foreach ($actions as $action)
+			$html .= (new ZendAfi_View_Helper_ModelAction($action, $this->view))
+				->render($model);
+
+		return $inline ?
+			$html : $this->view->tag('div', $html, ['class' => 'actions']);
+	}
+}
+
+
+
+class ZendAfi_View_Helper_ModelAction {
+	const CONDITION = 'condition';
+	const CAPTION = 'caption';
+	const ICON = 'icon';
+	const LABEL = 'label';
+	const ANCHOR_OPTIONS = 'anchorOptions';
+	const URL = 'url';
+
+	protected $_view, $_conf;
+
+	public function __construct($config, $view) {
+		$this->_view = $view;
+		$this->_conf = $config;
+	}
+
+
+	/**
+	 * @param $model Storm_Model_Abstract
+	 * @return string
+	 */
+	public function render($model) {
+		if (!$this->_meetsRequirements($model))
+			return '';
+
+		$caption = array_key_exists(self::CAPTION, $this->_conf) ?
+			$model->{$this->_conf[self::CAPTION]}() : '';
+
+		$url = sprintf($this->_conf[self::URL], $model->getId());
+		$icon = $this->_initIcon($model);
+		$anchorOptions = $this->_initAnchorOptions();
+
+		$content = $this->_view->tagImg(URL_ADMIN_IMG . $icon,
+																		['alt' => $this->_conf[self::LABEL],
+																		 'title' => $this->_conf[self::LABEL],
+																		 'class' => 'ico'])
+			. $caption;
+
+		return $this->_view->tagAnchor($url, $content, $anchorOptions);
+	}
+
+
+	protected function _meetsRequirements($model) {
+		if (!array_key_exists(self::CONDITION, $this->_conf))
+			return true;
+
+		$condition = $this->_conf[self::CONDITION];
+		if (is_string($condition) && is_callable([$model, $condition]))
+			return $model->{$condition}();
+
+		return is_callable($condition) ? $condition($model) : true;
+	}
+
+
+	protected function _initIcon($model) {
+		if (!array_key_exists(self::ICON, $this->_conf))
+			return null;
+
+		return is_a($this->_conf[self::ICON], 'Closure') ?
+			$this->_conf[self::ICON]($model) : $this->_conf[self::ICON];
+	}
+
+
+	protected function _initAnchorOptions() {
+		return array_key_exists(self::ANCHOR_OPTIONS, $this->_conf) ?
+			$this->_conf[self::ANCHOR_OPTIONS] : [];
+	}
+}
+?>
\ No newline at end of file
diff --git a/library/ZendAfi/View/Helper/Notice/Entete.php b/library/ZendAfi/View/Helper/Notice/Entete.php
index 73293c7a40baba01d2829ba1a805a7b796dc4eb3..f5950cec9c240db40004e284ca0b5c96085d2fdd 100644
--- a/library/ZendAfi/View/Helper/Notice/Entete.php
+++ b/library/ZendAfi/View/Helper/Notice/Entete.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 ZendAfi_View_Helper_Notice_Entete extends Zend_View_Helper_HtmlElement {
 	public function notice_Entete($notice, $preferences) {
@@ -33,12 +33,12 @@ class ZendAfi_View_Helper_Notice_Entete extends Zend_View_Helper_HtmlElement {
 
 
 	public function addSeries($notice) {
-		if (strlen($notice->getClefChapeau())<1)
+		if (!$notice->hasTome())
 			return '';
 
 		$criteres = new Class_CriteresRecherche();
 		$serie = $notice->getClefChapeau();
-		
+
 		$message=Class_Codification::getLibelleForSerie($notice);
 		$html = '<a class="serie" href="'.$this->view->url($criteres->getNewUrlCriteresSerie($serie), null, true).'">'.$message.'</a>';
 		return $html;
@@ -59,7 +59,7 @@ class ZendAfi_View_Helper_Notice_Entete extends Zend_View_Helper_HtmlElement {
 		$values = [];
 		$facettes = $notice->getFacettes();
 		array_walk(
-			$entetes, 
+			$entetes,
 			function($item) use (&$values, $facettes, $notice) {
 				$label = Class_Codification::getInstance()->getNomChamp($item, 1);
 				$champ_notice = $notice->getChampNotice($item, $facettes);
@@ -80,7 +80,7 @@ class ZendAfi_View_Helper_Notice_Entete extends Zend_View_Helper_HtmlElement {
 		$i=0;
 		foreach($libelles_valeurs as $item){
 			$class = is_odd($i) ? 'second' : 'first' ;
-			$html.= 
+			$html.=
 				'<dt class="'.$class.' '.str_replace('é','e',strtolower(str_replace(['(',')'], '',$item['label']))).'">'.$item['label'].'</dt>'.
 				'<dd class="'.$class.'">'.$this->getValeurHtml($item['champ']).'</dd>';
 			$i++;
@@ -91,18 +91,18 @@ class ZendAfi_View_Helper_Notice_Entete extends Zend_View_Helper_HtmlElement {
 		return $html;
 	}
 
-	
+
 	public function getValeurHtml($valeur){
 		$html='';
-		if(gettype($valeur) != "array") 
+		if(gettype($valeur) != "array")
 		 	$html.=$valeur;
 		else	{
 			foreach($valeur as $item) {
-				if (gettype($item) == "array"){ 
+				if (gettype($item) == "array"){
 					if($item["url"]) {
 						$html.='<a href="'.$this->view->url($item["url"]).'">'.$item["libelle"].'</a>';
 					}
-				} 
+				}
 				else if (gettype($item) == "string") {
 					$html.='<div>'.$item.'</div>';
 				}
diff --git a/library/ZendAfi/View/Helper/Notice/Exemplaires.php b/library/ZendAfi/View/Helper/Notice/Exemplaires.php
index e384cb464219debb27cb771e144837185fe10118..98a18408c8e89db4b155541fb5734b7148c2316d 100644
--- a/library/ZendAfi/View/Helper/Notice/Exemplaires.php
+++ b/library/ZendAfi/View/Helper/Notice/Exemplaires.php
@@ -302,7 +302,7 @@ class ZendAfi_View_Helper_Notice_Exemplaires_DateRetour extends ZendAfi_View_Hel
 		return $this->_('Retour');
 	}
 	public function getContent($exemplaire) {
-		return $exemplaire['date_retour'];
+		return isset($exemplaire['date_retour']) ? $exemplaire['date_retour'] : NULL;
 	}
 
 
diff --git a/library/ZendAfi/View/Helper/Notice/Unimarc.php b/library/ZendAfi/View/Helper/Notice/Unimarc.php
index 8fda09b962f478d27a34a19004a6121480fe7795..7f32dee82e99bbf72d31b200878e16e6cda643db 100644
--- a/library/ZendAfi/View/Helper/Notice/Unimarc.php
+++ b/library/ZendAfi/View/Helper/Notice/Unimarc.php
@@ -32,7 +32,25 @@ class ZendAfi_View_Helper_Notice_Unimarc extends Zend_View_Helper_HtmlElement {
 		$reader->setNotice($notice->getUnimarc(), 0);
 		$reader->acceptVisitor($this);
 
-		return $this->renderHeader($notice) . '<hr>' . $this->renderZones();
+		return
+			$this->renderHeader($notice)
+			. '<hr>'
+			. $this->renderNotice($notice)
+			. '<hr>'
+			. $this->renderZones();
+	}
+
+
+	protected function renderNotice($notice) {
+		$fields = ['facettes' => $this->_('Facettes'),
+							 'date_creation' => $this->_('Création / nouveauté'),
+							 'date_maj' => $this->_('Mis à jour le'),
+							 'clef_alpha' => $this->_('Clé alpha'),
+							 'clef_oeuvre' => $this->_('Clé oeuvre')];
+		$html = '';
+		foreach ($fields as $field => $label)
+			$html .= $label . ': ' . $notice->callGetterByAttributeName($field) . '<br>';
+		return $html;
 	}
 
 
diff --git a/library/ZendAfi/View/Helper/RecordAlbums.php b/library/ZendAfi/View/Helper/RecordAlbums.php
new file mode 100644
index 0000000000000000000000000000000000000000..0e51666721370e875429b0a592cfcea340d0b545
--- /dev/null
+++ b/library/ZendAfi/View/Helper/RecordAlbums.php
@@ -0,0 +1,61 @@
+<?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_RecordAlbums extends Zend_View_Helper_Abstract {
+	public function recordAlbums($record) {
+		$html = '';
+		foreach ($this->_getGroupedLinks($record) as $from_source => $links)
+			$html .= $this->_renderGroup($from_source, $links);
+
+		return $html;
+	}
+
+
+	protected function _renderGroup($label, $links) {
+		$html = $this->view->tag('div', $label, ['class' => 'notice_info_titre']);
+		foreach ($links as $link)
+			$html .= $this->_renderLink($link);
+
+		return $html;
+	}
+
+
+	protected function _renderLink($link) {
+		if (!$album = $link->getEntityOfType(Class_FRBR_Link::TYPE_ALBUM))
+			return '';
+
+		return $this->view->tag('h3', $album->getTitre())
+			. $this->view->renderAlbum($album);
+	}
+
+
+	protected function _getGroupedLinks($record) {
+		$link_sorted = [];
+		foreach(Class_FRBR_Link::findAllAlbumsFromNotice($record) as $link) {
+			$link_label = $link->getLinkLabelOfEntityType(Class_FRBR_Link::TYPE_NOTICE);
+			$link_sorted[$link_label][] = $link;
+		}
+
+		return $link_sorted;
+	}
+}
+?>
\ No newline at end of file
diff --git a/library/ZendAfi/View/Helper/ShareUrl.php b/library/ZendAfi/View/Helper/ShareUrl.php
index de93d96568f4fd110b31b7d4c2bd2d32e2af0411..bd26e36ea859ba944a7be54efdd6faeba3230440 100644
--- a/library/ZendAfi/View/Helper/ShareUrl.php
+++ b/library/ZendAfi/View/Helper/ShareUrl.php
@@ -16,12 +16,12 @@
  *
  * 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_ShareUrl extends Zend_View_Helper_HtmlElement {
 	static protected $_web_client;
-	protected $reseaux=["facebook" => ["url" => "https://www.facebook.com/sharer/sharer.php?m2w&s=100&p"],
+	protected $reseaux=["facebook" => ["url" => "https://www.facebook.com/sharer/sharer.php"],
 											"twitter"	 => ["url" => "http://twitter.com/share?"]];
 
 	protected $url_shortener="http://is.gd/api.php?longurl=";					// Url pour obtenir une une url courte
@@ -30,12 +30,12 @@ class ZendAfi_View_Helper_ShareUrl extends Zend_View_Helper_HtmlElement {
 	// Rend la structure
 	//------------------------------------------------------------------------------------------------------
 	public function getReseaux($id_reseau=false)	{
-		if($id_reseau) 
+		if($id_reseau)
 			return $this->reseaux[$id_reseau];
-		else 
+		else
 			return $this->reseaux;
 	}
-	
+
 
 	//------------------------------------------------------------------------------------------------------
 	// Rend l'url a passer en parametre
@@ -45,11 +45,7 @@ class ZendAfi_View_Helper_ShareUrl extends Zend_View_Helper_HtmlElement {
 
 		// Url réseau
 		if($id_reseau==='facebook')
-			return $this->reseaux[$id_reseau]["url"].
-				$this->getFacebookUrl($this->shortenUrl($url_afi),
-															$titre, 
-															$message, 
-															$url_img);
+			return $this->reseaux[$id_reseau]["url"];
 
 		if($id_reseau==='twitter')
 			return $this->reseaux[$id_reseau]["url"].
@@ -61,7 +57,7 @@ class ZendAfi_View_Helper_ShareUrl extends Zend_View_Helper_HtmlElement {
 
 	public function shortenUrl($original_url) {
 		$short_url = self::getDefaultWebClient()->open_url($this->url_shortener.urlencode($original_url));
-		if (!$short_url or substr($short_url,0,5)=="Error") 
+		if (!$short_url or substr($short_url,0,5)=="Error")
 			return $original_url;
 		return $short_url;
 	}
@@ -82,17 +78,6 @@ class ZendAfi_View_Helper_ShareUrl extends Zend_View_Helper_HtmlElement {
 		self::$_web_client = null;
 	}
 
-
-	public function getFacebookUrl($url_afi,$titre, $message, $url_img) {
-
-		return http_build_query(['[title]' => $titre,
-														 '[summary]' => $message,
-														 '[url]' => $url_afi,
-														 '[images][0]' =>  $url_img],
-														'','&p');
-	}
-
-	
 	public function getTwitterUrl($url_afi, $titre, $message) {
 
 		return http_build_query(['url' => $url_afi,
@@ -101,5 +86,5 @@ class ZendAfi_View_Helper_ShareUrl extends Zend_View_Helper_HtmlElement {
 														'','&');
 	}
 
-	
+
 }
\ No newline at end of file
diff --git a/library/ZendAfi/View/Helper/SuggestionAchat.php b/library/ZendAfi/View/Helper/SuggestionAchat.php
index 7d6f90abaacfb4c3d8c8091255f471566df0438e..d108b284cbe609b13ff3b6e6a18eab1b30a0c195 100644
--- a/library/ZendAfi/View/Helper/SuggestionAchat.php
+++ b/library/ZendAfi/View/Helper/SuggestionAchat.php
@@ -16,15 +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_SuggestionAchat extends ZendAfi_View_Helper_BaseHelper {
-	
+
 	public function suggestionAchat($suggestion_achat) {
-		$html = '<dl>';
+		$html = '<br /><dl>';
 		$attributes = [$this->view->_('Date de création') => $suggestion_achat->getDateCreation(),
 		$this->view->_('Type de document') => $suggestion_achat->getDocumentType(),
 									 $this->view->_('Titre') => $suggestion_achat->getTitre(),
@@ -34,11 +34,11 @@ class ZendAfi_View_Helper_SuggestionAchat extends ZendAfi_View_Helper_BaseHelper
 									 $this->view->_('Commentaire') => $suggestion_achat->getCommentaire()];
 
 		foreach($attributes as $libelle => $value) {
-			$html.= 
+			$html.=
 				'<dt>'.$libelle.'</dt>'.
 				'<dd>'.$value.'</dd>';
 		}
-		
+
 		return $html.= '</dl>';
 	}
 
diff --git a/library/ZendAfi/View/Helper/TagArticleEvent.php b/library/ZendAfi/View/Helper/TagArticleEvent.php
index d0b8f724657479f9055207b021e805bd23916c36..7498db7f390ae2b2bb1f9bc701344ec3f016d7da 100644
--- a/library/ZendAfi/View/Helper/TagArticleEvent.php
+++ b/library/ZendAfi/View/Helper/TagArticleEvent.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 ZendAfi_View_Helper_TagArticleEvent extends Zend_View_Helper_HtmlElement {
 	/**
@@ -24,7 +24,7 @@ class ZendAfi_View_Helper_TagArticleEvent extends Zend_View_Helper_HtmlElement {
 	 * @return string
 	 */
 	public function tagArticleEvent($article) {
-		return $article->hasEventsDebut() 
+		return $article->hasEventsDebut()
 			? sprintf('<span class="calendar_event_date">%s</span>', $this->formatEventString($article))
 			: '';
 	}
@@ -44,16 +44,16 @@ class ZendAfi_View_Helper_TagArticleEvent extends Zend_View_Helper_HtmlElement {
 		$date_end = $this->getEndDate($article);
 
 		if ($date_start == $date_end) {
-			$event_string = $this->view->_('Le %s', strftime('%d %B %Y', $date_start));
+			$event_string = strftime('%A %d %B %Y', $date_start);
 
-			if ($article->getAllDay()) 
+			if ($article->getAllDay())
 				return $event_string;
-				
+
 			$time_start = date('H:i', strtotime($article->getEventsDebut()));
 			$time_end = date('H:i', strtotime($article->getEventsFin()));
 
 
-			return $event_string . (($time_start == $time_end)	? $this->view->_(' à %s', $time_start) 
+			return $event_string . (($time_start == $time_end)	? $this->view->_(' à %s', $time_start)
 																													: $this->view->_(' de %s à %s', $time_start, $time_end));
 		}
 
@@ -71,8 +71,8 @@ class ZendAfi_View_Helper_TagArticleEvent extends Zend_View_Helper_HtmlElement {
 			$year_start = '';
 
 		return $this->view->_('Du %s au %s',
-				trim(strftime('%d', $date_start) . ' ' . $month_start . ' ' . $year_start),
-				trim(strftime('%d', $date_end) . ' ' . $month_end . ' ' . $year_end));
+				trim(strftime('%A %d', $date_start) . ' ' . $month_start . ' ' . $year_start),
+				trim(strftime('%A %d', $date_end) . ' ' . $month_end . ' ' . $year_end));
 	}
 }
 ?>
diff --git a/library/ZendAfi/View/Helper/TagCriteresRecherche.php b/library/ZendAfi/View/Helper/TagCriteresRecherche.php
index ad9a6bf269da0e98320815be35380ef0baa7bde1..e78627e92c6a75c0057031dcd51c74239f4859f3 100644
--- a/library/ZendAfi/View/Helper/TagCriteresRecherche.php
+++ b/library/ZendAfi/View/Helper/TagCriteresRecherche.php
@@ -16,14 +16,14 @@
  *
  * 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_TagCriteresRecherche extends Zend_View_Helper_HtmlElement {
 	use  Trait_Translator;
 
 	protected $_html = '';
 	protected $_current_facettes = [];
-	
+
 	public function tagCriteresRecherche($criteres_recherche) {
 		$this->visitCriteresRecherche($criteres_recherche);
 		return '<div class="criteres_recherche">'.$this->_html.'</div>';
@@ -36,10 +36,10 @@ class ZendAfi_View_Helper_TagCriteresRecherche extends Zend_View_Helper_HtmlElem
 
 		$this->_current_facettes = $criteres_recherche->getFacettes();
 
-		$this->_libelles_operateur = array("and" => $this->view->_(""), 
+		$this->_libelles_operateur = array("and" => $this->view->_(""),
 																			 "or" => $this->view->_(" ou "),
 																			 "and not" => $this->view->_(" sauf "));
-		$this->_libelles_criteres = 	[ 
+		$this->_libelles_criteres = 	[
 			'titres' => $this->_("Titre"),
 			'auteurs' => ($libelle = Class_AdminVar::get('FACETTE_AUTEUR_LIBELLE')) ? $libelle : $this->_("Auteur"),
 			'matieres' => ($libelle = Class_AdminVar::get('FACETTE_MATIERE_LIBELLE')) ? $libelle :$this->_("Sujet"),
@@ -47,15 +47,15 @@ class ZendAfi_View_Helper_TagCriteresRecherche extends Zend_View_Helper_HtmlElem
 			'editeur' => $this->_("Editeur"),
 			'collection' => $this->_("Collection") ];
 
-		if (isset($criteres_recherche)) 
+		if (isset($criteres_recherche))
 			$criteres_recherche->acceptVisitor($this);
 
-	
+
 	}
 
 	public function visitTextInput($name, $operateur, $type_recherche, $value) {
-	
-		if (!$operateur) 
+
+		if (!$operateur)
 			$libelle_operateur = " et ";
 		else $libelle_operateur = $this->_libelles_operateur[$operateur];
 		$libelle=$libelle_operateur.
@@ -78,9 +78,15 @@ class ZendAfi_View_Helper_TagCriteresRecherche extends Zend_View_Helper_HtmlElem
 		$this->htmlAppend($this->getSuppressionImgUrlForLibelle($libelle,$url));
 	}
 
+
 	public function visitClesNotices($cles_notices) {
 	}
 
+
+	public function visitClesNoticesForOrConditions($cles_notices) {
+	}
+
+
 	public function setErreur($message) {
 
 	}
@@ -124,8 +130,8 @@ class ZendAfi_View_Helper_TagCriteresRecherche extends Zend_View_Helper_HtmlElem
 
 	public function visitAnneeDebutFin($annee_debut, $annee_fin) {
 		$texte = $this->view->_("Documents parus ");
-		$texte .= ($annee_debut == $annee_fin) 
-			? "en " . $annee_debut 
+		$texte .= ($annee_debut == $annee_fin)
+			? "en " . $annee_debut
 			: $this->view->_("entre %s et %s", $annee_debut, $annee_fin);
 		$url = $this->_criteres_recherche->getUrlCriteresWithoutElement('annee_debut');
 		unset($url['annee_fin']);
@@ -188,11 +194,19 @@ class ZendAfi_View_Helper_TagCriteresRecherche extends Zend_View_Helper_HtmlElem
 	}
 
 
-	public function visitPage($page) {
-	}
+	public function visitPage($page) {}
 
-	public function visitTri($tri, $libelle) {
-	}
+
+	public function visitDomain($criteres) {}
+
+
+	public function visitTri($tri, $libelle) {}
+
+
+	public function visitFilterOnDomain($criteres) {}
+
+
+	public function visitFacetteDomainForOrConditions($facette) {}
 }
 
 ?>
\ No newline at end of file
diff --git a/library/ZendAfi/View/Helper/TagFormationVodeclic.php b/library/ZendAfi/View/Helper/TagFormationVodeclic.php
index 05c9a5b422bb0fd035e2ec77645fa726d648da86..ccc890efcba3e38454f50f28864e60e70b2ddaf5 100644
--- a/library/ZendAfi/View/Helper/TagFormationVodeclic.php
+++ b/library/ZendAfi/View/Helper/TagFormationVodeclic.php
@@ -16,12 +16,12 @@
  *
  * 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_TagFormationVodeclic extends ZendAfi_View_Helper_TagRessourceNumerique {
 	use Trait_Translator;
-	
+
 	public function tagFormationVodeclic($album) {
 		$current_user = Class_Users::getIdentity();
 		if (!$this->canAccessRessourceNumerique())
@@ -33,7 +33,7 @@ class ZendAfi_View_Helper_TagFormationVodeclic extends ZendAfi_View_Helper_TagRe
 		$vodeclic = Class_VodeclicLink::forUser($current_user);
 
 		Class_ScriptLoader::getInstance()
-			->addInlineScript("$('.vodeclic_link').click(function(){".
+			->addJQueryReady("$('.vodeclic_link').click(function(){".
 												"var vodeclic_window = window.open('');".
 												"vodeclic_window.document.write(".
 												"\"Veuillez patienter. Connexion en cours...".
@@ -46,7 +46,7 @@ class ZendAfi_View_Helper_TagFormationVodeclic extends ZendAfi_View_Helper_TagRe
 											                 "img_patience.hide();".
                                     "},5000);".
 											  "})");
-		
+
 		return '<button class="vodeclic_link">Accéder à la formation</button>'.
 				'<img  style="display:none" src="'.URL_ADMIN_IMG.'patience.gif">';
 	}
diff --git a/library/ZendAfi/View/Helper/TagModelTable.php b/library/ZendAfi/View/Helper/TagModelTable.php
index 1c3c6b24270ae5700ef6472fcf7cdf86fb25b871..8a14c7fb297bfc8f203846cfcb844996d0fcd89c 100644
--- a/library/ZendAfi/View/Helper/TagModelTable.php
+++ b/library/ZendAfi/View/Helper/TagModelTable.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 ZendAfi_View_Helper_TagModelTable extends Zend_View_Helper_HtmlElement {
 	/** @var boolean */
@@ -28,8 +28,8 @@ class ZendAfi_View_Helper_TagModelTable extends Zend_View_Helper_HtmlElement {
 	/**
 		 Exemple:
 
-		 echo $this->tagModelTable($this->relations, 
-													
+		 echo $this->tagModelTable($this->relations,
+
 													[$this->_('Libellé'), $this->_('De A à B'), $this->_('De B à A')],
 
 													['libelle', 'from_source', 'from_target'],
@@ -44,45 +44,48 @@ class ZendAfi_View_Helper_TagModelTable extends Zend_View_Helper_HtmlElement {
 
 		$this->_hasActions = 0 < count($actions);
 		$this->_cols_count = count($attribs) + ($this->_hasActions ? 1 : 0);
-		
-		return '<table id="'.$id.'" class="models tablesorter">'
-			.$this->head($cols)
-			.$this->tbody($models, $attribs, $actions, $group_by, $callbacks)
-			.'</table>';
+
+		return $this->tag('table',
+											$this->head($cols)
+											.$this->tbody($models, $attribs, $actions, $group_by, $callbacks),
+											['id' => $id,
+											 'class' => 'models tablesorter']);
 	}
 
 
 	public function head($cols) {
 		$cols_html = 	'';
-		foreach ($cols as $col) 
-			$cols_html .= '<th>'.$col.'</th>';
+		foreach ($cols as $col)
+			$cols_html .= $this->tag('th', $col);
 
-		$html = '<thead><tr>'.$cols_html;
-		if (0 < $this->_hasActions)
-			$html .= '<th class="actions" data-sorter="false" style="width:50px;">' . $this->view->_('Actions') . '</th>';
-		$html .= '</tr></thead>';
-		return $html;
+		$actions = $this->_hasActions ? $this->tag('th', $this->view->_('Actions'),
+																							 ['class' => 'actions',
+																								'data-sorter' => 'false',
+																								'style' => 'width:80px;']) : '';
+
+		return $this->tag('thead',
+											$this->tag('tr',  $cols_html . $actions));
 	}
 
 
 	public function tbody($models, $attribs, $actions, $group_by, $callbacks) {
 		$rows = '';
 
-		$groups = array();
+		$groups = [];
 		if (null != $group_by) {
 			foreach ($models as $model) {
 				$group = $model->callGetterByAttributeName($group_by);
 				if (!array_key_exists($group, $groups))
-					$groups[$group] = array();
+					$groups[$group] = [];
 				$groups[$group][] = $model;
 			}
 		} else {
 			$groups['no_group'] = $models;
 		}
 
-
-		$rows = $this->renderGroupsAsTableRows($groups, $attribs, $actions, $callbacks);
-		return '<tbody>'.$rows.'</tbody>';
+		return $this
+			->tag('tbody',
+						$this->renderGroupsAsTableRows($groups, $attribs, $actions, $callbacks));
 	}
 
 
@@ -91,7 +94,11 @@ class ZendAfi_View_Helper_TagModelTable extends Zend_View_Helper_HtmlElement {
 
 		foreach ($groups as $name => $groupModels) {
 			if ('no_group' != $name && '' != $name)
-				$rows .= '<tr><td style="background-color:#888;color:white;font-size:120%;padding:2px 10px;font-weight:bold;" colspan="' . $this->_cols_count . '">' . $this->view->escape($name) . '</td></tr>';
+				$rows .= $this->tag('tr',
+														$this->tag('td',
+																			 $this->view->escape($name),
+																			 ['style' => 'background-color:#888;color:white;font-size:120%;padding:2px 10px;font-weight:bold;',
+																				'colspan' => $this->_cols_count]));
 
 			$rows .= $this->renderModelsAsTableRows($groupModels, $attribs, $actions, $callbacks);
 		}
@@ -106,7 +113,7 @@ class ZendAfi_View_Helper_TagModelTable extends Zend_View_Helper_HtmlElement {
 			$rows .= $this->renderModelAsTableRow($model, $attribs, $actions, $callbacks);
 		return $rows;
 	}
-	
+
 
 	public function renderModelAsTableRow($model, $attribs, $actions, $callbacks) {
 		$cols = '';
@@ -114,22 +121,20 @@ class ZendAfi_View_Helper_TagModelTable extends Zend_View_Helper_HtmlElement {
 		$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 .= '<td>' . $callback($model, $attrib) . '</td>';
+			$cols .= $this->tag('td', $callback($model, $attrib));
 		}
 
-		$row = '<tr>'.$cols.'<td>';
-		if ($this->_hasActions)
-			$row .= $this->renderModelActions($model, $actions).'</td>';
-		$row .= '</tr>';
+		$actions = ($this->_hasActions) ?
+			$this->tag('td', $this->renderModelActions($model, $actions)) : '';
 
-		return $row;
+		return $this->tag('tr', $cols . $actions);
 	}
 
 
-	/*
+	/**
 	 * @param Storm_Model_Abstract $model
 	 * @param array of arrays of string / Closure $actions
 	 * @return string
@@ -143,7 +148,7 @@ class ZendAfi_View_Helper_TagModelTable extends Zend_View_Helper_HtmlElement {
 	}
 
 
-	/*
+	/**
 	 * @param Storm_Model_Abstract $model
 	 * @param array of strings/closure or closure
 	 * @return string
@@ -151,13 +156,18 @@ class ZendAfi_View_Helper_TagModelTable extends Zend_View_Helper_HtmlElement {
 	public function renderModelAction($model, $action) {
 		if (is_a($action, 'Closure'))
 			return $action($model);
-			
+
 		$content = $action['content'];
 		return  $this->view->tagAnchor(array('action' => $action['action'],
 																				 'id' => $model->getId()),
 																	 is_a($content, 'Closure') ? $content($model) : $content);
 	}
+
+
+	/** shortcut to $this->view->tag() */
+	protected function tag() {
+		return call_user_func_array([$this->view, 'tag'], func_get_args());
+	}
 }
 
 ?>
-
diff --git a/library/ZendAfi/View/Helper/Telephone/Tags/NoticeDetaillee.php b/library/ZendAfi/View/Helper/Telephone/Tags/NoticeDetaillee.php
index cff1ba99698370af5e66236798325febc22a3b1a..652e7c13607906aa36ce671a47cf5bdc62a37f34 100644
--- a/library/ZendAfi/View/Helper/Telephone/Tags/NoticeDetaillee.php
+++ b/library/ZendAfi/View/Helper/Telephone/Tags/NoticeDetaillee.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
  */
 
 
@@ -26,11 +26,11 @@ class ZendAfi_View_Helper_Telephone_Tags_NoticeDetaillee extends ZendAfi_View_He
 										$this->view->_('Titre(s)') => $notice->getTitrePrincipal(),
 										$this->view->_('Auteur(s)') => $notice->getAuteurPrincipal(),
 										$this->view->_('Editeur(s)') => $notice->getEditeur(),
-										$this->view->_('Collation') => $notice->getCollation(), 
-										$this->view->_('Collection(s)') => $notice->getCollection(), 
-										$this->view->_('Année') => $notice->getAnnee(), 
-										$this->view->_('Isbn') => $notice->getIsbn(), 
-										$this->view->_('Ean') => $notice->getEan(), 
+										$this->view->_('Collation') => $notice->getCollation(),
+										$this->view->_('Collection(s)') => $notice->getCollections(),
+										$this->view->_('Année') => $notice->getAnnee(),
+										$this->view->_('Isbn') => $notice->getIsbn(),
+										$this->view->_('Ean') => $notice->getEan(),
 										$this->view->_('Langue(s)') => $notice->getLanguesList(),
 										$this->view->_('Notes(s)') => $notice->getNotes()
 										);
@@ -41,7 +41,7 @@ class ZendAfi_View_Helper_Telephone_Tags_NoticeDetaillee extends ZendAfi_View_He
 
 	public function _fielsToHTMLTable($fields) {
 		$rows = array();
-		foreach ($fields as $label => $field) 
+		foreach ($fields as $label => $field)
 			$rows [] = sprintf('<tr><td>%s : </td><td>%s</td></tr>',
 												 $label,
 												 $this->_formatField($field));
@@ -53,13 +53,13 @@ class ZendAfi_View_Helper_Telephone_Tags_NoticeDetaillee extends ZendAfi_View_He
 		if (!$field) return '';
 		if (!is_array($field)) return $field;
 
-		$html = '<ul>'; 
-		foreach($field as $subfield) 
+		$html = '<ul>';
+		foreach($field as $subfield)
 			$html .= '<li>'.$subfield.'</li>';
-		$html .= '<ul>'; 
+		$html .= '<ul>';
 		return $html;
 	}
-	
+
 }
 
 ?>
\ No newline at end of file
diff --git a/library/ZendAfi/View/Helper/TreeView.php b/library/ZendAfi/View/Helper/TreeView.php
index 779f5c9f004bc4524ed873ec080ecdc8fb070218..c23a4a3b59f84ea3acf2adc44ac9e02bfcd53dbb 100644
--- a/library/ZendAfi/View/Helper/TreeView.php
+++ b/library/ZendAfi/View/Helper/TreeView.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 ZendAfi_View_Helper_TreeView extends Zend_View_Helper_Abstract {
@@ -29,9 +29,12 @@ class ZendAfi_View_Helper_TreeView extends Zend_View_Helper_Abstract {
 	/** @var array */
 	protected $_itemActions;
 
-	/** Abstract_TreeViewRenderItem  */
+	/** @var Abstract_TreeViewRenderItem  */
 	protected $_item_render_strategy;
 
+	/** @var callable */
+	protected $_containers_filter;
+
 
 	public function renderItemWithIconeSupport() {
 		$this->_item_render_strategy = new TreeViewRenderItemWithIconeSupportStrategy($this->view);
@@ -44,12 +47,14 @@ class ZendAfi_View_Helper_TreeView extends Zend_View_Helper_Abstract {
 	 * @return string
 	 */
 	public function treeView(array $elements,
-													 array $containerActions = array(),
-													 array $itemActions = array(),
-													 $withWorkflow = true) {
+													 array $containerActions = [],
+													 array $itemActions = [],
+													 $withWorkflow = true,
+													 $containers_filter = null) {
 		$this->_containerActions = $containerActions;
 		$this->_itemActions = $itemActions;
 		$this->_is_workflow = $withWorkflow && Class_AdminVar::isWorkflowEnabled();
+		$this->_containers_filter = $containers_filter;
 
 		if (0 == count($elements))
 			return '';
@@ -65,18 +70,23 @@ class ZendAfi_View_Helper_TreeView extends Zend_View_Helper_Abstract {
 
 
 	public function _renderStatusSelector() {
-		$html = '<div class="treeViewSearchStatus" style="margin:5px 0;float:right;">'
-		. $this->view->_('Filtrer par statut : ');
-		$statuses = array($this->view->tagAnchor(
-			'#', $this->view->_('Tous'), array('data-filter' => 'status-all')
-		));
+		$html = $this->view->_('Filtrer par statut : ');
+
+		$statuses = [$this->view
+								 ->tagAnchor('#',
+														 $this->view->_('Tous'),
+														 ['data-filter' => 'status-all']
+								 )];
+
 		foreach (Class_Article::getKnownStatus() as $k => $v) {
-			$statuses[] = $this->view->tagAnchor(
-				'#', $v, array('data-filter' => 'status-' . $k)
-			);
+			$statuses[] = $this->view
+				->tagAnchor('#', $v, ['data-filter' => 'status-' . $k]);
 		}
 
-		return $html . ' ' . implode(' - ', $statuses) . '</div>';
+		return $this->view->tag('div',
+														$html . ' ' . implode(' - ', $statuses),
+														['class' => 'treeViewSearchStatus',
+														 'style' => 'margin:5px 0;' . (Class_Article::hasCustomStatuses() ? '' : 'float:right;')]);
 	}
 
 
@@ -96,7 +106,7 @@ class ZendAfi_View_Helper_TreeView extends Zend_View_Helper_Abstract {
 
 			foreach ($data['containers'] as $container)
 				$html .= $this->_renderContainer($container);
-		 
+
 			$html .= '</ul></div>';
 		}
 
@@ -109,6 +119,10 @@ class ZendAfi_View_Helper_TreeView extends Zend_View_Helper_Abstract {
 	 * @return string
 	 */
 	protected function _renderContainer($container) {
+		if (($filter = $this->_containers_filter)
+				&& !$filter($container))
+			return '';
+
 		$html = '<li class="categorie">'
 						. '<div>' . $this->view->tagImg(URL_ADMIN_IMG . 'ico/cat.gif') . '</div>'
 						. '<div class="label">' . $this->view->tagAnchor(
@@ -125,7 +139,7 @@ class ZendAfi_View_Helper_TreeView extends Zend_View_Helper_Abstract {
 
 		if ($container->hasChildren()) {
 			$html .= '<ul style="display:none;" id="child-of-' . $container->getId() . '">';
-			
+
 			foreach ($container->getSousCategories() as $subContainer)
 				$html .= $this->_renderContainer($subContainer);
 
@@ -154,7 +168,7 @@ class ZendAfi_View_Helper_TreeView extends Zend_View_Helper_Abstract {
 	protected function _renderItem($item) {
 		$html = $this->getItemRenderStrategy()->render($item);
     $html .= $this->_renderItemActions($item);
-		$title = 'title="'.$item->getTitleInfo().'"'; 
+		$title = 'title="'.$item->getTitleInfo().'"';
 		$class = 'class="item'.($this->_is_workflow ? ' status-' . $item->getStatus() : '').'"';
 		return '<li ' . $class .' '. $title .'>' . $html . '</li>';
 	}
@@ -186,51 +200,7 @@ class ZendAfi_View_Helper_TreeView extends Zend_View_Helper_Abstract {
 	 * @return string
 	 */
 	protected function _renderActions($type, $model) {
-		$html = '';
-
-		foreach ($this->{'_' . $type . 'Actions'} as $key => $action) {
-			if (array_key_exists('condition', $action)){
-				$methodName = $action['condition'];
-
-				if (!$model->{$methodName}()) {
-					continue;
-				}
-			}
-
-			if (array_key_exists('caption', $action)) {
-				$action['caption'] = $model->{$action['caption']}();
-			}
-
-			$action['url'] = sprintf($action['url'], $model->getId());
-
-			if (isset($action['icon']) && is_a($action['icon'], 'Closure'))
-				$action['icon'] = $action['icon']($model);
-
-			$html .= $this->_renderAction($action);
-		}
-
-		return '<div class="actions">' . $html . '</div>';
-	}
-
-
-	/**
-	 * @param array $options
-	 * @return string
-	 */
-	protected function _renderAction(array $options) {
-		$anchorOptions = array();
-		if (array_key_exists('anchorOptions', $options)) {
-			$anchorOptions = array_merge($anchorOptions, $options['anchorOptions']);
-		}
-
-		$content = $this->view->tagImg(URL_ADMIN_IMG . $options['icon'],
-																	 ['alt' => $options['label'], 
-																		'title' => $options['label'], 
-																		'class' => 'ico']);
-		if (array_key_exists('caption', $options))
-			$content .= $options['caption'];
-
-		return $this->view->tagAnchor($options['url'], $content, $anchorOptions);
+		return $this->view->modelActions($model, $this->{'_' . $type . 'Actions'});
 	}
 }
 
diff --git a/library/startup.php b/library/startup.php
index 746f92dc87f52617f466b9eb77da125f020b3230..e89c8ac62463fc6274483fde3f0834f5628503f2 100644
--- a/library/startup.php
+++ b/library/startup.php
@@ -43,21 +43,20 @@ function setupOpac() {
 
 	$front_controller = setupFrontController($cfg);
 	setupPagination();
+	setupRestful();
 	return $front_controller;
 }
 
 
-
 function defineConstant($name, $value) {
 	if (!defined($name))
 		define($name, $value);
 }
 
 
-
 function setupConstants() {
-	defineConstant('BOKEH_MAJOR_VERSION','6.59');
-	defineConstant('BOKEH_RELEASE_NUMBER', BOKEH_MAJOR_VERSION . '.0');
+	defineConstant('BOKEH_MAJOR_VERSION','7.1');
+	defineConstant('BOKEH_RELEASE_NUMBER', BOKEH_MAJOR_VERSION . '.11');
 
 	defineConstant('ROOT_PATH',  realpath(dirname(__FILE__).'/..').'/');
 
@@ -101,6 +100,7 @@ function setupConstants() {
 	defineConstant('PATH_FONTS', ROOT_PATH . 'public/opac/fonts/');
 	defineConstant('URL_CAPTCHA', BASE_URL . '/public/captcha/');
 	defineConstant('PATH_CAPTCHA', ROOT_PATH . 'public/captcha/');
+	defineConstant('CACHE_LIFETIME', 3600);
 	defineConstant('MEMCACHED_ENABLE', false);
 	defineConstant('MEMCACHED_HOST', 'localhost');
 	defineConstant('MEMCACHED_PORT', '11211');
@@ -128,13 +128,13 @@ function loadConfig($config_init_file_path = './config.ini') {
 }
 
 
-
-
 function setupCache($cfg) {
-	$frontendOptions = [
-		'lifetime' => 3600, // durée du cache: 1h
-		'automatic_serialization' => false,
-		'caching' => true];
+	if (Class_Users::isCurrentUserCanAccesBackend())
+		return;
+
+	$frontendOptions = ['lifetime' => CACHE_LIFETIME, // durée du cache: 1h
+											'automatic_serialization' => false,
+											'caching' => true];
 
 	$use_memcached = (MEMCACHED_ENABLE === true);
 	$backendOptions = $use_memcached
@@ -148,14 +148,12 @@ function setupCache($cfg) {
 															 $frontendOptions,
 															 $backendOptions);
 	//	$cache->clean(Zend_Cache::CLEANING_MODE_ALL);
-	Zend_Registry::set('cache', $cache);
+
 	Storm_Cache::setDefaultZendCache($cache);
 	Storm_Cache::setSeed($cfg->sgbd->config->dbname);
 }
 
 
-
-
 function setupSession($cfg) {
 	// Start Session
 	$session = new Zend_Session_Namespace(md5(BASE_URL));
@@ -169,7 +167,6 @@ function setupSession($cfg) {
 }
 
 
-
 function setupLanguage() {
 	Zend_Locale::setDefault(Class_AdminVar::getDefaultLanguage());
 	Zend_Registry::set('locale', new Zend_Locale());
@@ -186,8 +183,6 @@ function setupLanguage() {
 }
 
 
-
-
 function setupDatabase($cfg) {
 	// setup database
 	$sql = Zend_Db::factory($cfg->sgbd->adapter, $cfg->sgbd->config->toArray());
@@ -201,7 +196,6 @@ function setupDatabase($cfg) {
 }
 
 
-
 function setupDevOptions($cfg) {
 	//permet d'activer les fonctions en développement
 	if (null !== $experimental_dev = $cfg->get('experimental_dev'))
@@ -209,8 +203,6 @@ function setupDevOptions($cfg) {
 }
 
 
-
-
 function setupControllerActionHelper() {
 	Zend_Controller_Action_HelperBroker::resetHelpers();
 	Zend_Controller_Action_HelperBroker::addHelper(new ZendAfi_Controller_Action_Helper_ViewRenderer());
@@ -218,8 +210,6 @@ function setupControllerActionHelper() {
 }
 
 
-
-
 function setupHTTPClient($cfg) {
 	//set up HTTP Client to use proxy settings
 	$httpClient = new Zend_Http_Client();
@@ -230,7 +220,7 @@ function setupHTTPClient($cfg) {
 												 'proxy_port' => $cfg->proxy->port,
 												 'proxy_user' => $cfg->proxy->user,
 												 'proxy_pass' => $cfg->proxy->pass
-												 );
+		);
 		Zend_Registry::set('http_proxy',$proxyConfig);
 
 		$proxy_adapter = new Zend_Http_Client_Adapter_Proxy();
@@ -293,15 +283,15 @@ function setupFrontController($cfg) {
 	return $front_controller
 		->registerPlugin(new ZFDebug_Controller_Plugin_Debug(['plugins' =>
 																													[
-																														'Variables',
-																														'Constants',
-																														'Html',
-																														'Database',
-																														'Memory',
-																														'Time',
-																														'Exception']
-																													 ]
-											 ));
+																													 'Variables',
+																													 'Constants',
+																													 'Html',
+																													 'Database',
+																													 'Memory',
+																													 'Time',
+																													 'Exception']
+																													]
+																												 ));
 }
 
 
@@ -319,16 +309,19 @@ function setupRoutes($front_controller, $cfg) {
 		->addRoute('embed',
 							 new Zend_Controller_Router_Route(
 																								'embed/:controller/:action/*',
-																								array('module' => 'telephone',
-																											'controller' => 'index',
-																											'action' => 'index')))
+																								['module' => 'telephone',
+																								 'controller' => 'index',
+																								 'action' => 'index']))
 		->addRoute('flash',
-							 new Zend_Controller_Router_Route(
-																								'flash/:action/*',
-																								array('module' => 'opacpriv',
-																											'controller' => 'flash',
-																											'action' => 'index')));
-
+							 new Zend_Controller_Router_Route('flash/:action/*',
+																								['module' => 'opacpriv',
+																								 'controller' => 'flash',
+																								 'action' => 'index']))
+		->addRoute('sitemap',
+							 new Zend_Controller_Router_Route_Static('sitemap.xml',
+																											 ['module' => 'opac',
+																												'controller' => 'index',
+																												'action' => 'sitemap']));
 	return $front_controller;
 }
 
@@ -337,3 +330,18 @@ function setupPagination() {
 	Zend_Paginator::setDefaultScrollingStyle('Sliding');
 	Zend_View_Helper_PaginationControl::setDefaultViewPartial('pagination.phtml');
 }
+
+
+function setupRestful() {
+	if (!(new Class_Testing_FileSystem())->file_exists(ROOT_PATH . 'restful.php'))
+		return;
+
+	set_include_path(ROOT_PATH . '/restful/library' . PATH_SEPARATOR .
+									 get_include_path());
+
+	$config = new Zend_Config(include ROOT_PATH . 'restful.php');
+	Restful_Model_Configuration::setConfig($config);
+	Restful_Bootstrap::asPlugin(Zend_Controller_Front::getInstance(),
+															'rest',
+															Zend_Controller_Action_HelperBroker::getExistingHelper('ViewRenderer'));
+}
diff --git a/library/storm b/library/storm
index 7b829d906775a1b51a7ab4d0f495d09ec1225be5..aa890c69508b0c9f62cb2313f9ae8269890d9ce1 160000
--- a/library/storm
+++ b/library/storm
@@ -1 +1 @@
-Subproject commit 7b829d906775a1b51a7ab4d0f495d09ec1225be5
+Subproject commit aa890c69508b0c9f62cb2313f9ae8269890d9ce1
diff --git a/public/admin/css/global.css b/public/admin/css/global.css
index a9d3386ce348b5b435d1bfc666a4b5422c42dc07..8be9bafdfec0ba1674eaef1c32c8aba880f160a6 100644
--- a/public/admin/css/global.css
+++ b/public/admin/css/global.css
@@ -148,7 +148,7 @@ div.boutons .bouton {
 }
 
 .form table {border:none;}
-.form td.gauche {text-align:left;padding-left:2px;}
+.form td.gauche {text-align:left;padding-left:2px;width:100%;}
 .form td.droite, .form td label.droite {text-align:right;padding-right:2px;font-weight:inherit;font-size:8pt;color:#575757; padding-top:5px;width:120px}
 
 .form td{padding-right:0px;font-size:8pt;color:#575757;}
@@ -1207,6 +1207,14 @@ span.ui-dialog-title {
     background: url(../images/patience.gif) no-repeat top left;
 }
 
+#suggestions { 
+  table-layout: fixed;
+}
+
+#suggestions td {
+  word-break: break-all;
+  word-wrap: break-word;
+}
 
 
 /** monocle preview epub */
diff --git a/public/admin/js/multi_inputs/multi_inputs.js b/public/admin/js/multi_inputs/multi_inputs.js
index bbc4fc93496bc5a34f2461270cb5d86a6ae3ada6..d1367e58ce29f1ce4fbecff0a50a485e4a5c05c1 100644
--- a/public/admin/js/multi_inputs/multi_inputs.js
+++ b/public/admin/js/multi_inputs/multi_inputs.js
@@ -24,10 +24,10 @@
     if (!options.fields || !options.values) {
       return;
     }
-    
+
     if (typeof imagesUrl === 'undefined')
       imagesUrl = '../../images/';
-    
+
     // header
     var header = [];
     for (var key in options.fields) {
@@ -61,22 +61,44 @@
 	var inputs = [];
 	for (var key in options.fields) {
 	  var input = options.fields[key];
-	  var value = (null != value_index && value_index < options.values[input.name].length) ? 
-	    options.values[input.name][value_index]: '';
-	  var node = $('<input type="text" />')
-	    .attr('name', input.name + '[]')
-	    .attr('value', value);
-	  
-	  for (var attrib_name in input.attribs) {
-	    if (input.attribs.hasOwnProperty(attrib_name)) {
-	      node.attr(attrib_name,input.attribs[attrib_name]);
+	  var value = (null != value_index && value_index < options.values[input.name].length) ?
+	    options.values[input.name][value_index] : '';
+	  var desc = (null != value_index
+								&& null != options.descs
+								&& input.name in options.descs
+								&& value_index < options.descs[input.name].length) ?
+	    options.descs[input.name][value_index] : '';
+	  var html = input.type == 'select' ? '<select></select>' : '<input type="text" />';
+	  var node = $(html).attr('name', input.name + '[]');
+
+	  if (input.type != 'select')
+	    node.attr('value', value);
+
+	  if (input.options) {
+	    for (var option_value in input.options) {
+	      var option = $('<option value="' + option_value + '">' + input.options[option_value] + '</option>');
+	      if (option_value == value)
+		option.attr('selected', 'selected');
+	      node.append(option);
 	    }
 	  }
-	  inputs[inputs.length] = $('<div/>').append( node ).html();
+	
+	  for (var attrib_name in input.attribs)
+	    if (input.attribs.hasOwnProperty(attrib_name))
+	      node.attr(attrib_name,input.attribs[attrib_name]);
+
+	  inputs[inputs.length] = $('<div/>').append( node ).append(desc).html();
 	}
 
-	var inputs_str = '<tr><td>' + inputs.join('</td><td>') + '</td><td data-type="actions"></td></tr>';
-	$(table).find('tbody').append(inputs_str);
+	var inputs_str = '<tr><td style="vertical-align:top;">' 
+	  + inputs.join('</td><td style="vertical-align:top;">') 
+	  + '</td><td data-type="actions"></td></tr>';
+	var tbody = $(table).find('tbody');
+	tbody.append(inputs_str);
+	if (null != value_index && options.line_errors && options.line_errors[value_index])
+	  tbody.append('<tr><td colspan="' + (inputs.length + 1) +'">' 
+		       + '<ul class="errors">' 
+		       + '<li>' + options.line_errors[value_index].join('</li><li>') + '</li></ul></td></tr>');
       },
 
       picto_add: function(td) {
diff --git a/public/admin/js/multi_inputs/test.js b/public/admin/js/multi_inputs/test.js
index 901d076d419e9b4256af850b856212639fac5490..65bdd674533eb416d1335f96ff400fcc77a12647 100644
--- a/public/admin/js/multi_inputs/test.js
+++ b/public/admin/js/multi_inputs/test.js
@@ -36,7 +36,8 @@ test('plugin is defined', function() {
 test('one input', function () {
   var insertion_point = call_multi_inputs_for({
     fields:[{name:'testing', label:'Test label'}],
-    values:{testing:[]}
+    values:{testing:[]},
+    descs:{testing:[]}
   });
   equal(insertion_point.find('input[name="testing[]"]').length, 1, 'one input in ' + insertion_point.html());
   equal(insertion_point.find('img[src*="ico/add.gif"]').length, 1, 'one add image');
@@ -48,30 +49,55 @@ test('one input', function () {
 test('two inputs', function () {
   var insertion_point = call_multi_inputs_for({
     fields:[{name:'testing'}, {name:'testing2'}],
-    values:{testing:[], testing2:[]}
+    values:{testing:[], testing2:[]},
+    descs:{testing2:[]}
   });
   equal(insertion_point.find('input[name="testing[]"]').length, 1, 'first input');
   equal(insertion_point.find('input[name="testing2[]"]').length, 1, 'second input');
 });
 
 
+test('one select input', function () {
+  var insertion_point = call_multi_inputs_for({
+    fields:[{name:'testing', type:'select', options:{1:'opt1', 3:'opt3', 8:'opt8'}}],
+    values:{testing:[3]}
+  });
+  equal(insertion_point.find('select[name="testing[]"]').length, 1, 'first input');
+  equal(insertion_point.find('select[name="testing[]"]').children('option[value="1"]').length, 1, 'opt1');
+  equal(insertion_point.find('select[name="testing[]"]').children('option[value="3"][selected="selected"]').length, 1, 'opt3 selected');
+});
+
+
 test('one input and two values', function () {
   var insertion_point = call_multi_inputs_for({
     fields:[{name:'testing'}], 
-    values:{testing:['value1', 'value2']}
+    values:{testing:['value1', 'value2']},
+    descs:{testing:['', '<a href="testing.com">Test</a>']}
   });
   equal(insertion_point.find('input[name="testing[]"]').length, 2, '2 inputs with []');
   equal(insertion_point.find('img[src*="ico/add.gif"]').length, 1, 'one add image');
   equal(insertion_point.find('img[src*="ico/del.gif"]').length, 1, 'one delete image');
+  equal(insertion_point.find('input[value="value2"]').siblings('a[href="testing.com"]').length, 
+	1, 'one desc');
+});
+
+
+test('one input with error message', function () {
+  var insertion_point = call_multi_inputs_for({
+    fields:[{name:'testing'}], 
+    values:{testing:['value1', 'value2']},
+    descs:{testing:['', '']},
+    line_errors:[["You should not do that"]]
+  });
+  equal(insertion_point.find('ul[class="errors"]').length, 1, 'one error message' + insertion_point.html());
 });
 
+
 test('one input with styles', function() {
   var insertion_point = call_multi_inputs_for({
     fields:[{name:'testing', attribs: {style:'width:20px'}}], 
-    values:{testing:['value1', 'value2']}
-    
+    values:{testing:['value1', 'value2']},
+    descs:{testing:['', '']}
   });
   equal(insertion_point.find('input[name="testing[]"][style="width:20px"]').length, 2, '2 inputs with []');
-
-
 });
diff --git a/public/admin/js/onload_utils.js b/public/admin/js/onload_utils.js
index 68ae717966c3cffd2b03423ddbbd466e02dfc1e7..f3c8223bf513e6d5688928aceb71180b961c8822 100644
--- a/public/admin/js/onload_utils.js
+++ b/public/admin/js/onload_utils.js
@@ -51,7 +51,8 @@ var setupAnchorsTarget = function() {
 	var internalLink = new RegExp('/' + window.location.host + '/');
 	$('a[href^="http"]').each(function() {
 		if (!internalLink.test($(this).attr('href')) && (undefined == this.onclick)  && (undefined == $(this).data('events') || undefined == $(this).data('events').click)) {
-			if ($.browser.msie) { //Sinon IE n'envoie pas le HTTP REFERRER
+			if ($.browser.msie || !!navigator.userAgent.match(/Trident/)) {
+        // Otherwise IE doesn't send HTTP Referer
 				this.target = '_blank';
 				return;
 			}
diff --git a/public/opac/css/global.css b/public/opac/css/global.css
index 37f0c91f172bda4e10c50e42de7e0b535ee825d6..18e385023957b4aba9fdea59513219a21ff8390a 100644
--- a/public/opac/css/global.css
+++ b/public/opac/css/global.css
@@ -2461,17 +2461,13 @@ div.suggestion-achat-liste span {
 }
 
 div.suggestion-achat-liste div {
-    display: block;
-    min-height:180px;
-    border-top: 2px solid grey ;
-    margin: 5px;
-    padding:5px;
+    clear:both;
 }
 
 div.suggestion-achat-liste dl {
     padding:5px;
-    line-height:25px;
-    font-size:1.2em;
+    line-height:20px;
+    border-top: 2px solid grey;
 }
 
 div.suggestion-achat-liste dt {
@@ -2479,7 +2475,7 @@ div.suggestion-achat-liste dt {
     clear:left;
     font-weight:bold;
     text-align:right;
-    width: 125px;
+    width: 200px;
     padding-right:0px;
     padding-left:30px;
 }
diff --git a/public/opac/js/accessibility.js b/public/opac/js/accessibility.js
index 75e474d840c786f6e604d8454d0be1f06202fb07..4efd8eccb6c735190bb228137c60011a5bdbd425 100644
--- a/public/opac/js/accessibility.js
+++ b/public/opac/js/accessibility.js
@@ -77,7 +77,7 @@ var initAccessibilityOptions = function() {
 
 
 		font_size_display = $('<span>' + bodyFontSize() + '</span>');
-		var font_size_reset = $('<button style="float: right">Taille par défaut</button>').click(
+		var font_size_reset = $('<button>Taille par défaut</button>').click(
 			function() {
 				$('#slider-font-size').slider('option', 'value', default_font_size);
 				updateFontSize(default_font_size);
@@ -86,8 +86,8 @@ var initAccessibilityOptions = function() {
 
 		$('<div style="margin: 15px 0px 10px 0px; font-size: 1.2em">Taille de la police: </div>').
 			appendTo(dialog).
-			append(font_size_reset).
-			append(font_size_display);
+			append(font_size_display).
+			append(font_size_reset);
 
 		$('<div id="slider-font-size"></div>').
 			appendTo(dialog).
diff --git a/scripts/emacs/phafi-mode.el b/scripts/emacs/phafi-mode.el
index c54f4c6cf25f2292aa731653c97160891d40d354..3d0b35b7ff86940a648f6fc9e00d31319b7a4f43 100644
--- a/scripts/emacs/phafi-mode.el
+++ b/scripts/emacs/phafi-mode.el
@@ -93,7 +93,7 @@
   (setq ac-sources '(ac-source-gtags ac-source-words-in-buffer ac-source-php-auto-yasnippets))
   (imenu-add-menubar-index)
   (phafi-init-menu)
-  (phafi-enable-autotest)
+;;(phafi-enable-autotest)
   (setq
    magit-diff-options '("-w")
    flymake-mode t
diff --git a/scripts/import_typo3.php b/scripts/import_typo3.php
index 13c0e102fae4c412b47f7b0f7b266e48766d25c9..07068d665db6b1a25005e0a0f028bdd7b6ca101a 100644
--- a/scripts/import_typo3.php
+++ b/scripts/import_typo3.php
@@ -2,8 +2,8 @@
 /*
  * Launch import_typo3.php like:
  *    php import_typo3.php <arg>
- *    <arg> can be users (import users only), articles (import articles only) or all (import all)
- *
+ *    <arg> can be users (import users only), docu(update dossiers documentaires only), articles (import articles only) or all (import all)
+ *   php import_typo3.php update <last_update_date 20-11-2015 >
  * Script import data in a database named miop_typo3. To change it, edit the line 109.
  */
 
@@ -12,7 +12,22 @@ require('console.php');
 $toRun = $argv[1];
 
 Class_Import_Typo3_Logs::getInstance()->activateOutput();
+Class_CustomField_Model::registerAll([
+																			new Class_CustomField_ModelConfiguration_Article(),
+																			new Class_CustomField_ModelConfiguration_Sitotheque(),
+																			new Class_CustomField_ModelConfiguration_SitothequeCategorie(),
+																			new Class_CustomField_ModelConfiguration_Catalogue(),
+																			new Class_CustomField_ModelConfiguration_ArticleCategorie()]);
 
 $migration = new Class_Import_Typo3();
-$migration->run($toRun);
+
+if ($toRun=="update" && sizeof($argv)<3) {
+	echo "Missing last update date\n";
+	return;
+}
+
+if (sizeof($argv)>2)
+	return $migration->run($toRun,$argv[2]);
+
+return $migration->run($toRun);
 ?>
\ No newline at end of file
diff --git a/scripts/utf8miop.sql b/scripts/utf8miop.sql
new file mode 100644
index 0000000000000000000000000000000000000000..aea0ca5e648daf1d28807d951deff8893d3bd478
--- /dev/null
+++ b/scripts/utf8miop.sql
@@ -0,0 +1,12 @@
+UPDATE tt_content SET short = CONVERT(CAST(CONVERT(short USING latin1) AS binary) USING utf8); 
+UPDATE tt_news SET bodytext = CONVERT(CAST(CONVERT(bodytext USING latin1) AS binary) USING utf8);
+UPDATE tt_news SET short = CONVERT(CAST(CONVERT(short USING latin1) AS binary) USING utf8);                                                                                      
+UPDATE tt_news SET category = CONVERT(CAST(CONVERT(category USING latin1) AS binary) USING utf8);                                                                                      
+
+ UPDATE tt_content SET bodytext = CONVERT(CAST(CONVERT(bodytext USING latin1) AS binary) USING utf8);                                                                             
+ UPDATE pages  SET title = CONVERT(CAST(CONVERT(title USING latin1) AS binary) USING utf8);                                                                                                                
+ UPDATE tt_news_cat  SET title = CONVERT(CAST(CONVERT(title USING latin1) AS binary) USING utf8);                                                                                                                
+ UPDATE tt_news  SET title = CONVERT(CAST(CONVERT(title USING latin1) AS binary) USING utf8);                                                                                                              
+ UPDATE tt_content  SET title = CONVERT(CAST(CONVERT(title USING latin1) AS binary) USING utf8);    
+ UPDATE tx_cal_category  SET title = CONVERT(CAST(CONVERT(title USING latin1) AS binary) USING utf8);    
+
diff --git a/tests/application/modules/AbstractControllerTestCase.php b/tests/application/modules/AbstractControllerTestCase.php
index f7a5ccec8db8cfc74680d7e901b9b9f2fe2a36eb..c8513965ba1ac6cbce78f33fa7ced1fbd347f09c 100644
--- a/tests/application/modules/AbstractControllerTestCase.php
+++ b/tests/application/modules/AbstractControllerTestCase.php
@@ -219,15 +219,10 @@ abstract class AbstractControllerTestCase extends Zend_Test_PHPUnit_ControllerTe
 		if ($content == null)
 			$content = $this->_response->getBody();
 
-		$httpClient = new Zend_Http_Client();
-		$httpClient->setUri('https://html5.validator.nu/');
-		$httpClient->setMethod(Zend_Http_Client::POST);
-		$httpClient->setEncType(Zend_Http_Client::ENC_FORMDATA);
-		$httpClient->setParameterPost('out', 'text');
-		$httpClient->setParameterPost('parser', 'html5');
-		$httpClient->setParameterPost('content', $content);
-		$httpClient->setParameterPost('showsource', 'yes');
-		$httpClient->setParameterPost('showoutline', 'yes');
+		$httpClient = (new Zend_Http_Client('http://sandbox.pergame.net/html-validator/'))
+			->setMethod(Zend_Http_Client::POST)
+			->setEncType(Zend_Http_Client::ENC_FORMDATA)
+			->setParameterPost('content', $content);
 		$response = $httpClient->request()->getBody();
 
 		$content_lines = explode("\n", $content);
@@ -236,7 +231,7 @@ abstract class AbstractControllerTestCase extends Zend_Test_PHPUnit_ControllerTe
 			$content_with_lines .= sprintf("%4s %s\n", $i + 1, $line);
 		}
 
-		$this->assertContains('is valid HTML', $response, $content_with_lines);
+		$this->assertContains('No errors found', $response, $content_with_lines);
 	}
 
 
diff --git a/tests/application/modules/admin/controllers/AccueilControllerConteneurDeuxColonnesTest.php b/tests/application/modules/admin/controllers/AccueilControllerConteneurDeuxColonnesTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..989a0429674f1866fff0ce60926ef0cec5ea2df0
--- /dev/null
+++ b/tests/application/modules/admin/controllers/AccueilControllerConteneurDeuxColonnesTest.php
@@ -0,0 +1,316 @@
+<?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
+ */
+
+abstract class AccueilControllerConteneurDeuxColonnesTestCase
+	extends Admin_AbstractControllerTestCase {
+	protected $_request_url = '/admin/accueil/conteneur2colonnes?id_profil=34&id_module=3&type_module=CONTENEUR_DEUX_COLONNES&config=accueil';
+
+	public function setUp() {
+		parent::setUp();
+
+		Class_AdminVar::newInstanceWithId('MENU_BOITE', ['valeur' => 1]);
+
+		$this
+			->fixture('Class_Profil', ['id' => 1,
+																 'libelle' => 'Main'])
+			->addBoiteOfTypeInBanniere('NEWS')
+			->save();
+
+		$this->profil_biologie = $this
+			->fixture('Class_Profil',
+								['id' => 34,
+								 'parent_id' => 1,
+								 'libelle' => 'Biologie'])
+			->updateModuleConfigAccueil(3,
+																	['type_module' => 'CONTENEUR_DEUX_COLONNES',
+																	 'division' => 2,
+																	 'id_module' => 3,
+																	 'preferences' => ['col_gauche_type' => 'NEWS',
+																										 'col_droite_type' => 'CRITIQUES',
+																										 'boite' => 'boite_de_la_division_du_milieu',
+																										 'titre' => 'A la Une']]);
+		$this->profil_biologie->save();
+		$this->profil_biologie->beCurrentProfil();
+	}
+}
+
+
+
+class AccueilControllerConteneurDeuxColonnesEmptyRenderTest extends AccueilControllerConteneurDeuxColonnesTestCase {
+	public function setUp() {
+		parent::setUp();
+		$this->dispatch('/index/index/id_profil/34', true);
+	}
+
+		/** @test */
+	public function firsModuleShouldHaveId1000() {
+		$this->assertXPath('//div[@class="conteneur2colonnes"]//div[@id="boite_1000"]');
+	}
+
+
+	/** @test */
+	public function secondModuleShouldHaveId1001() {
+		$this->assertXPath('//div[@class="conteneur2colonnes"]//div[@id="boite_1001"]');
+	}
+
+	/** @test */
+	public function secondModuleShouldBeCritics() {
+		$this->assertXPath('//div[@class="conteneur2colonnes"]//div[@class="boite critiques"]');
+	}
+
+
+	/** @test */
+	public function moduleNewsShouldHaveParentid3() {
+		$config = $this->profil_biologie->getModuleAccueilConfig(1000, 'NEWS');
+		$this->assertEquals(3, $config['parent_id']);
+	}
+}
+
+
+
+
+class AccueilControllerConteneurDeuxColonnesUpdateNewsTest extends AccueilControllerConteneurDeuxColonnesTestCase {
+	public function setUp() {
+		parent::setUp();
+
+		$this
+			->profil_biologie
+			->updateModuleConfigAccueil(3,
+																	['type_module' => 'CONTENEUR_DEUX_COLONNES',
+																	 'division' => 2,
+																	 'id_module' => 3,
+																	 'preferences' => ['col_gauche_type' => 'NEWS',
+																										 'col_gauche_module_id' => 1000,
+																										 'col_droite_type' => 'CRITIQUES',
+																										 'boite' => 'boite_de_la_division_du_milieu',
+																										 'titre' => 'A la Une']])
+			->updateModuleConfigAccueil(1000,
+																	['type_module' => 'NEWS',
+																	 'id_module' => 1000,
+																	 'parent_id' => 3,
+																	 'preferences' => ['titre' => 'Les infos']])
+			->save();
+	}
+
+
+	/** @test */
+	public function renderedModuleNewsTitleShouldBeLesInfos() {
+		$this->dispatch('/index/index/id_profil/34', true);
+		$this->assertXPathContentContains('//div[@class="boite news"]//div', 'Les infos',
+																			$this->_response->getBody());
+	}
+
+	/** @test */
+	public function updatedModuleNewsTitleShouldBeDesNews() {
+		$this->postDispatch('/admin/accueil/news/config/accueil/id_profil/34/id_module/1000/type_module/NEWS',
+												['id_items' => '34-12',
+												 'titre' => 'des news']);
+		$config = $this->profil_biologie->getModuleAccueilConfig(1000, 'NEWS');
+		$this->assertEquals('des news', $config['preferences']['titre']);
+	}
+}
+
+
+
+
+class AccueilControllerConteneurDeuxColonnesDefaultTest extends AccueilControllerConteneurDeuxColonnesTestCase {
+	public function setUp() {
+		parent::setUp();
+		$this->dispatch($this->_request_url, true);
+	}
+
+
+	/** @test */
+	public function profilShouldBeValid() {
+		$this->assertTrue($this->profil_biologie->isValid());
+	}
+
+
+	/** @test */
+	public function shouldRenderConteneur2ColonnesView() {
+		$this->assertController('accueil');
+		$this->assertAction('conteneur2colonnes');
+	}
+
+
+	/** @test */
+	public function inputTitleShouldDisplayALaUne() {
+		$this->assertXPath("//input[@name='titre'][@value='A la Une']");
+	}
+
+
+	/** @test */
+	public function selectColGaucheTypeShouldHaveNEWSSelected() {
+		$this->assertXPath("//select[@name='col_gauche_type']/option[@value='NEWS'][@selected='selected']");
+	}
+
+
+	/** @test */
+	public function selectColDroiteTypeShouldHaveCRITIQUESSelected() {
+		$this->assertXPath("//select[@name='col_droite_type']/option[@value='CRITIQUES'][@selected='selected']");
+	}
+}
+
+
+
+class AccueilControllerConteneurDeuxColonnesPostTest extends AccueilControllerConteneurDeuxColonnesTestCase {
+	public function setUp() {
+		parent::setUp();
+		$this->postDispatch($this->_request_url, ['col_gauche_type' => 'KIOSQUE',
+																							'col_droite_type' => 'TAGS',
+																							'titre' => 'Ce mois ci']);
+	}
+
+
+	/** @test */
+	public function moduleKiosqueShouldHaveBeenCreated() {
+		$this->assertEquals('KIOSQUE', $this->_getModuleTypeOf(1000, 'KIOSQUE'));
+	}
+
+
+	/** @test */
+	public function moduleKiosqueParentIdShouldBe() {
+		$this->assertEquals(3,
+												$this->profil_biologie->getModuleAccueilConfig(1000, 'KIOSQUE')['parent_id']);
+	}
+
+
+	/** @test */
+	public function moduleKiosqueShouldHaveBeenAddedToBox() {
+		$this->assertEquals(1000,
+												$this->_getModulePrefOf(3, 'col_gauche_module_id', 'CONTENEUR_DEUX_COLONNES'));
+	}
+
+
+	/** @test */
+	public function moduleTagsShouldHaveBeenCreated() {
+		$this->assertEquals('TAGS', $this->_getModuleTypeOf(1001, 'TAGS'));
+	}
+
+
+	/** @test */
+	public function moduleTagsShouldHaveBeenAddedToBox() {
+		$this->assertEquals(1001,
+												$this->_getModulePrefOf(3, 'col_droite_module_id', 'CONTENEUR_DEUX_COLONNES'));
+	}
+
+
+	/** @test */
+	public function moduleTagShouldHaveParentId3() {
+		$config = $this->profil_biologie->getModuleAccueilConfig(1001, 'TAG');
+		$this->assertEquals(3, $config['parent_id']);
+	}
+
+
+	protected function _getModuleTypeOf($id_module, $type) {
+		return $this->profil_biologie
+			->getModuleAccueilConfig($id_module, $type)['type_module'];
+	}
+
+
+	protected function _getModulePrefOf($id_module, $pref, $type) {
+		return $this->profil_biologie
+			->getModuleAccueilConfig($id_module, $type)['preferences'][$pref];
+	}
+}
+
+
+
+class AccueilControllerConteneurDeuxColonnesNewTest extends Admin_AbstractControllerTestCase {
+	public function setUp() {
+		parent::setUp();
+
+		$this->boite2cols = array('type_module' => 'CONTENEUR_DEUX_COLONNES',
+															'division' => 2,
+															'id_module' => 3);
+
+		$this->profil_biologie = Class_Profil::getLoader()
+			->newInstanceWithId(34)
+			->setMenuHautOn(false)
+			->setLibelle('Biologie')
+			->updateModuleConfigAccueil(3, $this->boite2cols);
+
+		Class_Profil::setCurrentProfil($this->profil_biologie);
+		$this->assertTrue($this->profil_biologie->isValid());
+	}
+
+
+	/** @test */
+	public function shouldRenderConteneur2ColonnesView() {
+		$this->request_url = '/admin/accueil/conteneur2colonnes?id_profil=34&id_module=3&type_module=CONTENEUR_DEUX_COLONNES&config=accueil';
+		$this->dispatch($this->request_url);
+
+		$this->assertController('accueil');
+		$this->assertAction('conteneur2colonnes');
+	}
+
+
+	/** @test */
+	public function fonctionAdminOnClickShouldHaveActionConteneur2Colonnes() {
+		$this->dispatch('/opac?id_profil=34');
+		$this->assertXPath("//img[contains(@onclick, 'conteneur2colonnes')]", $this->_response->getBody());
+	}
+
+
+	/** @test */
+	public function withModuleIdModulesShouldBeCreated() {
+		$this->boite2cols['preferences'] = ['col_gauche_module_id' => 234,
+																				'col_gauche_type' => 'COMPTEURS',
+																				'col_droite_module_id' => 235,
+																				'col_droite_type' => 'RECH_GUIDEE'];
+
+		$this->profil_biologie->updateModuleConfigAccueil(3, $this->boite2cols);
+		$this->dispatch('/opac?id_profil=34');
+
+		$this->assertXPathContentContains('//div', 'Le catalogue contient');
+		$this->assertXPath('//div[contains(@class,"conteneur_deux_colonnes")]//div[contains(@class,"rech_guide")]');
+	}
+}
+
+
+
+
+class AccueilControllerConteneurDeuxColonnesEditPostTest extends AccueilControllerConteneurDeuxColonnesTestCase {
+	public function setUp() {
+		parent::setUp();
+		$this->postDispatch($this->_request_url, ['col_gauche_type' => 'NEWS',
+																							'col_droite_type' => 'CRITIQUES',
+																							'titre' => 'Ce mois ci']);
+
+		$this->postDispatch($this->_request_url, ['col_gauche_type' => 'NEWS',
+																							'col_droite_type' => 'NEWS',
+																							'titre' => 'Ce mois ci']);
+
+	}
+
+
+	/** @test */
+	public function newsWidgetInLeftColShouldRemainTheSame() {
+		$this->assertEquals(1000, $this->profil_biologie->getCfgAccueilAsArray()['modules'][3]['preferences']['col_gauche_module_id']);
+	}
+
+
+	/** @test */
+	public function newsWidgetInRightColShouldBeNew() {
+		$this->assertEquals(1002, $this->profil_biologie->getCfgAccueilAsArray()['modules'][3]['preferences']['col_droite_module_id']);
+	}
+
+}
\ No newline at end of file
diff --git a/tests/application/modules/admin/controllers/AccueilControllerFormationsTest.php b/tests/application/modules/admin/controllers/AccueilControllerFormationsTest.php
index 5352c37be5542036ed1cf1503862ffb3c3ba3dd5..e95931f167eeafea35e53dca4f76117293eeb1ad 100644
--- a/tests/application/modules/admin/controllers/AccueilControllerFormationsTest.php
+++ b/tests/application/modules/admin/controllers/AccueilControllerFormationsTest.php
@@ -16,23 +16,18 @@
  *
  * 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 Admin_AccueilControllerFormationsTestWithDefaultSettings extends Admin_AbstractControllerTestCase {		
+class Admin_AccueilControllerFormationsTestWithDefaultSettings extends Admin_AbstractControllerTestCase {
 
 	public function setUp() {
 		parent::setUp();
-		
-		$this->fixture('Class_Profil', 
+
+		$this->fixture('Class_Profil',
 									 ['id' => 1,
-										'libelle' => 'Afibre'])
-			->updateModuleConfigAccueil(1, ['type_module' => 'FORMATIONS_WIDGET',
-																			'division' => 1,
-																			'id_module' => 1,
-																			'preferences' => ['selected_formations' => '']])
-			->beCurrentProfil();
+										'libelle' => 'Afibre'])->beCurrentProfil();
 
 		$this->dispatch('admin/accueil/formations-widget?config=accueil&id_profil=1&id_module=1&type_module=FORMATIONS_WIDGET', true);
 	}
@@ -43,10 +38,10 @@ class Admin_AccueilControllerFormationsTestWithDefaultSettings extends Admin_Abs
 		$this->assertXPathContentContains('//select/option', 'Boite par défaut de la division');
 	}
 
-	
+
 	/** @test */
 	public function treeselectFormationsBeDisplay() {
-		$this->assertXPath('//div[@class="treeselect"]', $this->_response->getBody());
+		$this->assertXPath('//div[@class="treeselect"]');
 	}
 
   /** @test **/
@@ -58,12 +53,12 @@ class Admin_AccueilControllerFormationsTestWithDefaultSettings extends Admin_Abs
 
 
 
-class Admin_AccueilControllerFormationsPostTest extends Admin_AbstractControllerTestCase {		
+class Admin_AccueilControllerFormationsPostTest extends Admin_AbstractControllerTestCase {
 
 	public function setUp() {
 		parent::setUp();
-		
-		$this->fixture('Class_Profil', 
+
+		$this->fixture('Class_Profil',
 									 ['id' => 1,
 										'libelle' => 'Afibre'])
 			->updateModuleConfigAccueil(1, ['type_module' => 'FORMATIONS_WIDGET',
@@ -71,15 +66,15 @@ class Admin_AccueilControllerFormationsPostTest extends Admin_AbstractController
 																			'id_module' => 1])
 			->beCurrentProfil();
 
-		$this->postDispatch('admin/accueil/formations-widget?config=accueil&id_profil=1&id_module=1&type_module=FORMATIONS_WIDGET', 
+		$this->postDispatch('admin/accueil/formations-widget?config=accueil&id_profil=1&id_module=1&type_module=FORMATIONS_WIDGET',
 												['selected_formations' => '1;2']);
 	}
 
-			
+
 	/** @test */
 	public function formations1And2ShouldHaveBeenSaved() {
 		$this->assertEquals(
-			'1;2', 
+			'1;2',
 			Class_Profil::getCurrentProfil()
 			->getModuleAccueilConfig(1, 'FORMATIONS_WIDGET')['preferences']['selected_formations']);
 	}
diff --git a/tests/application/modules/admin/controllers/AccueilControllerTest.php b/tests/application/modules/admin/controllers/AccueilControllerTest.php
index 1999e1edbf47647daef8b276cf1ec74008813491..65318879f5974e8bc9a87391c57969a262d4ce11 100644
--- a/tests/application/modules/admin/controllers/AccueilControllerTest.php
+++ b/tests/application/modules/admin/controllers/AccueilControllerTest.php
@@ -21,276 +21,6 @@
 require_once 'AdminAbstractControllerTestCase.php';
 
 
-abstract class AccueilControllerBoite2ColTestCase
-	extends Admin_AbstractControllerTestCase {
-	protected $_request_url = '/admin/accueil/conteneur2colonnes?id_profil=34&id_module=3&type_module=CONTENEUR_DEUX_COLONNES&config=accueil';
-
-	public function setUp() {
-		parent::setUp();
-
-		Class_AdminVar::newInstanceWithId('MENU_BOITE', ['valeur' => 1]);
-
-		$this
-			->fixture('Class_Profil', ['id' => 1,
-																 'libelle' => 'Main'])
-			->addBoiteOfTypeInBanniere('NEWS')
-			->save();
-
-		$this->profil_biologie = $this
-			->fixture('Class_Profil',
-								['id' => 34,
-								 'parent_id' => 1,
-								 'libelle' => 'Biologie'])
-			->updateModuleConfigAccueil(3,
-																	['type_module' => 'CONTENEUR_DEUX_COLONNES',
-																	 'division' => 2,
-																	 'id_module' => 3,
-																	 'preferences' => ['col_gauche_type' => 'NEWS',
-																										 'col_droite_type' => 'CRITIQUES',
-																										 'boite' => 'boite_de_la_division_du_milieu',
-																										 'titre' => 'A la Une']]);
-		$this->profil_biologie->save();
-		$this->profil_biologie->beCurrentProfil();
-	}
-}
-
-
-
-class AccueilControllerEmptyBoite2ColRenderTest extends AccueilControllerBoite2ColTestCase {
-	public function setUp() {
-		parent::setUp();
-		$this->dispatch('/index/index/id_profil/34', true);
-	}
-
-		/** @test */
-	public function firsModuleShouldHaveId1000() {
-		$this->assertXPath('//div[@class="conteneur2colonnes"]//div[@id="boite_1000"]');
-	}
-
-
-	/** @test */
-	public function secondModuleShouldHaveId1001() {
-		$this->assertXPath('//div[@class="conteneur2colonnes"]//div[@id="boite_1001"]');
-	}
-
-	/** @test */
-	public function secondModuleShouldBeCritics() {
-		$this->assertXPath('//div[@class="conteneur2colonnes"]//div[@class="boite critiques"]');
-	}
-
-
-	/** @test */
-	public function moduleNewsShouldHaveParentid3() {
-		$config = $this->profil_biologie->getModuleAccueilConfig(1000, 'NEWS');
-		$this->assertEquals(3, $config['parent_id']);
-	}
-}
-
-
-
-
-class AccueilControllerBoite2ColUpdateNewsTest extends AccueilControllerBoite2ColTestCase {
-	public function setUp() {
-		parent::setUp();
-
-		$this
-			->profil_biologie
-			->updateModuleConfigAccueil(3,
-																	['type_module' => 'CONTENEUR_DEUX_COLONNES',
-																	 'division' => 2,
-																	 'id_module' => 3,
-																	 'preferences' => ['col_gauche_type' => 'NEWS',
-																										 'col_gauche_module_id' => 1000,
-																										 'col_droite_type' => 'CRITIQUES',
-																										 'boite' => 'boite_de_la_division_du_milieu',
-																										 'titre' => 'A la Une']])
-			->updateModuleConfigAccueil(1000,
-																	['type_module' => 'NEWS',
-																	 'id_module' => 1000,
-																	 'parent_id' => 3,
-																	 'preferences' => ['titre' => 'Les infos']])
-			->save();
-	}
-
-
-	/** @test */
-	public function renderedModuleNewsTitleShouldBeLesInfos() {
-		$this->dispatch('/index/index/id_profil/34', true);
-		$this->assertXPathContentContains('//div[@class="boite news"]//div', 'Les infos',
-																			$this->_response->getBody());
-	}
-
-	/** @test */
-	public function updatedModuleNewsTitleShouldBeDesNews() {
-		$this->postDispatch('/admin/accueil/news/config/accueil/id_profil/34/id_module/1000/type_module/NEWS',
-												['id_items' => '34-12',
-												 'titre' => 'des news']);
-		$config = $this->profil_biologie->getModuleAccueilConfig(1000, 'NEWS');
-		$this->assertEquals('des news', $config['preferences']['titre']);
-	}
-}
-
-
-
-
-class AccueilControllerBoite2ColTest extends AccueilControllerBoite2ColTestCase {
-	public function setUp() {
-		parent::setUp();
-		$this->dispatch($this->_request_url, true);
-	}
-
-
-	/** @test */
-	public function profilShouldBeValid() {
-		$this->assertTrue($this->profil_biologie->isValid());
-	}
-
-
-	/** @test */
-	public function shouldRenderConteneur2ColonnesView() {
-		$this->assertController('accueil');
-		$this->assertAction('conteneur2colonnes');
-	}
-
-
-	/** @test */
-	public function inputTitleShouldDisplayALaUne() {
-		$this->assertXPath("//input[@name='titre'][@value='A la Une']");
-	}
-
-
-	/** @test */
-	public function selectColGaucheTypeShouldHaveNEWSSelected() {
-		$this->assertXPath("//select[@name='col_gauche_type']/option[@value='NEWS'][@selected='selected']");
-	}
-
-
-	/** @test */
-	public function selectColDroiteTypeShouldHaveCRITIQUESSelected() {
-		$this->assertXPath("//select[@name='col_droite_type']/option[@value='CRITIQUES'][@selected='selected']");
-	}
-}
-
-
-
-
-class AccueilControllerBoite2ColPostTest extends AccueilControllerBoite2ColTestCase {
-	public function setUp() {
-		parent::setUp();
-		$this->postDispatch($this->_request_url, ['col_gauche_type' => 'KIOSQUE',
-																							'col_droite_type' => 'TAGS',
-																							'titre' => 'Ce mois ci']);
-	}
-
-
-	/** @test */
-	public function moduleKiosqueShouldHaveBeenCreated() {
-		$this->assertEquals('KIOSQUE', $this->_getModuleTypeOf(1000, 'KIOSQUE'));
-	}
-
-
-	/** @test */
-	public function moduleKiosqueParentIdShouldBe() {
-		$this->assertEquals(3,
-												$this->profil_biologie->getModuleAccueilConfig(1000, 'KIOSQUE')['parent_id']);
-	}
-
-
-	/** @test */
-	public function moduleKiosqueShouldHaveBeenAddedToBox() {
-		$this->assertEquals(1000,
-												$this->_getModulePrefOf(3, 'col_gauche_module_id', 'CONTENEUR_DEUX_COLONNES'));
-	}
-
-
-	/** @test */
-	public function moduleTagsShouldHaveBeenCreated() {
-		$this->assertEquals('TAGS', $this->_getModuleTypeOf(1001, 'TAGS'));
-	}
-
-
-	/** @test */
-	public function moduleTagsShouldHaveBeenAddedToBox() {
-		$this->assertEquals(1001,
-												$this->_getModulePrefOf(3, 'col_droite_module_id', 'CONTENEUR_DEUX_COLONNES'));
-	}
-
-
-	/** @test */
-	public function moduleTagShouldHaveParentId3() {
-		$config = $this->profil_biologie->getModuleAccueilConfig(1001, 'TAG');
-		$this->assertEquals(3, $config['parent_id']);
-	}
-
-
-	protected function _getModuleTypeOf($id_module, $type) {
-		return $this->profil_biologie
-			->getModuleAccueilConfig($id_module, $type)['type_module'];
-	}
-
-
-	protected function _getModulePrefOf($id_module, $pref, $type) {
-		return $this->profil_biologie
-			->getModuleAccueilConfig($id_module, $type)['preferences'][$pref];
-	}
-}
-
-
-
-class AccueilControllerNewBoite2ColTest extends Admin_AbstractControllerTestCase {
-	public function setUp() {
-		parent::setUp();
-
-		$this->boite2cols = array('type_module' => 'CONTENEUR_DEUX_COLONNES',
-															'division' => 2,
-															'id_module' => 3);
-
-		$this->profil_biologie = Class_Profil::getLoader()
-			->newInstanceWithId(34)
-			->setMenuHautOn(false)
-			->setLibelle('Biologie')
-			->updateModuleConfigAccueil(3, $this->boite2cols);
-
-		Class_Profil::setCurrentProfil($this->profil_biologie);
-		$this->assertTrue($this->profil_biologie->isValid());
-	}
-
-
-	/** @test */
-	public function shouldRenderConteneur2ColonnesView() {
-		$this->request_url = '/admin/accueil/conteneur2colonnes?id_profil=34&id_module=3&type_module=CONTENEUR_DEUX_COLONNES&config=accueil';
-		$this->dispatch($this->request_url);
-
-		$this->assertController('accueil');
-		$this->assertAction('conteneur2colonnes');
-	}
-
-
-	/** @test */
-	public function fonctionAdminOnClickShouldHaveActionConteneur2Colonnes() {
-		$this->dispatch('/opac?id_profil=34');
-		$this->assertXPath("//img[contains(@onclick, 'conteneur2colonnes')]", $this->_response->getBody());
-	}
-
-
-	/** @test */
-	public function withModuleIdModulesShouldBeCreated() {
-		$this->boite2cols['preferences'] = array('col_gauche_module_id' => 234,
-																						 'col_gauche_type' => 'COMPTEURS',
-																						 'col_droite_module_id' => 235,
-																						 'col_droite_type' => 'RECH_GUIDEE');
-
-		$this->profil_biologie->updateModuleConfigAccueil(3, $this->boite2cols);
-		$this->dispatch('/opac?id_profil=34');
-
-		$this->assertXPathContentContains('//div', 'Le catalogue contient');
-		$this->assertXPathContentContains('//div', 'Recherche guidée');
-	}
-}
-
-
-
-
 class AccueilControllerLangueConfigurationTest extends Admin_AbstractControllerTestCase {
 	public function setUp() {
 		parent::setUp();
@@ -505,15 +235,29 @@ class AccueilControllerConfigSitothequeDefaultsTest extends Admin_AbstractContro
 		$this->assertXPath("//input[@name='nb_aff'][@value='2']");
 	}
 
+
 	/** @test */
 	public function checkboxGroupByCategorieShouldNotBeChecked() {
 		$this->assertXPath('//input[@type="checkbox"][@name="group_by_categorie"][not(@checked)]');
 	}
 
+
 	/** @test */
 	public function inputHiddenIdItemsShouldContainsSelectedItems() {
 		$this->assertXPath('//input[@type="hidden"][@name="id_items"][@value="12-13"]');
 	}
+
+
+	/** @test */
+	public function displayOrderRandomShouldBeChecked() {
+		$this->assertXPath('//input[@type="radio"][@name="display_order"][@value="Random"]');
+	}
+
+	/** @test */
+	public function displayOrderSelectionShouldBePresent() {
+		$this->assertXPath('//input[@type="radio"][@name="display_order"][@value="Selection"]');
+	}
+
 }
 
 
@@ -525,11 +269,12 @@ class AccueilControllerConfigSitothequeWithPreferencesTest extends Admin_Abstrac
 
 		Class_Profil::getCurrentProfil()
 			->updateModuleConfigAccueil(25,
-																	array('type_module' => 'SITO',
-																				'division' => 1,
-																				'id_module' => 25,
-																				'preferences' => array('titre' => 'Ma Sito',
-																															 'group_by_categorie' => true)));
+																	['type_module' => 'SITO',
+																	 'division' => 1,
+																	 'id_module' => 25,
+																	 'preferences' => ['titre' => 'Ma Sito',
+																										 'group_by_categorie' => true,
+																										 'display_order' => 'Selection']]);
 
 		$this->dispatch('/admin/accueil/sitotheque?config=accueil&type_module=SITO&id_module=25');
 	}
@@ -545,6 +290,12 @@ class AccueilControllerConfigSitothequeWithPreferencesTest extends Admin_Abstrac
 	public function checkboxGroupByCategorieShouldNotBeChecked() {
 		$this->assertXPath('//input[@type="checkbox"][@name="group_by_categorie"][@checked="checked"]');
 	}
+
+
+	/** @test */
+	public function displayOrderSelectionShouldBeChecked() {
+		$this->assertXPath('//input[@type="radio"][@name="display_order"][@checked="checked"][@value="Selection"]');
+	}
 }
 
 
@@ -793,7 +544,7 @@ class AccueilControllerWithParentProfileConfigBoiteLoginPostTest
 
 
 
-class AccueilControllerPostConfigBoiteKiosqueProfilLognesTestCase extends Admin_AbstractControllerTestCase {
+class AccueilControllerPostConfigBoiteKiosqueProfilLognesTest extends Admin_AbstractControllerTestCase {
 	public function setUp() {
 		parent::setUp();
 
diff --git a/tests/application/modules/admin/controllers/AdminIndexControllerTest.php b/tests/application/modules/admin/controllers/AdminIndexControllerTest.php
index cf6bc88df238d7fef828a54a30eb9cb14e98f9e6..461e2aa8cd21bd89e30cd9d51b3c7202a7c99807 100644
--- a/tests/application/modules/admin/controllers/AdminIndexControllerTest.php
+++ b/tests/application/modules/admin/controllers/AdminIndexControllerTest.php
@@ -246,6 +246,162 @@ class AdminIndexControllerAdminVarEditModoBlogActionTest extends Admin_AbstractC
 }
 
 
+abstract class AdminIndexControllerAdminVarEditWithWorkflowTestCase extends Admin_AbstractControllerTestCase {
+	public function setUp() {
+		parent::setUp();
+		Class_Permission::beVolatile();
+	}
+}
+
+
+
+class AdminIndexControllerAdminVarEditWorkflowPostTest extends AdminIndexControllerAdminVarEditWithWorkflowTestCase {
+	public function setUp() {
+		parent::setUp();
+		$this->fixture('Class_AdminVar', ['id' => 'WORKFLOW', 'valeur' => '']);
+		$this->postDispatch('/admin/index/adminvaredit/cle/WORKFLOW',
+												['cle' => 'WORKFLOW',
+												 'valeur' => '[{"id":"1", "label":"Testing test"}]']);
+	}
+
+
+	/** @test */
+	public function errorShouldBeNotified() {
+		$this->assertFlashMessengerContentContains('Erreur(s) : Le statut 1 est réservé par Bokeh, variable WORKFLOW NON sauvegardée');
+	}
+}
+
+
+
+class AdminIndexControllerAdminVarEditWorkflowPostPermissionTest extends AdminIndexControllerAdminVarEditWithWorkflowTestCase {
+	public function setUp() {
+		parent::setUp();
+		$this->fixture('Class_AdminVar', ['id' => 'WORKFLOW', 'valeur' => '']);
+	}
+
+
+	/** @test */
+	public function rightForStatus12ShouldBeCreated() {
+		$this->postDispatch('/admin/index/adminvaredit/cle/WORKFLOW',
+												['cle' => 'WORKFLOW',
+												 'valeur' => '[{"id":"12", "label":"Testing test"}]']);
+		$this->assertNotNull(Class_Permission::findDynamicWorkflow(12));
+	}
+
+
+	/** @test */
+	public function rightForStatus13ShouldBeCreated() {
+		$this->postDispatch('/admin/index/adminvaredit/cle/WORKFLOW',
+												['cle' => 'WORKFLOW',
+												 'valeur' => '[{"id":"13", "label":"Testing test 2"}]']);
+		$this->assertNotNull(Class_Permission::findDynamicWorkflow(13));
+	}
+
+
+	/** @test */
+	public function rightForStatus12ShouldNotBeCreated() {
+		$this->fixture('Class_AdminVar', ['id' => 'WORKFLOW',
+																			'valeur' => '[{"id":"12", "label":"Testing test"}]']);
+		$this->_fixDynamicWorkflowPermission(1, 12, 'Testing test');
+
+		$this->postDispatch('/admin/index/adminvaredit/cle/WORKFLOW',
+												['cle' => 'WORKFLOW',
+												 'valeur' => '[{"id":"12", "label":"Testing test"},{"id":"13", "label":"Testing test 2"}]']);
+		$this->assertEquals(1, Class_Permission::countBy(['code' => 'DYNAMIC_12']));
+	}
+
+
+	/** @test */
+	public function rightForStatus12ShouldBeDeleted() {
+		$this->fixture('Class_AdminVar', ['id' => 'WORKFLOW',
+																			'valeur' => '[{"id":"12", "label":"Testing test"}]']);
+		$this->_fixDynamicWorkflowPermission(1, 12, 'Testing test');
+
+		$this->postDispatch('/admin/index/adminvaredit/cle/WORKFLOW',
+												['cle' => 'WORKFLOW',
+												 'valeur' => '1']);
+		$this->assertNull(Class_Permission::findDynamicWorkflow(12));
+	}
+
+
+	/** @test */
+	public function rightForStatus12ShouldNotBeDeleted() {
+		$this->fixture('Class_AdminVar', ['id' => 'WORKFLOW',
+																			'valeur' => '[{"id":"12", "label":"Testing test"},{"id":"13", "label":"Testing test 2"}]']);
+		$this->_fixDynamicWorkflowPermission(1, 12, 'Testing test');
+		$this->_fixDynamicWorkflowPermission(2, 13, 'Testing test 2');
+
+		$this->postDispatch('/admin/index/adminvaredit/cle/WORKFLOW',
+												['cle' => 'WORKFLOW',
+												 'valeur' => '[{"id":"13", "label":"Testing test 2"}]']);
+
+		$this->assertNull(Class_Permission::findFirstBy(['code' => 'DYNAMIC_12']));
+		$this->assertNotNull(Class_Permission::findFirstBy(['code' => 'DYNAMIC_13']));
+	}
+
+
+	protected function _fixDynamicWorkflowPermission($id, $status_id, $label) {
+		$this->fixture('Class_Permission', ['id' => $id,
+																				'code' => 'DYNAMIC_' . $status_id,
+																				'module' => 'ARTICLE',
+																				'type' => 'Workflow',
+																				'label' => $label]);
+	}
+}
+
+
+
+class AdminIndexControllerAdminVarEditWorkflowWithUsedPostTest extends AdminIndexControllerAdminVarEditWithWorkflowTestCase {
+	public function setUp() {
+		parent::setUp();
+		$this->fixture('Class_AdminVar', ['id' => 'WORKFLOW',
+																			'valeur' => '[{"id":"10", "label":"Testing test"}]']);
+
+		$this->fixture('Class_Article',
+									 ['id' => 99,
+										'status' => "10",
+										'titre' => 'Arnaud Afi power',
+										'contenu' => 'It\'s alive!']);
+
+		$this->postDispatch('/admin/index/adminvaredit/cle/WORKFLOW',
+												['cle' => 'WORKFLOW',
+												 'valeur' => '[{"id":"78", "label":"Other test"}]']);
+	}
+
+
+	/** @test */
+	public function errorShouldBeNotified() {
+		$this->assertFlashMessengerContentContains('Le statut 10 est utilisé par un article et ne peut être supprimé');
+	}
+}
+
+
+
+class AdminIndexControllerAdminVarEditWorkflowGoSimpleWithUsedPostTest extends AdminIndexControllerAdminVarEditWithWorkflowTestCase {
+	public function setUp() {
+		parent::setUp();
+		$this->fixture('Class_AdminVar', ['id' => 'WORKFLOW',
+																			'valeur' => '[{"id":"10", "label":"Testing test"}]']);
+
+		$this->fixture('Class_Article',
+									 ['id' => 99,
+										'status' => "10",
+										'titre' => 'Arnaud Afi power',
+										'contenu' => 'It\'s alive!']);
+
+		$this->postDispatch('/admin/index/adminvaredit/cle/WORKFLOW',
+												['cle' => 'WORKFLOW',
+												 'valeur' => '1']);
+	}
+
+
+	/** @test */
+	public function errorShouldBeNotified() {
+		$this->assertFlashMessengerContentContains('Le statut 10 est utilisé par un article et ne peut être supprimé');
+	}
+}
+
+
 
 
 class AdminIndexControllerAdminVarEditResaConditionActionTest extends Admin_AbstractControllerTestCase {
diff --git a/tests/application/modules/admin/controllers/AlbumControllerPharoVideosTest.php b/tests/application/modules/admin/controllers/AlbumControllerPharoVideosTest.php
index a0f8d1b216cf52135c7969e59f32600dd749ac28..b644964ebb1336eeff6add240e324d1126dcc4a2 100644
--- a/tests/application/modules/admin/controllers/AlbumControllerPharoVideosTest.php
+++ b/tests/application/modules/admin/controllers/AlbumControllerPharoVideosTest.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
  */
 require_once 'AdminAbstractControllerTestCase.php';
 
@@ -29,7 +29,7 @@ abstract class Admin_AlbumControllerPharoVideosTestCase extends Admin_AbstractCo
 																																												 'libelle' => 'Languages de prog.'])
 										 ]);
 		Class_AlbumRessource::beVolatile();
-		
+
 
 	}
 }
@@ -75,6 +75,7 @@ class Admin_AlbumControllerPharoVideosPostAddTest extends Admin_AlbumControllerP
 
 	public function setUp() {
 		parent::setUp();
+		Class_Exemplaire::beVolatile();
 		$this->postDispatch('/admin/album/add-ressource/id/777', ['url' => 'http://www.pharocast.org',
 																															'media_type' => 3,
 																															'duration' => '00:20:00',
diff --git a/tests/application/modules/admin/controllers/AlbumControllerTest.php b/tests/application/modules/admin/controllers/AlbumControllerTest.php
index 4c35c0289b3583847ebb63189436b6fffd95077f..3097862887d8574370f20b19e5cf04129093fd45 100644
--- a/tests/application/modules/admin/controllers/AlbumControllerTest.php
+++ b/tests/application/modules/admin/controllers/AlbumControllerTest.php
@@ -27,7 +27,11 @@ abstract class Admin_AlbumControllerTestCase extends Admin_AbstractControllerTes
 		parent::setUp();
 
 		$time_source = new TimeSourceForTest('2013-12-14 09:00:00');
-		Class_Album::beVolatile();
+		Storm_Model_Loader::defaultToVolatile();
+
+		$this->fixture('Class_IntBib', ['id' => 1,
+																		'libelle' => 'La Bib']);
+
 		Class_Album::setTimeSource($time_source);
 		Class_CosmoVar::newInstanceWithId('types_docs',
 																			['liste' =>  "1:cd\r\n200:non identifié\r\n201:livres\r\n202:bd"]);
@@ -53,7 +57,7 @@ abstract class Admin_AlbumControllerTestCase extends Admin_AbstractControllerTes
 		$adulte = $this->fixture('Class_AlbumCategorie', ['id' => 6])
 			->setLibelle('Adulte')
 			->setParentId(2)
-			->setSousCategories([])
+									 ->setSousCategories([])
 			->setAlbums([]);
 		$adulte->save();
 
@@ -64,6 +68,15 @@ abstract class Admin_AlbumControllerTestCase extends Admin_AbstractControllerTes
 			->setLibelle('Patrimoine');
 		$patrimoine->save();
 
+		$this->fixture('Class_FRBR_LinkType', ['id' => 1,
+																					 'libelle' => 'Ebook',
+																					 'from_source' => 'A pour ebook',
+																					 'from_target' => 'Est l\'ebook de']);
+
+		$this->fixture('Class_FRBR_LinkType', ['id' => 2,
+																					 'libelle' => 'Diaporama',
+																					 'from_source' => 'Est le diaporama de',
+																					 'from_target' => 'A pour diaporama']);
 
 		$this->fixture('Class_Album', ['id' => 43,
 																	 'pcdm4' => 'Musique;Dessin',
@@ -104,14 +117,19 @@ abstract class Admin_AlbumControllerTestCase extends Admin_AbstractControllerTes
 
 		$adulte->addAlbum($romans)->assertSave();
 
+		$this->fixture('Class_Notice', ['id' => 8,
+																		'clef_alpha' => 'DIAMANTSEMPOISONNES--HENRYA--PAYOT-2007-1',
+																		'type_doc' => 1]);
 
-		Class_Notice::beVolatile();
-		Class_Exemplaire::beVolatile();
+		$this->fixture('Class_Notice', ['id' => 9,
+																		'clef_alpha' => 'CEQUETUVEUXMAISENMAJUSCULE--ALEX--PATBATOR-2015-1',
+																		'type_doc' => 1]);
 	}
 
 
 	public function tearDown() {
 		Class_Album::setTimeSource(null);
+		Storm_Model_Loader::defaultToDb();
 		parent::tearDown();
 	}
 }
@@ -562,6 +580,7 @@ class Admin_AlbumControllerAddAlbumToPatrimoineTest extends Admin_AlbumControlle
 	public function setUp() {
 		parent::setUp();
 		$this->dispatch('/admin/album/add_album_to/id/38');
+
 	}
 
 
@@ -647,7 +666,7 @@ class Admin_AlbumControllerAddAlbumToPatrimoineTest extends Admin_AlbumControlle
 
 	/** @test */
 	public function permalienShouldNotBeVisible() {
-		$this->assertNotXPathContentContains('//div', 'Permalien');
+		$this->assertNotXPath('input[@class="permalien"]');
 	}
 
 
@@ -667,12 +686,31 @@ class Admin_AlbumControllerAddAlbumToPatrimoineTest extends Admin_AlbumControlle
 	public function divMultiInputsCollectionsShouldBeBeVisible() {
 		$this->assertXPath('//div[@id="multi_inputs_collections"]');
 	}
-}
 
 
+	/** @test */
+	public function formShouldContainsFieldSetFrbr() {
+		$this->assertXPathContentContains('//form[@id="album"]/fieldset/legend','FRBR',$this->_response->getBody());
+	}
+
 
+	/** @test */
+	public function frbrMultiShouldContainFrbrUrl() {
+		$this->assertXPathContentContains('//script', '{"name":"frbr_url","label":"Permalien de la notice"');
+	}
 
-class Admin_AlbumControllerPostAlbumRenaissanceToPatrimoineTest extends Admin_AlbumControllerTestCase {
+
+	/** @test */
+	public function frbrMultiShouldContainFrbrType() {
+		$this->assertXPathContentContains('//script',
+																			'{"name":"frbr_type","label":"Type de lien","attribs":{"style":"width:180px;"},"type":"select","options":{"":"Aucun","2:source":"Diaporama - Est le diaporama de","2:target":"Diaporama - A pour diaporama","1:source":"Ebook - A pour ebook","1:target":"Ebook - Est l\'ebook de"}}',
+																			$this->_response->getBody());
+	}
+}
+
+
+
+abstract class Admin_AlbumControllerPostAlbumRenaissanceToPatrimoineTestCase extends Admin_AlbumControllerTestCase {
 	public function setUp() {
 		parent::setUp();
 
@@ -680,10 +718,12 @@ class Admin_AlbumControllerPostAlbumRenaissanceToPatrimoineTest extends Admin_Al
 													'name' => '',
 													'tmp_name' => '',
 													'error' => 4];
+
 		$_FILES['pdf'] = ['size' => 0,
 											'name' => '',
 											'tmp_name' => '',
 											'error' => 4];
+
 		$this->fixture('Class_CodifDewey', ['id' => '0277',
 																				'id_dewey' => '0277',
 																				'libelle' => 'Lecture']);
@@ -699,8 +739,16 @@ class Admin_AlbumControllerPostAlbumRenaissanceToPatrimoineTest extends Admin_Al
 		$this->fixture('Class_CodifPcdm4', ['id' => '0684',
 																				'id_pcdm4' => '0684',
 																				'libelle' => 'Trombone']);
+	}
+}
+
 
 
+class Admin_AlbumControllerPostAlbumRenaissanceToPatrimoineFrbrNoTypeTest
+	extends Admin_AlbumControllerPostAlbumRenaissanceToPatrimoineTestCase {
+	public function setUp() {
+		parent::setUp();
+
 		$this->postDispatch('/admin/album/add_album_to/id/38',
 												['titre' => 'Renaissance',
 												 'sous_titre' => 'Ze Renaissance',
@@ -716,7 +764,90 @@ class Admin_AlbumControllerPostAlbumRenaissanceToPatrimoineTest extends Admin_Al
 												 'editor' => ['Glo' , 'Pba'],
 												 'collection' => ['Ratm', 'Soad'],
 												 'duration' => '00:04:08',
-												 'distributor' => 'galoum']);
+												 'distributor' => 'galoum',
+											   'frbr_url' => [ROOT_URL . BASE_URL . '/recherche/viewnotice/id/8/id_notice/8/type_doc/1/clef/DIAMANTSEMPOISONNES--HENRYA--PAYOT-2007-1?id_profil=7'],
+											   'frbr_type' => ['']]);
+	}
+
+
+	/** @test */
+	public function shouldNotHaveSavedAlbum() {
+		$this->assertNull(Class_Album::findFirstBy(['titre' => 'Renaissance']));
+	}
+
+
+	/** @test */
+	public function errorMessageShouldBePresent() {
+		$this->assertXPathContentContains('//script', 'Un champ de cette ligne ne peut', $this->_response->getBody());
+	}
+}
+
+
+
+class Admin_AlbumControllerPostAlbumRenaissanceToPatrimoineFrbrNoLinkTest
+	extends Admin_AlbumControllerPostAlbumRenaissanceToPatrimoineTestCase {
+	public function setUp() {
+		parent::setUp();
+
+		$this->postDispatch('/admin/album/add_album_to/id/38',
+												['titre' => 'Renaissance',
+												 'sous_titre' => 'Ze Renaissance',
+												 'description' => 'Oeuvres majeures sous François 1er',
+												 'nature_doc' => '2;3',
+												 'type_doc_id' => '102',
+												 'visible' => '1',
+												 'genre' => '32;23',
+												 'dewey' => '0277;0285',
+												 'pcdm4' => '0683;0684',
+												 'author' => ['pba', 'lla'],
+												 'fonction' => ['realisateur', 'bassiste'],
+												 'editor' => ['Glo' , 'Pba'],
+												 'collection' => ['Ratm', 'Soad'],
+												 'duration' => '00:04:08',
+												 'distributor' => 'galoum',
+											   'frbr_url' => [''],
+											   'frbr_type' => ['1:target']]);
+	}
+
+
+	/** @test */
+	public function shouldNotHaveSavedAlbum() {
+		$this->assertNull(Class_Album::findFirstBy(['titre' => 'Renaissance']));
+	}
+
+
+	/** @test */
+	public function errorMessageShouldBePresent() {
+		$this->assertXPathContentContains('//script', 'Un champ de cette ligne ne peut');
+	}
+}
+
+
+
+class Admin_AlbumControllerPostAlbumRenaissanceToPatrimoineTest
+	extends Admin_AlbumControllerPostAlbumRenaissanceToPatrimoineTestCase {
+	public function setUp() {
+		parent::setUp();
+
+		$this->postDispatch('/admin/album/add_album_to/id/38',
+												['titre' => 'Renaissance',
+												 'sous_titre' => 'Ze Renaissance',
+												 'description' => 'Oeuvres majeures sous François 1er',
+												 'nature_doc' => '2;3',
+												 'type_doc_id' => '102',
+												 'visible' => '1',
+												 'genre' => '32;23',
+												 'dewey' => '0277;0285',
+												 'pcdm4' => '0683;0684',
+												 'author' => ['pba', 'lla'],
+												 'fonction' => ['realisateur', 'bassiste'],
+												 'editor' => ['Glo' , 'Pba'],
+												 'collection' => ['Ratm', 'Soad'],
+												 'duration' => '00:04:08',
+												 'distributor' => 'galoum',
+												 'frbr_url' => [ROOT_URL . BASE_URL . '/recherche/viewnotice/id/8/id_notice/8/type_doc/1/clef/DIAMANTSEMPOISONNES--HENRYA--PAYOT-2007-1?id_profil=7',
+												 ROOT_URL . BASE_URL . '/recherche/viewnotice/id/9/id_notice/9/type_doc/1/clef/CEQUETUVEUXMAISENMAJUSCULE--ALEX--PATBATOR-2015-1?id_profil=7'],
+													 'frbr_type' => ['1:target', '2:source']]);
 		$this->new_album = Class_Album::findFirstBy(['order'=>'id desc']);
 		$this->new_notice = $this->new_album->getNotice();
 	}
@@ -765,7 +896,6 @@ class Admin_AlbumControllerPostAlbumRenaissanceToPatrimoineTest extends Admin_Al
 	}
 
 
-
 	/** @test */
 	public function shouldHaveEditorPba() {
 		$this->assertContains('Pba', $this->new_album->getEditors());
@@ -851,19 +981,40 @@ class Admin_AlbumControllerPostAlbumRenaissanceToPatrimoineTest extends Admin_Al
 
 
 	/** @test */
-	public function shouldHaveEditorGloInNotice() {
+	public function noticeShouldContaindEditorGlo() {
 		$this->assertContains('Glo', $this->new_notice->getEditeur());
 	}
 
 
 	/** @test */
 	public function shouldHaveCollectionRatmInCollection() {
-		$this->assertContains('Ratm', $this->new_notice->getCollection());
+		$this->assertContains('Ratm', $this->new_notice->getCollections());
+	}
+
+
+	/** @test */
+	public function shouldHaveCreatedFrbrLinkForDiamantsEmpoisonnes() {
+		$this->assertNotNull(Class_FRBR_Link::findFirstBy(['source' => ROOT_URL . BASE_URL . '/recherche/viewnotice/id/8/id_notice/8/type_doc/1/clef/DIAMANTSEMPOISONNES--HENRYA--PAYOT-2007-1?id_profil=7',
+																											 'type_id' => 1,
+																											 'source_type' => Class_FRBR_Link::TYPE_NOTICE,
+																											 'target' => ROOT_URL . BASE_URL . '/bib-numerique/notice/id/'.$this->new_album->getId(),
+																											 'target_type' => Class_FRBR_Link::TYPE_ALBUM]));
+	}
+
+
+	/** @test */
+	public function shouldHaveCreatedFrbrLinkForCeQueTuVeuxMaisEnMajuscule() {
+		$this->assertNotNull(Class_FRBR_Link::findFirstBy(['target' => ROOT_URL . BASE_URL . '/recherche/viewnotice/id/9/id_notice/9/type_doc/1/clef/CEQUETUVEUXMAISENMAJUSCULE--ALEX--PATBATOR-2015-1?id_profil=7',
+																											 'type_id' => 2,
+																											 'target_type' => Class_FRBR_Link::TYPE_NOTICE,
+																											 'source' => ROOT_URL . BASE_URL . '/bib-numerique/notice/id/'.$this->new_album->getId(),
+																											 'source_type' => Class_FRBR_Link::TYPE_ALBUM]));
 	}
 }
 
 
 
+
 class Admin_AlbumControllerPostAlbumWithoutTitreToPatrimoineTest extends Admin_AlbumControllerTestCase {
 	public function setUp() {
 		parent::setUp();
@@ -900,7 +1051,7 @@ class Admin_AlbumControllerPostAlbumWithoutTitreToPatrimoineTest extends Admin_A
 
 
 
-class Admin_AlbumControllerEditAlbumMesBDTest extends Admin_AlbumControllerTestCase {
+abstract class Admin_AlbumControllerEditAlbumMesBDTestCase extends Admin_AlbumControllerTestCase {
 	public function setUp() {
 		parent::setUp();
 
@@ -937,6 +1088,13 @@ class Admin_AlbumControllerEditAlbumMesBDTest extends Admin_AlbumControllerTestC
 																				 'invisible' => 0,
 																				 'code' => 93]);
 
+		$this->fixture('Class_FRBR_Link', ['id' => 34,
+																			 'type' => Class_FRBR_LinkType::find(1),
+																			 'source' => ROOT_URL . BASE_URL . '/recherche/viewnotice/id/8/id_notice/8/type_doc/1/clef/DIAMANTSEMPOISONNES--HENRYA--PAYOT-2007-1?id_profil=7',
+																			 'source_type' => Class_FRBR_Link::TYPE_NOTICE,
+																			 'target' => ROOT_URL . BASE_URL . '/bib-numerique/notice/id/43/ido/DC023',
+																			 'target_type' => Class_FRBR_Link::TYPE_ALBUM]);
+
 		Class_Album::find(43)
 			->setCatId(2)
 			->setBibliotheques('74')
@@ -948,6 +1106,118 @@ class Admin_AlbumControllerEditAlbumMesBDTest extends Admin_AlbumControllerTestC
 			->addCollection('Ratm')
 			->setDistributor('Geffen Records')
 			->save();
+
+	}
+}
+
+
+
+class Admin_AlbumControllerEditAlbumMesBDPostFrbrDeleteTest extends Admin_AlbumControllerEditAlbumMesBDTestCase {
+	public function setUp() {
+		parent::setUp();
+
+		$_FILES['fichier'] = ['size' => 0,
+													'name' => '',
+													'tmp_name' => '',
+													'error' => 4];
+
+		$_FILES['pdf'] = ['size' => 0,
+											'name' => '',
+											'tmp_name' => '',
+											'error' => 4];
+
+		$this->postDispatch('/admin/album/edit_album/id/43',
+												['titre' => 'Renaissance',
+												 'sous_titre' => 'Ze Renaissance',
+												 'description' => 'Oeuvres majeures sous François 1er',
+												 'nature_doc' => '2;3',
+												 'type_doc_id' => '102',
+												 'visible' => '1',
+												 'genre' => '32;23',
+												 'dewey' => '0277;0285',
+												 'pcdm4' => '0683;0684',
+												 'author' => ['pba', 'lla'],
+												 'fonction' => ['realisateur', 'bassiste'],
+												 'editor' => ['Glo' , 'Pba'],
+												 'collection' => ['Ratm', 'Soad'],
+												 'duration' => '00:04:08',
+												 'distributor' => 'galoum',
+											   'frbr_url' => [''],
+											   'frbr_type' => ['']]);
+	}
+
+
+	/** @test */
+	public function shouldDeleteFrbrLink() {
+		$this->assertNull(Class_FRBR_Link::find(34));
+	}
+}
+
+
+
+class Admin_AlbumControllerEditAlbumMesBDPostFrbrModifyTest extends Admin_AlbumControllerEditAlbumMesBDTestCase {
+	public function setUp() {
+		parent::setUp();
+
+		$_FILES['fichier'] = ['size' => 0,
+													'name' => '',
+													'tmp_name' => '',
+													'error' => 4];
+
+		$_FILES['pdf'] = ['size' => 0,
+											'name' => '',
+											'tmp_name' => '',
+											'error' => 4];
+
+		$this->fixture('Class_Notice',
+									 ['id' => 12,
+										'clef_alpha' => 'ACESHIGH--IRONM---1984-1']);
+
+		$this->postDispatch('/admin/album/edit_album/id/43',
+												['titre' => 'Renaissance',
+												 'sous_titre' => 'Ze Renaissance',
+												 'description' => 'Oeuvres majeures sous François 1er',
+												 'nature_doc' => '2;3',
+												 'type_doc_id' => '102',
+												 'visible' => '1',
+												 'genre' => '32;23',
+												 'dewey' => '0277;0285',
+												 'pcdm4' => '0683;0684',
+												 'author' => ['pba', 'lla'],
+												 'fonction' => ['realisateur', 'bassiste'],
+												 'editor' => ['Glo' , 'Pba'],
+												 'collection' => ['Ratm', 'Soad'],
+												 'duration' => '00:04:08',
+												 'distributor' => 'galoum',
+											   'frbr_url' => [ROOT_URL . BASE_URL . '/recherche/viewnotice/id/12/id_notice/12/type_doc/1/clef/ACESHIGH--IRONM---1984-1?id_profil=7'],
+											   'frbr_type' => ['1:target']]);
+	}
+
+
+	/** @test */
+	public function shouldHaveOnlyOneFrbrLink() {
+		$this->assertEquals(1, Class_FRBR_Link::countBy(['target' => ROOT_URL . BASE_URL .'/bib-numerique/notice/id/43/ido/DC023']));
+	}
+
+
+	/** @test */
+	public function shouldSaveNewFrbrLink() {
+		$this->assertNotNull(Class_FRBR_Link::findFirstBy(['source' => ROOT_URL . BASE_URL . '/recherche/viewnotice/id/12/id_notice/12/type_doc/1/clef/ACESHIGH--IRONM---1984-1?id_profil=7']));
+	}
+
+
+	/** @test */
+	public function shouldDeletePreviousLink() {
+		$this->assertNull(Class_FRBR_Link::find(34));
+	}
+}
+
+
+
+
+class Admin_AlbumControllerEditAlbumMesBDTest extends Admin_AlbumControllerEditAlbumMesBDTestCase {
+	public function setUp() {
+		parent::setUp();
 		$this->dispatch('/admin/album/edit_album/id/43', true);
 	}
 
@@ -1200,6 +1470,30 @@ class Admin_AlbumControllerEditAlbumMesBDTest extends Admin_AlbumControllerTestC
 	public function pageShouldContainsScriptWithCollectionRatm() {
 		$this->assertXPathContentContains('//div[@id="multi_inputs_collections"]/following-sibling::script','Ratm',$this->_response->getBody());
 	}
+
+
+	/** @test */
+	public function formShouldContainsFieldSetFrbr() {
+		$this->assertXPathContentContains('//form[@id="album"]/fieldset/legend','FRBR');
+	}
+
+
+	/** @test */
+	public function frbrLinkUrlShouldBeDiamantsEmpoisonnes() {
+		$this->assertXPathContentContains('//script', '\\/recherche\\/viewnotice\\/id\\/8\\/id_notice\\/8\\/type_doc\\/1\\/clef\\/DIAMANTSEMPOISONNES--HENRYA--PAYOT-2007-1?id_profil=7"');
+	}
+
+
+	/** @test */
+	public function frbrLinkTypeShouldBeEbook() {
+		$this->assertXPathContentContains('//script', 'values:{"frbr_type":["1:target"]');
+	}
+
+
+	/** @test */
+	public function frbrLinkDescShouldBePresent() {
+		$this->assertXPathContentContains('//script', '<a  href=\\"' . str_replace('/', '\\/', ROOT_URL . BASE_URL) . '\\/recherche\\/viewnotice\\/id\\/8\\/id_notice\\/8\\/type_doc\\/1\\/clef\\/DIAMANTSEMPOISONNES--HENRYA--PAYOT-2007-1?id_profil=7', $this->_response->getBody());
+	}
 }
 
 
@@ -1260,11 +1554,13 @@ class Admin_AlbumControllerPostEditAlbumMesBDTest extends Admin_AlbumControllerT
 			->save();
 
 		$this->cache_mock = $this->mock()
-			->whenCalled('clean')
-			->answers(true);
+														 ->whenCalled('save')
+														 ->answers(true)
+
+														 ->whenCalled('clean')
+														 ->answers(true);
 
 		Storm_Cache::setDefaultZendCache($this->cache_mock);
-		Class_CodifAuteur::beVolatile();
 		$this->postDispatch('/admin/album/edit_album/id/43',
 												['titre' => 'Mes BD',
 												 'description' => "Les préférées de l'année",
@@ -1791,7 +2087,6 @@ class Admin_AlbumControllerAlbumHarlockPostRessourceOneActionTest extends Admin_
 	public function setUp() {
 		parent::setUp();
 
-		Class_AlbumRessource::beVolatile();
 		Class_AlbumRessource::find(1)
 			->getAlbum()->addAuthor('lla');
 
diff --git a/tests/application/modules/admin/controllers/BatchControllerTest.php b/tests/application/modules/admin/controllers/BatchControllerTest.php
index 5a8004b51057e2b583061577f35ea50ea22176bd..d30c1c065778acff98df45eddb90fa02f41f0718 100644
--- a/tests/application/modules/admin/controllers/BatchControllerTest.php
+++ b/tests/application/modules/admin/controllers/BatchControllerTest.php
@@ -128,6 +128,12 @@ class BatchControllerAddTest extends BatchControllerTestCase {
 	public function selectElementShouldContainsIndexAutocompleteRecordAuthor(){
 		$this->assertXPath('//select[@name="type"]/option[@value="AUTOCOMPLETE_RECORD_AUTHOR"][@label="Indexer les auteurs de notice pour l\'autocompletion"][not(@selected)]',$this->_response->getBody());
 	}
+
+
+	/** @test */
+	public function selectElementShouldContainsRegenereLeSiteMapXML(){
+		$this->assertXPath('//select[@name="type"]/option[@value="BUILD_SITE_MAP"][@label="Régénère le sitemap XML"][not(@selected)]',$this->_response->getBody());
+	}
 }
 
 
diff --git a/tests/application/modules/admin/controllers/BibControllerTest.php b/tests/application/modules/admin/controllers/BibControllerTest.php
index 12319db7a05fc5f4da59c9c5dbf5f2ed5f424627..3d488c17f46bfa12c4e968aa4aeb5e77fa5c4eea 100644
--- a/tests/application/modules/admin/controllers/BibControllerTest.php
+++ b/tests/application/modules/admin/controllers/BibControllerTest.php
@@ -23,7 +23,10 @@ require_once 'AbstractControllerTestCase.php';
 abstract class BibControllerTestCase extends AbstractControllerTestCase {
 	public function setUp() {
 		parent::setUp();
-		Class_TypeDoc::beVolatile();
+		Storm_Model_Loader::defaultToVolatile();
+
+		$this->fixture('Class_CosmoVar', ['id' => 'types_docs', 'liste' => '']);
+
 		$this->bib_annecy = $this->fixture('Class_Bib',
 																			 ['id' => 2,
 																				'libelle' => 'Annecy',
@@ -76,6 +79,12 @@ abstract class BibControllerTestCase extends AbstractControllerTestCase {
 		Class_Bib::getPortail()->setArticleCategories([$cat_portail_infos]);
 
 	}
+
+
+	public function tearDown() {
+		Storm_Model_Loader::defaultToDb();
+		parent::tearDown();
+	}
 }
 
 
@@ -533,7 +542,6 @@ class BibControllerLocalisationNewPostTest extends BibControllerTestCase {
 			->answers(true)
 			->whenCalled('getcwd')
 			->answers('path');
-		Class_Localisation::beVolatile();
 		Class_Localisation::setFileSystem($file_system);
 
 		$this->postDispatch('admin/bib/addlocalisation/id_bib/2',
@@ -845,8 +853,7 @@ class BibControllerAjaxImagePlanTest extends BibControllerTestCase {
 														'description' => '',
 														'image' => 'mezzanine.png']);
 
-		$this->dispatch('/admin/bib/ajaximageplan/id_plan/3');
-
+		$this->dispatch('/admin/bib/ajaximageplan/id_plan/3', true);
 	}
 
 
@@ -855,6 +862,118 @@ class BibControllerAjaxImagePlanTest extends BibControllerTestCase {
 		$this->assertContains('photobib/plans/bib_3_plan_3.png',
 													$this->_response->getBody());
 	}
+}
+
+
+
+abstract class BibControllerPermissionsTestCase extends BibControllerTestCase {
+	public function setUp() {
+		parent::setUp();
+		$this->setupPermissions();
+		$this->group = $this->fixture('Class_UserGroup',
+														['id' => 324,
+														 'libelle' => 'Testing Permissions Group']);
+		$this->group
+			->addRight(Class_UserGroup::RIGHT_USER_ACCES_ARTICLES)
+			->save();
+
+		Class_Users::getIdentity()
+			->addUserGroup($this->group)
+			->save();
+	}
 
+
+	public function setupPermissions() {
+		$cnt = 1;
+		foreach ([['CATEGORY', 'ARTICLE', 'Sur la catégorie', 1, 'Créer des sous-catégories'],
+							['ARTICLE', 'ARTICLE', 'Sur la catégorie', 2, 'Créer des articles']] as $permission)
+			$this->fixture('Class_Permission',
+										 ['id' => $cnt++,
+											'code' => $permission[0],
+											'module' => $permission[1],
+											'type' => $permission[2],
+											'sorting' => $permission[3],
+											'description' => $permission[4]]);
+	}
+
+
+	protected function checkboxPath($permission, $predicates='') {
+		return sprintf('//input[@type="checkbox"][@name="perms[]"][@value="%s-%s"]%s',
+									 $this->group->getId(),
+									 $permission->getId(),
+									 $predicates);
+	}
 }
-?>
\ No newline at end of file
+
+
+
+class BibControllerPermissionsPortalActionTest extends BibControllerPermissionsTestCase {
+	public function setUp() {
+		parent::setUp();
+		Class_Permission::createArticle()->permitTo($this->group, Class_Bib::getPortail());
+		$this->dispatch('/admin/bib/permissions/id/0', true);
+	}
+
+
+	/** @test */
+	public function formShouldBeDisplayed() {
+		$this->assertXPath('//form[contains(@action, "bib/permissions")]',
+											 $this->_response->getBody());
+	}
+
+
+	/** @test */
+	public function createArticleCategoryShouldBeDisplayed() {
+		$this->assertXPath($this->checkboxPath(Class_Permission::createArticleCategory()));
+	}
+
+
+	/** @test */
+	public function createArticleShouldBeDisplayed() {
+		$this->assertXPath($this->checkboxPath(Class_Permission::createArticle()));
+	}
+
+
+	/** @test */
+	public function createArticleCategoryShouldNotBeChecked() {
+		$this->assertNotXPath($this->checkboxPath(Class_Permission::createArticleCategory(),
+																							'[@checked="checked"]'));
+	}
+
+
+	/** @test */
+	public function createArticleShouldBeChecked() {
+		$this->assertXPath($this->checkboxPath(Class_Permission::createArticle(),
+																					 '[@checked="checked"]'));
+	}
+}
+
+
+
+class BibControllerPermissionsPortalPostActionTest
+	extends BibControllerPermissionsTestCase {
+
+	public function setUp() {
+		parent::setUp();
+		Class_Permission::createArticle()->permitTo($this->group, Class_Bib::getPortail());
+		$this->postDispatch('/admin/bib/permissions/id/0',
+												['perms' => [$this->group->getId()
+																		 . '-' . Class_Permission::createArticleCategory()->getId()]]);
+	}
+
+
+	/** @test */
+	public function createArticleCategoryShouldBePermitted() {
+		$this->assertTrue(Class_UserGroup_Permission::isPermitted(Class_Permission::createArticleCategory(),
+																															$this->group,
+																															Class_Bib::getPortail()));
+	}
+
+
+	/** @test */
+	public function createArticleShouldNotBePermitted() {
+		$this->assertFalse(Class_UserGroup_Permission::isPermitted(Class_Permission::createArticle(),
+																															 $this->group,
+																															 Class_Bib::getPortail()));
+	}
+}
\ No newline at end of file
diff --git a/tests/application/modules/admin/controllers/CatalogueControllerTest.php b/tests/application/modules/admin/controllers/CatalogueControllerTest.php
index 82778a8a24a275d70c1ea207970f0c4d84563ee4..c59af9b2f539a5a20df1d199ff26aaa128da2998 100644
--- a/tests/application/modules/admin/controllers/CatalogueControllerTest.php
+++ b/tests/application/modules/admin/controllers/CatalogueControllerTest.php
@@ -528,7 +528,7 @@ class CatalogueControllerWithAModoBibWithRightAccesDomainesIndexTest extends Adm
 
 
 
-class CatalogueControllerActionTesterTest extends AdminCatalogueControllerTestCase {
+class CatalogueControllerActionTesterTest extends AbstractControllerTestCase {
 	protected function _loginHook($account) {
 		$account->ROLE_LEVEL = ZendAfi_Acl_AdminControllerRoles::SUPER_ADMIN;
 	}
@@ -536,33 +536,22 @@ class CatalogueControllerActionTesterTest extends AdminCatalogueControllerTestCa
 	public function setUp() {
 		parent::setUp();
 
-		Class_Catalogue::newInstanceWithId(6,
-																			 ['libelle' => 'nouveautés',
-																				'type_doc' => '1;3;4;5',
-																				'annee_debut' => 2012,
-																				'annee_fin' => 2012,
-																				'annexe' => 0,
-																				'dewey' => Class_Matiere::newInstanceWithId(78308)->getId(),
-																				'bibliotheque' => 1,
-																				'indexer' =>false]);
-
-		Storm_Test_ObjectWrapper::onLoaderOfModel('Class_Notice')
-			->whenCalled('findAllBy')
-			->answers([Class_Notice::newInstanceWithId(2)]);
-
-		$this->mock_sql
-		  ->whenCalled('fetchOne')
-			->with("select count(*) from notices  where MATCH(facettes) AGAINST(' +(B1) +( D78308*)' IN BOOLEAN MODE) and type_doc IN (1, 3, 4, 5) and annee >= '2012' and annee <= '2012'")
-			->answers(1)
+		Class_Notice::beVolatile();
 
-		  ->whenCalled('fetchOne')
-			->with("select count(*) from notices  where MATCH(facettes) AGAINST(' +(B1) +( D78308*)' IN BOOLEAN MODE) and type_doc IN (1, 3, 4, 5) and annee >= '2012' and annee <= '2012' and url_vignette > '' and url_vignette != 'NO'")
-			->answers(1)
-
-			->whenCalled('fetchAll')
-			->with("select id_notice  from notices  where MATCH(facettes) AGAINST(' +(B1) +( D78308*)' IN BOOLEAN MODE) and type_doc IN (1, 3, 4, 5) and annee >= '2012' and annee <= '2012' order by alpha_titre  LIMIT 5000")
-			->answers([ ['id_notice' => 2] ]);
+		$this->fixture('Class_Matiere',
+									 ['id' => 78308,
+									 ]);
 
+		$this->fixture('Class_Catalogue',
+									 ['id' => 6,
+									 'libelle' => 'nouveautés',
+										'type_doc' => '1;3;4;5',
+										'annee_debut' => 2012,
+										'annee_fin' => 2012,
+										'annexe' => 0,
+										'dewey' => 78308,
+										'bibliotheque' => 1,
+										'indexer' =>false]);
 
 		$this->dispatch('admin/catalogue/tester/id_catalogue/6', true);
 	}
@@ -570,26 +559,17 @@ class CatalogueControllerActionTesterTest extends AdminCatalogueControllerTestCa
 
 	/** @test */
 	public function pageShouldDisplayRequest() {
-		$this->assertContains("select * from notices where MATCH(facettes) AGAINST(' +(B1) +( D78308*)' IN BOOLEAN MODE) and type_doc IN (1, 3, 4, 5) and annee >= '2012' and annee <= '2012' order by alpha_titre  LIMIT 0,20",
+		$this->assertContains("select * from notices Where MATCH(facettes) AGAINST(' +(B1) +( D78308*)' IN BOOLEAN MODE) and type_doc IN (1, 3, 4, 5) and annee >= '2012' and annee <= '2012' order by alpha_titre  LIMIT 0,20",
 													$this->_response->getBody());
 	}
 
 
 	/** @test */
-	public function findAllByRequestShouldHaveSameWhereAsGetRequetes() {
-		$params = Class_Notice::getLoader()->getFirstAttributeForLastCallOn('findAllBy');
-		$this->assertEquals('MATCH(facettes) AGAINST(\' +(B1) +( D78308*)\' IN BOOLEAN MODE) and type_doc IN (1, 3, 4, 5) and annee >= \'2012\' and annee <= \'2012\'',
-												$params['where']);
+	public function noResultMessageShouldBePresent() {
+		$this->assertXPathContentContains('//p', 'Ce catalogue ne renvoie aucun résultat.');
 	}
 
 
-	/** @test */
-	public function findAllByRequestShouldHaveOrderByAlphaTitre() {
-		$params = Class_Notice::getLoader()->getFirstAttributeForLastCallOn('findAllBy');
-		$this->assertEquals('alpha_titre',
-												$params['order']);
-	}
-
 	/** @test */
 	public function modifyLinkShouldContainsSlashes() {
 		$this->assertXPathContentContains('//a[contains(@href, "/admin/catalogue/edit/id_catalogue/6")]', "Modifier la définition du catalogue");
@@ -598,6 +578,29 @@ class CatalogueControllerActionTesterTest extends AdminCatalogueControllerTestCa
 
 
 
+class CatalogueControllerActionTesterWithEmptyDomainTest extends AbstractControllerTestCase {
+	protected function _loginHook($account) {
+		$account->ROLE_LEVEL = ZendAfi_Acl_AdminControllerRoles::SUPER_ADMIN;
+	}
+
+	public function setUp() {
+		parent::setUp();
+
+		$this->fixture('Class_Catalogue',
+									 ['id' => 6,
+										'libelle' => 'nouveautés']);
+
+		$this->dispatch('admin/catalogue/tester/id_catalogue/6', true);
+	}
+
+
+	/** @test */
+	public function noResultMessageShouldBePresent() {
+		$this->assertXPathContentContains('//p', 'Ce catalogue ne renvoie aucun résultat.');
+	}
+}
+
+
 
 class CatalogueControllerEditUnknownCatalogueTest extends AdminCatalogueControllerTestCase {
 	/** @test */
@@ -1322,13 +1325,22 @@ class CatalogueControllerDomaintePaniersJsonActionTest extends AbstractControlle
 
 	public function setup() {
 		parent::setup();
-
-		Class_Users::beVolatile();
 		Class_Catalogue::beVolatile();
-		Class_PanierNotice::beVolatile();
+
+		$this->fixture('Class_PanierNotice',
+									 ['id' => 22,
+										'libelle' => 'Top 5 CDs']);
+
+		$this->fixture('Class_Users',
+									 ['id' => 22,
+										'login' => 'Marie-Laure',
+										'password' => 'only1Password'])->addPanier(Class_PanierNotice::find(22))
+				 ->setRoleLevel(ZendAfi_Acl_AdminControllerRoles::MODO_PORTAIL)
+				 ->save();
+
 		Storm_Test_ObjectWrapper::onLoaderOfModel('Class_PanierNotice')
 			->whenCalled('findAllBelongsToAdmin')
-			->answers([]);
+			->answers([Class_PanierNotice::find(22)]);
 	}
 
 
@@ -1353,14 +1365,19 @@ class CatalogueControllerDomaintePaniersJsonActionTest extends AbstractControlle
 
 				 ['id' => 'paniers_by_users',
 					'label' => 'Paniers sans domaine, rattachés à leur créateur',
-					'categories' => [],
+					'categories' => [22 => ['id' => 'cart-owner-id-22',
+																	'label' => 'Marie-Laure',
+																	'categories' => [],
+																	'items' => [['id' => 22,
+																							 'label' => 'Top 5 CDs',
+																							 'options' => ['ico' => URL_ADMIN_IMG.'picto/paniers_16.png']]],
+																	'options' => ['ico' => URL_ADMIN_IMG.'picto/abonnes.gif',
+																								'removeCheckbox' => true]]],
 					'items' => [],
 					'options' => ['multipleSelection' => false]]];
 
-
-
 		$this->assertJsonStringEqualsJsonString(json_encode($expectedJSON),
-																						$this->_response->getBody(),json_encode($this->_response->getBody()));
+																						$this->_response->getBody(), $this->_response->getBody());
 	}
 }
 
@@ -1387,4 +1404,119 @@ class AdminCatalogueControllerEditCatalogueWithWrongGenre extends AbstractContro
 		$this->assertXPath('//input[@name="genre"][@value=""]', $this->_response->getBody());
 	}
 }
-?>
+
+
+
+class AdminCatalogueControllerUnindexOnDeleteTest extends AbstractControllerTestCase {
+	public function setUp() {
+		parent::setUp();
+
+		$this->fixture('Class_Notice',
+									 ['id' => 1456,
+										'clef_alpha' => 'POMME',
+										'facettes' => 'Q1']);
+
+		$this->fixture('Class_Catalogue',
+									 ['id' => 1,
+										'libelle' => 'new books']);
+
+		$this->fixture('Class_NoticeDomain',
+									 ['id' => 456,
+										'domain_id' => 1,
+										'panier_id' => 0,
+										'record_alpha_key' => 'POMME']);
+
+		$this->dispatch('/admin/catalogue/delete/id_catalogue/1', true);
+	}
+
+
+	/** @test */
+	public function noRecordShouldContainsFacetH1() {
+		$this->assertNotContains('Q1', Class_Notice::find(1456)->getFacettes());
+	}
+
+
+	/** @test */
+	public function noticeDomainShouldBeEmpty() {
+		$this->assertEmpty(Class_NoticeDomain::findAll());
+	}
+}
+
+
+
+class CatalogueControllerPaniersRemovePanierAndUnindexTest extends AbstractControllerTestCase {
+	public function setUp() {
+		parent::setUp();
+
+		Class_PanierNoticeCatalogue::beVolatile();
+		Class_PanierNotice::beVolatile();
+
+		$pomme = $this->fixture('Class_Notice',
+														['id' => 1456,
+														 'clef_alpha' => 'POMME',
+														 'facettes' => '']);
+
+		$poire = $this->fixture('Class_Notice',
+														['id' => 897,
+														 'clef_alpha' => 'POIRE',
+														 'facettes' => 'Q98']);
+
+		$this->fixture('Class_Catalogue',
+									 ['id' => 98,
+										'libelle' => 'new books']);
+
+		$this->fixture('Class_NoticeDomain',
+									 ['id' => 654,
+										'domain_id' => 98,
+										'panier_id' => 0,
+										'record_alpha_key' => 'POIRE']);
+
+		$this->fixture('Class_PanierNotice',
+													 ['id' => 951,
+														'notices' => '',
+														'domaine_ids' => '98'])
+				 ->addNotice($poire)
+				 ->addNotice($pomme)
+				 ->save();
+
+		Class_PanierNotice::indexAll();
+
+		$this->dispatch('admin/catalogue/paniers/id_catalogue/98/remove/951', true);
+	}
+
+
+	/** @test */
+	public function poireShouldHaveQ1Facet() {
+		$this->assertContains('Q98', Class_Notice::find(897)->getFacettes());
+	}
+
+
+	/** @test */
+	public function noticeDomainForPoireAndBooksShouldExist() {
+		$this->assertNotNull(Class_NoticeDomain::findFirstBy(['record_alpha_key' => 'POIRE',
+																													'domain_id' => 98,
+																													'panier_id' => 0]));
+	}
+
+
+	/** @test */
+	public function noticeDomainForPoireAndBooksAndCartShouldNotExist() {
+		$this->assertNull(Class_NoticeDomain::findFirstBy(['record_alpha_key' => 'POIRE',
+																											 'domain_id' => 98,
+																											 'panier_id' => 951]));
+	}
+
+
+	/** @test */
+	public function linkBetweenPommeAndDomainShouldBeDeleted() {
+		$this->assertNull(Class_NoticeDomain::findFirstBy(['domain_id' => 98,
+																											 'panier_id' => 951,
+																											 'record_alpha_key' => 'POMME']));
+	}
+
+
+	/** @test */
+	public function pommeShouldNotContainsFacetH1() {
+		$this->assertNotContains('Q98', Class_Notice::find(1456)->getFacettes());
+	}
+}
\ No newline at end of file
diff --git a/tests/application/modules/admin/controllers/CmsCategoryControllerTest.php b/tests/application/modules/admin/controllers/CmsCategoryControllerTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..fce4349a915e419e48ca25ab4cb0a1be2d3ee5b3
--- /dev/null
+++ b/tests/application/modules/admin/controllers/CmsCategoryControllerTest.php
@@ -0,0 +1,609 @@
+<?php
+/**
+ * Copyright (c) 2012-2014, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * AFI-OPAC 2.0 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).
+ *
+ * AFI-OPAC 2.0 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 AFI-OPAC 2.0; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+require_once 'AdminAbstractControllerTestCase.php';
+
+abstract class CmsCategoryControllerActionTestCase
+	extends Admin_AbstractControllerTestCase {
+	protected $cran_gevrier;
+
+	public function setUp() {
+		parent::setUp();
+		Storm_Model_Loader::defaultToVolatile();
+		Class_AdminVar::set('ARTICLES_LIST_MODE', '');
+		$annecy = $this->fixture('Class_Bib',
+														 ['id' => 1, 'id_zone' => 4, 'libelle' => 'Annecy']);
+
+		$this->cran_gevrier = $this->fixture('Class_Bib',
+																				 ['id' => 2, 'id_zone' => 4,
+																					'libelle' => 'Cran']);
+
+
+		$this->fixture('Class_ArticleCategorie',
+									 ['id' => 28, 'libelle' => 'Documentaire', 'bib' => $annecy]);
+
+		$cinema = $this->fixture('Class_ArticleCategorie',
+														 ['id' => 23, 'libelle' => 'Cinéma', 'bib' => $annecy]);
+
+		$this->fixture('Class_ArticleCategorie',
+									 ['id' => 34,
+										'libelle' => 'Audiovisuel innovant',
+										'parent_categorie' => $cinema,
+										'bib' => $annecy]);
+	}
+
+
+	public function tearDown() {
+		Storm_Model_Loader::defaultToDb();
+		parent::tearDown();
+	}
+}
+
+
+
+class CmsCategoryControllerAddActionTest extends CmsCategoryControllerActionTestCase {
+	public function setUp() {
+		parent::setUp();
+		$this->dispatch('/admin/cms-category/add',true);
+	}
+
+
+	/** @test */
+	public function libelleShouldBeDisplayed() {
+		$this->assertXPath("//input[@name='libelle']");
+	}
+
+
+	/** @test */
+	public function categoriesShouldNotBeDisplayed() {
+		$this->assertNotXPath('//select[@name="id_cat_mere"]');
+	}
+
+
+  /** @test */
+	public function localisationShouldBePortail() {
+		$this->assertXPathContentContains('//p', 'Localisation: Portail');
+	}
+
+
+	/** @test */
+	public function permissionsMessageShouldBePresent() {
+		$this->assertXPathContentContains('//div', 'après la création de cette catégorie');
+	}
+}
+
+
+
+class CmsCategoryControllerAddActionModoTest
+	extends CmsCategoryControllerActionTestCase {
+
+	protected function _loginHook($account) {
+		$account->ROLE = 'modo_bib';
+		$account->ROLE_LEVEL = ZendAfi_Acl_AdminControllerRoles::MODO_BIB;
+		$account->LOGIN = 'testingmodo';
+		$account->PSEUDO = 'testingmodo';
+	}
+
+
+	public function setUp() {
+		parent::setUp();
+		$this->dispatch('/admin/cms-category/add',true);
+	}
+
+
+	/** @test */
+	public function permissionsMessageShouldNotBePresent() {
+		$this->assertNotXPathContentContains('//div',
+																				 'après la création de cette catégorie');
+	}
+}
+
+
+
+class CmsCategoryControllerAddActionAdminBibAtRootTest extends CmsCategoryControllerActionTestCase {
+	public function setUp() {
+		parent::setUp();
+		Class_Users::getIdentity()
+			->setRoleLevel(ZendAfi_Acl_AdminControllerRoles::ADMIN_BIB)
+			->setBib($this->cran_gevrier)->save();
+
+		$this->dispatch('/admin/cms-category/add', true);
+	}
+
+
+  /** @test */
+	public function localisationShouldBePortail() {
+		$this->assertXPathContentContains('//p', 'Localisation: Cran',
+																			$this->_response->getBody());
+	}
+}
+
+
+
+class CmsCategoryControllerAddActionAdminBibUnderAnnecyTest extends CmsCategoryControllerActionTestCase {
+	public function setUp() {
+		parent::setUp();
+		Class_Users::getIdentity()
+			->setRoleLevel(ZendAfi_Acl_AdminControllerRoles::ADMIN_BIB)
+			->setBib($this->cran_gevrier)->save();
+
+		$this->dispatch('/admin/cms-category/add/id/34', true);
+	}
+
+
+	/** @test */
+	public function localisationShouldBeAnnecy() {
+		$this->assertXPathContentContains('//p', 'Localisation: Annecy',
+																			$this->_response->getBody());
+	}
+
+
+	/** @test */
+	public function cancelShouldLinkToIndexUnderParentCategory() {
+		$this->assertXPath("//div[contains(@onclick, '/admin/cms/index/id_cat/34')]",
+											 $this->_response->getBody());
+	}
+}
+
+
+
+class CmsCategoryControllerAddPostWithoutBibActionTest extends CmsCategoryControllerActionTestCase {
+	protected $parent;
+	public function setUp() {
+		parent::setUp();
+
+		$this->postDispatch('/admin/cms-category/add',['libelle' => 'Art']);
+		$this->category=Class_ArticleCategorie::findFirstBy(['libelle' => 'Art']);
+	}
+
+
+	/** @test */
+	public function bibShouldBePortail() {
+		$this->assertEquals('Portail', $this->category->getBib()->getLibelle());
+	}
+}
+
+
+
+class CmsCategoryControllerAddPostWithAdminBibActionTest extends CmsCategoryControllerActionTestCase {
+	protected $parent;
+	public function setUp() {
+		parent::setUp();
+		Class_Users::getIdentity()->setRoleLevel(ZendAfi_Acl_AdminControllerRoles::ADMIN_BIB)->setBib($this->cran_gevrier)->save();
+		$this->postDispatch('/admin/cms-category/add',['libelle' => 'Art']);
+		$this->category=Class_ArticleCategorie::findFirstBy(['libelle' => 'Art']);
+	}
+
+
+	/** @test */
+	public function bibShouldBeCran() {
+		$this->assertEquals('Cran', $this->category->getBib()->getLibelle(),$this->_response->getBody());
+	}
+}
+
+
+
+class CmsCategoryControllerAddPostActionTest extends CmsCategoryControllerActionTestCase {
+	protected $parent;
+	public function setUp() {
+		parent::setUp();
+		Class_ArticleCategorie::beVolatile();
+		$this->postDispatch('/admin/cms-category/add/id/34',['libelle' => 'Art']);
+		$this->category=Class_ArticleCategorie::findFirstBy(['libelle' => 'Art']);
+	}
+
+	/** @test */
+	public function parentCategoryShouldBeAudiovisuelInnovant() {
+		$this->assertEquals('Audiovisuel innovant',$this->category->getParentCategorie()->getLibelle());
+	}
+
+
+	/** @test */
+	public function bibShouldBeAnnecy() {
+		$this->assertEquals('Annecy',$this->category->getBib()->getLibelle());
+	}
+
+
+	/** @test */
+	public function shouldRedirectToTreeView() {
+		$this->assertRedirectTo('/admin/cms/index/id_cat/'.$this->category->getId());
+	}
+}
+
+
+
+class CmsCategoryControllerAddPostActionWithBibTest extends CmsCategoryControllerActionTestCase {
+	protected $parent;
+	public function setUp() {
+		parent::setUp();
+		Class_ArticleCategorie::beVolatile();
+		$this->postDispatch('/admin/cms-category/add/id_bib/1',['libelle' => 'Art']);
+		$this->category=Class_ArticleCategorie::findFirstBy(['libelle' => 'Art']);
+	}
+
+
+	/** @test */
+	public function bibShouldBeAnnecy() {
+		$this->assertEquals('Annecy',$this->category->getBib()->getLibelle());
+	}
+}
+
+
+
+class CmsCategoryControllerEditActionTest extends CmsCategoryControllerActionTestCase {
+ 	public function setUp() {
+		parent::setUp();
+		$this->dispatch('/admin/cms-category/edit/id/23', true);
+	}
+
+
+	/** @test */
+	public function libelleShouldBeDisplayed() {
+		$this->assertXPath('//input[@name="libelle"][@value="Cinéma"]');
+	}
+
+
+	/** @test */
+	public function parentCategoryShouldBeNone() {
+		$this->assertNotXPath('//select[@name="id_cat_mere"]//option[@selected="selected"]');
+	}
+
+
+  /** @test */
+	public function localisationShouldBePortail() {
+		$this->assertXPathContentContains('//p','Localisation: Annecy');
+	}
+}
+
+
+
+abstract class CmsCategoryControllerPermissionsTestCase extends CmsCategoryControllerActionTestCase {
+	public function setUp() {
+		parent::setUp();
+		$group_category = $this->fixture('Class_UserGroupCategorie',
+																		 ['id' => 1,
+																			'libelle' => 'Catégorie par défaut']);
+
+		$user_group45 = $this->fixture('Class_UserGroup',
+									 ['id' => 45,
+										'libelle' => 'Editors',
+										'categorie' => $group_category]);
+
+		$user_group46 = $this->fixture('Class_UserGroup',
+									 ['id' => 46,
+										'libelle' => 'Admins',
+										'categorie' => $group_category]);
+
+		$user_group46->setRights([Class_UserGroup::RIGHT_USER_ACCES_ARTICLES]);
+
+		$this->fixture('Class_Permission',
+									 ['id' => 1,
+										'module' => 'ARTICLE',
+										'type' => 'Droits',
+										'code' => 'ARTICLE',
+										'sorting' => 1,
+										'description' => 'Créer des articles et des sous-catégories']);
+
+		$this->fixture('Class_Permission',
+									 ['id' => 2,
+										'module' => 'ARTICLE',
+										'type' => 'Nouveaux statuts autorisés',
+										'code' => 'PENDING',
+										'sorting' => 1,
+										'description' => 'À valider']);
+
+		Class_Permission::find(1)
+			->permitTo(Class_UserGroup::find(46),
+								 Class_ArticleCategorie::find(23));
+	}
+}
+
+
+
+class CmsCategoryControllerEditActionPermissionsModoTest extends CmsCategoryControllerPermissionsTestCase {
+	protected function _loginHook($account) {
+		$account->ROLE = 'modo_bib';
+		$account->ROLE_LEVEL = ZendAfi_Acl_AdminControllerRoles::MODO_PORTAIL;
+		$account->LOGIN = 'testingmodo';
+		$account->PSEUDO = 'testingmodo';
+	}
+
+
+	public function setUp() {
+		parent::setUp();
+		$this->dispatch('/admin/cms-category/edit/id/23', true);
+	}
+
+
+	/** @test */
+	public function permissionsFormShouldBePresent() {
+		$this->assertNotXPath('//form[contains(@action, "cms-category/permissions/id/23")]');
+	}
+}
+
+
+
+class CmsCategoryControllerEditActionPermissionsTest extends CmsCategoryControllerPermissionsTestCase {
+	public function setUp() {
+		parent::setUp();
+		$this->dispatch('/admin/cms-category/edit/id/23', true);
+	}
+
+
+	/** @test */
+	public function permissionsFormShouldBePresent() {
+		$this->assertXPath('//form[contains(@action, "cms-category/permissions/id/23")]');
+	}
+
+
+	/** @test */
+	public function permissionsSubmitButtonShouldBePresent() {
+		$this->assertXPath('//form[contains(@action, "cms-category/permissions/id/23")]//div[@id="menu_itemperms23"]');
+	}
+
+
+	/** @test */
+	public function permissionsColumnForCreerDesArticlesShouldBePresent() {
+		$this->assertXPathContentContains('//th', 'Créer des articles');
+	}
+
+
+	/** @test */
+	public function permissionsGroupWorkflowShouldNotBePresent() {
+		$this->assertNotXPathContentContains('//th', 'Nouveaux statuts autorisés');
+	}
+
+
+	/** @test */
+	public function permissionsColumnForAValiderShouldNotBePresent() {
+		$this->assertNotXPathContentContains('//th', 'À valider');
+	}
+
+	/** @test */
+	public function permissionCheckboxForGroupWithoutPermissionShouldNotBePresent() {
+		$this->assertNotXPath('//td/input[@type="checkbox"][@name="perms[]"][@value="45-1"]');
+	}
+
+
+	/** @test */
+	public function permissionCheckboxForGroupWithPermissionShouldBeChecked() {
+		$this->assertXPath('//td/input[@type="checkbox"][@name="perms[]"][@value="46-1"][@checked="checked"]');
+	}
+}
+
+
+
+class CmsCategoryControllerEditActionPermissionsWithWorkflowTest
+	extends CmsCategoryControllerPermissionsTestCase {
+	public function setUp() {
+		parent::setUp();
+		$this->fixture('Class_AdminVar', ['id' => 'WORKFLOW', 'valeur' => '1']);
+		$this->dispatch('/admin/cms-category/edit/id/23', true);
+	}
+
+
+	/** @test */
+	public function permissionsGroupSurLaCategorieShouldBePresent() {
+		$this->assertXPathContentContains('//th', 'Droits', $this->_response->getBody());
+	}
+	/** @test */
+	public function permissionsGroupWorkflowShouldBePresent() {
+		$this->assertXPathContentContains('//th', 'Nouveaux statuts autorisés');
+	}
+
+
+	/** @test */
+	public function permissionsColumnForAValiderShouldBePresent() {
+		$this->assertXPathContentContains('//th', 'À valider');
+	}
+}
+
+
+
+class CmsCategoryControllerEditActionPermissionsOnParentTest extends CmsCategoryControllerPermissionsTestCase {
+	public function setUp() {
+		parent::setUp();
+		$this->dispatch('/admin/cms-category/edit/id/34', true);
+	}
+
+
+	/** @test */
+	public function permissionShouldBeInheritedFromParent() {
+		$this->assertXPath('//td/input[@type="checkbox"][@name="perms[]"][@value="46-1"][@checked="checked"][@disabled="disabled"]');
+	}
+
+
+	/** @test */
+	public function inheritedLinkToParentEditShouldBePresent() {
+		$this->assertXPath('//td/a[contains(@href, "admin/cms-category/edit/id/23")]', $this->_response->getBody());
+	}
+}
+
+
+
+class CmsCategoryControllerPostPermissionsActionTest extends CmsCategoryControllerPermissionsTestCase {
+	public function setUp() {
+		parent::setUp();
+		$this->postDispatch('/admin/cms-category/permissions/id/23',
+												['perms' => ['45-1']]);
+	}
+
+
+	/** @test */
+	public function shouldRedirectToCategoyEdit() {
+		$this->assertRedirectTo('/admin/cms-category/edit/id/23');
+	}
+
+
+	/** @test */
+	public function group45ShouldHavePermissionCreerDesArticlesOnCategory() {
+		$this->assertTrue(Class_UserGroup::find(45)
+											->hasPermissionOn(Class_Permission::find(1),
+																				Class_ArticleCategorie::find(23)));
+	}
+
+
+	/** @test */
+	public function group46ShouldNotHavePermissionCreerDesArticlesOnCategory() {
+		$this->assertFalse(Class_UserGroup::find(46)
+											->hasPermissionOn(Class_Permission::find(1),
+																				Class_ArticleCategorie::find(23)));
+	}
+}
+
+
+
+class CmsCategoryControllerEditWithParentActionTest extends CmsCategoryControllerActionTestCase {
+ 	public function setUp() {
+		parent::setUp();
+		$this->dispatch('/admin/cms-category/edit/id/34',true);
+	}
+
+
+	/** @test */
+	public function parentCategoryShouldBeCinema() {
+		$this->assertXPath('//select[@name="id_cat_mere"]//option[@value="23"][@selected="selected"]');
+	}
+}
+
+
+
+class CmsCategoryControllerEditPostActionTest extends CmsCategoryControllerActionTestCase {
+	protected $category;
+	public function setUp() {
+		parent::setUp();
+		$this->postDispatch('/admin/cms-category/edit/id/23',['libelle' => 'Art',
+																													'id_cat_mere' => 28]);
+		$this->category=Class_ArticleCategorie::find(23);
+	}
+
+
+	/** @test */
+	public function categoryArtShouldBeSaved() {
+		$this->assertEquals('Art',$this->category->getLibelle());
+	}
+
+
+	/** @test */
+	public function parentCategoryShouldBeDocumentaire() {
+		$this->assertEquals('Documentaire' , $this->category->getParentCategorie()->getLibelle());
+	}
+
+
+	/** @test */
+	public function shouldRedirectToTreeView() {
+		$this->assertRedirectTo('/admin/cms/index/id_cat/'.$this->category->getId());
+	}
+}
+
+
+
+class CmsCategoryControllerDeleteActionTest
+	extends CmsCategoryControllerPermissionsTestCase {
+	public function setUp() {
+		parent::setUp();
+		$this->dispatch('/admin/cms-category/delete/id/23',true);
+	}
+
+
+	/** @test */
+	public function categoryShouldBeDeleted() {
+		$this->assertNull(Class_ArticleCategorie::find(23));
+	}
+
+
+	/** @test */
+	public function userGroupPermissionShouldBeDeleted() {
+		$this->assertNull(Class_UserGroup_Permission::find(1622));
+	}
+
+
+	/** @test */
+	public function shouldRedirectToTreeView() {
+		$this->assertRedirectTo('/admin/cms/index');
+	}
+}
+
+
+
+class CmsCategoryControllerDeleteWithParentTest extends CmsCategoryControllerActionTestCase {
+	public function setUp() {
+		parent::setUp();
+		$this->dispatch('/admin/cms-category/delete/id/34',true);
+	}
+
+
+	/** @test */
+	public function categoryShouldBeDeleted() {
+		$this->assertNull(Class_ArticleCategorie::find(34));
+	}
+
+
+	/** @test */
+	public function shouldRedirectToTreeView() {
+		$this->assertRedirectTo('/admin/cms/index/id_cat/23');
+	}
+}
+
+
+
+class CmsCategoryControllerEditLimitedPermissionTest extends CmsCategoryControllerActionTestCase {
+	public function setUp() {
+		parent::setUp();
+		$group = $this->fixture('Class_UserGroup',
+														['id' => 22, 'libelle' => 'Testing group']);
+
+		Class_Users::getIdentity()
+			->setUserGroups([$group])
+			->changeRoleTo(ZendAfi_Acl_AdminControllerRoles::MODO_BIB)
+			->save();
+
+		$this
+			->fixture('Class_Permission',
+								['id' => '4600',
+								 'code' => 'CATEGORY',
+								 'module' => 'ARTICLE',
+								 'type' => 'Droits',
+								 'description' => 'Créer des sous-catégories et des articles'])
+			->permitTo($group, Class_ArticleCategorie::find(23));
+
+		$this->dispatch('/admin/cms-category/edit/id/34', true);
+	}
+
+
+	/** @test */
+	public function noneOptionShouldNotBeDisplayed() {
+		$this->assertNotXPath('//select[@name="id_cat_mere"]//option[@value="0"]');
+	}
+
+
+
+	/** @test */
+	public function currentParentShouldBeSelected() {
+		$this->assertXPath('//select[@name="id_cat_mere"]//option[@value="23"][@selected="selected"]',
+											 $this->_response->getBody());
+	}
+
+
+	/** @test */
+	public function categoryDocumentaireShouldNotBeDisplayed() {
+		$this->assertNotXPath('//select[@name="id_cat_mere"]//option[@value="28"]');
+	}
+}
\ No newline at end of file
diff --git a/tests/application/modules/admin/controllers/CmsControllerListModeTest.php b/tests/application/modules/admin/controllers/CmsControllerListModeTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..56c21cf9b7dffdc11fce08ab301645c05b050c66
--- /dev/null
+++ b/tests/application/modules/admin/controllers/CmsControllerListModeTest.php
@@ -0,0 +1,187 @@
+<?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
+ */
+
+require_once dirname(__FILE__).'/CmsControllerTest.php';
+
+
+abstract class CmsControllerListModeTestCase extends CmsControllerWithPermissionTestCase {
+	public function setUp() {
+		parent::setUp();
+		$this->fixture('Class_AdminVar', ['id' => 'ARTICLES_LIST_MODE', 'valeur' => '1']);
+	}
+}
+
+
+
+class CmsControllerListModeAdminRootTest extends CmsControllerListModeTestCase {
+	public function setUp() {
+		parent::setUp();
+		ZendAfi_Auth::getInstance()->logUser($this->_bernard);
+		$this->dispatch('/admin/cms', true);
+	}
+
+
+	/** @test */
+	public function shouldDisplayPortal() {
+		$this->assertXPathContentContains('//td/a[contains(@href, "cms/index/id_bib/0")]',
+																			'Portail');
+	}
+
+
+	/** @test */
+	public function portalShouldHaveAddCategoryAction() {
+		$this->assertXPath('//td//a[contains(@href, "cms-category/add/id_bib/0")]',
+											 $this->_response->getBody());
+	}
+
+
+	/** @test */
+	public function portalShouldHaveDefaultPermissionsAction() {
+		$this->assertXPath('//td//a[contains(@href, "bib/permissions/id/0")]',
+											 $this->_response->getBody());
+	}
+}
+
+
+
+class CmsControllerListModeAdminBibRootTest extends CmsControllerListModeTestCase {
+	public function setUp() {
+		parent::setUp();
+		ZendAfi_Auth::getInstance()->logUser($this->_admin_bib);
+		$this->dispatch('/admin/cms', true);
+	}
+
+
+	/** @test */
+	public function shouldNotDisplayPortal() {
+		$this->assertNotXPath('//td/a[contains(@href, "cms/index/id_bib/0")]');
+	}
+
+
+	/** @test */
+	public function shouldDisplayLibraryInBreadcrumbAtRoot() {
+		$this->assertXPathContentContains('//a[contains(@href, "admin/cms")]',
+																			'Annecy');
+	}
+
+
+	/** @test */
+	public function shouldDisplayRootCategory() {
+		$this->assertXPathContentContains('//td/a[contains(@href, "cms/index/id_cat/1")]',
+																			'Root');
+	}
+}
+
+
+
+class CmsControllerListModeAdminBibRootWithoutPermissionsTest
+	extends CmsControllerListModeTestCase {
+	public function setUp() {
+		parent::setUp();
+		ZendAfi_Auth::getInstance()->logUser($this->_admin_bib);
+		Class_UserGroup_Permission::deleteBy([]);
+		$this->dispatch('/admin/cms', true);
+	}
+
+
+	/** @test */
+	public function shouldNotDisplayRootCategory() {
+		$this->assertNotXPath('//td/a[contains(@href, "cms/index/id_cat/1")]');
+	}
+}
+
+
+
+class CmsControllerListModeAdminBibSubCategoryTest
+	extends CmsControllerListModeTestCase {
+	public function setUp() {
+		parent::setUp();
+		ZendAfi_Auth::getInstance()->logUser($this->_admin_bib);
+		$this->dispatch('/admin/cms/index/id_cat/1', true);
+	}
+
+
+	/** @test */
+	public function shouldDisplayLibraryInBreadcrumbAtRoot() {
+		$this->assertXPathContentContains('//a[contains(@href, "admin/cms")]',
+																			'Annecy');
+	}
+
+
+	/** @test */
+	public function shouldNotRepeatAnnecyInBreadcrumb() {
+		$this->assertNotXPath('//a[contains(@href, "admin/cms/index/id_bib/1")]');
+	}
+
+
+	/** @test */
+	public function shouldDisplayALaUneCategory() {
+		$this->assertXPathContentContains('//td/a[contains(@href, "cms/index/id_cat/23")]',
+																			'A la Une');
+	}
+}
+
+
+
+class CmsControllerListModeEditWithPaginationTest
+	extends CmsControllerListModeTestCase {
+	public function setUp() {
+		parent::setUp();
+		$this->dispatch('/admin/cms/edit/id/4/page/34', true);
+	}
+
+
+	/** @test */
+	public function formCancelShouldContainPage34() {
+		$this->assertXPath('//div[@class="bouton"][contains(@onclick, "/page/34")]');
+	}
+}
+
+
+
+class CmsControllerListModeEditPostWithPaginationTest
+	extends CmsControllerListModeTestCase {
+	public function setUp() {
+		parent::setUp();
+		$this->postDispatch('/admin/cms/edit/id/4/page/34',
+												['titre' => 'Erik Truffaz - Ladyland quartet en concert',
+												 'auteur' => $this->fixture('Class_Users',
+																										['id' => 1,
+																										 'login' => 'tom',
+																										 'password' => 'pwd']),
+												 'id_cat' => 34,
+												 'debut' => '01/03/2011',
+												 'fin' => '26/03/2011',
+												 'events_debut' => '02/03/2011 08:35',
+												 'events_fin' => '05/03/2011  10:42',
+												 'contenu' => 'Ici: <img src="../../images/bonlieu.jpg" />',
+												 'description' => 'Affiche: <img src="http://localhost' . BASE_URL . '/images/concert.jpg" />',
+												 'id_lieu' => 3,
+												 'domaine_ids' => ['10'],
+												 'id_items' => ['1']]);
+	}
+
+
+	/** @test */
+	public function redirectShouldContainPage34() {
+		$this->assertRedirectRegex('|/edit/id/4/page/34|');
+	}
+}
\ No newline at end of file
diff --git a/tests/application/modules/admin/controllers/CmsControllerPermissionsTest.php b/tests/application/modules/admin/controllers/CmsControllerPermissionsTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..e8e7283e9fe07ae35a705536b6c021b8c11603b2
--- /dev/null
+++ b/tests/application/modules/admin/controllers/CmsControllerPermissionsTest.php
@@ -0,0 +1,360 @@
+<?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
+ */
+
+require_once dirname(__FILE__).'/CmsControllerTest.php';
+
+
+class CmsControllerPermissionsWithoutPermissionsTest extends CmsControllerTestCase {
+	/** @test */
+	public function shouldNotBeAbleToAdd() {
+		$this->dispatch('admin/cms/add/id_cat/34', true);
+		$this->assertRedirectTo('/admin/cms/index');
+	}
+
+
+	/** @test */
+	public function shouldNotBeAbleToEdit() {
+		$this->dispatch('admin/cms/edit/id/4', true);
+		$this->assertRedirectTo('/admin/cms/index');
+	}
+
+
+	/** @test */
+	public function shouldNotBeAbleToDelete() {
+		$this->dispatch('admin/cms/delete/id/4', true);
+		$this->assertRedirectTo('/admin/cms/index');
+	}
+
+
+	/** @test */
+	public function shouldNotBeAbleToDuplicate() {
+		$this->dispatch('admin/cms/newsduplicate/id/4', true);
+		$this->assertRedirectTo('/admin/cms/index');
+	}
+
+
+	/** @test */
+	public function shouldNotBeAbleToMakeVisible() {
+		Class_Article::find(4)->beInvisible();
+		$this->dispatch('admin/cms/makevisible/id/4', true);
+		$this->assertFlashMessengerContentContains('Vous n\'avez pas la permission "Rendre visible');
+		$this->assertFalse(Class_Article::find(4)->isVisible());
+	}
+
+
+	/** @test */
+	public function shouldNotBeAbleToMakeInvisible() {
+		Class_Article::find(4)->beVisible();
+		$this->dispatch('admin/cms/makeinvisible/id/4', true);
+		$this->assertFlashMessengerContentContains('Vous n\'avez pas la permission "Rendre invisible');
+		$this->assertTrue(Class_Article::find(4)->isVisible());
+	}
+
+
+	/** @test */
+	public function shouldNotViewAddArticleLink() {
+		$this->dispatch('admin/cms', true);
+		$this->assertNotXPath('//a[contains(@href, "cms/add/")]');
+	}
+
+
+	/** @test */
+	public function shouldNotViewDefaultPermissionsLink() {
+		$this->dispatch('admin/cms', true);
+		$this->assertNotXPath('//a[contains(@href, "bib/permissions/")]');
+	}
+
+
+	/** @test */
+	public function shouldNotViewEditArticleLink() {
+		$this->dispatch('admin/cms', true);
+		$this->assertNotXPath('//a[contains(@href, "cms/edit/id/4")]');
+	}
+
+
+	/** @test */
+	public function shouldNotViewDeleteArticleLink() {
+		$this->dispatch('admin/cms', true);
+		$this->assertNotXPath('//a[contains(@href, "cms/delete/id/4")]');
+	}
+
+
+	/** @test */
+	public function shouldNotViewDuplicateArticleLink() {
+		$this->dispatch('admin/cms', true);
+		$this->assertNotXPath('//a[contains(@href, "cms/newsduplicate/id/4")]');
+	}
+
+
+	/** @test */
+	public function shouldNotViewMakeVisibleArticleLink() {
+		$this->dispatch('admin/cms', true);
+		$this->assertNotXPath('//a[contains(@href, "cms/makevisible/id/4")]');
+	}
+
+
+	/** @test */
+	public function shouldNotViewMakeInvisibleArticleLink() {
+		Class_Article::find(4)->beVisible();
+		$this->dispatch('admin/cms', true);
+		$this->assertNotXPath('//a[contains(@href, "cms/makeinvisible/id/4")]');
+	}
+
+
+	/** @test */
+	public function shouldNotViewAddCategoryLink() {
+		$this->dispatch('admin/cms', true);
+		$this->assertNotXPath('//a[contains(@href, "cms-category/add")]');
+	}
+
+
+	/** @test */
+	public function shouldNotViewAnyCategory() {
+		$this->dispatch('admin/cms', true);
+		$this->assertNotXPath('//li[@class="categorie"]');
+	}
+}
+
+
+
+abstract class CmsControllerPermissionsWithPermissionsTestCase
+	extends CmsControllerWithPermissionTestCase {
+
+	/** @test */
+	public function shouldViewCategories() {
+		$this->dispatch('admin/cms', true);
+		$this->assertXPath('//li[@class="categorie"]');
+	}
+
+
+	/** @test */
+	public function shouldBeAbleToAdd() {
+		$this->dispatch('admin/cms/add/id_cat/34', true);
+		$this->assertNotRedirect();
+	}
+
+
+	/** @test */
+	public function shouldBeAbleToEdit() {
+		$this->dispatch('admin/cms/edit/id/4', true);
+		$this->assertNotRedirect();
+	}
+
+
+	/** @test */
+	public function shouldBeAbleToDelete() {
+		$this->dispatch('admin/cms/delete/id/4', true);
+		$this->assertNotRedirectTo('/admin/cms/index');
+	}
+
+
+	/** @test */
+	public function shouldBeAbleToDuplicate() {
+		$this->dispatch('admin/cms/newsduplicate/id/4', true);
+		$this->assertNotRedirectTo('/admin/cms/index');
+	}
+
+
+	/** @test */
+	public function shouldBeAbleToMakeVisible() {
+		Class_Article::find(4)->beInvisible();
+		$this->dispatch('admin/cms/makevisible/id/4', true);
+		$this->assertTrue(Class_Article::find(4)->isVisible());
+	}
+
+
+	/** @test */
+	public function shouldBeAbleToMakeInvisible() {
+		Class_Article::find(4)->beVisible();
+		$this->dispatch('admin/cms/makeinvisible/id/4', true);
+		$this->assertFalse(Class_Article::find(4)->isVisible());
+	}
+
+
+	/** @test */
+	public function shouldViewAddArticleLink() {
+		$this->dispatch('admin/cms', true);
+		$this->assertXPath('//a[contains(@href, "cms/add/")]');
+	}
+
+
+	/** @test */
+	public function shouldViewEditArticleLink() {
+		$this->dispatch('admin/cms', true);
+		$this->assertXPath('//a[contains(@href, "cms/edit/id/4")]');
+	}
+
+
+	/** @test */
+	public function shouldViewDeleteArticleLink() {
+		$this->dispatch('admin/cms', true);
+		$this->assertXPath('//a[contains(@href, "cms/delete/id/4")]');
+	}
+
+
+	/** @test */
+	public function shouldViewDuplicateArticleLink() {
+		$this->dispatch('admin/cms', true);
+		$this->assertXPath('//a[contains(@href, "cms/newsduplicate/id/4")]');
+	}
+
+
+	/** @test */
+	public function shouldViewMakeVisibleArticleLink() {
+		$this->dispatch('admin/cms', true);
+		$this->assertXPath('//a[contains(@href, "cms/makevisible/id/4")]');
+	}
+
+
+	/** @test */
+	public function shouldViewMakeInvisibleArticleLink() {
+		Class_Article::find(4)->beVisible();
+		$this->dispatch('admin/cms', true);
+		$this->assertXPath('//a[contains(@href, "cms/makeinvisible/id/4")]');
+	}
+
+
+	/** @test */
+	public function shouldViewAddSubCategoryLinks() {
+		$this->dispatch('admin/cms', true);
+		$this->assertXPath('//a[contains(@href, "cms-category/add/id/23")]',
+											 $this->_response->getBody());
+		$this->assertXPath('//a[contains(@href, "cms-category/add/id/34")]',
+											 $this->_response->getBody());
+	}
+}
+
+
+
+class CmsControllerPermissionsWithPermissionsTest
+	extends CmsControllerPermissionsWithPermissionsTestCase {
+	public function setUp() {
+		parent::setUp();
+		$this->fixture('Class_ArticleCategorie',
+									 ['id' => 123,
+										'libelle' => 'Sibling',
+										'parent_categorie' => $this->root_category]);
+	}
+
+
+	/** @test */
+	public function shouldNotViewAddRootCategoryLink() {
+		$this->dispatch('admin/cms', true);
+		$this->assertNotXPath('//a[contains(@href, "cms-category/add/id_bib/")]');
+	}
+
+
+	/** @test */
+	public function shouldViewEditSubCategoryLink() {
+		$this->dispatch('admin/cms', true);
+		$this->assertXPath('//a[contains(@href, "cms-category/edit/id/34")]',
+											 $this->_response->getBody());
+	}
+
+
+	/** @test */
+	public function shouldNotViewEditCategoryLink() {
+		$this->dispatch('admin/cms', true);
+		$this->assertNotXPath('//a[contains(@href, "cms-category/edit/id/23")]');
+	}
+
+
+	/** @test */
+	public function shouldViewRootCategory() {
+		$this->dispatch('admin/cms', true);
+		$this->assertXPathContentContains('//a', 'Root');
+	}
+
+
+	/** @test */
+	public function shouldNotViewSiblingCategory() {
+		$this->dispatch('admin/cms', true);
+		$this->assertNotXPathContentContains('//a', 'Sibling');
+	}
+}
+
+
+
+/**
+ * Permissions should be the same as when ARTICLE is explicitly given
+ */
+class CmsControllerPermissionsCategoryGivesArticlesTest
+	extends CmsControllerPermissionsWithPermissionsTestCase {
+
+	public function setUp() {
+		parent::setUp();
+		$group = Class_UserGroup::find(22);
+		$category = Class_ArticleCategorie::find(23);
+		Class_Permission::createArticle()->denyTo($group, $category);
+	}
+}
+
+
+
+class CmsControllerPermissionsWithDefaultPermissionsTest
+	extends CmsControllerPermissionsWithPermissionsTestCase {
+	public function setUp() {
+		parent::setUp();
+		Class_UserGroup_Permission::deleteBy([]);
+		$group = Class_UserGroup::find(22);
+		Class_Permission::createArticle()->permitTo($group, $this->annecy);
+		Class_Permission::createArticleCategory()->permitTo($group, $this->annecy);
+	}
+
+
+	/** @test */
+	public function shouldViewAddRootCategoryLink() {
+		$this->dispatch('admin/cms', true);
+		$this->assertXPath('//a[contains(@href, "cms-category/add/id_bib/")]');
+	}
+}
+
+
+
+class CmsControllerPermissionsWithoutPermissionsAdminPortalTest
+	extends CmsControllerPermissionsWithPermissionsTestCase {
+	public function setUp() {
+		parent::setUp();
+		Class_UserGroup_Permission::deleteBy([]);
+		ZendAfi_Auth::getInstance()->logUser($this->_laurent);
+	}
+
+
+	/** @test */
+	public function shouldViewAddRootCategoryLink() {
+		$this->dispatch('admin/cms', true);
+		$this->assertXPath('//a[contains(@href, "cms-category/add/id_bib/")]');
+	}
+
+
+		/** @test */
+	public function shouldViewEditSubCategoryLink() {
+		$this->dispatch('admin/cms', true);
+		$this->assertXPath('//a[contains(@href, "cms-category/edit/id/34")]');
+	}
+
+
+	/** @test */
+	public function shouldViewEditCategoryLink() {
+		$this->dispatch('admin/cms', true);
+		$this->assertXPath('//a[contains(@href, "cms-category/edit/id/23")]');
+	}
+}
\ No newline at end of file
diff --git a/tests/application/modules/admin/controllers/CmsControllerTest.php b/tests/application/modules/admin/controllers/CmsControllerTest.php
index 36326484a73799af7710d7ffbdd25d6f28534341..8401f015acbe9b13cb4074ae9821cecac33a70d1 100644
--- a/tests/application/modules/admin/controllers/CmsControllerTest.php
+++ b/tests/application/modules/admin/controllers/CmsControllerTest.php
@@ -28,22 +28,27 @@ abstract class CmsControllerTestCase extends Admin_AbstractControllerTestCase {
 	protected $_admin_bib, $_laurent,$_bernard;
 	public function setUp() {
 		parent::setUp();
+		Storm_Model_Loader::defaultToVolatile();
 		$this->setupBib();
-		Class_CustomField::beVolatile();
-		Class_CustomField_Meta::beVolatile();
+
+		$this->fixture('Class_UserGroup', ['id' => 22,
+																			 'libelle' => 'Testing group']);
+
 		$this->_laurent = $this->fixture('Class_Users', ['id'=>20,
 																										 'login' => 'laurent',
 																										 'mail' => 'laurent@afi-sa.fr',
 																										 'bib' => $this->annecy,
 																										 'password' => 'toto',
 																										 'role_level' => ZendAfi_Acl_AdminControllerRoles::ADMIN_PORTAIL]);
+
 		$this->_admin_bib = $this->fixture('Class_Users',['id' => 10,
 																											'login' => 'AdminBibConnected',
 																											'bib' => $this->annecy,
 																											'role' => 'admin_bib',
 																											'mail' => 'admin@afi-sa.fr',
 																											'password' => 'toto',
-																											'role_level' => ZendAfi_Acl_AdminControllerRoles::ADMIN_BIB]);
+																											'role_level' => ZendAfi_Acl_AdminControllerRoles::ADMIN_BIB,
+																											'user_groups' => [Class_UserGroup::find(22)]]);
 
 		$this->_bernard = $this->fixture('Class_Users',['id' =>30,
 																										'login' => 'bernie',
@@ -58,10 +63,17 @@ abstract class CmsControllerTestCase extends Admin_AbstractControllerTestCase {
 		$this
 			->setupLieux()
 			->setupDomaines()
-			->setupArticles();
+			->setupArticles()
+			->setupPermissions();
+
+		$this->fixture('Class_AdminVar', ['id' => 'LANGUES', 'valeur' => 'en;ro']);
+		$this->fixture('Class_AdminVar', ['id' => 'ARTICLES_LIST_MODE', 'valeur' => '']);
+	}
 
 
-		Class_AdminVar::newInstanceWithId('LANGUES', ['valeur' => 'en;ro']);
+	public function tearDown() {
+		Storm_Model_Loader::defaultToDb();
+		parent::tearDown();
 	}
 
 
@@ -86,41 +98,30 @@ abstract class CmsControllerTestCase extends Admin_AbstractControllerTestCase {
 
 
 	public function setupDomaines() {
-		$this->domaine_histoire = Class_Catalogue::newInstanceWithId(10,
-						 ['libelle' => 'Histoire',
-							'sous_domaines' => [$this->domaine_art = Class_Catalogue::newInstanceWithId(11,
-																																['parent_id' => 10,
-																																 'libelle' => 'Art'])]]);
-
-		$domaine_a_la_une = Class_Catalogue::newInstanceWithId(66,
-																													 ['libelle' => 'A la Une']);
-
-		$this
-			->onLoaderOfModel('Class_Catalogue')
-			->whenCalled('findAllBy')
-			->with(['order' => 'libelle'])
-			->answers([$this->domaine_art,
-								 $this->domaine_histoire])
-
-			->whenCalled('findAllBy')
-			->with(['where' => 'parent_id is null',
-							'order' => 'libelle'])
-			->answers([$this->domaine_histoire])
-
-			->whenCalled('findFirstBy')
-			->answers(null)
-
-			->whenCalled('findFirstBy')
-			->with(['parent_id' => null,
-							'libelle' => 'A la Une'])
-			->answers($domaine_a_la_une);
-
+		// /!\ disabling afterSave call to saveThesaurus which make direct SQL queries
+		$this->onLoaderOfModel('Class_Catalogue')
+				 ->whenCalled('saveThesaurus')
+				 ->answers(null);
+
+		$this->domaine_art = $this->fixture('Class_Catalogue',
+																				['id' => 11, 'libelle' => 'Art']);
+
+		$this->domaine_histoire = $this->fixture('Class_Catalogue',
+																						 ['id' => 10,
+																							'libelle' => 'Histoire',
+																							'sous_domaines' => [$this->domaine_art]]);
+
+		$domaine_a_la_une = $this->fixture('Class_Catalogue',
+																			 ['id' => 66, 'libelle' => 'A la Une']);
+		$this->fixture('Class_Catalogue',
+									 ['id' => 78,
+										'libelle' => 'Root',
+										'sous_domaines' => [$domaine_a_la_une]]);
 		return $this;
 	}
 
 
 	public function setupBib() {
-		Class_Bib::beVolatile();
 		$this->annecy = Class_Bib::newInstanceWithId(1, ['id_zone' => 4,
 																										 'libelle' => 'Annecy'
 																										]);
@@ -136,10 +137,13 @@ abstract class CmsControllerTestCase extends Admin_AbstractControllerTestCase {
 
 
 	public function setupArticles() {
+		$this->root_category = $this->fixture('Class_ArticleCategorie',
+																					['id' => 1, 'libelle' => 'Root']);
+
 		$cat_a_la_une = $this->fixture('Class_ArticleCategorie',
 																	 ['id' => 23,
-																		'libelle' => 'A la Une']);
-		$this->annecy->setArticleCategories([$cat_a_la_une]);
+																		'libelle' => 'A la Une',
+																		'parent_categorie' => $this->root_category]);
 
 		$this->cat_evenements = $this->fixture('Class_ArticleCategorie',
 																					 ['id' => 34,
@@ -148,6 +152,8 @@ abstract class CmsControllerTestCase extends Admin_AbstractControllerTestCase {
 																						'sous_categories' => []]);
 		$cat_a_la_une->setSousCategories([$this->cat_evenements]);
 
+		$this->annecy
+			->setArticleCategories([$this->root_category]);
 
 		$this->concert = $this->fixture('Class_Article',
 																		['id' => 4,
@@ -175,12 +181,6 @@ abstract class CmsControllerTestCase extends Admin_AbstractControllerTestCase {
 
 		$this->cat_evenements->setArticles([$this->concert]);
 
-
-		$this->article_wrapper = $this
-			->onLoaderOfModel('Class_Article')
-			->getWrapper();
-
-
 		$this->categorie_wrapper = $this->onLoaderOfModel('Class_ArticleCategorie');
 
 		$this->cat_evenements->setBib($this->annecy);
@@ -189,6 +189,24 @@ abstract class CmsControllerTestCase extends Admin_AbstractControllerTestCase {
 	}
 
 
+	function setupPermissions() {
+		$cnt = 1;
+		foreach ([['CATEGORY', 'ARTICLE', 'Sur la catégorie', 1, 'Créer des sous-catégories'],
+							['ARTICLE', 'ARTICLE', 'Sur la catégorie', 2, 'Créer des articles'],
+							['PENDING', 'ARTICLE', 'Nouveaux statuts autorisés', 1, 'À valider'],
+							['VALIDATED', 'ARTICLE', 'Nouveaux statuts autorisés', 101, 'Validé'],
+							['REFUSED', 'ARTICLE', 'Nouveaux statuts autorisés', 102, 'Refusé'],
+							['ARCHIVED', 'ARTICLE', 'Nouveaux statuts autorisés', 103, 'Archivé']] as $permission)
+			$this->fixture('Class_Permission',
+										 ['id' => $cnt++,
+											'code' => $permission[0],
+											'module' => $permission[1],
+											'type' => $permission[2],
+											'sorting' => $permission[3],
+											'description' => $permission[4]]);
+	}
+
+
 	function assertInputValueEquals($input_name, $value) {
 		$this->assertXPath(sprintf('//input[@name="%s"][@value="%s"]',
 															 $input_name, $value));
@@ -220,11 +238,24 @@ abstract class CmsControllerTestCase extends Admin_AbstractControllerTestCase {
 
 
 
+abstract class CmsControllerWithPermissionTestCase extends CmsControllerTestCase {
+	protected $_current_id = 1;
 
-class CmsControllerArticleEditWithoutLanguesTest extends CmsControllerTestCase {
 	public function setUp() {
 		parent::setUp();
+		$group = Class_UserGroup::find(22);
+		$category = Class_ArticleCategorie::find(23);
+		Class_Permission::createArticle()->permitTo($group, $category);
+		Class_Permission::createArticleCategory()->permitTo($group, $category);
+	}
+}
+
+
 
+class CmsControllerArticleEditWithoutLanguesTest extends CmsControllerWithPermissionTestCase {
+	public function setUp() {
+		parent::setUp();
+		Class_Exemplaire::beVolatile();
 		Class_AdminVar::getLoader()
 			->newInstanceWithId('LANGUES')
 			->setValeur('');
@@ -241,8 +272,8 @@ class CmsControllerArticleEditWithoutLanguesTest extends CmsControllerTestCase {
 
 
 
+class CmsControllerArticleConcertAsAdminPortailEditActionTest extends CmsControllerWithPermissionTestCase {
 
-class CmsControllerArticleConcertAsAdminPortailEditActionTest extends CmsControllerTestCase {
 	protected function _loginHook($account) {
 		$account->ROLE = "admin_portail";
 		$account->ROLE_LEVEL = ZendAfi_Acl_AdminControllerRoles::ADMIN_PORTAIL;
@@ -253,7 +284,7 @@ class CmsControllerArticleConcertAsAdminPortailEditActionTest extends CmsControl
 		Class_Users::getLoader()
 			->getIdentity()
 			->setRoleLevel(ZendAfi_Acl_AdminControllerRoles::ADMIN_PORTAIL);
-
+		Class_Exemplaire::beVolatile();
 		$this->dispatch('/admin/cms/edit/id/4', true);
 	}
 
@@ -373,9 +404,6 @@ class CmsControllerArticleConcertAsReferentEditActionTest extends CmsControllerA
 
 	public function setUp() {
 		parent::setUp();
-		Class_Users::beVolatile();
-		Class_UserGroup::beVolatile();
-		Class_UserGroupMembership::beVolatile();
 		$this->modo = Class_Users::newInstanceWithId(22, ['login' => 'modo',
 																									 'role_level' =>ZendAfi_Acl_AdminControllerRoles::MODO_PORTAIL ]);
 		$this->modo->save();
@@ -396,7 +424,7 @@ class CmsControllerArticleConcertAsReferentEditActionTest extends CmsControllerA
 
 
 
-class CmsControllerArticleWithoutCategoryAddActionTest extends CmsControllerTestCase {
+class CmsControllerArticleWithoutCategoryAddActionTest extends CmsControllerWithPermissionTestCase {
 	public function setUp() {
 		parent::setUp();
 		$this->dispatch('/admin/cms/add/id_cat/99999');
@@ -404,15 +432,14 @@ class CmsControllerArticleWithoutCategoryAddActionTest extends CmsControllerTest
 
 	/** @test */
 	function answerShouldRedirectToAdminCms() {
-		$this->assertRedirectTo('/admin/cms', $this->getResponseLocation());
+		$this->assertRedirectTo('/admin/cms/index', $this->getResponseLocation());
 	}
 }
 
 
 
 
-
-class CmsControllerArticleDuplicateActionTest extends AbstractControllerTestCase {
+class CmsControllerArticleDuplicateActionTest extends CmsControllerWithPermissionTestCase {
 	protected
 		$_admin_bib,
 		$_concert;
@@ -485,7 +512,7 @@ class CmsControllerArticleDuplicateActionTest extends AbstractControllerTestCase
 
 
 
-class CmsControllerArticleConcertEditActionTest extends CmsControllerTestCase {
+class CmsControllerArticleConcertEditActionTest extends CmsControllerWithPermissionTestCase {
 	public function setUp() {
 		parent::setUp();
 		$this->dispatch('/admin/cms/edit/id/4', true);
@@ -673,7 +700,7 @@ class CmsControllerArticleConcertEditActionTest extends CmsControllerTestCase {
 
 
 
-class CmsControllerArticleConcertEditRenderPopupActionTest extends CmsControllerTestCase {
+class CmsControllerArticleConcertEditRenderPopupActionTest extends CmsControllerWithPermissionTestCase {
 	protected
 		$_json,
 		$_xpath;
@@ -773,7 +800,7 @@ class CmsControllerArticleConcertEditRenderPopupActionTest extends CmsController
 
 
 
-class CmsControllerArticleConcertEditArticleWithQuotesActionTest extends CmsControllerTestCase {
+class CmsControllerArticleConcertEditArticleWithQuotesActionTest extends CmsControllerWithPermissionTestCase {
 	public function setUp() {
 		parent::setUp();
 		$this->concert->setTitre('"Erik Truffaz" en concert');
@@ -797,10 +824,15 @@ class CmsControllerArticleConcertEditArticleWithQuotesActionTest extends CmsCont
 
 
 
-class CmsControllerArticleConcertEditActionPostTest extends CmsControllerTestCase {
+class CmsControllerArticleConcertEditActionPostTest extends CmsControllerWithPermissionTestCase {
 	public function setUp() {
 		parent::setUp();
 
+		Class_Notice::beVolatile();
+		Class_NoticeDomain::beVolatile();
+		Class_IntBib::beVolatile();
+		Class_Exemplaire::beVolatile();
+
 		$this->fixture('Class_Article',
 									 ['id' => 4,
 										'auteur' => null,
@@ -819,6 +851,7 @@ class CmsControllerArticleConcertEditActionPostTest extends CmsControllerTestCas
 																						 ['id' => 1,
 																							'login' => 'tom',
 																							'password' => 'pwd']),
+									'indexation' => 1,
 									'id_cat' => 34,
 									'debut' => '01/03/2011',
 									'fin' => '26/03/2011',
@@ -834,6 +867,24 @@ class CmsControllerArticleConcertEditActionPostTest extends CmsControllerTestCas
 	}
 
 
+	/** @test */
+	public function erikTruffazShouldBeIndexed() {
+		$this->assertNotNull(Class_Notice::findFirstBy(['alpha_titre' =>'ERIK TRUFFAZ   LADYLAND QUARTET EN CONCERT']));
+	}
+
+
+	/** @test */
+	public function clefAlphaShouldBeAsExpected() {
+		$this->assertEquals('ERIKTRUFFAZLADYLANDQUARTETENCONCERT------8',
+												Class_Article::find(4)->getAlphaKey());
+	}
+
+
+	/** @test */
+	public function erikTruffazShouldBeLinkedToExpectedDomain() {
+		$this->assertEquals(10, Class_NoticeDomain::findFirstBy(['record_alpha_key' => 'ERIKTRUFFAZLADYLANDQUARTETENCONCERT------8'])->getDomainId());
+	}
+
 
 	/** @test */
 	public function articleShouldNotHaveIdItems() {
@@ -934,32 +985,6 @@ class CmsControllerArticleConcertEditActionPostTest extends CmsControllerTestCas
 	}
 
 
-	/** @test */
-	function withDomainesThreeShouldRenderArtJSON() {
-		$this->dispatch('admin/catalogue/domaines',true);
-		$expectedJSON = <<<JSON
-			[{"id":0,
-				"label": "Domaines",
-				"categories": [{"id":10,
-												"label": "Histoire",
-												"categories": [
-														{ "id":11,
-															"label":"Art",
-															"categories": [],
-															"items": []}],
-												"items": []
-												}],
-				"items": []}]
-JSON;
-
-		$json = json_decode($this->_response->getBody(), true);
-		$this->assertEquals('Histoire', $json[0]['categories'][0]['label']);
-		$this->assertEquals(10, $json[0]['categories'][0]['id']);
-		$this->assertEquals('Art', $json[0]['categories'][0]['categories'][0]['label']);
-		$this->assertEquals(11, $json[0]['categories'][0]['categories'][0]['id']);
-	}
-
-
 	/** @test */
 	public function flashMessengerShouldNotContainsPopup() {
 		$this->assertNotFlashMessengerPopup();
@@ -968,8 +993,7 @@ JSON;
 
 
 
-
-class CmsControllerArticleConcertEditActionPostRenderPopupTest extends CmsControllerTestCase {
+class CmsControllerArticleConcertEditActionPostRenderPopupTest extends CmsControllerWithPermissionTestCase {
 	protected
 		$_data,
 		$_xpath,
@@ -1022,7 +1046,7 @@ class CmsControllerArticleConcertEditActionPostRenderPopupTest extends CmsContro
 
 
 
-class CmsControllerArticleConcertEditActionPostWithWrongDataRenderPopupTest extends CmsControllerTestCase {
+class CmsControllerArticleConcertEditActionPostWithWrongDataRenderPopupTest extends CmsControllerWithPermissionTestCase {
 	protected
 		$_data,
 		$_json,
@@ -1058,7 +1082,8 @@ class CmsControllerArticleConcertEditActionPostWithWrongDataRenderPopupTest exte
 	/**
 	 * @test
 	 * @pagetitles
-	 */	public function responseTitleShouldBeModifierUnArticle() {
+	 */
+	public function responseTitleShouldBeModifierUnArticle() {
 		$this->assertEquals('Modifier un article: Erik Truffaz en concert', $this->_json->title);
 	}
 
@@ -1087,48 +1112,42 @@ class CmsControllerArticleConcertEditActionPostWithWrongDataRenderPopupTest exte
 
 
 
-class CmsControllerArticleConcertEditActionPostWithErrorTest extends CmsControllerTestCase {
+class CmsControllerArticleConcertEditActionPostWithErrorTest extends CmsControllerWithPermissionTestCase {
 	public function setUp() {
 		parent::setUp();
 
-		$data = array('titre' => '',
-									'debut' => '01/04/2011',
-									'fin' => '26/03/2011',
-									'events_debut' => '',
-									'events_fin' => '01/04/2011',
-									'description' => '',
-									'contenu' => '',
-									'id_cat' => 23);
-
-
-		$this
-			->getRequest()
-			->setMethod('POST')
-			->setPost($data);
-		$this->dispatch('/admin/cms/edit/id/4');
+		$this->postDispatch('/admin/cms/edit/id/4',
+												['titre' => '',
+												 'debut' => '01/04/2011',
+												 'fin' => '26/03/2011',
+												 'events_debut' => '',
+												 'events_fin' => '01/04/2011',
+												 'description' => '',
+												 'contenu' => '',
+												 'id_cat' => 23]);
 	}
 
 
 	/** @test */
-	function actionShouldBeEdit() {
+	public function actionShouldBeEdit() {
 		$this->assertAction('edit');
 	}
 
 
 	/** @test */
-	function shouldNotRedirect() {
+	public function shouldNotRedirect() {
 		$this->assertNotRedirect();
 	}
 
 
 	/** @test */
-	function loaderSaveShouldNotHaveBeenCalled() {
-		$this->assertFalse($this->article_wrapper->methodHasBeenCalled('save'));
+	public function currentTitleShouldBeDifferentFromDatabaseOne() {
+		$this->assertTrue(Class_Article::find(4)->hasChangedAttribute('titre'));
 	}
 
 
 	/** @test */
-	function errorShouldContainsDateDebutError() {
+	public function errorShouldContainsDateDebutError() {
 		$this->assertXPathContentContains('//ul[@class="errors"]',
 																			"La date de début de publication doit être plus récente que la date de fin",
 																			$this->_response->getBody());
@@ -1136,7 +1155,7 @@ class CmsControllerArticleConcertEditActionPostWithErrorTest extends CmsControll
 
 
 	/** @test */
-	function errorShouldContainsVousDevezCompleterLeChampTitre() {
+	public function errorShouldContainsVousDevezCompleterLeChampTitre() {
 		$this->assertXPathContentContains('//ul[@class="errors"]', "Vous devez compléter le champ 'Titre'");
 	}
 }
@@ -1144,7 +1163,7 @@ class CmsControllerArticleConcertEditActionPostWithErrorTest extends CmsControll
 
 
 
-class CmsControllerArticleAddActionPostTest extends CmsControllerTestCase {
+class CmsControllerArticleAddActionPostTest extends CmsControllerWithPermissionTestCase {
 	public function setUp() {
 		parent::setUp();
 
@@ -1152,17 +1171,33 @@ class CmsControllerArticleAddActionPostTest extends CmsControllerTestCase {
 						 'debut' => '',
 						 'fin' => '',
 						 'events_debut' => '',
+						 'auteur' => $this->fixture('Class_Users',
+																				['id' => 1,
+																				 'login' => 'tom',
+																				 'mail' => 'tom@afi-sa.fr',
+																				 'password' => 'pwd']),
+
 						 'events_fin' => '',
 						 'description' => '',
 						 'id_cat' => 23,
 						 'contenu' => 'Youpi!!',
 						 'domaine_ids' => ['11','12']];
-
+		$this->mock_transport = new MockMailTransport();
+		Zend_Mail::setDefaultTransport($this->mock_transport);
+		Class_Profil::getCurrentProfil()->setMailSite('laurent@afi-sa.fr');
+		Class_AdminVar::newInstanceWithId('WORKFLOW')->setValeur('0');
 		$this->postDispatch('/admin/cms/add/id_cat/23', $data);
 		$this->new_article = Class_Article::findFirstBy(['order' => 'id desc']);
 	}
 
 
+
+
+	/** @test */
+	public function MailShouldNotBeSendToUserIfWorkflowIsNotActive() {
+		$this->assertEquals([],$this->mock_transport->getSentMails());
+	}
+
 	/** @test */
 	function contenuShouldEqualsYoupii() {
 		$this->assertEquals('Youpi!!', $this->new_article->getContenu());
@@ -1211,7 +1246,7 @@ class CmsControllerArticleAddActionPostTest extends CmsControllerTestCase {
 
 
 
-class CmsControllerNewsAddToCatALaUneActionTest extends CmsControllerTestCase {
+class CmsControllerNewsAddToCatALaUneActionTest extends CmsControllerWithPermissionTestCase {
 	public function setUp() {
 		parent::setUp();
 		$this->dispatch('admin/cms/add/id_cat/23',true);
@@ -1276,7 +1311,7 @@ class CmsControllerNewsAddToCatALaUneActionTest extends CmsControllerTestCase {
 
 
 
-class CmsControllerNewsAddActionWithoutWorkflowTest extends CmsControllerTestCase {
+class CmsControllerNewsAddActionWithoutWorkflowTest extends CmsControllerWithPermissionTestCase {
 	public function setUp() {
 		parent::setUp();
 		Class_AdminVar::newInstanceWithId('WORKFLOW')->setValeur('');
@@ -1297,11 +1332,11 @@ class CmsControllerNewsAddActionWithoutWorkflowTest extends CmsControllerTestCas
 
 
 
-class CmsControllerArticleVisibilityTest extends CmsControllerTestCase {
+class CmsControllerArticleVisibilityTest extends CmsControllerWithPermissionTestCase {
 	/** @test */
 	function makeVisibleShouldRedirectToCategorieEvenements() {
 		$this->dispatch('admin/cms/makevisible/id/4');
-		$this->assertRedirectTo('/admin/cms/index/id_cat/34');
+		$this->assertRedirectTo('/admin/cms/index/id/4');
 		$this->assertTrue($this->concert->isVisible());
 	}
 
@@ -1309,7 +1344,7 @@ class CmsControllerArticleVisibilityTest extends CmsControllerTestCase {
 	/** @test */
 	function makeInvisibleShouldRedirectToCategorieEvenements() {
 		$this->dispatch('admin/cms/makeinvisible/id/4');
-		$this->assertRedirectTo('/admin/cms/index/id_cat/34');
+		$this->assertRedirectTo('/admin/cms/index/id/4');
 		$this->assertFalse($this->concert->isVisible());
 	}
 }
@@ -1317,63 +1352,138 @@ class CmsControllerArticleVisibilityTest extends CmsControllerTestCase {
 
 
 
-class CmsControllerNewsAddActionPostWithoutWorkflowTest extends CmsControllerTestCase {
+class CmsControllerNewsAddActionPostWithoutWorkflowTest extends CmsControllerWithPermissionTestCase {
 	/** @var Class_Article */
 	protected $_article;
 
 	public function setUp() {
 		parent::setUp();
 
-		Class_AdminVar::getLoader()->newInstanceWithId('WORKFLOW')->setValeur('0');
-
-		$data = array(
-			'titre' => 'Katsuhiro Otomo en dédicace !',
-			'debut' => '',
-			'fin' => '',
-			'events_debut' => '',
-			'events_fin' => '',
-			'description' => '',
-			'id_cat' => 23,
-			'contenu' => 'Ne manquez pas cet évènement.',
-			'status' => Class_Article::STATUS_DRAFT,
-		);
-
-		$this->getRequest()->setMethod('POST')
-												->setPost($data);
-
-		$this->dispatch('/admin/cms/add/id_cat/23');
+		$this->fixture('Class_AdminVar', ['id' => 'WORKFLOW', 'valeur' => 0]);
 
-		$this->_article = $this->article_wrapper->getFirstAttributeForLastCallOn('save');
+		$this->postDispatch('/admin/cms/add/id_cat/23',
+												['titre' => 'Katsuhiro Otomo en dédicace !',
+												 'debut' => '',
+												 'fin' => '',
+												 'events_debut' => '',
+												 'events_fin' => '',
+												 'description' => '',
+												 'id_cat' => 23,
+												 'contenu' => 'Ne manquez pas cet évènement.',
+												 'status' => Class_Article::STATUS_DRAFT,
+												]);
 	}
 
 	/** @test */
 	public function statusShouldNotBeUpdatable() {
-		$this->assertEquals(3, $this->_article->getStatus());
+		$this->assertEquals(3, Class_Article::find(5)->getStatus());
 	}
-
 }
 
 
 
-abstract class CmsControllerWorkflowTestCase extends CmsControllerTestCase {
+abstract class CmsControllerWorkflowTestCase extends CmsControllerWithPermissionTestCase {
 	public function setUp() {
 		parent::setUp();
-		Class_Article::beVolatile();
-		Class_CustomField_Meta::beVolatile();
-		Class_Customfield::beVolatile();
-		Class_Users::beVolatile();
 		$this->mock_transport = new MockMailTransport();
 		Zend_Mail::setDefaultTransport($this->mock_transport);
 		Class_Profil::getCurrentProfil()->setMailSite('laurent@afi-sa.fr');
 
-		Class_AdminVar::newInstanceWithId('WORKFLOW')->setValeur('1');
-		Class_AdminVar::newInstanceWithId('WORKFLOW_TEXT_MAIL_ARTICLE_VALIDATED')->setValeur('');
+		$this->fixture('Class_AdminVar', ['id' => 'WORKFLOW', 'valeur' => '1']);
+		$this->fixture('Class_AdminVar',
+									 ['id' => 'WORKFLOW_TEXT_MAIL_ARTICLE_VALIDATED',
+										'valeur' => 'L\'article TITRE_ARTICLE a été validé. URL_ARTICLE']);
+		$this->fixture('Class_AdminVar',
+									 ['id' => 'WORKFLOW_TEXT_MAIL_ARTICLE_PENDING',
+										'valeur' => 'Un nouvel article est à valider. TITRE_ARTICLE URL_ARTICLE']);
+		$this->fixture('Class_AdminVar',
+									 ['id' => 'WORKFLOW_TEXT_MAIL_ARTICLE_REFUSED',
+										'valeur' => 'L\'article a été refusé.']);
 	}
 }
 
 
 
-class CmsControllerNewsAddActionPostWithWorkflowTest extends CmsControllerWorkflowTestCase {
+
+abstract class CmsControllerWorkflowPermissionsTestCase extends CmsControllerWorkflowTestCase {
+	public function setUp() {
+		parent::setUp();
+		$this->fixture('Class_AdminVar',
+									 ['id' => 'WORKFLOW',
+										'valeur' => '[{"id":"12", "label":"À valider urgemment"}]']);
+
+		$this->fixture('Class_Article',
+									 ['id' => 18,
+										'id_user' => '',
+										'titre' => 'News',
+										'contenu' => 'September News',
+										'description' => '',
+										'status' => '1',
+										'categorie' => Class_ArticleCategorie::find(34)]);
+	}
+}
+
+
+
+
+class CmsControllerWorkflowWithoutPermissionsTest extends CmsControllerWorkflowPermissionsTestCase {
+	public function setUp() {
+		parent::setUp();
+		Class_Users::getIdentity()
+			->setRoleLevel(ZendAfi_Acl_AdminControllerRoles::MODO_PORTAIL)
+			->addUserGroup($this->fixture('Class_UserGroup',
+																		['id' => 46,
+																		 'libelle' => 'Modérateurs',
+																		 'group_type' => Class_UserGroup::TYPE_MANUAL,
+																		 'users' => [],
+																		 'rights' => [],
+																		 'role_level' => ZendAfi_Acl_AdminControllerRoles::MODO_PORTAIL,
+																		 'permissions' => []]));
+
+
+		$this->dispatch('/admin/cms/edit/id/18', true);
+	}
+
+
+	/** @test */
+	public function dynamicStatusShouldBeDisabled() {
+		$this->assertXPath('//input[@name="status"][@value="12"][@disabled="disabled"]');
+	}
+}
+
+
+
+class CmsControllerWorkflowWithPermissionsTest extends CmsControllerWorkflowPermissionsTestCase {
+	public function setUp() {
+		parent::setUp();
+		Class_Users::getIdentity()
+			->setRoleLevel(ZendAfi_Acl_AdminControllerRoles::MODO_PORTAIL)
+			->addUserGroup($this->fixture('Class_UserGroup',
+																		['id' => 46,
+																		 'libelle' => 'Modérateurs',
+																		 'group_type' => Class_UserGroup::TYPE_MANUAL,
+																		 'users' => [],
+																		 'rights' => [],
+																		 'role_level' => ZendAfi_Acl_AdminControllerRoles::MODO_PORTAIL]));
+
+		Class_Permission::findDynamicWorkflow(12)
+			->permitTo(Class_UserGroup::find(46), Class_ArticleCategorie::find(34));
+
+		$this->dispatch('/admin/cms/edit/id/18', true);
+	}
+
+
+	/** @test */
+	public function dynamicStatusShouldBeEnabled() {
+		$this->assertNotXPath('//input[@name="status"][@value="12"][@disabled="disabled"]');
+		$this->assertXPath('//input[@name="status"][@value="12"]');
+	}
+}
+
+
+
+class CmsControllerNewsAddActionPostWithWorkflowTest
+	extends CmsControllerWorkflowTestCase {
 	protected $_article,
 		$mock_transport,
 		$_basePostDatas = ['titre' => 'Katsuhiro Otomo en dédicace !',
@@ -1398,7 +1508,8 @@ class CmsControllerNewsAddActionPostWithWorkflowTest extends CmsControllerWorkfl
 										'titre' => 'News',
 										'contenu' => 'September News',
 										'description' => '',
-										'status' =>'']);
+										'status' =>'',
+										'categorie' => Class_ArticleCategorie::find(34)]);
 	}
 
 
@@ -1409,17 +1520,37 @@ class CmsControllerNewsAddActionPostWithWorkflowTest extends CmsControllerWorkfl
 	}
 
 
+	protected function postArticleToDynamicStatus() {
+		$this->fixture('Class_AdminVar',
+									 ['id' => 'WORKFLOW',
+										'valeur' => '[{"id":"12", "label":"A valider "}]']);
+		$data = $this->_basePostDatas;
+		$data['status'] = 12;
+		$this->postDispatch('/admin/cms/edit/id/18', $data);
+	}
+
+
 	/** @test */
 	public function statusShouldBeUpdated() {
 		$this->postArticleValidated();
-		$this->assertEquals(Class_Article::STATUS_VALIDATED, Class_Article::findFirstBy(['order' => 'id desc'])->getStatus());
+		$this->assertEquals(Class_Article::STATUS_VALIDATED,
+												Class_Article::findFirstBy(['order' => 'id desc'])->getStatus());
 	}
 
 
 	/** @test */
 	public function validatedNewsShouldSendMailToAuthor() {
 		$this->postArticleValidated();
-		$this->assertEquals('l@afi-sa.fr',$this->mock_transport->getSentMails()[0]->getRecipients()[0]);
+		$this->assertEquals('l@afi-sa.fr',
+												$this->mock_transport->getSentMails()[0]->getRecipients()[0]);
+	}
+
+
+	/** @test */
+	public function dynamicStatusShouldSendMailToAdmins() {
+		$this->postArticleToDynamicStatus();
+		$this->assertEquals('laurent@afi-sa.fr',
+												$this->mock_transport->getSentMails()[0]->getRecipients()[0]);
 	}
 
 
@@ -1450,7 +1581,6 @@ class CmsControllerNewsAddActionPostWithWorkflowTest extends CmsControllerWorkfl
 
 	/** @test */
 	public function newArticleSavedWithStatusAValiderShouldSendMail() {
-		Class_Article::beVolatile();
 		$data = $this->_basePostDatas;
 		$data['status'] = Class_Article::STATUS_VALIDATION_PENDING;
 		$this->postDispatch('/admin/cms/add/id_cat/23',
@@ -1491,7 +1621,7 @@ class CmsControllerNewsAddActionPostWithWorkflowTest extends CmsControllerWorkfl
 	/** @test */
 	public function sentMailToAdminWhenValidationPendingShouldContainsDefaultText() {
 		$this->postArticleAValider();
-		 $this->assertEquals('Un nouvel article est à valider. Katsuhiro Otomo en dédicace ! http://localhost'.BASE_URL.'/cms/articleview/id/18',quoted_printable_decode($this->mock_transport->getSentMails()[0]->getBodyText()->getContent()));
+		$this->assertEquals('Un nouvel article est à valider. Katsuhiro Otomo en dédicace ! http://localhost'.BASE_URL.'/cms/articleview/id/18',quoted_printable_decode($this->mock_transport->getSentMails()[0]->getBodyText()->getContent()));
 	}
 
 
@@ -1507,12 +1637,9 @@ class CmsControllerNewsAddActionPostWithWorkflowTest extends CmsControllerWorkfl
 		$data = $this->_basePostDatas;
 		$data['domaine_ids'] = '11;12';
 
-		$this->getRequest()->setMethod('POST')->setPost($data);
-		$this->dispatch('/admin/cms/add/id/23');
-
-		$this->_article = $this->article_wrapper->getFirstAttributeForLastCallOn('save');
-
-		$this->assertEquals('11;12', $this->_article->getDomaineIds());
+		$this->postDispatch('/admin/cms/add/id/23', $data);
+		$this->assertEquals('11;12',
+												Class_Article::findFirstBy(['order' => 'id desc'])->getDomaineIds());
 	}
 
 
@@ -1520,13 +1647,9 @@ class CmsControllerNewsAddActionPostWithWorkflowTest extends CmsControllerWorkfl
 	public function withUnknownStatusShouldNotBeUpdated() {
 		$data = $this->_basePostDatas;
 		$data['status'] = 999999999;
-
-		$this->getRequest()->setMethod('POST')->setPost($data);
-		$this->dispatch('/admin/cms/add/id_cat/23');
-
-		$this->_article = $this->article_wrapper->getFirstAttributeForLastCallOn('save');
-
-		$this->assertEquals(Class_Article::STATUS_DRAFT, $this->_article->getStatus());
+		$this->postDispatch('/admin/cms/add/id_cat/23', $data);
+		$this->assertEquals(Class_Article::STATUS_DRAFT,
+												Class_Article::findFirstBy(['order' => 'id desc'])->getStatus());
 	}
 }
 
@@ -1611,34 +1734,102 @@ class CmsControllerWorkflowArticleRefusedTest extends CmsControllerWorkflowTestC
 }
 
 
-class CmsControllerNewsAddActionWithWorkflowTest extends CmsControllerTestCase {
+
+abstract class CmsControllerNewsAddActionPostWithWorkflowTestCase
+	extends CmsControllerWithPermissionTestCase {
 	public function setUp() {
 		parent::setUp();
-		Class_AdminVar::getLoader()->newInstanceWithId('WORKFLOW')->setValeur('1');
-		Class_Users::getLoader()->getIdentity()->setRoleLevel(ZendAfi_Acl_AdminControllerRoles::MODO_BIB);
-		$this->dispatch('admin/cms/add/id_cat/23');
+		$this->fixture('Class_AdminVar',
+									 ['id' => 'WORKFLOW',
+										'valeur' => '[{"id":10, "label":"A valider service COM"}]']);
+		Class_Users::getIdentity()->setRoleLevel(ZendAfi_Acl_AdminControllerRoles::MODO_BIB);
+	}
+}
+
+
+
+
+class CmsControllerNewsAddActionPostWithCustomStatusesWorkflowTest
+	extends CmsControllerNewsAddActionPostWithWorkflowTestCase {
+	public function setUp() {
+		parent::setUp();
+		$this->postDispatch('admin/cms/add/id_cat/23',
+												['titre' => 'Nothing else matters',
+												 'contenu' => 'So close, no matter how faaaaaaar.',
+												 'status' => 10,
+												 'debut' => '',
+												 'fin' => '',
+												 'events_debut' => '',
+												 'events_fin' => '']);
 	}
 
+
+	/** @test */
+	public function statusShouldBe10() {
+		$this->assertEquals(10,
+												Class_Article::findFirstBy(['order' => 'id desc'])->getStatus());
+	}
+}
+
+
+
+class CmsControllerNewsAddActionWithCustomStatusesWorkflowTest
+	extends CmsControllerNewsAddActionPostWithWorkflowTestCase {
+	public function setUp() {
+		parent::setUp();
+		$this->dispatch('admin/cms/add/id_cat/23', true);
+	}
+
+
 	/** @test */
 	public function workflowInputsShouldAppear() {
-		$this->assertXpathCount('//input[@name="status"]', 5);
+		$this->assertXpathCount('//input[@name="status"]', 6);
 	}
 
+
+	/** @test */
+	public function customStatusShouldBeAtThirdPosition() {
+		$this->assertXPath('//td/label[3]//input[@name="status"][@value="10"]',
+											 $this->_response->getBody());
+	}
+
+
 	/** @test */
 	public function withUserRedacteurBibStatusValideShouldBeDisabled() {
 		$this->assertXpath('//input[@name="status"][@value="3"][@disabled="disabled"]');
 	}
 
+
 	/** @test */
 	public function withUserAdminStatusValideShouldNotBeDisabled() {
-		Class_Users::getLoader()->getIdentity()->setRoleLevel(ZendAfi_Acl_AdminControllerRoles::ADMIN_BIB);
+		Class_Users::getIdentity()->setRoleLevel(ZendAfi_Acl_AdminControllerRoles::ADMIN_BIB);
 		$this->bootstrap();
 		$this->dispatch('admin/cms/add/id_cat/23');
 		$this->assertNotXpath('//input[@name="status"][@value="3"][@disabled="disabled"]');
 	}
 }
 
-class CmsControllerArticleConcertEditActionWithoutWorkflowTest extends CmsControllerTestCase {
+
+
+class CmsControllerNewsAddActionWithSimpleWorkflowTest extends CmsControllerWithPermissionTestCase {
+	public function setUp() {
+		parent::setUp();
+		Class_AdminVar::newInstanceWithId('WORKFLOW')
+			->setValeur('1');
+		Class_Users::getIdentity()->setRoleLevel(ZendAfi_Acl_AdminControllerRoles::MODO_BIB);
+		$this->dispatch('admin/cms/add/id_cat/23', true);
+	}
+
+
+	/** @test */
+	public function workflowInputsShouldAppear() {
+		$this->assertXpathCount('//input[@name="status"]', 5);
+	}
+}
+
+
+
+class CmsControllerArticleConcertEditActionWithoutWorkflowTest extends CmsControllerWithPermissionTestCase {
 	public function setUp() {
 		parent::setUp();
 		Class_AdminVar::getLoader()->newInstanceWithId('WORKFLOW')->setValeur('0');
@@ -1651,38 +1842,97 @@ class CmsControllerArticleConcertEditActionWithoutWorkflowTest extends CmsContro
 	}
 }
 
-class CmsControllerArticleConcertEditActionWithWorkflowTest extends CmsControllerTestCase {
+
+
+abstract class CmsControllerArticleConcertEditActionWithWorkflowAndModoTestCase
+	extends CmsControllerWithPermissionTestCase {
+	public function setUp() {
+		parent::setUp();
+		Class_Users::getIdentity()->beModoPortail()->save();
+		$this->fixture('Class_AdminVar', ['id' => 'WORKFLOW', 'valeur' => '1']);
+	}
+
+
+	protected function assertStatusXpath($value, $predicates='') {
+		$this->assertXpath(sprintf('//input[@name="status"][@value="%d"]%s',
+															 $value, $predicates),
+											 $this->_response->getBody());
+	}
+
+
+	protected function assertCurrentStatus($id) {
+		$this->assertStatusXpath($id, '[@checked="checked"]');
+	}
+
+
+	protected function assertDisabledStatus($id) {
+		$this->assertStatusXpath($id, '[@disabled="disabled"]');
+	}
+
+
+	protected function assertEnabledStatus($id) {
+		$this->assertStatusXpath($id, '[not(@disabled="disabled")]');
+	}
+}
+
+
+
+class CmsControllerArticleConcertEditActionWithWorkflowTest
+	extends CmsControllerArticleConcertEditActionWithWorkflowAndModoTestCase {
 	public function setUp() {
 		parent::setUp();
-		Class_Users::getLoader()->getIdentity()->setRoleLevel(ZendAfi_Acl_AdminControllerRoles::MODO_PORTAIL);
-		Class_AdminVar::getLoader()->newInstanceWithId('WORKFLOW')->setValeur('1');
 		$this->concert->setStatus(Class_Article::STATUS_VALIDATED);
-		$this->dispatch('/admin/cms/edit/id/4');
+		$this->dispatch('/admin/cms/edit/id/4', true);
 	}
 
+
 	/** @test */
 	public function workflowInputsShouldAppear() {
 		$this->assertXpathCount('//input[@name="status"]', 5);
 	}
 
+
 	/** @test */
 	public function checkedRadioShouldBeValidatedStatus() {
-		$this->assertXpath(sprintf('//input[@name="status"][@value="%d"][@checked="checked"]', Class_Article::STATUS_VALIDATED));
+		$this->assertCurrentStatus(Class_Article::STATUS_VALIDATED);
 	}
 
+
 	/** @test */
-	public function withUserRedacteurPortailStatusValideShouldBeDisabled() {
-		$this->assertXpath('//input[@name="status"][@value="3"][@disabled="disabled"]');
+	public function validatedStatusShouldBeDisabled() {
+		$this->assertDisabledStatus(Class_Article::STATUS_VALIDATED);
 	}
 }
 
 
 
-class CmsControllerArticleConcertEditActionWithWorkflowStatusRefusedTest extends CmsControllerTestCase {
+class CmsControllerArticleConcertEditActionWithWorkflowValidatePermissionTest
+	extends CmsControllerArticleConcertEditActionWithWorkflowAndModoTestCase {
+	public function setUp() {
+		parent::setUp();
+		$this->concert->setStatus(Class_Article::STATUS_VALIDATED);
+		$group = $this->fixture('Class_UserGroup',
+														['id' => 677, 'libelle' => 'Testing permitted group']);
+		Class_Users::getIdentity()->addUserGroup($group);
+		Class_Permission::getWorkflow(Class_Article::STATUS_VALIDATED)
+			->permitTo($group, Class_Article::find(4)->getCategorie());
+		$this->dispatch('/admin/cms/edit/id/4', true);
+	}
+
+
+	/** @test */
+	public function validatedStatusShouldNotBeDisabled() {
+		$this->assertEnabledStatus(Class_Article::STATUS_VALIDATED);
+	}
+}
+
+
+
+class CmsControllerArticleConcertEditActionWithWorkflowStatusRefusedTest
+	extends CmsControllerArticleConcertEditActionWithWorkflowAndModoTestCase {
+
 	public function setUp() {
 		parent::setUp();
-		Class_Users::getLoader()->getIdentity()->setRoleLevel(ZendAfi_Acl_AdminControllerRoles::MODO_PORTAIL);
-		Class_AdminVar::getLoader()->newInstanceWithId('WORKFLOW')->setValeur('1');
 		$this->concert->setStatus(Class_Article::STATUS_REFUSED);
 		$this->dispatch('/admin/cms/edit/id/4', true);
 	}
@@ -1690,7 +1940,7 @@ class CmsControllerArticleConcertEditActionWithWorkflowStatusRefusedTest extends
 
 	/** @test */
 	public function checkedRadioShouldBeRefusedStatus() {
-		$this->assertXpath(sprintf('//input[@name="status"][@value="%d"][@checked="checked"]', Class_Article::STATUS_REFUSED));
+		$this->assertCurrentStatus(Class_Article::STATUS_REFUSED);
 	}
 
 
@@ -1715,24 +1965,19 @@ class CmsControllerArticleConcertEditActionWithWorkflowStatusRefusedTest extends
 
 
 
-class CmsControllerArticleAddActionInvalidDatePostTest extends CmsControllerTestCase {
+class CmsControllerArticleAddActionInvalidDatePostTest extends CmsControllerWithPermissionTestCase {
 	public function setUp() {
 		parent::setUp();
 
-		$data = array('titre' => '',
-									'debut' => '',
-									'fin' => '',
-									'events_debut' => '',
-									'events_fin' => '',
-									'description' => '',
-									'contenu' => '',
-									'id_cat' => 23);
-
-		$this
-			->getRequest()
-			->setMethod('POST')
-			->setPost($data);
-		$this->dispatch('/admin/cms/add/id_cat/23');
+		$this->postDispatch('/admin/cms/add/id_cat/23',
+												['titre' => '',
+												 'debut' => '',
+												 'fin' => '',
+												 'events_debut' => '',
+												 'events_fin' => '',
+												 'description' => '',
+												 'contenu' => '',
+												 'id_cat' => 23]);
 	}
 
 
@@ -1749,8 +1994,8 @@ class CmsControllerArticleAddActionInvalidDatePostTest extends CmsControllerTest
 
 
 	/** @test */
-	function loaderSaveShouldNotHaveBeenCalled() {
-		$this->assertFalse($this->article_wrapper->methodHasBeenCalled('save'));
+	function articleShouldNotBeSaved() {
+		$this->assertNull(Class_Article::find(5));
 	}
 
 
@@ -1762,8 +2007,7 @@ class CmsControllerArticleAddActionInvalidDatePostTest extends CmsControllerTest
 
 
 
-
-class CmsControllerArticleIndexActionTest extends CmsControllerTestCase {
+class CmsControllerArticleIndexActionTest extends CmsControllerWithPermissionTestCase {
 	public function setUp() {
 		parent::setUp();
 		$this->dispatch('admin/cms', true);
@@ -1796,8 +2040,42 @@ class CmsControllerArticleIndexActionTest extends CmsControllerTestCase {
 
 
 
+class CmsControllerArticleIndexActionWithSimpleWorkflowTest extends CmsControllerTestCase {
+	public function setUp() {
+		parent::setUp();
+		Class_AdminVar::newInstanceWithId('WORKFLOW')
+			->setValeur('1');
+		$this->dispatch('admin/cms', true);
+	}
 
-class CmsControllerArticleTraductionFREditTest extends CmsControllerTestCase {
+
+	/** @test */
+	public function searchStatusShouldBePresent() {
+		$this->assertXPath('//div[@class="treeViewSearchStatus"][contains(@style, "float")]');
+	}
+}
+
+
+
+class CmsControllerArticleIndexActionWithCustomWorkflowTest extends CmsControllerTestCase {
+	public function setUp() {
+		parent::setUp();
+		Class_AdminVar::newInstanceWithId('WORKFLOW')
+			->setValeur('[{"id":89, "label":"Testing test test"}]');
+		$this->dispatch('admin/cms', true);
+	}
+
+
+	/** @test */
+	public function searchStatusShouldBePresent() {
+		$this->assertXPath('//div[@class="treeViewSearchStatus"][not(contains(@style, "float"))]');
+	}
+}
+
+
+
+
+class CmsControllerArticleTraductionFREditTest extends CmsControllerWithPermissionTestCase {
 	public function setUp() {
 		parent::setUp();
 		$this->dispatch('/admin/cms/edit/id/4/lang/fr');
@@ -1812,7 +2090,7 @@ class CmsControllerArticleTraductionFREditTest extends CmsControllerTestCase {
 
 
 
-class CmsControllerArticleWithUnknownLangueTraductionEditTest extends CmsControllerTestCase {
+class CmsControllerArticleWithUnknownLangueTraductionEditTest extends CmsControllerWithPermissionTestCase {
 	public function setUp() {
 		parent::setUp();
 
@@ -1841,7 +2119,7 @@ class CmsControllerArticleWithUnknownLangueTraductionEditTest extends CmsControl
 
 
 
-class CmsControllerArticleNewTraductionEditTest extends CmsControllerTestCase {
+class CmsControllerArticleNewTraductionEditTest extends CmsControllerWithPermissionTestCase {
 	public function setUp() {
 		parent::setUp();
 		$this->dispatch('/admin/cms/edit/id/4/lang/ro');
@@ -1900,7 +2178,7 @@ class CmsControllerArticleNewTraductionEditTest extends CmsControllerTestCase {
 
 
 
-class CmsControllerArticleTraductionEditWithoutDescriptionTest extends CmsControllerTestCase {
+class CmsControllerArticleTraductionEditWithoutDescriptionTest extends CmsControllerWithPermissionTestCase {
 	public function setUp() {
 		parent::setUp();
 		$this->concert->setDescription('');
@@ -1910,13 +2188,14 @@ class CmsControllerArticleTraductionEditWithoutDescriptionTest extends CmsContro
 
 	/** @test */
 	function descriptionOriginalShouldNotBeVisible() {
-		$this->assertNotXPathContentContains('//div[@class="art_original"]', 'Venez nombreux ici: <img src="'.BASE_URL.'/images/bonlieu.jpg" />');
+		$this->assertNotXPathContentContains('//div[@class="art_original"]',
+																				 'Venez nombreux ici: <img src="'.BASE_URL.'/images/bonlieu.jpg" />');
 	}
 }
 
 
 
-class CmsControllerArticleExistingTraductionEditTest extends CmsControllerTestCase {
+class CmsControllerArticleExistingTraductionEditTest extends CmsControllerWithPermissionTestCase {
 	public function setUp() {
 		parent::setUp();
 
@@ -1959,19 +2238,16 @@ class CmsControllerArticleExistingTraductionEditTest extends CmsControllerTestCa
 
 
 
-class CmsControllerArticleNewTraductionPostTest extends CmsControllerTestCase {
+class CmsControllerArticleNewTraductionPostTest extends CmsControllerWithPermissionTestCase {
 	public function setUp() {
 		parent::setUp();
-		$data = array('titre' => 'Erik în concert',
-									'description' => 'Mulţi vin',
-									'contenu' => 'la Bonlieu');
-		$this
-			->getRequest()
-			->setMethod('POST')
-			->setPost($data);
-		$this->dispatch('/admin/cms/edit/id/4/lang/ro');
 
-		$this->article_roumain = $this->article_wrapper->getFirstAttributeForLastCallOn('save');
+		$this->postDispatch('/admin/cms/edit/id/4/lang/ro',
+												['titre' => 'Erik în concert',
+												 'description' => 'Mulţi vin',
+												 'contenu' => 'la Bonlieu']);
+
+		$this->article_roumain = Class_Article::findFirstBy(['order' => 'id desc']);
 	}
 
 
@@ -2002,7 +2278,7 @@ class CmsControllerArticleNewTraductionPostTest extends CmsControllerTestCase {
 
 
 
-class CmsControllerDeleteArticleTest extends CmsControllerTestCase {
+class CmsControllerDeleteArticleTest extends CmsControllerWithPermissionTestCase {
 	public function setUp() {
 		parent::setUp();
 
@@ -2036,22 +2312,16 @@ class CmsControllerDeleteArticleTest extends CmsControllerTestCase {
 
 
 
-class CmsControllerForceDeleteArticleTest extends CmsControllerTestCase {
+class CmsControllerForceDeleteArticleTest extends CmsControllerWithPermissionTestCase {
 	public function setUp() {
 		parent::setUp();
-
-		$this->article_wrapper
-			->whenCalled('delete')
-			->answers(true);
-
 		$this->dispatch('/admin/cms/force-delete/id/4', true);
 	}
 
 
 	/** @test */
 	public function deleteShouldHaveBeenCalledOnConcert() {
-		$this->deleted_art = $this->article_wrapper->getFirstAttributeForLastCallOn('delete');
-		$this->assertEquals($this->concert, $this->deleted_art);
+		$this->assertNull(Class_Article::find(4));
 	}
 
 
@@ -2064,11 +2334,11 @@ class CmsControllerForceDeleteArticleTest extends CmsControllerTestCase {
 
 
 
-class CmsControllerCategorieEvenementTest extends CmsControllerTestCase {
+class CmsControllerCategorieEvenementTest extends CmsControllerWithPermissionTestCase {
 
 	/** @test */
-	function deleteShouldRedirectToAdminCmsParentCategorie() {
-		$this->dispatch('/admin/cms/catdel/id/34');
+	public function deleteShouldRedirectToAdminCmsParentCategorie() {
+		$this->dispatch('/admin/cms-category/delete/id/34');
 
 		$this->assertRedirectTo('/admin/cms/index/id_cat/23');
 
@@ -2079,37 +2349,37 @@ class CmsControllerCategorieEvenementTest extends CmsControllerTestCase {
 
 	/** @test */
 	function addCategorieShouldDisplayCatEvenementsAsTitle() {
-		$this->dispatch('/admin/cms/catadd/id/34');
-		$this->assertXPathContentContains('//p', 'Localisation : Annecy');
+		$this->dispatch('/admin/cms-category/add/id/34');
+		$this->assertXPathContentContains('//p', 'Localisation: Annecy');
 	}
 
 
 	/** @test */
 	public function addCategorieCancelButtonShouldLinkToIndexIdCat34() {
-		$this->dispatch('/admin/cms/catadd/id/34');
-		$this->assertXPath("//div[contains(@onclick, '/admin/cms/index/id_cat/34')]");
+		$this->dispatch('/admin/cms-category/add/id/34');
+		$this->assertXPath("//div[contains(@onclick, '/admin/cms/index/id_cat/34')]",
+											 $this->_response->getBody());
 	}
 
 
 	/** @test */
-	function editCategorieShouldDisplayAllCat() {
-		$this->dispatch('/admin/cms/catedit/id/34');
-		$this->assertXPath('//input[@value="Evènements"]');
-		$this->assertXPathContentContains('//select[@name="id_cat_mere"]//option[@value="0"]', "Aucune");
+	public function editCategorieShouldDisplayAllCat() {
+		$this->dispatch('/admin/cms-category/edit/id/34');
+		$this->assertXPath('//input[@value="Evènements"]', $this->_response->getBody());
 		$this->assertXPathContentContains('//select[@name="id_cat_mere"]//option[@value="23"]', "A la Une");
 	}
 
 
 	/** @test */
 	public function editCategorieCancelButtonShouldLinkToIndexIdCat34() {
-		$this->dispatch('/admin/cms/catedit/id/34');
+		$this->dispatch('/admin/cms-category/edit/id/34');
 		$this->assertXPath("//div[contains(@onclick, '/admin/cms/index/id_cat/34')]");
 	}
 
 
 	/** @test */
-	function postAddCategorieShouldRedirectWithIdCat() {
-		$this->postDispatch('/admin/cms/catadd/id/34',
+	public function postAddCategorieShouldRedirectWithIdCat() {
+		$this->postDispatch('/admin/cms-category/add/id/34',
 												['libelle' => 'concerts',
 												 'id_cat_mere' => 34]);
 
@@ -2123,7 +2393,7 @@ class CmsControllerCategorieEvenementTest extends CmsControllerTestCase {
 
 
 	/** @test */
-	function postEditCategoriePostShouldRedirectWithIdCat() {
+	public function postEditCategoriePostShouldRedirectWithIdCat() {
 		Class_ArticleCategorie::getLoader()->newInstanceWithId(254);
 
 		$this
@@ -2131,7 +2401,7 @@ class CmsControllerCategorieEvenementTest extends CmsControllerTestCase {
 			->setMethod('POST')
 			->setPost(array('libelle' => 'Actualite',
 											'id_cat_mere' => 254));
-		$this->dispatch('/admin/cms/catedit/id/34');
+		$this->dispatch('/admin/cms-category/edit/id/34');
 		$this->assertEquals('/admin/cms/index/id_cat/34', $this->getResponseLocation());
 		$this->assertEquals('Actualite', $this->cat_evenements->getLibelle());
 		$this->assertEquals(254, $this->cat_evenements->getIdCatMere());
@@ -2140,12 +2410,9 @@ class CmsControllerCategorieEvenementTest extends CmsControllerTestCase {
 }
 
 
-
-
 class CmsControllerCategorieNotFoundTest extends CmsControllerTestCase {
 	/** @test */
 	function deleteShouldRedirectToAdminCms() {
-		Class_Article::beVolatile();
 		$this->dispatch('/admin/cms/delete/id/999');
 		$this->assertRedirect('admin/cms');
 	}
@@ -2153,15 +2420,14 @@ class CmsControllerCategorieNotFoundTest extends CmsControllerTestCase {
 
 	/** @test */
 	function addCategorieShouldDisplayAnnecy() {
-		$this->dispatch('/admin/cms/catadd');
-		$this->assertXPathContentContains('//p', 'Localisation : Annecy');
+		$this->dispatch('/admin/cms-category/add');
+		$this->assertXPathContentContains('//p', 'Localisation: Annecy');
 	}
 
 }
 
 
-class CmsControllerNewsAddToCatALaUneInNewsModuleActionTest extends CmsControllertestCase {
-
+class CmsControllerNewsAddToCatALaUneInNewsModuleActionTest extends CmsControllerWithPermissionTestCase {
 	public function setup() {
 		parent::setup();
 
@@ -2180,13 +2446,6 @@ class CmsControllerNewsAddToCatALaUneInNewsModuleActionTest extends CmsControlle
 			->whenCalled('save')
 			->answers(true);
 
-		Class_Article::whenCalled('save')
-			->willDo(
-				function($model){
-					$model->setId(4);
-					return true;
-				});
-
 		$this->postDispatch('/admin/cms/add/id_module/10/id_cat/34',
 												['titre' => 'Erik Truffaz - Ladyland quartet en concert',
 												 'debut' => '',
@@ -2202,10 +2461,8 @@ class CmsControllerNewsAddToCatALaUneInNewsModuleActionTest extends CmsControlle
 
 	/** @test **/
 	public function boiteNewsShouldDisplayArticleConcert() {
-		$this->assertEquals(
-			4,
-			Class_Profil::getCurrentProfil()
-			->getModuleAccueilPreferences(10, 'NEWS')['id_items']);
+		$this->assertEquals(Class_Article::findFirstBy(['order' => 'id desc'])->getId(),
+												Class_Profil::getCurrentProfil()->getModuleAccueilPreferences(10, 'NEWS')['id_items']);
 	}
 
 
@@ -2218,31 +2475,38 @@ class CmsControllerNewsAddToCatALaUneInNewsModuleActionTest extends CmsControlle
 
 
 
-class CmsControllerWithOptionPublicationDirecteWorkflowTest extends CmsControllerTestCase {
+class CmsControllerWithOptionPublicationDirecteWorkflowTest
+extends CmsControllerWithPermissionTestCase {
 	public function setup() {
 		parent::setup();
-		Class_Users::beVolatile();
-		Class_UserGroup::beVolatile();
-		Class_AdminVar::getLoader()->newInstanceWithId('WORKFLOW')->setValeur('1');
-		$referent = Class_Users::newInstanceWithId(23, ['pseudo' => 'referenctL',
-																											 'nom' => 'referent',
-																											 'login' => 'referent',
-																											 'password' => '123']);
-		$referent->changeRoleTo(ZendAfi_Acl_AdminControllerRoles::ADMIN_PORTAIL);
-		$referent->save();
-
-		$group = Class_UserGroup::newInstanceWithId(280)
-			->setLibelle('Referent')
-			->setUsers([$this->invite])
-			->setRights([Class_UserGroup::RIGHT_USER_PUBLICATION_DIRECTE]);
-		$group->save();
+		Class_AdminVar::newInstanceWithId('WORKFLOW')
+			->setValeur('1');
+		$referent = $this->fixture('Class_Users',
+															 ['id' => 23,
+																'pseudo' => 'referenctL',
+																'nom' => 'referent',
+																'login' => 'referent',
+																'password' => '123',
+																'user_groups' => [Class_UserGroup::find(22)]]);
+
+		$referent->changeRoleTo(ZendAfi_Acl_AdminControllerRoles::ADMIN_PORTAIL)
+						 ->save();
+
+		$group = $this->fixture('Class_UserGroup',
+														['id' => 280,
+														 'libelle' => 'Referent',
+														 'users' => [$this->invite],
+														 'rights' => [Class_UserGroup::RIGHT_USER_PUBLICATION_DIRECTE]]);
+
 		ZendAfi_Auth::getInstance()->logUser($referent);
+
 		$this->dispatch('admin/cms/add/id_cat/23');
 	}
 
 
   /** @test */
 	public function withUserPublicationDirecteStatusValideShouldNotBeDisabled() {
+		$this->assertXpath('//input[@name="status"][@value="3"]');
 		$this->assertNotXpath('//input[@name="status"][@value="3"][@disabled="disabled"]');
 	}
 }
@@ -2250,7 +2514,6 @@ class CmsControllerWithOptionPublicationDirecteWorkflowTest extends CmsControlle
 
 
 class CmsControllerCategoriesActionTest extends AbstractControllerTestCase {
-
 	protected $json = [['id' => 0,
 											'label' => 'Portail',
 											'categories'=> [['id' => 5,
@@ -2310,7 +2573,9 @@ class CmsControllerEditActionWithCustomFieldsTest extends AbstractControllerTest
 	public function setUp() {
 		parent::setUp();
 
+		Class_Exemplaire::beVolatile();
 		Class_CustomField_Meta::beVolatile();
+
 		$this->fixture('Class_CustomField',
 									 ['id' => 5,
 										'priority' => 3,
@@ -2339,4 +2604,25 @@ class CmsControllerEditActionWithCustomFieldsTest extends AbstractControllerTest
 		$this->assertXPathContentContains('//select[@name="field_5"]//option', 'newbies');
 	}
 }
-?>
\ No newline at end of file
+
+
+
+
+class CmsControllerEditArticleWithDate30December2014Test extends CmsControllertestCase {
+
+	public function setup() {
+		parent::setup();
+		$this->concert->setFin('2014-12-30');
+
+		Class_Users::getIdentity()->setRoleLevel(ZendAfi_Acl_AdminControllerRoles::SUPER_ADMIN);
+
+		$this->dispatch('/admin/cms/edit/id/4');
+	}
+
+
+	/** @test */
+	public function inputFinShouldContains30_12_2014() {
+		$this->assertXPath('//input[@name="fin"][@value="30/12/2014"]');
+	}
+}
+?>
diff --git a/tests/application/modules/admin/controllers/FrbrLinkControllerTest.php b/tests/application/modules/admin/controllers/FrbrLinkControllerTest.php
index dcbb9bccfbcd3e50135110e656d20bb2450851e6..bfcd7803438bf2ef00216bddaf79e0519d67a421 100644
--- a/tests/application/modules/admin/controllers/FrbrLinkControllerTest.php
+++ b/tests/application/modules/admin/controllers/FrbrLinkControllerTest.php
@@ -16,49 +16,60 @@
  *
  * 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
  */
 require_once 'AdminAbstractControllerTestCase.php';
 
 abstract class Admin_FrbrLinkControllerTestCase extends Admin_AbstractControllerTestCase {
 	public function setUp() {
 		parent::setUp();
-		$type = Class_FRBR_LinkType::newInstanceWithId(3)
-			->setLibelle('Suite')
-			->setFromSource('a pour suite')
-			->setFromTarget('est une suite de');
-
-		Storm_Test_ObjectWrapper::onLoaderOfModel('Class_FRBR_Link')
-			->whenCalled('findAllBy')
-			->with(['order' => 'source'])
-			->answers([
-								 Class_FRBR_Link::newInstanceWithId(2)
-								 ->setType($type)
-								 ->setSource('http://localhost' . BASE_URL . '/recherche/viewnotice/clef/LESGRANDSTEXTESDEDROITINTERNATIONALPUBLIC--DUPUYP--DALLOZ-2010-1?id_profil=1&type_doc=4')
-								 ->setSourceType(Class_FRBR_Link::TYPE_NOTICE)
-								 ->setTarget('http://localhost' . BASE_URL . '/recherche/viewnotice/clef/LESGRANDSTEXTESDEDROITINTERNATIONALPUBLIC--DUPUYP--DALLOZ-2010-2?id_profil=1&type_doc=4')
-								 ->setTargetType(Class_FRBR_Link::TYPE_NOTICE),
-
-								 Class_FRBR_Link::newInstanceWithId(3)
-								 ->setType($type)
-								 ->setSource('http://localhost' . BASE_URL . '/recherche/viewnotice/clef/AMNESTYINTERNATIONAL--GRANTR--GAMMA-2002-1?id_profil=1&type_doc=4')
-								 ->setSourceType(Class_FRBR_Link::TYPE_NOTICE)
-								 ->setTarget('http://localhost' . BASE_URL . '/recherche/viewnotice/clef/AMNESTYINTERNATIONAL--GRANTR--GAMMA-2002-2?id_profil=1&type_doc=4')
-								 ->setTargetType(Class_FRBR_Link::TYPE_NOTICE)
-								 ]);
-
-		Storm_Test_ObjectWrapper::onLoaderOfModel('Class_Notice')
-			->whenCalled('getNoticeByClefAlpha')
-			->with('LESGRANDSTEXTESDEDROITINTERNATIONALPUBLIC--DUPUYP--DALLOZ-2010-1')
-			->answers(Class_Notice::newInstanceWithId(33)
-				          ->setTitrePrincipal('Les grands textes tome 1')
-				          ->setAuteurPrincipal('Vidalio Nesti'))
-
-			->whenCalled('getNoticeByClefAlpha')
-			->with('LESGRANDSTEXTESDEDROITINTERNATIONALPUBLIC--DUPUYP--DALLOZ-2010-2')
-			->answers(Class_Notice::newInstanceWithId(33)
-				          ->setTitrePrincipal('Les grands textes tome 2')
-				          ->setAuteurPrincipal('Vidalio Nesto'));
+		Storm_Model_Loader::defaultToVolatile();
+
+		$this->fixture('Class_Notice',
+									 ['id' => 33,
+										'titre_principal' => 'Les grands textes tome 1',
+										'auteur_principal' => 'Vidalio Nesti',
+										'clef_alpha' => 'LESGRANDSTEXTESDEDROITINTERNATIONALPUBLIC--DUPUYP--DALLOZ-2010-1']);
+
+		$this->fixture('Class_Notice',
+									 ['id' => 34,
+										'titre_principal' => 'Les grands textes tome 2',
+										'auteur_principal' => 'Vidalio Nesti Target',
+										'clef_alpha' => 'LESGRANDSTEXTESDEDROITINTERNATIONALPUBLIC--DUPUYP--DALLOZ-2010-2']);
+
+		$this->fixture('Class_Album',
+									 ['id' => 12,
+										'titre' => 'Diaporama grands textes']);
+
+		$type = $this->fixture('Class_FRBR_LinkType',
+													 ['id' => 3,
+														'libelle' => 'Suite',
+														'from_source' => 'a pour suite',
+														'from_target' => 'est une suite de']);
+
+		$this->fixture('Class_FRBR_Link',
+									 ['id' => 2,
+										'type' => $type,
+										'source' => ROOT_URL . BASE_URL . '/recherche/viewnotice/clef/LESGRANDSTEXTESDEDROITINTERNATIONALPUBLIC--DUPUYP--DALLOZ-2010-1?id_profil=1&type_doc=4',
+										'target' => ROOT_URL . BASE_URL . '/recherche/viewnotice/clef/LESGRANDSTEXTESDEDROITINTERNATIONALPUBLIC--DUPUYP--DALLOZ-2010-2?id_profil=1&type_doc=4']);
+
+		$this->fixture('Class_FRBR_Link',
+									 ['id' => 3,
+										'type' => $type,
+										'source' => ROOT_URL . BASE_URL . '/recherche/viewnotice/clef/AMNESTYINTERNATIONAL--GRANTR--GAMMA-2002-1?id_profil=1&type_doc=4',
+										'target' => ROOT_URL . BASE_URL . '/recherche/viewnotice/clef/AMNESTYINTERNATIONAL--GRANTR--GAMMA-2002-2?id_profil=1&type_doc=4']);
+
+				$this->fixture('Class_FRBR_Link',
+									 ['id' => 43,
+										'type' => $type,
+										'source' => ROOT_URL . BASE_URL . '/recherche/viewnotice/clef/LESGRANDSTEXTESDEDROITINTERNATIONALPUBLIC--DUPUYP--DALLOZ-2010-1?id_profil=1&type_doc=4',
+										'target' => ROOT_URL . BASE_URL . '/bib-numerique/notice/id/12']);
+	}
+
+
+	public function tearDown() {
+		Storm_Model_Loader::defaultToDb();
+		parent::tearDown();
 	}
 }
 
@@ -80,55 +91,61 @@ class Admin_FrbrLinkControllerIndexTest extends Admin_FrbrLinkControllerTestCase
 
 	/** @test */
 	public function firstRowTDShouldContainsSourceTitle() {
-		$this->assertXPathContentContains('//tbody/tr[1]//td', 'Les grands textes tome 1');
+		$this->assertXPathContentContains('//tbody/tr//td', 'Les grands textes tome 1');
 	}
 
 
 	/** @test */
 	public function firstRowTDShouldContainsSourceAuthor() {
-		$this->assertXPathContentContains('//tbody/tr[1]//td', 'Vidalio Nesti');
+		$this->assertXPathContentContains('//tbody/tr//td', 'Vidalio Nesti');
 	}
 
 
 	/** @test */
 	public function firstRowTDShouldContainsSourceLink() {
-		$this->assertXPath('//tbody/tr[1]//td//a[contains(@href, "LESGRANDSTEXTESDEDROITINTERNATIONALPUBLIC--DUPUYP--DALLOZ-2010-1")]');
+		$this->assertXPath('//tbody/tr//td//a[contains(@href, "LESGRANDSTEXTESDEDROITINTERNATIONALPUBLIC--DUPUYP--DALLOZ-2010-1")]');
 	}
 
 
 	/** @test */
 	public function firstRowTDShouldContainsTargetTitle() {
-		$this->assertXPathContentContains('//tbody/tr[1]//td', 'Les grands textes tome 2');
+		$this->assertXPathContentContains('//tbody/tr//td', 'Les grands textes tome 2');
 	}
 
 
 	/** @test */
 	public function firstRowTDShouldContainsTargetAuthor() {
-		$this->assertXPathContentContains('//tbody/tr[1]//td', 'Vidalio Nesto');
+		$this->assertXPathContentContains('//tbody/tr//td', 'Vidalio Nesti Target');
+	}
+
+
+	/** @test */
+	public function shouldContainsAlbumTitle() {
+		$this->assertXPathContentContains('//tbody/tr//td', 'Diaporama grands textes');
 	}
 
 
 	/** @test */
 	public function firstRowTDShouldContainsTypeLabelFromSource() {
-		$this->assertXPathContentContains('//tbody/tr[1]//td', 'a pour suite');		
+		$this->assertXPathContentContains('//tbody/tr//td', 'a pour suite');
 	}
 
 
 	/** @test */
 	public function firstRowTDShouldContainsTypeLabelFromTarget() {
-		$this->assertXPathContentContains('//tbody/tr[1]//td', 'est une suite de');
+		$this->assertXPathContentContains('//tbody/tr//td', 'est une suite de');
 	}
 
 
 	/** @test */
 	function firstRowTDShouldHaveLinkToEdit() {
-		$this->assertXPath('//tbody/tr[1]//a[contains(@href, "frbr-link/edit/id/2")]');
+		$this->assertXPath('//tbody/tr//a[contains(@href, "frbr-link/edit/id/2")]');
 	}
 
 
 	/** @test */
 	public function firstRowTDShouldHaveLinkToDelete() {
-		$this->assertXPath('//tbody/tr[1]//a[contains(@href, "frbr-link/delete/id/2")]');
+		$this->assertXPath('//tbody/tr//a[contains(@href, "frbr-link/delete/id/2")]');
 	}
 
 
@@ -190,8 +207,8 @@ abstract class Admin_FrbrLinkControllerEditSuiteValidPostTestCase extends Admin_
 	public function shouldSave() {
 		$this->assertTrue(Class_FRBR_Link::methodHasBeenCalled('save'));
 	}
-	
-	
+
+
 	/** @test */
 	public function shouldRedirect() {
 		$this->assertRedirect();
@@ -202,20 +219,20 @@ abstract class Admin_FrbrLinkControllerEditSuiteValidPostTestCase extends Admin_
 
 class Admin_FrbrLinkControllerEditSuiteValidWithLocalUrlsPostTest extends Admin_FrbrLinkControllerEditSuiteValidPostTestCase {
 	protected $_link;
-	
+
 	public function setUp() {
 		parent::setUp();
 
 		$this->postDispatch('/admin/frbr-link/edit/id/2',
 			                  ['type_id' => 3,
-												 'source' => 'http://localhost/afi-opac3-ce/recherche/viewnotice/clef/LES1000MOTSDELINFO-POURMIEUXCOMPRENDREE-COMBRESE--GALLIMARDJEUNESSE-2003-1/type_doc/1/id/44275',
-												 'target' => 'http://localhost/afi-opac3-ce/recherche/viewnotice/clef/1928--ELLINGTOND-VOLUME4-MEDIA7-1992-3?id_profil=1&type_doc=3'],
+												 'source' => ROOT_URL . BASE_URL . '/recherche/viewnotice/clef/LES1000MOTSDELINFO-POURMIEUXCOMPRENDREE-COMBRESE--GALLIMARDJEUNESSE-2003-1/type_doc/1/id/44275',
+												 'target' => ROOT_URL . BASE_URL . '/recherche/viewnotice/clef/1928--ELLINGTOND-VOLUME4-MEDIA7-1992-3?id_profil=1&type_doc=3'],
 			                  true);
 
 		$this->_link = Class_FRBR_Link::find(2);
 	}
 
-	
+
 	/** @test */
 	public function sourceTypeShouldBeNotice() {
 		$this->assertEquals(Class_FRBR_Link::TYPE_NOTICE,
diff --git a/tests/application/modules/admin/controllers/HarvestControllerTest.php b/tests/application/modules/admin/controllers/HarvestControllerTest.php
index 9408ce5adba3b9283a2af8a5758d323e5afb19da..f74a94ef0eb32848589a5ced35e4fc4e4fb53a84 100644
--- a/tests/application/modules/admin/controllers/HarvestControllerTest.php
+++ b/tests/application/modules/admin/controllers/HarvestControllerTest.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
  */
 require_once 'AdminAbstractControllerTestCase.php';
 
@@ -122,7 +122,7 @@ class HarvestControllerArteVodActionNotActivatedTest extends HarvestControllerAr
 class HarvestControllerArteVodActivatedWithErrorTest extends HarvestControllerArteVodActivatedTestCase {
 	public function setUp() {
 		parent::setUp();
-		$this->dispatch('/admin/harvest/arte-vod', true);		
+		$this->dispatch('/admin/harvest/arte-vod', true);
 	}
 
 
@@ -146,10 +146,13 @@ class HarvestControllerArteVodActivatedWithFilmsTest extends HarvestControllerAr
 		parent::setUp();
 
 		$this->cache_mock = $this->mock()
-			->whenCalled('clean')
-			->answers(true);
+														 ->whenCalled('save')
+														 ->answers(true)
+
+														 ->whenCalled('clean')
+														 ->answers(true);
 		(new Storm_Cache())->setDefaultZendCache($this->cache_mock);
-		$this->dispatch('/admin/harvest/arte-vod', true);		
+		$this->dispatch('/admin/harvest/arte-vod', true);
 	}
 
 
diff --git a/tests/application/modules/admin/controllers/IndexControllerTest.php b/tests/application/modules/admin/controllers/IndexControllerTest.php
index e150d634fba89cd7f8a35786e4648f38fc1bdb3b..8ab74d02ab33189cccf23e9daa4ee6f2d23f290c 100644
--- a/tests/application/modules/admin/controllers/IndexControllerTest.php
+++ b/tests/application/modules/admin/controllers/IndexControllerTest.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
  */
 require_once 'AdminAbstractControllerTestCase.php';
 
@@ -59,4 +59,17 @@ class IndexControllerHeartBeatTest extends Admin_AbstractControllerTestCase {
 }
 
 
-?>
\ No newline at end of file
+
+class IndexControllerClearCacheActionTest extends Admin_AbstractControllerTestCase {
+	public function setUp() {
+		parent::setUp();
+		Storm_Cache::beVolatile();
+		$this->dispatch('/admin/index/clearcache', true);
+	}
+
+
+	/** @test */
+	public function clearCacheShouldRedirectToAdminVar() {
+		$this->assertRedirectTo('/admin/index/adminvar');
+	}
+}
\ No newline at end of file
diff --git a/tests/application/modules/admin/controllers/ModoControllerTest.php b/tests/application/modules/admin/controllers/ModoControllerTest.php
index 13110c12a1a3134169dd725f0594daaca271e7f1..03e5c3bc71cbc27be9c9de22f21cd175617daf61 100644
--- a/tests/application/modules/admin/controllers/ModoControllerTest.php
+++ b/tests/application/modules/admin/controllers/ModoControllerTest.php
@@ -463,18 +463,24 @@ class ModoControllerDeleteAvisCmsTest extends Admin_AbstractControllerTestCase {
 	public function setUp() {
 		parent::setUp();
 
-		Class_Avis::getLoader()
-			->newInstanceWithId(34)
-			->setAuteur(Class_Users::getLoader()
-				          ->newInstanceWithId(98)
-				          ->setPseudo('Mimi'))
-			->setDateAvis('2012-02-05')
-			->setNote(4)
-			->setEntete('Hmmm')
-			->setAvis('ça a l\'air bon')
-			->beWrittenByAbonne()
-      ->setIdCms(28);
+		$this->fixture('Class_Article', ['id' => 28,
+																		 'titre' => 'test',
+																		 'contenu' => 'test']);
 
+		$this->fixture('Class_Avis',
+									 ['id' => 34,
+										'auteur' => $this->fixture('Class_Users',
+																							 ['id' => 98,
+																								'login' => 'mimi',
+																								'password' => 'secret',
+																								'pseudo' => 'mimi']),
+										'date_avis' => '2012-02-05',
+										'note' => 4,
+										'entete' => 'Hmmmm',
+										'avis' => 'ça a l\'air bon',
+										'id_cms' => 28])
+				 ->beWrittenByAbonne()
+				 ->assertSave();
 
 	  Storm_Test_ObjectWrapper::onLoaderOfModel('Class_Avis')
 			->whenCalled('delete')
diff --git a/tests/application/modules/admin/controllers/ModulesControllerTest.php b/tests/application/modules/admin/controllers/ModulesControllerTest.php
index a7f93d5d8042af25c9091068a9934c2e27213549..ba59622c2aab83d006d3f0a210d4577ad228a5f5 100644
--- a/tests/application/modules/admin/controllers/ModulesControllerTest.php
+++ b/tests/application/modules/admin/controllers/ModulesControllerTest.php
@@ -613,16 +613,23 @@ abstract class ModulesControllerChangeKiosqueSelectionTestCase extends Admin_Abs
 
 		$_SERVER['HTTP_REFERER'] = 'opac/index';
 
-		Storm_Test_ObjectWrapper::onLoaderOfModel('Class_Profil')
-			->whenCalled('save')
-			->answers(true);
+		Class_Profil::beVolatile();
+
+		$this->fixture('Class_Catalogue',
+									 ['id' => 22,
+										'libelle' => 'Books']);
+
+		$this->fixture('Class_PanierNotice',
+									 ['id' => 555,
+										'libelle' => 'Top 5 Books']);
 
 		Class_Profil::getCurrentProfil()
 			->updateModuleConfigAccueil(3,
 																	['type_module' => 'KIOSQUE',
 																	 'division' => 4,
 																	 'id_module' => 3,
-																	 'preferences' => ['id_panier' => 555]]);
+																	 'preferences' => ['id_panier' => 555,
+																										 'id_catalogue' => 22]]);
 	}
 }
 
@@ -652,7 +659,7 @@ class ModulesControllerChangeKiosqueSelectionPostSaveIdCatalogueTest extends Mod
 		$this->postDispatch('/admin/modules/kiosque-change-data/id_module/3',
 												['id_catalogue' => 5,
 												 'id_panier' => 0]);
-		$this->saved_preferences = Class_Profil::getFirstAttributeForLastCallOn('save')
+		$this->saved_preferences = Class_Profil::getCurrentProfil()
 			->getModuleAccueilPreferences(3, 'KIOSQUE');
 	}
 
@@ -680,7 +687,7 @@ class ModulesControllerChangeKiosqueSelectionPostSaveNullPanierTest extends Modu
 		$this->postDispatch('/admin/modules/kiosque-change-data/id_module/3',
 												['id_panier' => 0,
 												 'id_catalogue' => 0]);
-		$this->saved_preferences = Class_Profil::getFirstAttributeForLastCallOn('save')
+		$this->saved_preferences = Class_Profil::getCurrentProfil()
 			->getModuleAccueilPreferences(3, 'KIOSQUE');
 	}
 
diff --git a/tests/application/modules/admin/controllers/NewsletterControllerTest.php b/tests/application/modules/admin/controllers/NewsletterControllerTest.php
index 548fe301051c9ccd5619ba12c8bfdf182717d27f..132d2d49e3b1d430c6d972f7b33c0dd34d3f2a49 100644
--- a/tests/application/modules/admin/controllers/NewsletterControllerTest.php
+++ b/tests/application/modules/admin/controllers/NewsletterControllerTest.php
@@ -469,7 +469,9 @@ class Admin_NewsletterControllerPreviewActionTest extends Admin_NewsletterContro
 		$this->fixture('Class_Notice', ['id' => 42,
 																		'titre_principal' => 'Martine à la plage']);
 
-		$this->fixture('Class_Catalogue',  ['id' => 1, 'libelle' => 'Mon catalogue']);
+		$this->fixture('Class_Catalogue',  ['id' => 1,
+																				'libelle' => 'Mon catalogue',
+																				'type_doc' => 5]);
 
 		$nouveautes = $this->fixture('Class_Newsletter',
 																 ['id' => 3,
diff --git a/tests/application/modules/admin/controllers/ProfilControllerPageAccueilTest.php b/tests/application/modules/admin/controllers/ProfilControllerPageAccueilTest.php
index 42e4733aa2c1600992183900d344e366f5f0cb21..51a1d8fcaf97da0404e7b11171fce4f2fdca4bb7 100644
--- a/tests/application/modules/admin/controllers/ProfilControllerPageAccueilTest.php
+++ b/tests/application/modules/admin/controllers/ProfilControllerPageAccueilTest.php
@@ -29,6 +29,7 @@ abstract class Admin_ProfilControllerPageAccueilJeunesseTestCase extends Admin_A
 
 		$cfg_accueil = [ 'page_css' => '',
 										'use_parent_css' => 1,
+										'sitemap' => 1,
 										'modules' => ['1' => ['division' => 4,
 																					'type_module' => 'RECH_SIMPLE',
 																					'preferences' => []],
@@ -213,7 +214,13 @@ class Admin_ProfilControllerPageAccueilJeunesseTest extends Admin_ProfilControll
 
 	/** @test **/
 	public function useParentCssCheckboxShouldBeChecked() {
-		$this->assertXPath('//input[@name="use_parent_css"][contains(@value,1)]');
+		$this->assertXPath('//input[@name="use_parent_css"][@checked="checked"]');
+	}
+
+
+	/** @test **/
+	public function sitemapCheckboxShouldBeChecked() {
+		$this->assertXPath('//input[@name="sitemap"][@checked="checked"]');
 	}
 
 
@@ -222,6 +229,29 @@ class Admin_ProfilControllerPageAccueilJeunesseTest extends Admin_ProfilControll
 		$this->assertXPath('//input[@name="page_css"][contains(@value,"")]',$this->_response->getBody());
 	}
 
+	/** @test */
+	public function postingDataWithSitemapModification() {
+		$this
+			->getRequest()
+			->setMethod('POST')
+			->setPost(array('sitemap' => 0));
+		$this->dispatch('/admin/profil/accueil/id_profil/7');
+
+		$this->assertTrue($this->profil_wrapper->methodHasBeenCalled('save'));
+		$this->assertRedirect('/admin/profil/accueil/id_profil/7');
+
+		return $this->profil_jeunesse;
+	}
+
+	/**
+	 * @depends postingDataWithSitemapModification
+	 * @test
+	 */
+	public function shouldEnabledSitemap($profil_jeunesse) {
+		$sitemap = $profil_jeunesse->getCfgAccueilParam('sitemap');
+		$this->assertEquals(0, $sitemap);
+	}
+
 
 	/** @test */
 	public function postingDataWithNoModifications() {
@@ -743,6 +773,7 @@ abstract class Admin_ProfilControllerPageAccueilNoBoitePanierTestCase extends Ad
 
 		$cfg_accueil = ['page_css' => '',
 										'use_parent_css' => 1,
+										'sitemap' => 1,
 										'modules' => ['1' => ['division' => 4,
 																					'type_module' => 'RECH_SIMPLE',
 																					'preferences' => []],
diff --git a/tests/application/modules/admin/controllers/ProfilControllerTest.php b/tests/application/modules/admin/controllers/ProfilControllerTest.php
index 2eb7d917e69c62d30d418bd7d5eda00f8db87c12..2a84ff649fe5ff6673ece84f1ded140ef7c86000 100644
--- a/tests/application/modules/admin/controllers/ProfilControllerTest.php
+++ b/tests/application/modules/admin/controllers/ProfilControllerTest.php
@@ -51,25 +51,23 @@ abstract class Admin_ProfilControllerProfilJeunesseTestCase extends Admin_Abstra
 																		 'resa' => 2]];
 
 
-		$this->profil_jeunesse = Class_Profil::getLoader()
-			->newInstanceWithId(5)
-			->setBrowser('opac')
-			->setTitreSite('Médiathèque de Melun')
-			->setLibelle('Profil Jeunesse')
-			->setCommentaire('Pour les jeunes')
-			->setSkin('modele')
-			->setCfgSite(ZendAfi_Filters_Serialize::serialize($cfg_site))
-			->setCfgNotice($cfg_notice)
-			->setMailSite('tintin@herge.be')
-			->setMailSuggestionAchat('suggestion@herge.be')
-			->setHauteurBanniere(150)
-			->setBoiteLoginInBanniere(true)
-			->setRewriteUrl('jeunesse');
-
-
-		$this->profil_wrapper = Storm_Test_ObjectWrapper
-			::onLoaderOfModel('Class_Profil')
-			->whenCalled('save')->answers(true)->getWrapper();
+		$this->profil_jeunesse = $this->fixture('Class_Profil',
+																						['id' => 5,
+																						 'browser' => 'opac',
+																						 'titre_site' => 'Médiathèque de Melun',
+																						 'libelle' => 'Profil Jeunesse',
+																						 'commentaire' => 'Pour les jeunes',
+																						 'skin' => 'modele',
+																						 'cfg_site' => $cfg_site,
+																						 'cfg_notice' => $cfg_notice,
+																						 'mail_site' => 'tintin@herge.be',
+																						 'mail_suggestion_achat' => 'suggestion@herge.be',
+																						 'hauteur_banniere' => 150,
+																						 'boite_login_in_banniere' => true,
+																						 'rewrite_url' => 'jeunesse']);
+
+
+		$this->profil_wrapper = Storm_Test_ObjectWrapper::onLoaderOfModel('Class_Profil');
 
 		ZendAfi_Auth::getInstance()->getIdentity()->ROLE_LEVEL = 7;
 
@@ -1272,25 +1270,35 @@ class Admin_ProfilControllerCopyProfilJeunesseTest extends Admin_ProfilControlle
 
 
 class Admin_ProfilControllerDeepCopyProfilJeunesseTest extends Admin_ProfilControllerProfilJeunesseWithPagesTestCase {
-	public function setUp() {
-		parent::setUp();
-
-		$id = 500;
-		Storm_Test_ObjectWrapper::onLoaderOfModel('Class_Profil')
-			->whenCalled('findAllBy')
-			->answers([])
-			->whenCalled('save')
-			->willDo(function ($model) use (&$id) {
-					$model->setId($id++);
-				} );
-
+	/** @test */
+	public function withoutErrorShouldRedirectToEditProfilWithNewId() {
+		$max_id = Class_Profil::findFirstBy(['order' => 'id desc'])->getId();
 		$this->dispatch('/admin/profil/deep_copy/id_profil/5', true);
+
+		$this->assertRedirectTo('/admin/profil/edit/id_profil/'.($max_id+1),
+														$this->getResponseLocation());
 	}
 
 
 	/** @test */
-	public function assertRedirectToEditProfilId100() {
-		$this->assertRedirect('admin/profil/edit/id_profil/500');
+	public function withErrorShouldRedirectToIndexAndNotify() {
+		$profil_mock = Storm_Test_ObjectWrapper::mock();
+		$profil_mock
+			->whenCalled('getId')
+			->answers(7)
+
+			->whenCalled('deepCopy')
+			->answers(
+								Storm_Test_ObjectWrapper::mock()
+								->whenCalled('save')->answers(false)
+								->whenCalled('getErrors')->answers(['something unexpected happened']));
+
+
+		Class_Profil::getLoader()->cacheInstance($profil_mock);
+
+		$this->dispatch('/admin/profil/deep_copy/id_profil/7', true);
+		$this->assertRedirectTo('/admin/profil', $this->getResponseLocation());
+		$this->assertFlashMessengerContentContains('Duplication impossible: something unexpected happened');
 	}
 }
 
diff --git a/tests/application/modules/admin/controllers/SitothequeControllerTest.php b/tests/application/modules/admin/controllers/SitothequeControllerTest.php
index f49268a6a2fedeae593ccc777e599e6678477f13..b07d08fc6ce1c9ac62a25bb9487bffc8714f5ed0 100644
--- a/tests/application/modules/admin/controllers/SitothequeControllerTest.php
+++ b/tests/application/modules/admin/controllers/SitothequeControllerTest.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
  */
 require_once 'AdminAbstractControllerTestCase.php';
 
@@ -25,34 +25,34 @@ abstract class SitothequeControllerTestCase extends Admin_AbstractControllerTest
 
 	public function setUp() {
 		parent::setUp();
-		
-		$this->setupDomaines();
 
+		$this->setupDomaines();
+		Class_Exemplaire::beVolatile();
 		Class_Notice::beVolatile();
 		Class_CodifThesaurus::beVolatile();
 
 		$categorie_informations = $this->fixture('Class_SitothequeCategorie',
-																						 ['id' => 2, 
+																						 ['id' => 2,
 																							'libelle' => 'Informations',
 																							'id_site' => 3,
 																							'sous_categories' => [],
 																							'sitotheques' => [
 																								$this->fixture('Class_Sitotheque',
-																															 ['id' => 22, 
+																															 ['id' => 22,
 																																'titre' => 'Le Canard',
 																																'url' => 'http://www.canard.fr',
 																																'description' => 'indépendant',
 																																'domaine_ids' => [10]]),
 
 																								$this->_le_monde = $this->fixture('Class_Sitotheque',
-																																									['id' => 23, 
+																																									['id' => 23,
 																																									 'titre' => 'Le Monde',
 																																									 'url' => 'http://www.monde.fr'])]
 																							 ]);
-																						 
+
 
 		$bib_annecy = $this->fixture('Class_Bib',
-																 ['id' => 3, 
+																 ['id' => 3,
 																	'libelle' => 'Annecy',
 																	'sitotheque_categories' => [$categorie_informations]]);
 
@@ -94,7 +94,7 @@ class SitothequeControllerIndexTest extends SitothequeControllerTestCase {
 		$this->dispatch('/admin/sito/index/id/5/id_cat/34', true);
 	}
 
-	
+
 	/** @test */
 	public function pageShouldContainsDivTreeView() {
 		$this->assertXPath('//div[@class="treeView"]');
@@ -107,7 +107,7 @@ class SitothequeControllerIndexTest extends SitothequeControllerTestCase {
 																			'Ajouter une catégorie');
 	}
 
-	
+
 	/** @test */
 	public function liWithSiteLeCanardShouldBePresent() {
 		$this->assertXPathContentContains('//ul/li[1]//div', 'Le Canard');
@@ -154,9 +154,9 @@ class SitothequeControllerSitoEditTest extends SitothequeControllerTestCase {
 	}
 
 
-	/** 
+	/**
 	 * @group integration
-	 * @test 
+	 * @test
 	 */
 	public function pageShouldBeHtml5Valid() {
 		$this->assertHTML5();
@@ -168,7 +168,7 @@ class SitothequeControllerSitoEditTest extends SitothequeControllerTestCase {
 		$this->assertXPathContentContains('//textarea[@name="description"]', 'indépendant');
 	}
 
-	
+
 	/** @test */
 	public function formShouldActionShouldBeSitoEditId22() {
 		$this->assertXPath('//form[contains(@action, "admin/sito/sitoedit/id/22")]',
@@ -198,7 +198,7 @@ class SitothequeControllerSitoViewTest extends SitothequeControllerTestCase {
 		$this->dispatch('/admin/sito/sitoview/id/22', true);
 	}
 
-	
+
 	/** @test */
 	public function responseShouldRedirectToCanardFr() {
 		$this->assertRedirectTo('http://www.canard.fr');
@@ -233,14 +233,19 @@ class SitothequeControllerPostAddActionTest extends SitothequeControllerTestCase
 	public function setUp() {
 		parent::setUp();
 
-		$this->cache = Storm_Test_ObjectWrapper::mock()->whenCalled('clean')->answers(true);
+		$this->cache = Storm_Test_ObjectWrapper::mock()
+			->whenCalled('save')
+			->answers(true)
+
+			->whenCalled('clean')
+			->answers(true);
 		Storm_Cache::setDefaultZendCache($this->cache);
 
-		$this->postDispatch('/admin/sito/sitoadd/id_cat/2', 
+		$this->postDispatch('/admin/sito/sitoadd/id_cat/2',
 												['titre' => 'google',
 												 'url' => 'http://www.google.fr',
 												 'id_items' => '23-23',
-												 'domaine_ids' => 10], 
+												 'domaine_ids' => 10],
 												true);
 	}
 
@@ -272,7 +277,7 @@ class SitothequeControllerPostAddActionTest extends SitothequeControllerTestCase
 
 	/** @test */
 	public function pseudoNoticeShouldHaveIndexForTypeDocAndDomaineHistoire() {
-		$this->assertEquals('T10 HC0001', Class_Notice::find(1)->getFacettes());
+		$this->assertContains('T10 HC0001', Class_Notice::find(1)->getFacettes());
 	}
 
 	/** @test */
@@ -302,7 +307,7 @@ class SitothequeControllerInvalidPostActionTest extends SitothequeControllerTest
 class SitothequeControllerSitoPostEditLeMondeTest extends SitothequeControllerTestCase {
 	public function setUp() {
 		parent::setUp();
-		$this->postDispatch('/admin/sito/sitoedit/id/23', 
+		$this->postDispatch('/admin/sito/sitoedit/id/23',
 												['titre' => 'Times'],
 												true);
 		Class_Sitotheque::clearCache();
@@ -364,7 +369,7 @@ class SitothequeControllerActionErrorsTest extends SitothequeControllerTestCase
 	 */
 	public function responseShouldRedirectToIndex($url) {
 		$this->dispatch($url, true);
-		$this->assertRedirectTo('/admin/sito');		
+		$this->assertRedirectTo('/admin/sito');
 	}
 }
 
@@ -396,7 +401,7 @@ class SitothequeControllerEditCategorieInformationsTest extends SitothequeContro
 class SitothequeControllerPostEditCategorieInformationsTest extends SitothequeControllerTestCase {
 	public function setUp() {
 		parent::setUp();
-		$this->postDispatch('/admin/sito/catedit/id/2', 
+		$this->postDispatch('/admin/sito/catedit/id/2',
 												['libelle' => 'News'],
 												true);
 		Class_SitothequeCategorie::clearCache();
@@ -438,7 +443,7 @@ class SitothequeControllerAddCategorieTest extends SitothequeControllerTestCase
 class SitothequeControllerPostAddEmptyCategorieTest extends SitothequeControllerTestCase {
 	public function setUp() {
 		parent::setUp();
-		$this->postDispatch('/admin/sito/catadd/id/2', 
+		$this->postDispatch('/admin/sito/catadd/id/2',
 												['libelle' => ''],
 												true);
 	}
@@ -459,7 +464,7 @@ class SitothequeControllerPostAddCategorieNationalesTest extends SitothequeContr
 	public function setUp() {
 		parent::setUp();
 
-		$this->postDispatch('/admin/sito/catadd/id/2', 
+		$this->postDispatch('/admin/sito/catadd/id/2',
 												['libelle' => 'Nationales'],
 												true);
 
@@ -482,7 +487,7 @@ class SitothequeControllerPostAddCategorieInBibTest extends SitothequeController
 	public function setUp() {
 		parent::setUp();
 
-		$this->postDispatch('/admin/sito/catadd/id_bib/5', 
+		$this->postDispatch('/admin/sito/catadd/id_bib/5',
 												['libelle' => 'Dans la bib'],
 												true);
 
diff --git a/tests/application/modules/admin/controllers/UserGroupControllerTest.php b/tests/application/modules/admin/controllers/UserGroupControllerTest.php
index b46941b5878517812dc63213850acf5e58198fb9..ac2ab3ea0836c209def839ce81e7031817563a13 100644
--- a/tests/application/modules/admin/controllers/UserGroupControllerTest.php
+++ b/tests/application/modules/admin/controllers/UserGroupControllerTest.php
@@ -30,30 +30,27 @@ abstract class Admin_UserGroupControllerTestCase extends Admin_AbstractControlle
 		$_group_moderateurs,
 		$_group_subscribers;
 
-	public function setUp() {
-		parent::setUp();
+	public function tearDown() {
+		Storm_Model_Loader::defaultToDb();
+		parent::tearDown();
+	}
 
-		Class_CustomField_Meta::beVolatile();
-		Class_CustomField::beVolatile();
 
-		Class_AdminVar::getLoader()
-			->newInstanceWithId('FORMATIONS')
-			->setValeur('1');
+	public function setUp() {
+		parent::setUp();
+		Storm_Model_Loader::defaultToVolatile();
 
-		Class_AdminVar::getLoader()
-			->newInstanceWithId('MULTIMEDIA_KEY')
-			->setValeur('');
+		$this->fixture('Class_AdminVar',
+									 ['id' => 'FORMATIONS', 'valeur' => '1']);
 
+		$this->fixture('Class_AdminVar',
+									 ['id' => 'MULTIMEDIA_KEY', 'valeur' => '']);
 
 		$this->_etablissement = $this->fixture('Class_UserGroupCategorie',
 																					 ['id' => 2,
 																						'libelle'=>'Etablissement',
 																						'parent_id' => 0]);
 
-		Storm_Test_ObjectWrapper::onLoaderOfModel('Class_UserGroupCategorie')
-			->whenCalled('getBibRootCategories')
-			->answers([$this->_etablissement]);
-
 		$this->_etablissement = $this->fixture('Class_UserGroupCategorie',
 																					 ['id' => 2,
 																						'libelle'=>'Etablissement',
@@ -90,7 +87,8 @@ abstract class Admin_UserGroupControllerTestCase extends Admin_AbstractControlle
 																								'group_type' => Class_UserGroup::TYPE_DYNAMIC,
 																								'users' => [],
 																								'rights' => [],
-																								'role_level' => ZendAfi_Acl_AdminControllerRoles::MODO_BIB]);
+																								'role_level' => ZendAfi_Acl_AdminControllerRoles::MODO_BIB,
+																								'id_bib' => 9]);
 
 		$this->_group_subscribers = $this->fixture('Class_UserGroup',
 																							 ['id' => 7,
@@ -108,6 +106,12 @@ abstract class Admin_UserGroupControllerTestCase extends Admin_AbstractControlle
 																				 'users' => [$this->_batman,
 																										 $this->_spiderman]]);
 
+		$this->fixture('Class_Bib',
+									 ['id' => 9,
+										'libelle' => 'Annecy',
+										'zone' => $this->fixture('Class_Zone',
+																						 ['id' => 12,
+																						  'libelle' => 'Haute-Savoie'])]);
 
 
 		$this->_association->setUserGroups([$this->_user_group,
@@ -151,10 +155,6 @@ class Admin_UserGroupControllerListTest extends Admin_UserGroupControllerTestCas
 	public function setUp() {
 		parent::setUp();
 
-		Storm_Test_ObjectWrapper::onLoaderOfModel('Class_UserGroupCategorie')
-			->whenCalled('getTopCategories')
-			->answers([$this->_etablissement]);
-
 		$this->dispatch('admin/usergroup/list.json', true);
 		$this->_json = json_decode($this->_response->getBody());
 	}
@@ -359,8 +359,7 @@ class Admin_UserGroupControllerAddPostTest extends Admin_UserGroupControllerTest
 
 	/** @test */
 	public function newGroupShouldBeCreatedWithLibelleIntervenants() {
-		$this->assertEquals('Intervenants',
-												$this->last->getLibelle());
+		$this->assertEquals('Intervenants', $this->last->getLibelle());
 	}
 
 
@@ -372,8 +371,7 @@ class Admin_UserGroupControllerAddPostTest extends Admin_UserGroupControllerTest
 
 	/** @test */
 	public function intervenantShouldBeInCategorieAssociation() {
-		$this->assertEquals(2,
-												$this->last->getIdCat());
+		$this->assertEquals(2, $this->last->getIdCat());
 	}
 
 
@@ -527,15 +525,32 @@ class Admin_UserGroupControllerEditGroupModerateursBibTest extends Admin_UserGro
 
 	/** @test */
 	public function roleLevelSelectShouldHaveRoleModoBibSelected() {
-		$this->assertXPath('//select[@name="role_level"]/option[@value="0"][@label="invité"][not(@selected)]');
-		$this->assertXPath('//select[@name="role_level"]/option[@value="2"][@label="abonné identifié SIGB"][not(@selected)]');
-
 		$this->assertXPath('//select[@name="role_level"]/option[@value="3"][@label="rédacteur bibliothèque"][@selected="selected"]');
+	}
 
-		$this->assertXPath('//select[@name="role_level"]/option[@value="6"][@label="administrateur portail"][not(@selected)]');
 
+	/** @test */
+	public function roleLevelSelectShouldHaveOnlyOneSelected() {
+		$this->assertXPathCount('//select[@name="role_level"]/option[@selected="selected"]', 1);
+
+	}
+
+	/** @test */
+	public function roleLevelSelectShouldNotDisplaySysAdmRole() {
 		$this->assertNotXPath('//select[@name="role_level"]/option[@value="7"]');
 	}
+
+
+	/** @test */
+	public function librarySelectShouldBePresent() {
+		$this->assertXPath('//select[@name="id_bib"]');
+	}
+
+
+	/** @test */
+	public function librarySelectShouldHaveAnnecySelected() {
+		$this->assertXPath('//select[@name="id_bib"]//option[@value="9"][@selected="selected"]');
+	}
 }
 
 
@@ -645,7 +660,7 @@ class Admin_UserGroupControllerEditStagiairesPostDataTest extends Admin_UserGrou
 												['libelle' => 'étudiants',
 												 'rights' => [Class_UserGroup::RIGHT_SUIVRE_FORMATION,
 																			Class_UserGroup::RIGHT_DIRIGER_FORMATION]]);
-		$this->_group = Class_UserGroup::getLoader()->find(3);
+		$this->_group = Class_UserGroup::find(3);
 	}
 
 
@@ -737,11 +752,10 @@ class Admin_UserGroupControllerEditStagiairesPostDataWithCustomFieldsTest extend
 			'field_5' => 'disabled',
 			'field_6' => '74000',
 			'field_7' => '',
-			'rights' => [
-				Class_UserGroup::RIGHT_SUIVRE_FORMATION,
-				Class_UserGroup::RIGHT_DIRIGER_FORMATION]]);
+			'rights' => [Class_UserGroup::RIGHT_SUIVRE_FORMATION,
+									 Class_UserGroup::RIGHT_DIRIGER_FORMATION]]);
 
-		$this->_group = Class_UserGroup::getLoader()->find(3);
+		$this->_group = Class_UserGroup::find(3);
 		Class_CustomField_Value::clearCache();
 	}
 
@@ -792,9 +806,8 @@ class Admin_UserGroupControllerEditStagiairesPostDataWithEmptyCustomFieldsTest e
 			'field_5' => '',
 			'field_6' => '74000',
 			'field_7' => '',
-			'rights' => [
-				Class_UserGroup::RIGHT_SUIVRE_FORMATION,
-				Class_UserGroup::RIGHT_DIRIGER_FORMATION]]);
+			'rights' => [Class_UserGroup::RIGHT_SUIVRE_FORMATION,
+									 Class_UserGroup::RIGHT_DIRIGER_FORMATION]]);
 
 		$this->_group = Class_UserGroup::getLoader()->find(3);
 		Class_CustomField_Value::clearCache();
@@ -1062,14 +1075,14 @@ class Admin_UserGroupControllerDeleteCategorieTest extends Admin_UserGroupContro
 		parent::setup();
 
 		$association =	$this->fixture('Class_UserGroupCategorie',
-																				 ['id' => 2,
-																					'libelle' => 'Association',
-																					'parent_id' => 5]);
+																	 ['id' => 2,
+																		'libelle' => 'Association',
+																		'parent_id' => 5]);
 
 		$etablissement = $this->fixture('class_UserGroupCategorie',
-																					 ['id' => 5,
-																						'libelle'=>'Etablissement',
-																						'parent_id' => 0]);
+																		['id' => 5,
+																		 'libelle'=>'Etablissement',
+																		 'parent_id' => 0]);
 
 		$this->dispatch('admin/usergroup/catdel/id/2',true);
 	}
@@ -1089,15 +1102,14 @@ class Admin_UserGroupControllerCateditCategorieTest extends Admin_Abstractcontro
 		parent::setup();
 
 		$association =	$this->fixture('Class_UserGroupCategorie',
-																				 ['id' => 2,
-																					'libelle' => 'Association',
-																					'parent_id' => 5]);
+																	 ['id' => 2,
+																		'libelle' => 'Association',
+																		'parent_id' => 5]);
 
 		$_etablissement = $this->fixture('class_UserGroupCategorie',
-																					 ['id' => 5,
-																						'libelle'=>'Etablissement',
-																						'parent_id' => 0]);
-
+																		 ['id' => 5,
+																			'libelle'=>'Etablissement',
+																			'parent_id' => 0]);
 
 		$this->dispatch('admin/usergroup/catedit/id/2',true);
 	}
@@ -1123,20 +1135,19 @@ class Admin_UserGroupControllerEditMembersWithPaginationTest extends Admin_Abstr
 		$user_group = $this->fixture('Class_UserGroup',
 																 ['id' => 1,
 																	'libelle' => "Group",
-																	'rights' => [Class_UserGroup::RIGHT_SUIVRE_FORMATION]
-																	 ]);
-		$members=[];
+																	'rights' => [Class_UserGroup::RIGHT_SUIVRE_FORMATION]]);
+
+		$members = [];
 		for ($i=1; $i<30; $i++) {
-			$user = $this->fixture('Class_Users', [
-				'id' => $i,
-				'nom' => $i,
-				'login' => 'tot'.$i,
-				'password' => '123',
-				'user_groups' => [$user_group]]);
+			$user = $this->fixture('Class_Users',
+														 ['id' => $i,
+															'nom' => $i,
+															'login' => 'tot'.$i,
+															'password' => '123',
+															'user_groups' => [$user_group]]);
 
 			$members[] = $this->fixture('Class_UserGroupMembership',
-																	['id' => $i,
-																	 'user' => $user]);
+																	['id' => $i, 'user' => $user]);
 
 		}
 		$user_group->setUsers($members);
@@ -1163,8 +1174,6 @@ class Admin_UserGroupControllerEditMembersWithPaginationTest extends Admin_Abstr
 class Admin_UserGroupControllerEditMembersWithPaginationAsAutomaticUserGroupTest extends Admin_AbstractControllerTestCase {
 	public function setup() {
 		parent::setup();
-		Class_Formation::beVolatile();
-		Class_SessionFormation::beVolatile();
 		$user_group = $this->fixture('Class_UserGroup',
 																 ['id' => 1,
 																	'libelle' => "Group",
@@ -1197,6 +1206,4 @@ class Admin_UserGroupControllerEditMembersWithPaginationAsAutomaticUserGroupTest
 	public function nb29UsersShouldBeDisplay() {
 		$this->assertXPathContentContains('//div','29 utilisateurs', $this->_response->getBody());
 	}
-
-}
-?>
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/tests/application/modules/opac/controllers/AbonneControllerCmsAvisTest.php b/tests/application/modules/opac/controllers/AbonneControllerCmsAvisTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..40647670994b4d6fce3435a0d326fb3c75fa8684
--- /dev/null
+++ b/tests/application/modules/opac/controllers/AbonneControllerCmsAvisTest.php
@@ -0,0 +1,266 @@
+<?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
+ */
+
+abstract class AbonneControllerCmsAvisTestCase extends AbstractControllerTestCase {
+	public function setUp() {
+		parent::setUp();
+
+		Storm_Model_Loader::defaultToVolatile();
+		$this->fixture('Class_Article', ['id' => 1,
+																		 'titre' => 'Je suis Charlie',
+																		 'contenu' => 'C\'est de la balle d\'être Charlie',
+																		 'avis' => 1]);
+
+	}
+
+
+	public function tearDown() {
+		Storm_Model_Loader::defaultToDb();
+		parent::tearDown();
+	}
+}
+
+
+
+
+class AbonneControllerCmsAvisViewArticleWithoutCommentTest extends AbonneControllerCmsAvisTestCase {
+	public function setUp() {
+		parent::setUp();
+
+		$this->dispatch('/cms/articleview/id/1', true);
+	}
+
+
+	/** @test */
+	public function articleJeSuisCharlieShouldHaveLinkToAddComment() {
+		$this->assertXPathContentContains('//a[@href = "/abonne/cmsavis/id/1"][@data-popup="true"]',
+																			'Donner ou modifier votre avis');
+	}
+
+
+	/** @test */
+	public function articleWithoutCommentShouldNotHaveLinkToAccessLibrariansComments() {
+		$this->assertNotXPathContentContains('//a[contains(@onclick, "showAvis(1")]',
+																				 'Avis de bibliothécaires');
+	}
+
+
+	/** @test */
+	public function articleWithoutCommentShouldNotHaveLinkToAccessBorrowersComments() {
+		$this->assertNotXPathContentContains('//a[contains(@onclick, "showAvis(1")]',
+																				 'Avis de lecteurs du portail');
+	}
+}
+
+
+
+class AbonneControllerCmsAvisPostActionTest extends AbonneControllerCmsAvisTestCase {
+	public function setUp() {
+		parent::setUp();
+
+		Class_AdminVar::newInstanceWithId('MODO_AVIS_BIBLIO', ['valeur' => 0]);
+		Class_AdminVar::newInstanceWithId('MODO_AVIS', ['valeur' => 0]);
+
+		Class_Avis::setTimeSource(new TimeSourceForTest('2015-01-15 17:18:35'));
+
+		$this->postDispatch('/abonne/cmsavis/id/1',
+												['avisSignature' => 'toto',
+												 'avisEntete' => 'Vive les dessins',
+												 'avisTexte' => 'A vos jolis crayons !',
+												 'avisNote' => 3]);
+	}
+
+
+	/** @test */
+	public function responseShouldRedirectToArticle() {
+		$this->assertRedirectTo('/opac/cms/articleview/id/1', $this->getResponseLocation());
+	}
+
+
+	/** @test */
+	public function createdAvisEnTeteShouldBeViveLesDessins() {
+		$this->assertEquals('Vive les dessins', Class_Avis::find(1)->getEntete());
+
+	}
+
+
+	/** @test */
+	public function createdAvisContentShouldBeAVosCrayons() {
+		$this->assertEquals('A vos jolis crayons !', Class_Avis::find(1)->getAvis());
+	}
+
+
+	/** @test */
+	public function createdAvisNoteShouldBeThree() {
+		$this->assertEquals(3, Class_Avis::find(1)->getNote());
+	}
+
+	/** @test */
+	public function currentUsedPseudoShouldBeToto() {
+		$this->assertEquals('toto', Class_Users::getIdentity()->getPseudo());
+	}
+
+	/** @test */
+	public function dateAvisShouldBeNow() {
+		$this->assertEquals('2015-01-15 17:18:35', Class_Avis::find(1)->getDateAvis());
+	}
+
+
+	/** @test */
+	public function createdAvisModerationShouldBeOK() {
+		$this->assertTrue(Class_Avis::find(1)->isModerationOK());
+	}
+
+
+	/** @test */
+	public function rankNoteShouldBeThree() {
+		$this->assertEquals(3, Class_Avis::find(1)->getArticle()->getRank()->getBibNote());
+	}
+
+
+	/** @test */
+	public function userIdShouldBe666() {
+		$this->assertEquals(666, Class_Avis::find(1)->getIdUser());
+	}
+}
+
+
+
+
+
+class AbonneControllerCmsAvisPostWithErrorsActionTest extends AbonneControllerCmsAvisTestCase {
+	public function setUp() {
+		parent::setUp();
+
+		$this->postDispatch('/abonne/cmsavis/id/1',
+												['avisSignature' => 'toto',
+												 'avisEntete' => 'Vive les dessins',
+												 'avisTexte' => '',
+												 'avisNote' => 3]);
+	}
+
+
+	/** @test */
+	public function pageShouldContainsContentTooShort() {
+		$this->assertXPathContentContains('//p[@class="error"]',
+																			'doit avoir une longueur comprise entre');
+	}
+}
+
+
+
+
+abstract class AbonneControllerCmsAvisViewArticleWithCommentTestCase extends AbonneControllerCmsAvisTestCase {
+	public function setUp() {
+		parent::setUp();
+
+		ZendAfi_Auth::getInstance()
+			->logUser(
+								$this->fixture('Class_Users' ,
+															 ['id' => 112,
+																'login' => 'max',
+																'password' => 'maxLaMenace']));
+
+		$this->fixture('Class_CmsRank', ['id' => 1,
+																		 'abon_nombre_avis' => 1,
+																		 'abon_note' => '4.0',
+																		 'bib_nombre_avis' => 0,
+																		 'bib_note' => '0.0']);
+
+		$this->fixture('Class_Avis' , ['id' => 7,
+																	 'id_user' => 112,
+																	 'id_cms' => '1',
+																	 'entete' => 'Pas mal !',
+																	 'avis' => 'J\'aime cet article',
+																	 'note' => 4,
+																	 'status' => 1]);
+	}
+}
+
+
+
+
+class AbonneControllerCmsAvisEditExistingTest extends AbonneControllerCmsAvisViewArticleWithCommentTestCase {
+	public function setUp() {
+		parent::setUp();
+
+		Class_AdminVar::newInstanceWithId('MODO_AVIS_BIBLIO', ['valeur' => 0]);
+		Class_AdminVar::newInstanceWithId('MODO_AVIS', ['valeur' => 0]);
+
+		$this->dispatch('/abonne/cmsavis/id/1', true);
+	}
+
+	/** @test */
+	public function inputAvisEnteteShouldContainsPasMal() {
+		$this->assertXPath('//input[@name="avisEntete"][@value="Pas mal !"]');
+	}
+
+
+	/** @test */
+	public function avisContentShouldBeJaimeCetArticle() {
+		$this->assertXPathContentContains('//textarea[@name="avisTexte"]',
+																			'J\'aime cet article');
+	}
+
+
+	/** @test */
+	public function avisSignatureShouldBeMax() {
+		$this->assertXPath('//input[@name="avisSignature"][@value="max"]');
+	}
+
+
+	/** @test */
+	public function selectedNoteShouldBeFour() {
+		$this->assertXPath('//select[@name="avisNote"]/option[@selected="1"][@value="4"]');
+	}
+}
+
+
+
+
+class AbonneControllerCmsAvisViewArticleWithCommentTest extends AbonneControllerCmsAvisViewArticleWithCommentTestCase {
+	public function setUp() {
+		parent::setUp();
+		$this->dispatch('/cms/articleview/id/1', true);
+	}
+
+
+	/** @test */
+	public function articleJeSuisCharlieShouldHaveLinkToAccessBorrowersComments() {
+		$this->assertXPathContentContains('//a[contains(@onclick, "showAvis(1")]', 'Avis de lecteurs du portail', $this->_response->getBody());
+	}
+
+
+	/** @test */
+	public function articleJeSuisCharlieShouldHaveOneBorrowersComments() {
+		$this->assertXPathContentContains('//div[@id="avis_1"]/ul[@class="notice_info"]/li[1]', "(1 évaluation)");
+	}
+
+
+	/** @test */
+	public function articleJeSuisCharlieShouldHaveFourStarsImage() {
+		$this->assertXPath('//div[@id="avis_1"]/ul[@class="notice_info"]/li[1]/img[contains(@src, "stars-4.gif")]');
+	}
+}
+
+
+
+?>
\ No newline at end of file
diff --git a/tests/application/modules/opac/controllers/AbonneControllerPretsTest.php b/tests/application/modules/opac/controllers/AbonneControllerPretsTest.php
index d1515d1f422c4d7053b2faeb925fffc870f7e828..c18f4296cf90cf42cc776627447823a29f611cd1 100644
--- a/tests/application/modules/opac/controllers/AbonneControllerPretsTest.php
+++ b/tests/application/modules/opac/controllers/AbonneControllerPretsTest.php
@@ -41,6 +41,8 @@ abstract class AbstractAbonneControllerPretsTestCase extends AbstractControllerT
 		$this->zend_cache = Storm_Test_ObjectWrapper::mock();
 		Storm_Cache::setDefaultZendCache($this->zend_cache);
 		$this->zend_cache
+			->whenCalled('save')
+			->answers(true)
 			->whenCalled('remove')
 			->answers(true);
 	}
@@ -298,31 +300,37 @@ class AbonneControllerPretsListReservationTest extends AbstractAbonneControllerP
 		$potter =
 			(new Class_WebService_SIGB_Reservation('12',
 																						 (new Class_WebService_SIGB_Exemplaire(123))->setTitre('Potter')))
-			->parseExtraAttributes(array('Etat' => 'Réservation émise',
-																	 'Rang' => '2',
-																	 'Bibliotheque' => 'Tombouctou',
-																	 'N° de notice' => 564));
-
-		Class_Exemplaire::newInstance(['id_origine' => 564,
-																	 'notice' => $this->fixture('Class_Notice',
-																															 ['id' => 823,
-																																'titre_principal' => 'Potter'])
-																		])->assertSave();
+			->parseExtraAttributes(['Etat' => 'Réservation émise',
+															'Rang' => '2',
+															'Bibliotheque' => 'Tombouctou',
+															'N° de notice' => 564]);
 
 		// This item has no library: non-regression test.
 		$dobby =
 			(new Class_WebService_SIGB_Reservation('13',
 																						 (new Class_WebService_SIGB_Exemplaire(124))->setTitre('Dobby')))
-			->parseExtraAttributes(array('Etat' => 'Réservation émise',
-																	 'Rang' => '2',
-																	 'N° de notice' => 565));
+			->parseExtraAttributes(['Etat' => 'Réservation émise',
+															'Rang' => '2',
+															'N° de notice' => 565]);
+
+		$this->fixture('Class_Notice',
+									 ['id' => 820,
+										'titre_principal' => 'Potter']);
 
-		Class_Exemplaire::newInstance(['id_origine' => 565,
-																	 'notice' => $this->fixture('Class_Notice',
-																															['id' => 824,
-																															 'titre_principal' => 'Dobby'])
-																		])->assertSave();
+		$this->fixture('Class_Notice',
+									 ['id' => 824,
+										'titre_principal' => 'Dobby']);
 
+		$this->fixture('Class_Exemplaire',
+									 ['id' => 120,
+										'id_origine' => 564,
+										'id_notice' => 820]);
+
+		$this->fixture('Class_Exemplaire',
+									 ['id' => 130,
+										'id_origine' => 565,
+										'id_notice' => '824',
+										'zone995' => '']);
 
 		$this->florence
 			->setFicheSigb(['type_comm' => Class_IntBib::COM_OPSYS,
@@ -374,7 +382,7 @@ class AbonneControllerPretsListReservationTest extends AbstractAbonneControllerP
 
 	/** @test */
 	public function titreShouldBePotterAndLinkToNotice() {
-		$this->assertXPathContentContains('//tbody/tr[1]//td//a[contains(@href, "recherche/viewnotice/id/823/retour_abonne/reservations")]',
+		$this->assertXPathContentContains('//tbody/tr[1]//td//a[contains(@href, "recherche/viewnotice/id/820/retour_abonne/reservations")]',
 																			'Potter',
 																			$this->_response->getBody());
 	}
diff --git a/tests/application/modules/opac/controllers/BibControllerIndexActionTest.php b/tests/application/modules/opac/controllers/BibControllerIndexActionTest.php
index ca44692c979462b36308c08d3d4adebd66a87a79..d969d03100c58cc39c10e1890daff455bffc0d12 100644
--- a/tests/application/modules/opac/controllers/BibControllerIndexActionTest.php
+++ b/tests/application/modules/opac/controllers/BibControllerIndexActionTest.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
  */
 require_once 'AbstractControllerTestCase.php';
 
@@ -55,7 +55,7 @@ class BibControllerIndexActionTest extends AbstractControllerTestCase {
 			->newInstanceWithId(2)
 			->setIdSite(0)
 			->setTitre('Ecrivez des tests !');
-		
+
 		Storm_Test_ObjectWrapper::onLoaderOfModel('Class_Article')
 			->whenCalled('getArticlesByPreferences')
 			->with(array('id_bib' => 3))
@@ -94,14 +94,14 @@ class BibControllerIndexActionTest extends AbstractControllerTestCase {
 	/** @test */
 	function setTooltipJSShouldBeGenerated() {
 		$this->assertTrue(false !== strpos($this->_response->getBody(),
-																			 "setTooltip($('.tooltip_bib3'), '<a href=\"" . BASE_URL . "/bib/bibview/id/3\"><b>BibZone3</b></a><br />'"),
+																			 "setTooltip($('.tooltip_bib3'), '<a href=\"/bib/bibview/id/3\"><b>BibZone3</b></a><br />'"),
 											$this->_response->getBody());
 	}
 
 
 	/** @test */
 	function articleEcrivezDesTestShouldBeVisible() {
-		$this->assertXPathContentContains("//li//a", "Ecrivez des tests !", $this->_response->getBody());		
+		$this->assertXPathContentContains("//li//a", "Ecrivez des tests !", $this->_response->getBody());
 	}
 }
 
diff --git a/tests/application/modules/opac/controllers/BibControllerTest.php b/tests/application/modules/opac/controllers/BibControllerTest.php
index 3c37849df3c2462234517ec56330a82e4ec06598..cc9814c65bca68b5ec3a51095b11ec9b98927e7d 100644
--- a/tests/application/modules/opac/controllers/BibControllerTest.php
+++ b/tests/application/modules/opac/controllers/BibControllerTest.php
@@ -201,11 +201,15 @@ class BibControllerMapViewTest extends BibControllerWithZoneTestCase {
 	public function setUp() {
 		parent::setUp();
 
-		$aff_zone = array('profilID' => '3',
-											'libelle' => 'Annecy',
-											'posX' => 2,
-											'posY' => 5,
-											'posPoint' => 'droite');
+		$this->fixture('Class_Profil',
+									 ['id' => 3,
+										'libelle' => 'Annecy']);
+
+		$aff_zone = ['profilID' => '3',
+								 'libelle' => 'Annecy',
+								 'posX' => 2,
+								 'posY' => 5,
+								 'posPoint' => 'droite'];
 		$this->bib_annecy->setAffZone(ZendAfi_Filters_Serialize::serialize($aff_zone));
 
 		$this->dispatch('bib/mapzoneview/id/1');
@@ -232,7 +236,7 @@ class BibControllerMapViewTest extends BibControllerWithZoneTestCase {
 
 	/** @test */
 	public function bibAnnecyShouldBeVisibleOnMap() {
-		$this->assertXPathContentContains("//span[contains(@onclick, '?id_profil')]", "Annecy");
+		$this->assertXPathContentContains("//span[contains(@onclick, '/id_profil/3')]", "Annecy");
 	}
 }
 
diff --git a/tests/application/modules/opac/controllers/CasServerControllerTest.php b/tests/application/modules/opac/controllers/CasServerControllerTest.php
index df8fb1012514480e396f61dbab91a20946bb23b7..c67cef6acd870bf98c52ff74ae6bc2bed38d6cae 100644
--- a/tests/application/modules/opac/controllers/CasServerControllerTest.php
+++ b/tests/application/modules/opac/controllers/CasServerControllerTest.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
  */
 
 
@@ -26,46 +26,47 @@ class CasServerControllerValidateActionTest extends AbstractControllerTestCase {
 
 	public function setUp() {
 		parent::setUp();
+		Storm_Cache::beVolatile();
 		$user = new StdClass();
 		$user->ID_USER=300;
-		Class_Users::newInstanceWithId(300, 
+		Class_Users::newInstanceWithId(300,
 		                               ['login' => '87364',
 																	  'pseudo' => 'georges']);
-		Zend_Registry::get('cache')->save('300',
-																			md5(Zend_Session::getId().'300'));
+		(new Storm_Cache())->save('300',
+															md5(Zend_Session::getId().'300'));
 	}
 
-	
+
 	/** @test */
-	public function requestWithNoTicketShouldRespondinvalidRequestFailureXML() {		
+	public function requestWithNoTicketShouldRespondinvalidRequestFailureXML() {
 		$this->dispatch('/opac/cas-server/validate?service=http://test.com');
 		$this->assertContains('<cas:authenticationFailure code="INVALID_REQUEST">',$this->_response->getBody());
 	}
 
 
 	/** @test */
-	public function requestWithInvalidTicketShouldRespondInvalidTicketFailureXML() {		
+	public function requestWithInvalidTicketShouldRespondInvalidTicketFailureXML() {
 		$this->dispatch('/opac/cas-server/validate?ticket=STmarchepo&service=http://test.com',true);
 		$this->assertContains('<cas:authenticationFailure code="INVALID_TICKET"> Ticket STmarchepo not recognized</cas:authenticationFailure>',$this->_response->getBody());
 	}
 
 
 	/** @test */
-	public function requestWithInvalidTicketOnAuthShouldRespondInvalidTicketFailureXML() {		
+	public function requestWithInvalidTicketOnAuthShouldRespondInvalidTicketFailureXML() {
 		$this->dispatch('/opac/auth/validate?ticket=STmarchepo&service=http://test.com',true);
 		$this->assertContains('<cas:authenticationFailure code="INVALID_TICKET"> Ticket STmarchepo not recognized</cas:authenticationFailure>',$this->_response->getBody());
 	}
 
 
 	/** @test */
-	public function requestWithValidTicketShouldRespondValidXML() {		
+	public function requestWithValidTicketShouldRespondValidXML() {
 		$this->dispatch('/opac/cas-server/validate?ticket='.md5(Zend_Session::getId().'300').'&service=http://test.com');
 		$this->assertContains('<cas:user>300</cas:user>',$this->_response->getBody());
 		$this->assertContains('<cas:proxyGrantingTicket>',$this->_response->getBody());
 	}
 
 	/** @test */
-	public function requestWithValidTicketPrefixedBySTShouldRespondValidXML() {		
+	public function requestWithValidTicketPrefixedBySTShouldRespondValidXML() {
 		$this->dispatch('/opac/cas-server/validate?ticket=ST-'.md5(Zend_Session::getId().'300').'&service=http://test.com');
 		$this->assertContains('<cas:user>300</cas:user>',$this->_response->getBody());
 		$this->assertContains('<cas:proxyGrantingTicket>',$this->_response->getBody());
@@ -73,20 +74,20 @@ class CasServerControllerValidateActionTest extends AbstractControllerTestCase {
 
 
 	/** @test */
-	public function requestWithValidTicketPrefixedBySTOnAuthenticateControllerShouldRespondValidXML() {		
+	public function requestWithValidTicketPrefixedBySTOnAuthenticateControllerShouldRespondValidXML() {
 		$this->dispatch('/opac/auth/validate?ticket=ST-'.md5(Zend_Session::getId().'300').'&service=http://test.com');
 		$this->assertContains('<cas:user>300</cas:user>',$this->_response->getBody());
 		$this->assertContains('<cas:proxyGrantingTicket>',$this->_response->getBody());
 	}
 
 
-	/** 
+	/**
 	 * see http://www.jasig.org/cas/protocol#validate-cas-1.0
-	 * @test 
+	 * @test
 	 */
 	public function validateOnCasOneZeroWithValidTicketShouldAnswerYesLFUsernameLogin() {
 		$this->dispatch(
-			'/opac/cas-server-v10/validate?ticket=ST-'.md5(Zend_Session::getId().'300').'&service=http://test.com', 
+			'/opac/cas-server-v10/validate?ticket=ST-'.md5(Zend_Session::getId().'300').'&service=http://test.com',
 			true);
 		$this->assertEquals('yes'.chr(10).'georges|87364'.chr(10), $this->_response->getBody());
 	}
@@ -95,7 +96,7 @@ class CasServerControllerValidateActionTest extends AbstractControllerTestCase {
 	/**  @test  */
 	public function validateOnCasOneZeroWithInValidTicketShouldAnswerNoLF() {
 		$this->dispatch(
-			'/opac/cas-server-v10/validate?ticket=zork&service=http://test.com', 
+			'/opac/cas-server-v10/validate?ticket=zork&service=http://test.com',
 			true);
 		$this->assertEquals('no'.chr(10), $this->_response->getBody());
 	}
@@ -142,22 +143,23 @@ class CasServerControllerMusicMeValidateActionTest extends AbstractControllerTes
 
 	public function setUp() {
 		parent::setUp();
+		Storm_Cache::beVolatile();
 		$tom = Class_Users::newInstanceWithId(300,
 																					['nom' => 'Ate',
 																					 'prenom' => 'Tom']);
 		ZendAfi_Auth::getInstance()->logUser($tom);
 	}
 
-	
+
 	/** @test */
-	public function requestMusicMeWithNoTicketShouldRespondAccountDisabledXML() {		
+	public function requestMusicMeWithNoTicketShouldRespondAccountDisabledXML() {
 		$this->dispatch('/opac/cas-server/validate-musicme?MediaLibraryID=150&ticket=ST-0a1b2c3d');
 		$this->assertContains('<User />',$this->_response->getBody());
 	}
 
 
 	/** @test */
-	public function requestMusicMeWithValidTicketShouldRespondValidXML() {			
+	public function requestMusicMeWithValidTicketShouldRespondValidXML() {
 		$this->dispatch('/opac/cas-server/validate-musicme?ticket=ST-'.md5(Zend_Session::getId().'300').'&MediaLibraryID=http://test.com');
 		$this->assertContains('<ID>300</ID>',$this->_response->getBody());
 	}
diff --git a/tests/application/modules/opac/controllers/CmsControllerCalendarActionTest.php b/tests/application/modules/opac/controllers/CmsControllerCalendarActionTest.php
index 9649a27c33d4a8c1a6e4fe90f90d2cb0461989d0..b49997b1ee78e045b625aa31be8421c5e6b6f65e 100644
--- a/tests/application/modules/opac/controllers/CmsControllerCalendarActionTest.php
+++ b/tests/application/modules/opac/controllers/CmsControllerCalendarActionTest.php
@@ -75,10 +75,15 @@ class CmsControllerCalendarActionCacheTest extends CmsControllerCalendarActionTe
 	public function setUp() {
 		parent::setUp();
 		$cache = Storm_Test_ObjectWrapper::mock();
+		$cache->whenCalled('save')
+					->answers(true);
+
 		Storm_Cache::setDefaultZendCache($cache);
 		$module_cal = new ZendAfi_View_Helper_Accueil_Calendar(1, $this->cfg_accueil['modules']['1']);
 		$module_cal->setView(new ZendAfi_Controller_Action_Helper_View());
 	}
+
+
 	/**
 	 * Test non-regression vu sur Bucarest (prenait le layout normal au lieu du layout iframe)
 	 * @test
diff --git a/tests/application/modules/opac/controllers/CmsControllerTest.php b/tests/application/modules/opac/controllers/CmsControllerTest.php
index 30545e04696b58dd41eef6c4ca2fc7a8e4fe1bde..1c58018fa0fd160779c6a804b27178abeb2e2dea 100644
--- a/tests/application/modules/opac/controllers/CmsControllerTest.php
+++ b/tests/application/modules/opac/controllers/CmsControllerTest.php
@@ -18,8 +18,7 @@
  * along with BOKEH; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
-require_once 'AbstractControllerTestCase.php';
-
+require_once 'application/modules/opac/controllers/CmsController.php';
 
 
 
@@ -45,7 +44,7 @@ class CmsControllerRssNoProfileTest extends AbstractControllerTestCase {
 
 
 
-class CmsControllerCalendarRssWithProfileAndArticleTest
+class CmsControllerdCalendarRssWithProfileAndArticleTest
 extends AbstractControllerTestCase {
 	/**
 	 * @param Class_Profil $profil
@@ -75,49 +74,101 @@ extends AbstractControllerTestCase {
 	public function setUp() {
 		parent::setUp();
 
+		CmsController::setTimeSource(new TimeSourceForTest('2014-12-01 14:00:00'));
+		Class_Calendar::setTimeSource(new TimeSourceForTest('2014-12-01 14:00:00'));
+		$fete =$this->fixture('Class_Article',
+									 ['id' => 1,
+										'titre' => 'La fête de la banane',
+										'contenu' => 'Une fête qui glisse !<img src="'.BASE_URL.'/image/banane.jpg"/>'])
+								->setDateMaj('2011-11-11 11:11:11')
+								->setEventsDebut('2009-09-09 09:09:09');
+
+		$fete_frite=$this->fixture('Class_Article',
+									 ['id' => 2,
+										'titre' => 'La fête de la frite',
+										'contenu' => 'Une fête qui sent !',
+										'debut' => '2012-12-12 12:12:12',
+										'events_debut' => '2012-12-13',
+										'all_day' => true]);
+
 		Storm_Test_ObjectWrapper::onLoaderOfModel('Class_Article')
 			->whenCalled('getArticlesByPreferences')
-			->answers(array(
-									Class_Article::getLoader()
-									->newInstanceWithId(1)
-									->setTitre('La fête de la banane')
-									->setContenu('Une fête qui glisse !<img src="'.BASE_URL.'/image/banane.jpg"/>')
-									->setDateMaj('2011-11-11 11:11:11'),
-									Class_Article::getLoader()
-									->newInstanceWithId(2)
-									->setTitre('La fête de la frite')
-									->setContenu('Une fête qui sent !'),
-									));
+			->with(['titre' => 'Les dernières nouvelles',
+							'type_aff' => 2,
+							'id_categorie' => '',
+							'id_items' => '',
+							'nb_aff' => 2,
+							'nb_analyse' => 5,
+							'display_order' => 'EventDebut',
+							'display_titles_only' => false,
+							'rss_avis' => 1,
+							'op_largeur_img' => 200,
+							'op_hauteur_boite' => 400,
+							'boite' => null,
+							'id_module' => 1,
+							'event_end_after' => "2014-12-01",
+							'events_only' => true,
+							'published' => true])
+			->answers([$fete,
+								 $fete_frite ])
 
-		$this->dispatch('cms/calendarrss?id_profil=2&id_module=1');
+			->beStrict();
+
+		$this->dispatch('cms/calendarrss?id_profil=2&id_module=1', true);
 
 	}
 
 
+
 	/** @test */
 	public function imgShouldBeDisplayWithAbsoluteUrl() {
-		$this->assertXPathContentContains('//description', '<img src="http://localhost'.BASE_URL.'/image/banane.jpg"');
+		$this->assertXPathContentContains('//description',
+																			'<img src="http://localhost'.BASE_URL.'/image/banane.jpg"');
 	}
 
 
 	/** @test */
 	public function channelTitleShouldBeLesDernieresNouvelles() {
-		$this->assertXpathContentContains('//channel/title', 'Les dernières nouvelles');
+		$this->assertXpathContentContains('//channel/title',
+																			'Les dernières nouvelles');
 	}
 
+
 	/** @test */
 	public function channelDescriptionShouldBeAgendaColonLesDerniereNouvelles() {
-		$this->assertXpathContentContains('//channel/description', 'Agenda: Les dernières nouvelles');
+		$this->assertXpathContentContains('//channel/description',
+																			'Agenda: Les dernières nouvelles');
 	}
 
+
 	/** @test */
 	public function itemsCountShouldBeTwo() {
 		$this->assertXpathCount('//channel/item', 2);
 	}
 
+
 	/** @test */
-	public function firstItemDateShouldBe11_11_2011() {
-		$this->assertXpathContentContains('//channel/item/pubDate', '11 Nov 2011');
+	public function firstItemDateShouldBe09_09_2009() {
+		$this->assertXpathContentContains('//channel/item[1]/pubDate',
+																			'09 Sep 2009',
+																			$this->_response->getBody());
+	}
+
+
+	/** @test */
+	public function secondItemDateShouldBe12_12_2012() {
+		$this->assertXpathContentContains('//channel/item[2]/pubDate',
+																			'12 Dec 2012',
+																			$this->_response->getBody());
+	}
+
+
+	/** @test */
+	public function secondItemContentShouldContainsLe13Decembre() {
+		$this->assertXPathContentContains('//channel/item[2]/description',
+																			'jeudi 13 décembre',
+																			$this->_response->getBody());
+
 	}
 }
 
@@ -325,7 +376,7 @@ class CmsControllerArticleViewByDateTest extends AbstractCmsControllerArticleVie
 
 	/** @test */
 	public function dateForFeteDeLaBananeShouldBePresent() {
-		$this->assertXpathContentContains('//ul//li//span', 'Du 03 septembre au 03 octobre');
+		$this->assertXpathContentContains('//ul//li//span', 'Du samedi 03 septembre au lundi 03 octobre');
 	}
 
 
@@ -337,7 +388,7 @@ class CmsControllerArticleViewByDateTest extends AbstractCmsControllerArticleVie
 
 	/** @test */
 	public function dateForFeteDeLaFriteShouldBePresent() {
-		$this->assertXpathContentContains('//ul//li//span', 'Le 03 septembre');
+		$this->assertXpathContentContains('//ul//li//span', 'samedi 03 septembre');
 	}
 
 
@@ -747,6 +798,25 @@ abstract class CmsControllerWithFeteDeLaFriteTestCase extends AbstractController
 	}
 }
 
+class CmsControllerViewNoticeMetasTest extends CmsControllerWithFeteDeLaFriteTestCase {
+	public function setUp() {
+		parent::setUp();
+		$this->dispatch('/cms/articleview/id/224', true);
+	}
+
+
+	/** @test */
+	public function pageShouldContainTitleMeta() {
+		$this->assertXPathContentContains('//meta[@property="og:title"]/@content', 'La fête de la frite', $this->_response->getBody());
+	}
+
+	/** @test */
+	public function pageShouldContainDescriptionMeta() {
+		$this->assertXPath('//meta[@property="og:description"]/@content');
+	}
+
+}
+
 
 
 class CmsControllerArticleViewInEnglishTest extends CmsControllerWithFeteDeLaFriteTestCase {
@@ -761,7 +831,7 @@ class CmsControllerArticleViewInEnglishTest extends CmsControllerWithFeteDeLaFri
 	function withLanguageEnEventDateShouldBeTranslated() {
 		$this->dispatch('/cms/articleview/id/224/language/en', true);
 		$this->assertXpathContentContains('//span[@class="calendar_event_date"]',
-																			'From 03 September to 05 October 2011',
+																			'From Saturday 03 September to Wednesday 05 October 2011',
 																			$this->_response->getBody());
 	}
 
@@ -886,7 +956,7 @@ class CmsControllerArticleViewTest extends CmsControllerWithFeteDeLaFriteTestCas
 
 	/** @test */
 	public function calendarDateShouldBeDu3SeptembreAu5Octobre() {
-		$this->assertXpathContentContains('//span[@class="calendar_event_date"]', 'Du 03 septembre au 05 octobre');
+		$this->assertXpathContentContains('//span[@class="calendar_event_date"]', 'Du samedi 03 septembre au mercredi 05 octobre');
 	}
 
 
@@ -923,7 +993,7 @@ class CmsControllerArticleViewTest extends CmsControllerWithFeteDeLaFriteTestCas
 		$this->bootstrap();
 		$this->dispatch('/cms/articleview/id/224/language/en');
 		$this->assertXpathContentContains('//span[@class="calendar_event_date"]',
-																			'From 03 September to 05 October 2011',
+																			'From Saturday 03 September to Wednesday 05 October 2011',
 																			$this->_response->getBody());
 	}
 
diff --git a/tests/application/modules/opac/controllers/IndexControllerTest.php b/tests/application/modules/opac/controllers/IndexControllerTest.php
index e53a5020aeccec16c0d353a8faf952fd22a65d4d..5140400d2bd567cf9844c65fd38b38cd3aba1808 100644
--- a/tests/application/modules/opac/controllers/IndexControllerTest.php
+++ b/tests/application/modules/opac/controllers/IndexControllerTest.php
@@ -117,10 +117,9 @@ class IndexControllerWithInvitedLevelRestrictionForProfilTest extends AbstractCo
 
 
 	/** @test **/
-	public function profilWithAccessLevelShouldContainsFormAdminAuthLogin() {
+	public function anonymousAccessingProfilWithAccessLevelShouldBeRedirectedToOpacLogin() {
 		$this->dispatch('/opac/index/index/id_profil/1');
-		$this->assertXPath('//form[@name="form"][contains(@action,"/admin/auth/login")]',
-											 $this->_response->getBody());
+		$this->assertRedirectRegex('|^'.BASE_URL.'/auth/login/id_profil/1|');
 	}
 }
 
@@ -179,4 +178,98 @@ class IndexControllerAsPhoneWithNoPhoneProfilTest extends AbstractControllerTest
 	}
 }
 
-?>
\ No newline at end of file
+
+
+class IndexControllerSitemapTest extends AbstractControllerTestCase {
+	protected $_file_system;
+	protected $_sitemap;
+
+	public function setUp() {
+		parent::setUp();
+
+		$this->_sitemap = '<?xml version="1.0" encoding="UTF-8"?>
+<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
+  <url>
+    <loc>'.BASE_URL.'?id_profil=1</loc>
+    <priority>1.0</priority>
+  </url>
+  <url>
+    <loc>'.BASE_URL.'?id_profil=2</loc>
+    <priority>0.5</priority>
+  </url>
+</urlset>
+';
+
+		$this->_file_system = $this->mock()
+			->whenCalled('file_exists')->answers(true)
+			->whenCalled('file_get_contents')->answers($this->_sitemap);
+		Class_Sitemap::setFileSystem($this->_file_system);
+
+		$this->dispatch('/sitemap.xml', true);
+	}
+
+
+	/** @test */
+	public function contentShouldBeXml() {
+		$this->assertHeaderContains('Content-Type', 'text/xml; charset=utf-8');
+	}
+
+
+	/** @test */
+	public function withNoSitemapShouldRedirectToIndex() {
+		$this->_file_system = $this->mock()
+			->whenCalled('file_exists')->answers(false);
+		Class_Sitemap::setFileSystem($this->_file_system);
+		$this->dispatch('/sitemap.xml', true);
+
+		$this->assertRedirectTo('/index');
+	}
+
+	/** @test */
+	public function contentShouldBeAsExpected() {
+		$this->assertEquals($this->_response->getBody(), $this->_sitemap);
+	}
+
+}
+
+
+
+class IndexControllerRewriteUrlTest extends AbstractControllerTestCase {
+	public function setUp() {
+		parent::setUp();
+
+		ZendAfi_Auth::getInstance()->logUser($this->fixture('Class_Users',
+																												['id' => 1,
+																												 'login' => 'guest',
+																												 'password' => 'guest',
+																												 'role_level' => ZendAfi_Acl_AdminControllerRoles::INVITE]));
+		$this->fixture('Class_Profil', ['id' => 345,
+																		'libelle' => 'Zork - Main',
+																		'parent_id' => null,
+																		'rewrite_url' => 'zork',
+																		'access_level' => ZendAfi_Acl_AdminControllerRoles::ABONNE_SIGB]);
+
+		$this->fixture('Class_Profil', ['id' => 879,
+																		'libelle' => 'Zork - Child',
+																		'parent_id' => 345,
+																		'rewrite_url' => 'zork-child']);
+	}
+
+
+	/** @test */
+	public function privateProfilShouldRedirectToLogin() {
+		$this->dispatch('/zork', true);
+		$this->assertModule('opac');
+		$this->assertController('auth');
+		$this->assertAction('login');
+	}
+
+
+	/** @test */
+	public function publicProfilWithPrivateParentProfilShouldRedirectToLogin() {
+		$this->dispatch('/zork-child', true);
+		$this->assertModule('opac');
+		$this->assertController('auth');
+		$this->assertAction('login');
+	}
+}
\ No newline at end of file
diff --git a/tests/application/modules/opac/controllers/JavaControllerTest.php b/tests/application/modules/opac/controllers/JavaControllerTest.php
index a8f6e0d3ce2ddcdd5e8be6d59202813dbdfbba42..f33a806a96c37f14466cc9d3310ff9f622068248 100644
--- a/tests/application/modules/opac/controllers/JavaControllerTest.php
+++ b/tests/application/modules/opac/controllers/JavaControllerTest.php
@@ -105,7 +105,8 @@ class JavaControllerWithKiosqueMurPageTest extends AbstractControllerTestCase {
 																					'type_module' => 'KIOSQUE',
 																					'preferences' => 	['style_liste' => 'mur',
 																														 'nb_notices' => 10,
-																														 'nb_analyse' => 50]]]];
+																														 'nb_analyse' => 50,
+																														 'only_img' => 0]]]];
 
 		Class_Profil::getCurrentProfil()->setCfgAccueil($cfg_accueil);
 
@@ -115,7 +116,7 @@ class JavaControllerWithKiosqueMurPageTest extends AbstractControllerTestCase {
 
 	/** @test */
 	public function firstControlShouldLinkToPageOne() {
-		$this->assertXPath('//div[@class="controls"]/a[1][contains(@href, "java/page/no/1/id_module/1")]');
+		$this->assertXPath('//div[@class="controls"]/a[1][contains(@href, "java/page/no/1/id_module/1")]', $this->_response->getBody());
 	}
 
 
@@ -174,7 +175,7 @@ class JavaControllerKiosqueSlideShowWidthWrongParamsTest extends AbstractControl
 
 		$this->mock_sql
 			->whenCalled('fetchAll')
-			->with("select notices.id_notice, notices.editeur, notices.annee, notices.date_creation, notices.date_maj, notices.facettes, notices.clef_oeuvre from notices where url_vignette > '' and url_vignette != 'NO' order by date_creation DESC  LIMIT 0,50",
+			->with("select notices.id_notice, notices.editeur, notices.annee, notices.date_creation, notices.date_maj, notices.facettes, notices.clef_oeuvre from notices Where url_vignette > '' and url_vignette != 'NO' order by date_creation DESC  LIMIT 0,50",
 						 false)
 			->answers([])
 			->beStrict();
diff --git a/tests/application/modules/opac/controllers/MigrateControllerTest.php b/tests/application/modules/opac/controllers/MigrateControllerTest.php
index 966ff6761a4afd13654a16a6400947a25e6b510d..6fcd0f0ad5e2dba244c9440d9235c8b7c73233e3 100644
--- a/tests/application/modules/opac/controllers/MigrateControllerTest.php
+++ b/tests/application/modules/opac/controllers/MigrateControllerTest.php
@@ -22,8 +22,14 @@
 abstract class MigrateControllerTestCase extends AbstractControllerTestCase {
 	public function setUp() {
 		parent::setUp();
+		Storm_Model_Loader::defaultToVolatile();
 	  $this->fixture('Class_CosmoVar', ['id' => 'patch_level', 'valeur' => 10]);
 	}
+
+	public function tearDown() {
+		Storm_Model_Loader::defaultToDb();
+		parent::tearDown();
+	}
 }
 
 
@@ -45,7 +51,7 @@ class MigrateControllerPhpDisplayTest extends MigrateControllerTestCase{
 
   /** @test */
 	public function migrationPatchShouldBe10() {
-		$this->assertXPathContentContains('//h3', 'Niveau de patch : 10 / 14',
+		$this->assertXPathContentContains('//h3', 'Niveau de patch : 10 / 15',
 																			$this->_response->getBody());
 	}
 
diff --git a/tests/application/modules/opac/controllers/NoticeAjaxControllerTest.php b/tests/application/modules/opac/controllers/NoticeAjaxControllerTest.php
index e600010a60b9ca52f16f1e9cbc251745fb8c8c73..4c50518dc858150a67f2464adb693b2fc97f3bf8 100644
--- a/tests/application/modules/opac/controllers/NoticeAjaxControllerTest.php
+++ b/tests/application/modules/opac/controllers/NoticeAjaxControllerTest.php
@@ -121,6 +121,10 @@ class NoticeAjaxControllerNoticeWithCustomLinks extends AbstractControllerTestCa
 		$this->dispatch('/opac/noticeajax/detail/id_notice/999', true);
 	}
 
+	/** @test */
+	public function detailsShouldContainPrice() {
+		$this->assertXPathContentContains('//dt[contains(@class, "prix")]/following-sibling::dd', '18,80');
+	}
 
 	/** @test */
 	public function linkToPageDesLibrairesFrShouldBePresent() {
@@ -261,7 +265,6 @@ class NoticeAjaxControllerNoticeSimilairesSouleymaneTest extends NoticeAjaxContr
 
 
 class NoticeAjaxControllerResNumeriquesTest extends AbstractControllerTestCase {
-
 	public function setup() {
 		parent::setup();
 		$this->fixture('Class_Notice', ['id' => 123]);
@@ -273,7 +276,6 @@ class NoticeAjaxControllerResNumeriquesTest extends AbstractControllerTestCase {
 
 	/** @test */
 	function bookletShouldBeLoadedWithAlbumTypeLivreNumerique() {
-		$this->fixture('Class_Notice', ['id' => 123]);
 		$album = $this->fixture('Class_Album', ['id' => 8,
 																						'type_doc_id' => Class_TypeDoc::LIVRE_NUM]);
 
@@ -307,6 +309,69 @@ class NoticeAjaxControllerResNumeriquesTest extends AbstractControllerTestCase {
 }
 
 
+
+class NoticeAjaxControllerResNumeriquesFromFrbrLinkTest extends AbstractControllerTestCase {
+	public function setup() {
+		parent::setup();
+		$this->fixture('Class_Notice', ['id' => 123, 'clef_alpha' => 'ACESHIGH--IRONM---1984-1']);
+		$album = $this->fixture('Class_Album',
+														['id' => 42,
+														 'titre' => 'Aces High',
+														 'type_doc_id' => Class_TypeDoc::AUDIO_RECORD]);
+
+		$this->fixture('Class_FRBR_LinkType',
+									 ['id' => 1,
+									  'libelle' => 'CD MP3',
+										'from_source' => 'A pour pistes MP3',
+										'from_target' => 'Pistes MP3 de']);
+
+		$this->fixture('Class_FRBR_LinkType',
+									 ['id' => 2,
+									  'libelle' => 'Pistes audio',
+										'from_target' => 'A pour pistes audio',
+										'from_source' => 'Pistes audio de']);
+
+		$this->fixture('Class_FRBR_Link',
+									 ['id' => 1,
+										'type_id' => 1,
+										'source' => ROOT_URL . BASE_URL . '/recherche/viewnotice/clef/ACESHIGH--IRONM---1984-1',
+										'target' => ROOT_URL . BASE_URL . '/bib-numerique/notice/id/42']);
+
+		$this->fixture('Class_FRBR_Link',
+									 ['id' => 2,
+										'type_id' => 2,
+										'target' => ROOT_URL . BASE_URL . '/recherche/viewnotice/clef/ACESHIGH--IRONM---1984-1',
+										'source' => ROOT_URL . BASE_URL . '/bib-numerique/notice/id/42']);
+
+		$this->dispatch('noticeajax/resnumeriques?id_notice=123', true);
+	}
+
+
+	/** @test */
+	public function audioPlayerShouldBeLoaded() {
+		$this->assertXPathContentContains('//script', 'audiojs.createAll');
+	}
+
+
+	/** @test */
+	public function frbrLinkSourceLabelShouldBeDisplayed() {
+		$this->assertXPathContentContains('//div[@class="notice_info_titre"]', 'A pour pistes MP3');
+	}
+
+
+	/** @test */
+	public function frbrLinkTargetLabelShouldBeDisplayed() {
+		$this->assertXPathContentContains('//div[@class="notice_info_titre"]', 'A pour pistes audio', $this->_response->getBody());
+	}
+
+
+	/** @test */
+	public function frbrLinkedAlbumTitleShouldBeDisplayed() {
+		$this->assertXPathContentContains('//h3', 'Aces High');
+	}
+}
+
+
 class NoticeAjaxControllerBandeAnnonceTest extends AbstractControllerTestCase {
 
 	/** @test */
@@ -360,7 +425,7 @@ class NoticeAjaxControllerPhotosTest extends NoticeAjaxControllerLastFmTestCase
 class NoticeAjaxControllerBibliographiesTest extends NoticeAjaxControllerLastFmTestCase {
 	public function setUp() {
 		parent::setUp();
-		$this->lastfm->whenCalled('getBibliographie')->with('Frodo')
+		$this->lastfm->whenCalled('getDiscographie')->with('Frodo')
 			->answers([['vignette' => '', 'titre' => '']]);
 		$this->dispatch('noticeajax/bibliographie?id_notice=777', true);
 	}
@@ -716,60 +781,59 @@ abstract class NoticeAjaxControllerFrbrWithLinksTestCase extends AbstractControl
 
 	public function setUp() {
 		parent::setUp();
+		Storm_Model_Loader::defaultToVolatile();
 
 		$leCombat = 'LECOMBATDESJUGES--BILLYY--ZARAFAFILMSDISTRIB-2006-4';
 		$this->_lesGrandsTextes = 'LESGRANDSTEXTESDEDROITINTERNATIONALPUBLIC--DUPUYP--DALLOZ-2010-1';
 		$this->_moiCEstQuoi = 'MOICESTQUOI--BRENIFIERO--NATHANJEUNESSE-2004-1';
 
-		$type = Class_FRBR_LinkType::newInstanceWithId(1)
-				->setLibelle('Suite')
-				->setFromSource('a pour suite')
-				->setFromTarget('est une suite de');
-
-		Storm_Test_ObjectWrapper::onLoaderOfModel('Class_FRBR_Link')
-				->whenCalled('getLinksForSource')
-				->with($leCombat)
-				->answers([Class_FRBR_Link::newInstanceWithId(1)
-						       ->setType($type)
-						       ->setSource($this->_getUrlForKey($leCombat))
-						       ->setSourceType(Class_FRBR_Link::TYPE_NOTICE)
-						       ->setTarget($this->_getUrlForKey($this->_lesGrandsTextes))
-						       ->setTargetType(Class_FRBR_Link::TYPE_NOTICE)])
-
-				->whenCalled('getLinksForTarget')
-				->with($leCombat)
-				->answers([Class_FRBR_Link::newInstanceWithId(2)
-						       ->setType($type)
-						       ->setSource($this->_getUrlForKey($this->_moiCEstQuoi))
-						       ->setSourceType(Class_FRBR_Link::TYPE_NOTICE)
-						       ->setTarget($this->_getUrlForKey($leCombat))
-						       ->setTargetType(Class_FRBR_Link::TYPE_NOTICE)]);
 
+		$type = $this->fixture('Class_FRBR_LinkType',
+													 ['id' => 1,
+														'libelle' => 'Suite',
+														'from_source' => 'a pour suite',
+														'from_target' => 'est une suite de']);
 
-		Storm_Test_ObjectWrapper::onLoaderOfModel('Class_Notice')
-				->whenCalled('getNoticeByClefAlpha')
-				->with($this->_lesGrandsTextes)
-				->answers(Class_Notice::newInstanceWithId(888)
-					        ->setTitrePrincipal('Les grands textes de droit')
-					        ->setClefAlpha($this->_lesGrandsTextes)
-									->setUrlVignette('NO')
-									->setUrlImage('NO')
-									->setTypeDoc(2))
-
-				->whenCalled('getNoticeByClefAlpha')
-				->with($this->_moiCEstQuoi)
-				->answers(Class_Notice::newInstanceWithId(999)
-									->setTypeDoc(2)
-					        ->setTitrePrincipal('Moi, c\'est quoi ?')
-					        ->setClefAlpha($this->_moiCEstQuoi)
-									->setUrlVignette('moi_c_est_quoi.jpg')
-									->setUrlImage('moi_c_est_quoi.jpg'))
+		$this->fixture('Class_FRBR_Link',
+									 ['id' => 1,
+										'type' => $type,
+										'source' => $this->_getUrlForKey($leCombat),
+										'target' => $this->_getUrlForKey($this->_lesGrandsTextes)]);
+
+		$this->fixture('Class_FRBR_Link',
+									 ['id' => 2,
+										'type' => $type,
+										'source' => $this->_getUrlForKey($this->_moiCEstQuoi),
+										'target' => $this->_getUrlForKey($leCombat)]);
+
+		$this->fixture('Class_Notice',
+									 ['id' => 888,
+										'titre_principal' => 'Les grands textes de droit',
+										'clef_alpha' => $this->_lesGrandsTextes,
+										'url_vignette' => 'NO',
+										'url_image' => 'NO',
+										'type_doc' => 2]);
+
+		$this->fixture('Class_Notice',
+									 ['id' => 999,
+										'titre_principal' => 'Moi, c\'est quoi ?',
+										'clef_alpha' => $this->_moiCEstQuoi,
+										'url_vignette' => 'moi_c_est_quoi.jpg',
+										'url_image' => 'moi_c_est_quoi.jpg',
+										'type_doc' => 2]);
+
+		$this->fixture('Class_Notice',
+									 ['id' => 777,
+										'clef_alpha' => $leCombat,
+										'url_vignette' => '',
+										'url_image' => '']);
 
-				->whenCalled('find')
-				->with(777)
-				->answers(Class_Notice::newInstanceWithId(777, ['clef_alpha' => $leCombat,
-																												'url_vignette' => '',
-																												'url_image' => '']));
+	}
+
+
+	public function tearDown() {
+		Storm_Model_Loader::defaultToDb();
+		parent::tearDown();
 	}
 
 
@@ -882,7 +946,7 @@ class NoticeAjaxControllerVideoMorceauTest extends AbstractControllerTestCase {
 	 * @test
 	 */
 	public function responseShouldContainsPlayer() {
-		$this->assertXPath('//param[@name="movie"][@value="http://youtube.googleapis.com/v/axb2sHpGwHQ&source=uds&autoplay=1"]');
+		$this->assertXPath('//param[@name="movie"][contains(@value,"//youtube.googleapis.com/v/axb2sHpGwHQ&source=uds&autoplay=1")]');
 	}
 
 
@@ -929,14 +993,14 @@ class NoticeAjaxControllerResumeNoticeTest extends AbstractControllerTestCase {
 class NoticeAjaxControllerDVDSeriesTest extends AbstractControllerTestCase {
 	/** @test */
 	public function withSeriesNoticeShouldOzSeason3() {
-		Class_Notice::newInstanceWithId(234,
+		$oz_season1=Class_Notice::newInstanceWithId(234,
 																		['titre_principal' => 'Oz Season 1',
 																		 'clef_chapeau' => 'OZ',
 																		 'tome_alpha' => '4',
 																		 'type_doc' => Class_TypeDoc::DVD,
 																		 'resume' => 'Serie TV']);
 
-
+		$oz_season1->set_subfield('461','t', 'OZ');
 		$oz_season3 = Class_Notice::newInstanceWithId(2345,
 																		['titre_principal' => 'Oz saison 3',
 
@@ -946,6 +1010,7 @@ class NoticeAjaxControllerDVDSeriesTest extends AbstractControllerTestCase {
 																		 'url_vignette' => '',
 																		 'url_image' => '',
 																		 'resume' => 'Serie TV']);
+		$oz_season3->set_subfield('461','t', 'OZ');
 		$oz_season2 = Class_Notice::newInstanceWithId(23456,
 																		['titre_principal' => 'Oz saison 2',
 																		 'clef_chapeau' => 'OZ',
@@ -954,7 +1019,7 @@ class NoticeAjaxControllerDVDSeriesTest extends AbstractControllerTestCase {
 																		 'url_image' => '',
 																		 'type_doc' => Class_TypeDoc::DVD,
 																		 'resume' => 'Serie TV']);
-
+		$oz_season2->set_subfield('461','t', 'OZ');
 		Storm_Test_ObjectWrapper::onLoaderOfModel('Class_Notice')
 			->whenCalled('findAllBy')
 			->with(['clef_chapeau' => 'OZ',
@@ -963,7 +1028,7 @@ class NoticeAjaxControllerDVDSeriesTest extends AbstractControllerTestCase {
 
 
 		$this->dispatch('/noticeajax/series?id_notice=234',true);
-		$this->assertXPathContentContains('//div', utf8_encode('Voir tous les épisodes d\'Oz'),$this->_response->getBody());
+		$this->assertXPathContentContains('//div', utf8_encode('Voir tous les épisodes d\'OZ'),$this->_response->getBody());
 	}
 }
 
@@ -1574,6 +1639,8 @@ abstract class NoticeAjaxControllerLocalisationTestCase extends AbstractControll
 
 		Class_Exemplaire::beVolatile();
 		$file_system = Storm_Test_ObjectWrapper::mock()
+			->whenCalled('unlink')
+			->answers(true)
 			->whenCalled('file_exists')
 			->answers(true)
 			->whenCalled('getimagesize')
@@ -1630,6 +1697,9 @@ class NoticeAjaxControllerNoLocalisationTest extends NoticeAjaxControllerLocalis
 class NoticeAjaxControllerNoLocaltionForExemplaireTest extends NoticeAjaxControllerLocalisationTestCase {
 	public function setUp() {
 		parent::setUp();
+
+		Class_Localisation::deleteBy([]);
+
 		$this->fixture('Class_Exemplaire',
 									 ['id' => 1,
 										'id_bib' => 1,
diff --git a/tests/application/modules/opac/controllers/OAIControllerListRecordsTest.php b/tests/application/modules/opac/controllers/OAIControllerListRecordsTest.php
index 0e14b68f453262d15231dc766cf2205b7b52ca2d..0f4a68c9f2baeee077cb8ac21e83ff52e23ce22b 100644
--- a/tests/application/modules/opac/controllers/OAIControllerListRecordsTest.php
+++ b/tests/application/modules/opac/controllers/OAIControllerListRecordsTest.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
  */
 require_once 'AbstractControllerTestCase.php';
 
@@ -102,7 +102,7 @@ class OAIControllerListRecordsInZorkSetTest extends OAIControllerListRecordsInZo
 
 
 	protected function _assertHeaderContentAt($header, $content, $position) {
-		$path = sprintf('//oai:ListRecords/oai:record[%s]/oai:header/oai:%s', 
+		$path = sprintf('//oai:ListRecords/oai:record[%s]/oai:header/oai:%s',
 										$position, $header);
 		$this->_xpath->assertXPathContentContains($this->_response->getBody(), $path, $content);
 	}
@@ -116,6 +116,8 @@ class OAIControllerListRecordsInZorkSetWithBadResumptionTokenTest extends OAICon
 		parent::setUp();
 
 		$cache = Storm_Test_ObjectWrapper::mock()
+			->whenCalled('save')
+			->answers(true)
 			->whenCalled('load')
 			->answers(false);
 		Storm_Cache::setDefaultZendCache($cache);
diff --git a/tests/application/modules/opac/controllers/PanierControllerTest.php b/tests/application/modules/opac/controllers/PanierControllerTest.php
index 047c6c517703de9999da9f68eea5e6b69192795a..e48b1467ca0f5c90e57b359773941e9169d7049d 100644
--- a/tests/application/modules/opac/controllers/PanierControllerTest.php
+++ b/tests/application/modules/opac/controllers/PanierControllerTest.php
@@ -55,7 +55,7 @@ abstract class PanierControllerTestCase extends AbstractControllerTestCase {
 	public function setUp() {
 		parent::setUp();
 		Class_Users::beVolatile();
-
+		Class_NoticeDomain::beVolatile();
 		Class_UserGroup::beVolatile();
 		Class_Notice::beVolatile();
 		Class_PanierNotice::beVolatile();
@@ -1216,24 +1216,35 @@ class PanierControllerAjouterNoticeDansBoitePanierTest extends AbstractControlle
 																					'type_module' => 'PANIER',
 																					'preferences' => 	['titre' => 'Mon panier']]]];
 
-		$notice = Class_Notice::newInstanceWithId(4,
-																		['titre_principal' => 'Le Montespan',
-																		 'auteur_principal' => 'Jean Teulテゥ',
-																		 'clef_alpha' => 'MONTESPAN']);
+		$this->fixture('Class_Notice',
+									 ['id' => 4,
+										'titre_principal' => 'Le Montespan',
+										'auteur_principal' => 'Jean Teulテゥ',
+										'clef_alpha' => 'MONTESPAN']);
+	}
 
-		$notice->save();
-		$this->postDispatch('/panier/ajout-ajax/id_notice/4',  ['id_panier' => '0']);
+	/** @test */
+	public function contextShouldExpectation() {
+		$this->fixture('Class_PanierNotice',
+									 ['id' => 1,
+										'titre' => 'my cart',
+										'user' => Class_Users::getIdentity()]);
 
+		$this->postDispatch('/panier/ajout-ajax/id_notice/4/id_panier/1', ['id_notice' => 4,
+																																			 'id_panier' => 1]);
+		$this->assertEquals([Class_Notice::find(4)], Class_PanierNotice::find(1)->getNoticesAsArray());
 	}
 
 
 	/** @test */
 	public function newPanierShouldNotHaveBeenCreated() {
+		$this->postDispatch('/panier/ajout-ajax/id_notice/4', ['id_panier' => '0']);
 		$this->assertEmpty(Class_PanierNotice::find(1));
 	}
 
 	/** @test */
 	public function responseShouldRedirectToIndex() {
+		$this->postDispatch('/panier/ajout-ajax/id_notice/4',  ['id_panier' => '0']);
 		$this->assertRedirectTo('/index');
 	}
 }
@@ -1604,6 +1615,246 @@ class PanierControllerChangePanierAjaxPostRenderPopupTest extends PanierControll
 
 
 
+class PanierControllerIndexedByMajTitreTest extends PanierControllerTestCase {
+	public function setUp() {
+		parent::setUp();
+		$this->postDispatch('/panier/majtitrepanier/id_panier/15',
+												['new_libelle' => 'Mes supers romans',
+												 'domaine_ids' => '97-199'],
+												true);
+	}
+
+
+	/** @test */
+	public function mesSupersRomainsShouldBeIndexedInDomain97() {
+		$this->assertEquals(Class_Notice::find(4), Class_NoticeDomain::findFirstBy(['domain_id' => 97])->getNotice());
+	}
+
+
+	/** @test */
+	public function mesSupersRomainsShouldBeIndexedInDomain199() {
+		$this->assertEquals(Class_Notice::find(4), Class_NoticeDomain::findFirstBy(['domain_id' => 199])->getNotice());
+	}
+}
+
+
+
+class PanierControllerIndexedByEditActionTest extends PanierControllerTestCase {
+	public function setUp() {
+		parent::setUp();
+		$this->postDispatch('/panier/edit/id_panier/15',
+												['new_libelle' => 'Mes supers romans',
+												 'domaine_ids' => '97-199'],
+												true);
+	}
+
+
+	/** @test */
+	public function mesSupersRomainsShouldBeIndexedInDomain97() {
+		$this->assertEquals(Class_Notice::find(4), Class_NoticeDomain::findFirstBy(['domain_id' => 97,
+																																								'panier_id' => 15])->getNotice());
+	}
+
+
+	/** @test */
+	public function mesSupersRomainsShouldBeIndexedInDomain199() {
+		$this->assertEquals(Class_Notice::find(4), Class_NoticeDomain::findFirstBy(['domain_id' => 199,
+																																								'panier_id' => 15])->getNotice());
+	}
+
+}
+
+
+
+class PanierControllerDesindexedByMajTitreTest extends PanierControllerTestCase {
+	public function setUp() {
+		parent::setUp();
+		$this->fixture('Class_NoticeDomain',
+									 ['id' => 1,
+										'domain_id' => 97,
+										'notice_id' => 4,
+										'panier_id' => 15]);
+
+		$this->fixture('Class_NoticeDomain',
+									 ['id' => 2,
+										'domain_id' => 199,
+										'notice_id' => 4,
+										'panier_id' => 15]);
+
+		$this->fixture('Class_NoticeDomain',
+									 ['id' => 3,
+										'domain_id' => 1789,
+										'notice_id' => 4,
+										'panier_id' => 15]);
+
+		$this->postDispatch('/panier/majtitrepanier/id_panier/15',
+												['new_libelle' => 'Mes supers romans',
+												 'domaine_ids' => '1789'],
+												true);
+	}
+
+
+	/** @test */
+	public function mesSupersRomainsShouldNotBeIndexedInDomain97() {
+		$this->assertNull(Class_NoticeDomain::findFirstBy(['domain_id' => 97]));
+	}
+
+
+	/** @test */
+	public function mesSupersRomainsShouldNotBeIndexedInDomain199() {
+		$this->assertNull(Class_NoticeDomain::findFirstBy(['domain_id' => 199]));
+	}
+
+
+	/** @test */
+	public function mesSupersRomainsShouldBeIndexedInDomain456() {
+		$this->assertEquals($this->panier_romans, Class_NoticeDomain::findFirstBy(['domain_id' => 1789])->getPanierNotice());
+	}
+}
+
+
+abstract class PanierControllerUnindexTestCase extends AbstractControllerTestCase {
+	public function setUp() {
+		parent::setUp();
+		Class_Exemplaire::beVolatile();
+		Class_PanierNoticeCatalogue::beVolatile();
+
+		$this->fixture('Class_Catalogue',
+									 ['id' => 1,
+										'libelle' => 'my cata']);
+
+		$this->fixture('Class_Notice',
+									 ['id' => 10,
+										'titre_principal' => 'Le combat ordinaire',
+										'clef_alpha' => 'COMBAT ORDINAIRE']);
+
+		$this->fixture('Class_Notice',
+									 ['id' => 12,
+										'titre_principal' => 'Blacksad',
+										'clef_alpha' => 'BLACKSAD']);
+
+		$this->fixture('Class_PanierNotice',
+									 ['id' => 2,
+										'libelle' => 'Mes BD',
+										'date_maj' => '10/02/2011',
+										'notices' => 'COMBAT ORDINAIRE;BLACKSAD',
+										'user' => Class_Users::getIdentity()]);
+
+		Class_PanierNotice::find(2)->setDomaineIds(1)->save();
+		Class_PanierNotice::find(2)->index();
+	}
+}
+
+
+
+class PanierControllerUnindexedBySupprimerNoticePanierTest extends PanierControllerUnindexTestCase {
+	public function setUp() {
+		parent::setUp();
+		$this->dispatch('/panier/paniersupprimernotice/id_panier/2/id_notice/10',	true);
+	}
+
+
+	/** @test */
+	public function cartShouldContainsOneRecord() {
+		$this->assertCount(1, Class_PanierNotice::find(2)->getNoticesAsArray());
+	}
+
+
+	/** @test */
+	public function domainShouldRemainLinkedToRecord12() {
+		$this->assertNotNull(Class_NoticeDomain::findFirstBy(['panier_id' => 2,
+																													'domain_id' => 1,
+																													'record_alpha_key' => 'BLACKSAD']));
+	}
+
+
+	/** @test */
+	public function record12ShouldHaveFacetH1() {
+		$this->assertContains('Q1', Class_Notice::find(12)->getFacettes());
+	}
+
+
+	/** @test */
+	public function record10ShouldNotContainsFacetH1() {
+		$this->assertNotContains('H1', Class_Notice::find(10)->getFacettes());
+	}
+
+
+	/** @test */
+	public function domainShouldNotBeLinkedToRecord10() {
+		$this->assertNull(Class_NoticeDomain::findFirstBy(['panier_id' => 2,
+																											 'domain_id' => 1,
+																											 'record_alpha_key' => 'COMBAT ORDINAIRE']));
+	}
+}
+
+
+
+class PanierControllerUnindexedByDeleteFullCartTest extends PanierControllerUnindexTestCase {
+	public function setUp() {
+		parent::setUp();
+
+		$this->fixture('Class_Article',
+									 ['id' => 1,
+										'titre' => 'Article',
+									  'contenu' => 'Mon contenu']);
+
+		Class_Article::find(1)->setDomaineIds(1)->save();
+		Class_Article::find(1)->index();
+
+		$panier = Class_PanierNotice::find(2);
+		$panier->addNotice(Class_Article::find(1)->getNotice())->save();
+		$panier->index();
+
+		$this->dispatch('/panier/supprimerpanier/id_panier/2',	true);
+	}
+
+
+	/** @test */
+	public function articleShouldStillBeInDomain() {
+		$this->assertNotNull(Class_NoticeDomain::findFirstBy(['record_alpha_key' => Class_Article::find(1)->getAlphaKey(),
+																												 'domain_id' => 1]));
+	}
+
+
+	/** @test */
+	public function articleShouldNotBeInDomainWithCart() {
+		$this->assertNull(Class_NoticeDomain::findFirstBy(['record_alpha_key' => Class_Article::find(1)->getAlphaKey(),
+																											 'domain_id' => 1,
+																											 'panier_id' => 2]));
+	}
+
+
+	/** @test */
+	public function domainShouldNotRemainLinkedToRecord12() {
+		$this->assertNull(Class_NoticeDomain::findFirstBy(['panier_id' => 2,
+																											 'domain_id' => 1,
+																											 'record_alpha_key' => 'BLACKSAD']));
+	}
+
+
+	/** @test */
+	public function record12ShouldNotHaveFacetH1() {
+		$this->assertNotContains('H1', Class_Notice::find(12)->getFacettes());
+	}
+
+
+	/** @test */
+	public function record10ShouldNotContainsFacetH1() {
+		$this->assertNotContains('H1', Class_Notice::find(10)->getFacettes());
+	}
+
+
+	/** @test */
+	public function domainShouldNotBeLinkedToRecord10() {
+		$this->assertNull(Class_NoticeDomain::findFirstBy(['panier_id' => 2,
+																											 'domain_id' => 1,
+																											 'record_alpha_key' => 'COMBAT ORDINAIRE']));
+	}
+}
+
+
+
 class PanierControllerSavePanierTest extends AbstractControllerTestCase {
 	public function setUp() {
 		parent::setUp();
@@ -1646,7 +1897,6 @@ class PanierControllerSavePanierTest extends AbstractControllerTestCase {
 		$this->assertEquals(date('Y-m-d'), Class_PanierNotice::find(1)->getDateMaj());
 	}
 
-
 	/** @test */
 	public function responseShouldContainsAjaxSucessJson() {
 		$this->assertContains('<div class=\"ajout-result\"><span>La notice Pomme a bien \u00e9t\u00e9 ajout\u00e9e au panier <\/span>\n  <a href=\"\/panier\/index\/id_panier\/1#panier_contenu\">Voir le panier<\/a>\n  <a href=\"#\" onclick=\"opacDialogClose();return false\">Fermer<\/a>\n<\/div>\n', $this->_response->getBody());
diff --git a/tests/application/modules/opac/controllers/ProfilOptionsControllerTest.php b/tests/application/modules/opac/controllers/ProfilOptionsControllerTest.php
index dedf18b57b1d53b20de1d90c9ae4250a8209f6c0..df595d8ec415833790219697f0480fdeee4f9b3d 100644
--- a/tests/application/modules/opac/controllers/ProfilOptionsControllerTest.php
+++ b/tests/application/modules/opac/controllers/ProfilOptionsControllerTest.php
@@ -386,7 +386,7 @@ class ProfilOptionsControllerTwitterLinkWithProfilAdulteTest extends ProfilOptio
 	/** @test */
 	public function facebookLinkShouldReturnJavascriptForTweet() {
 		$this->dispatch('/opac/index/share/on/facebook/titre/Profil+Adulte?url='.urlencode('/index/index'), true);
-		$this->assertContains("window.open('https://www.facebook.com/sharer/sharer.php?m2w&s=100&p%5Btitle%5D=Profil+Adulte&p%5Burl%5D=http%3A%2F%2Fis.gd%2FPkdNgD','_blank','toolbar=0,status=0,width=800, height=410');",
+		$this->assertContains("window.open('https://www.facebook.com/sharer/sharer.php','_blank','toolbar=0,status=0,width=800, height=410');",
 													$this->_response->getBody());
 	}
 }
@@ -1579,7 +1579,7 @@ class UserRoleLevelThreeViewPrivateProfilTest extends AbstractControllerTestCase
 	public function shouldRenderLoginPageWhenProfilAccessLevelIsFour() {
 		$this->private_profil->setAccessLevel(4);
 		$this->dispatch('/opac/');
-		$this->assertModule('admin');
+		$this->assertModule('opac');
 		$this->assertController('auth');
 		$this->assertAction('login');
 	}
@@ -1657,7 +1657,7 @@ class ProfilOptionsControllerProfilBoiteCalendarWithCalendarDisplayedAndModeSimp
 
 	/** @test **/
 	public function boiteCalendarLiShouldContainsCalendarEventDate() {
-		$this->assertXPathContentContains('//li/span[@class="calendar_event_date"]', 'Le 03 septembre 2011',$this->_response->getBody());
+		$this->assertXPathContentContains('//li/span[@class="calendar_event_date"]', 'samedi 03 septembre 2011');
 	}
 
 
diff --git a/tests/application/modules/opac/controllers/RechercheControllerAlbumAudioRecordTest.php b/tests/application/modules/opac/controllers/RechercheControllerAlbumAudioRecordTest.php
index 77cb03a5c0c7a1e987241eec32585a8d2d68ff34..c7083ed5988f2b80cade9221dcebd2230378978f 100644
--- a/tests/application/modules/opac/controllers/RechercheControllerAlbumAudioRecordTest.php
+++ b/tests/application/modules/opac/controllers/RechercheControllerAlbumAudioRecordTest.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
  */
 
 
@@ -24,13 +24,17 @@ require_once 'AbstractControllerTestCase.php';
 
 
 abstract class RechercheControllerAlbumAudioRecordTestCase extends AbstractControllerTestCase {
-	protected 
+	protected
 		$_notice,
 		$_codif_auteur_wrapper;
 
+
 	public function setUp() {
 		parent::setUp();
 
+		$_http_client = Storm_Test_ObjectWrapper::mock()->whenCalled('open_url')->answers(null);
+		Class_WebService_AllServices::setHttpClient($_http_client);
+
 		Class_Notice::beVolatile();
 		Class_Notice::setTimeSource(new TimeSourceForTest('2014-01-19 09:00:00'));
 		Class_Exemplaire::beVolatile();
@@ -42,7 +46,7 @@ abstract class RechercheControllerAlbumAudioRecordTestCase extends AbstractContr
 		Class_CosmoVar::newInstanceWithId('black_list_856', ['valeur' => 'mabib']);
 		Class_CosmoVar::newInstanceWithId('unimarc_zone_titre',
 																			['valeur' => '200$a;200$e;200$d;200$i;327$a;464$a;461$t;464$t']);
-		
+
 		$album = $this->fixture('Class_Album',
 														['id' => 4,
 														 'type_doc_id' => Class_TypeDoc::AUDIO_RECORD,
@@ -88,7 +92,7 @@ abstract class RechercheControllerAlbumAudioRecordTestCase extends AbstractContr
 		$code_alpha = str_replace(' ', 'x', $indexation->alphaMaj($name.'|'));
 		$author = Class_CodifAuteur::newInstance([ 'formes' => $code_alpha,
 																							 'libelle' => $name]);
-		$author->assertSave();
+		$author->save();
 
 		$this->_codif_auteur_wrapper
 			->whenCalled('findFirstBy')
@@ -116,7 +120,7 @@ class RechercheControllerAlbumAudioRecordViewNoticeTest extends RechercheControl
 
 	/** @test */
 	public function clefAlphaShouldBeSEVENTHSON_IRONMAIDEN() {
-		$this->assertEquals('SEVENTHSONOFASEVENTHSON--IRONMAIDENBRUCEDICKINSONSTEVEHARRISDAVEMURRAYNICKOMCBRAINADRIANSMITH----109', 
+		$this->assertEquals('SEVENTHSONOFASEVENTHSON--IRONMAIDENBRUCEDICKINSONSTEVEHARRISDAVEMURRAYNICKOMCBRAINADRIANSMITH----109',
 												$this->_notice->getClefAlpha());
 	}
 
@@ -133,7 +137,7 @@ class RechercheControllerAlbumAudioRecordViewNoticeTest extends RechercheControl
 	}
 
 
-	/** 
+	/**
 	 * @disabledtest
 	 * en attendant de bien gérer la nouveauté pour les albums bib num (update de notice et non nouvelle création
 	 */
@@ -151,7 +155,7 @@ class RechercheControllerAlbumAudioRecordViewNoticeTest extends RechercheControl
 
 	/** @test */
 	public function titrePrincipalShouldBeSeventhSonOfASeventhSon() {
-		$this->assertXPathContentContains('//h1', 
+		$this->assertXPathContentContains('//h1',
 																			'Seventh Son of a Seventh Son',
 																			$this->_response->getBody());
 	}
@@ -186,28 +190,28 @@ class RechercheControllerAlbumAudioRecordViewNoticeTest extends RechercheControl
 
 	/** @test */
 	public function titresFulltextShouldContainsMOONCHILD() {
-		$this->assertContains('MOONCHILD', 
+		$this->assertContains('MOONCHILD',
 													explode(' ', $this->_notice->getRawAttributes()['titres']));
 	}
 
 
 	/** @test */
 	public function titresFulltextShouldContainsUNKNOWN() {
-		$this->assertContains('UNKNOWN', 
+		$this->assertContains('UNKNOWN',
 													explode(' ', $this->_notice->getRawAttributes()['titres']));
 	}
 
 
 	/** @test */
 	public function titresFulltextShouldContainsPROPHECY() {
-		$this->assertContains('PROPHECY', 
+		$this->assertContains('PROPHECY',
 													explode(' ', $this->_notice->getRawAttributes()['titres']));
 	}
 
 
 	/** @test */
 	public function titresFulltextShouldNotContains502() {
-		$this->assertNotContains('502', 
+		$this->assertNotContains('502',
 														 explode(' ', $this->_notice->getRawAttributes()['titres']));
 	}
 
@@ -291,7 +295,7 @@ class RechercheControllerAlbumAudioRecordViewDetailsTest extends RechercheContro
 
 	/** @test */
 	public function titresShouldContainsIronMaidenAsCodeRebond() {
-		$this->assertXPathContentContains('//dd//a[contains(@href, "code_rebond/A1")]', 
+		$this->assertXPathContentContains('//dd//a[contains(@href, "code_rebond/A1")]',
 																			'Iron Maiden');
 	}
 
@@ -300,13 +304,13 @@ class RechercheControllerAlbumAudioRecordViewDetailsTest extends RechercheContro
 		$this->assertNotXPath('//dt[contains(@class, "internet")]', $this->_response->getBody());
 	}
 
-	
+
 	/** @test */
 	public function distributorShouldBeGeffenRecords() {
-		$this->assertXPathContentContains('//dl//dd', 
+		$this->assertXPathContentContains('//dl//dd',
 																			'Geffen Records',
 																			$this->_response->getBody());
-	} 
+	}
 
 
 	/** @test */
@@ -317,7 +321,7 @@ class RechercheControllerAlbumAudioRecordViewDetailsTest extends RechercheContro
 
 	/** @test */
 	public function aDDForAuthorsShouldContainsBruceDickinsonAsChanteur() {
-		$this->assertXPathContentContains('//dl//dd//a', 
+		$this->assertXPathContentContains('//dl//dd//a',
 																			'Bruce Dickinson (Chanteur)',
 																			$this->_response->getBody());
 	}
@@ -375,7 +379,7 @@ class RechercheControllerAlbumAudioRecordViewRessourcesNumeriquesTest extends Re
 		$this->assertXPathContentContains('//ol//li', 'The prophecy');
 	}
 
-	
+
 	/** @test */
 	public function pageShouldContainsLinkToXSPFPlayList() {
 		$this->assertXPath('//a[contains(@href,"/bib-numerique/album-xspf-playlist/id/4.xspf")]');
@@ -396,7 +400,7 @@ class RechercheControllerAlbumAudioRecordViewRessourceInTelephoneModeTest extend
 		$this->dispatch('/opac/recherche/viewnotice/id/1', true);
 	}
 
-	
+
 	/** @test */
 	public function pageShouldContainsLinkToOpenRessourcesNumeriques() {
 		$this->assertXPathContentContains('//a[contains(@href,"recherche/ressourcesnumeriques/id/1")]','Ecouter l\'album',$this->_response->getBody());
diff --git a/tests/application/modules/opac/controllers/RechercheControllerTest.php b/tests/application/modules/opac/controllers/RechercheControllerTest.php
index 807e2b4058319f92329a3eaa3cf81d767f1a62c3..5a2435388de9db9596331694ed656e59d3957645 100644
--- a/tests/application/modules/opac/controllers/RechercheControllerTest.php
+++ b/tests/application/modules/opac/controllers/RechercheControllerTest.php
@@ -44,7 +44,8 @@ abstract class RechercheControllerNoticeTestCase extends AbstractControllerTestC
 									 ['id' => 4,
 										'libelle' => 'rock']);
 
-
+		Class_Notice::beVolatile();
+		Class_Exemplaire::beVolatile();
 		$this->notice = Class_Notice::newInstanceWithId(345,
 																										['annee' => 2002,
 																										 'editeur' => 'Gallimard',
@@ -86,6 +87,7 @@ class RechercheControllerReseauTest extends RechercheControllerNoticeTestCase {
 		ZendAfi_View_Helper_ShareUrl::setDefaultWebClient(null);
 	}
 
+
 	/** @test */
 	public function getResauShouldContainsTwitterGif() {
 		$this->assertXPath('//img[contains(@src, "twitter.gif")]');
@@ -103,6 +105,12 @@ class RechercheControllerReseauTest extends RechercheControllerNoticeTestCase {
 
 
 class RechercheControllerViewNoticeBabelthequeTest extends RechercheControllerNoticeTestCase {
+	public function setUp() {
+		parent::setUp();
+
+	}
+
+
 	/** @test */
 	public function withoutBabelthequeJSShouldNotBeLoaded() {
 		Class_AdminVar::newInstanceWithId('BABELTHEQUE_JS')->setValeur('');
@@ -117,6 +125,15 @@ class RechercheControllerViewNoticeBabelthequeTest extends RechercheControllerNo
 		$this->dispatch(sprintf('recherche/viewnotice/id/%d', $this->notice->getId()), true);
 		$this->assertXpath('//script[contains(@src, "babeltheque.js?bwid=666")]');
 	}
+
+	/** @test */
+	public function noticeShouldNotHaveTome() {
+		$this->notice->setClefChapeau("test")->save();
+		$this->dispatch(sprintf('recherche/viewnotice/id/%d', $this->notice->getId()), true);
+
+		$this->assertNotXPathContentContains('//div[@class="entete_notice"]',"Voir tous les tomes");
+	}
+
 }
 
 
@@ -166,6 +183,7 @@ class RechercheControllerViewNoticeWithPreferencesTest extends RechercheControll
 		Class_AdminVar::newInstanceWithId('FACETTE_PCDM4_LIBELLE', ['valeur' => 'Classification']);
 
 		$this->notice->setClefChapeau('Gallimard serie');
+		$this->notice->set_subfield('461','t', 'Gallimard serie');
 		$preferences = [
 										'barre_nav' => 'Document',
 										'entete' =>"ABCDEFGIKLMNOPRSTtYZ8v9",
@@ -221,7 +239,7 @@ class RechercheControllerViewNoticeWithPreferencesTest extends RechercheControll
 
 	/** @test */
 	public function enteteShouldDisplayDocumentDeLaMemeSerie() {
-		$this->assertXPathContentContains('//div//a[contains(@href,"Gallimard+serie")]', 'Voir tous les tomes de Cinéma d\'animation',$this->_response->getBody());
+		$this->assertXPathContentContains('//div//a[contains(@href,"Gallimard+serie")]', 'Voir tous les tomes de Gallimard serie',$this->_response->getBody());
 	}
 
 
@@ -373,7 +391,7 @@ class RechercheControllerViewNoticeWithPreferencesTest extends RechercheControll
 	public function trackEventShouldContainsNoticeTitreAuteur() {
 		$this->assertEquals(['recherche',
 												 'notice',
-												 '"titre: Cinéma d\'animation","auteur: Bernard Génin","editeur: Gallimard","tome: ","support: livres","ressource: http://localhost'.BASE_URL.'/recherche/viewnotice/id/345"',
+												 '"titre: Gallimard serie<br /> Cinéma d\'animation","auteur: Bernard Génin","editeur: Gallimard","tome: ","support: livres","ressource: http://localhost'.BASE_URL.'/recherche/viewnotice/id/345"',
 												 345],
 												$this->_web_analytics_client->getAttributesForLastCallOn('trackEvent'));
 	}
@@ -399,7 +417,13 @@ class RechercheControllerViewNoticeWithPreferencesTest extends RechercheControll
 
 
 
-abstract class RechercheControllerViewNoticeTestCase extends RechercheControllerNoticeTestCase {
+class RechercheControllerViewNoticeTest extends RechercheControllerNoticeTestCase {
+	public function setUp() {
+		parent::setUp();
+		$this->dispatch('recherche/viewnotice/id/345', true);
+	}
+
+
 	/** @test */
 	public function titleShouldBeDisplayed() {
 		$this->assertXPathContentContains('//h1',
@@ -431,8 +455,49 @@ abstract class RechercheControllerViewNoticeTestCase extends RechercheController
 
 
 
+class RechercheControllerViewNoticeMetasTest extends AbstractControllerTestCase {
+	public function setUp() {
+		parent::setUp();
+
+		$this->fixture('Class_Notice',
+									 ['id' => 345,
+										'titre_principal' => 'Cinéma d\'animation<br/>3D',
+										'auteur_principal' => 'Bernard Génin',
+										'annee' => 2002,
+										'editeur' => 'Gallimard',
+										'isbn' => '1-234-56789-0',
+										'type_doc' => Class_TypeDoc::LIVRE,
+										'url_vignette' => 'http://linux.org',
+										'url_image' => 'http://linux.org',
+										'facettes' => 'A123 F2 G1 P4 Y2 B1 Lfre M1 Z3 D1',
+										'date_creation' => '2013-12-31']);
+
+		$this->dispatch('recherche/viewnotice/id/345', true);
+	}
+
+
+	/** @test */
+	public function pageShouldContainTitleMeta() {
+		$this->assertXPathContentContains('//meta[@property="og:title"]/@content', 'Cinéma d\'animation br/ 3D - Bernard Génin', $this->_response->getBody());
+	}
+
+
+	/** @test */
+	public function pageShouldContainImageMeta() {
+		$this->assertXPathContentContains('//meta[@property="og:image"]/@content', 'http://linux.org');
+	}
+
+
+	/** @test */
+	public function pageShouldContainDescriptionMeta() {
+		$this->assertXPath('//meta[@property="og:description"]/@content');
+	}
 
-class RechercheControllerViewNoticeClefAlphaTest extends RechercheControllerViewNoticeTestCase {
+}
+
+
+
+class RechercheControllerViewNoticeClefAlphaTest extends RechercheControllerNoticeTestCase {
 	public function setUp() {
 		parent::setUp();
 
@@ -865,7 +930,7 @@ class RechercheControllerSimpleActionWithListeFormatMurTest extends RechercheCon
 
 	/** @test **/
 	public function resumeNoticeLinkShouldBeReset() {
-		$this->assertXPathContentContains('//head/script', "/noticeajax/resumenotice';");
+		$this->assertXPathContentContains('//head/script', "/noticeajax/resumenotice';", $this->_response->getBody());
 	}
 
 
@@ -950,7 +1015,7 @@ class RechercheControllerListeCodeWithNouveauteTest extends AbstractControllerTe
 
 	/** @test **/
 	public function pageShouldContainsNoticeWithDivNouveauté() {
-		$this->assertXPathContentContains('//div[@class="liste_mur"]//div[@class="notice"]//span[@class="notice_nouveaute"]','Nouveauté',$this->_response->getBody());
+		$this->assertXPathContentContains('//div[@class="liste_mur"]//div[@class="notice"]//span[@class="notice_nouveaute"]','Nouveauté');
 	}
 }
 
@@ -961,13 +1026,14 @@ class RechercheControllerFacetteSelectedTest extends AbstractControllerTestCase
 
 	public function setUp() {
 		parent::setUp();
+
 		Class_Profil::getCurrentProfil()
 			->setCfgModules(['recherche' => ['resultatsimple' => [
 																														'liste_format' => $this->_liste_format ,
 																														'liste_codes' => "TANECR"]]])
 			->setSelTypeDoc('1;2;3;4;5');
-		$this->dispatch('/recherche/simple/expressionRecherche/hello/tri/%2A/facette/T1', true);
 
+		$this->dispatch('/recherche/simple/expressionRecherche/hello/tri/%2A/facette/T1', true);
 	}
 
 	/** @test */
@@ -1561,7 +1627,6 @@ class RechercheControllerSimpleActionWithCatalogueAndDomainBrowserWidgetTest ext
 										'domaine_parent' => $this->fixture('Class_Catalogue',
 																											 ['id'=>3,
 																												'libelle' => 'Nouveautés'])]);
-
 	}
 
 
@@ -1774,9 +1839,8 @@ class RechercheControllerNavigationTest extends RechercheControllerNoticeTestCas
 
 		Storm_Test_ObjectWrapper::onLoaderOfModel('Class_Notice')
 			->whenCalled('getNoticeIdsByRequeteRecherche')
-			->with('Select id_notice from notices  order by alpha_titre')
+			->with('Select id_notice from notices order by alpha_titre')
 			->answers([28,12,345,1,99]);
-
 	}
 
 	/** @test */
@@ -1785,6 +1849,7 @@ class RechercheControllerNavigationTest extends RechercheControllerNoticeTestCas
 		$this->assertRedirectRegex('|/recherche/viewnotice/id/1/tri/alpha_titre/clef/batman|');
 	}
 
+
 	/** @test */
 	public function navigationPrecedentShouldRedirectToTarzanViewnotice() {
 		$this->dispatch('/recherche/viewnotice/id/345/tri/alpha_titre/navigation/precedent',true);
@@ -1797,38 +1862,43 @@ class RechercheControllerNavigationTest extends RechercheControllerNoticeTestCas
 		$this->assertRedirectRegex('|/recherche/viewnotice/id/28/tri/alpha_titre/clef/musso|');
 	}
 
+
   /** @test */
 	public function navigationSuivantOnTheLastNoticeLesArbresOfResultatRechercheShouldRedirectToLesarbres() {
 		$this->dispatch('/recherche/viewnotice/id/99/tri/alpha_titre/navigation/suivant',true);
 		$this->assertRedirectRegex('|/recherche/viewnotice/id/99/tri/alpha_titre/clef/lesarbres|');
 	}
 
+
 	/** @test */
 	public function navigationSuivantOnTheFirstNoticeMussoOfResultatRechercheShouldRedirectToTarzan() {
 		$this->dispatch('/recherche/viewnotice/id/28/tri/alpha_titre/navigation/suivant',true);
 		$this->assertRedirectRegex('|/recherche/viewnotice/id/12/tri/alpha_titre/clef/tarzan|');
 	}
 
+
   /** @test */
 	public function navigationPrecedentOnTheLastNoticeLesarbresOfResultatRechercheShouldRedirectToBatman() {
 		$this->dispatch('/recherche/viewnotice/id/99/tri/alpha_titre/navigation/precedent',true);
 		$this->assertRedirectRegex('|/recherche/viewnotice/id/1/tri/alpha_titre/clef/batman|');
 	}
 
+
 	/** @test */
 	public function navigationSuivantOnNoticeLesarbresWhenResultatRechercheIsEmptyShouldRedirectToCurrentNoticeLsearbres() {
 		Class_Notice::whenCalled('getNoticeIdsByRequeteRecherche')
-			->with('Select id_notice from notices  order by alpha_titre')
+			->with('Select id_notice from notices order by alpha_titre')
 			->answers([]);
 
 		$this->dispatch('/recherche/viewnotice/id/99/tri/alpha_titre/navigation/suivant',true);
 		$this->assertRedirectRegex('|/recherche/viewnotice/id/99/tri/alpha_titre/clef/lesarbres|');
 	}
 
+
 	/** @test */
 	public function navigationSuivantOnNoticeLesarbresWhenResultatRechercheAsMussoResultShouldRedirectToCurrentNoticeMusso() {
 		Class_Notice::whenCalled('getNoticeIdsByRequeteRecherche')
-			->with('Select id_notice from notices  order by alpha_titre')
+			->with('Select id_notice from notices order by alpha_titre')
 			->answers([28]);
 
 		$this->dispatch('/recherche/viewnotice/id/28/tri/alpha_titre/navigation/suivant',true);
@@ -2282,6 +2352,7 @@ class RechercheControllerViewnoticeWithBreadcrumbAndIdModuleParamTest extends Re
 class RechercheControllerViewnoticeWithBreadcrumbAndIdCatalogueParamTest extends RechercheControllerViewnoticeWithBreadcrumbTestCase {
 	public function setUp() {
 		parent::setUp();
+
 		$this->fixture('Class_Catalogue',
 									 ['id'=> 2,
 										'libelle' => 'my catalogue']);
@@ -2322,6 +2393,7 @@ class RechercheControllerViewnoticeWithBreadcrumbAndIdPanierParamTest extends Re
 }
 
 
+
 class RechercheControllerPrintActionTest extends AbstractControllerTestCase {
 
 	public function setUp() {
@@ -2380,3 +2452,147 @@ class RechercheControllerPrintActionTest extends AbstractControllerTestCase {
 
 	}
 }
+
+
+
+class RechercheControlleSimpleActionWithEmptyDomainSettingsTest extends AbstractControllerTestCase {
+	public function setUp() {
+		parent::setUp();
+
+		$this->fixture('Class_Catalogue',
+									 ['id'=>3,
+										'libelle' => 'Nouveautés',
+										'auteur' => 'Paul']);
+		$this->dispatch('/recherche/simple/id_catalogue/3/id_module/9/aleatoire/1', true);
+	}
+
+
+  /** @test */
+	public function onRechercheSimpleCatalogue3IdModule9ResultsShouldBeEmpty() {
+		$this->assertXPathContentContains('//div[@class="info-recherche"]', 'Aucun résultat trouvé',											 $this->_response->getBody());
+	}
+}
+
+
+
+class RechercheControlleSimpleActionWithEmptyDomainSettingsAndSiteLinkedTest extends AbstractControllerTestCase {
+	public function setUp() {
+		parent::setUp();
+		$this->fixture('Class_Catalogue',
+									 ['id'=>3,
+										'libelle' => 'Nouveautés']);
+		$this->fixture('Class_Sitotheque',
+									 ['id' => 1,
+										'titre' => 'Mon lien',
+										'url' => 'http://monlien.com',
+										'domaine_ids' => 3,
+									 ]);
+
+		Class_Exemplaire::beVolatile();
+		Class_Notice::beVolatile();
+		Class_NoticeDomain::beVolatile();
+		Class_Sitotheque::find(1)->index();
+
+		$mock_sql = $this->mock()
+										 ->whenCalled('fetchAllByColumn')
+										 ->with("Select id_notice from notices Where MATCH(facettes) AGAINST('Q3' IN BOOLEAN MODE)")
+										 ->answers([1])
+										 ->whenCalled('fetchOne')
+										 ->answers(1)
+										 ->whenCalled('fetchAll')
+										 ->answers([Class_Notice::find(1)->toArray()]);
+
+		Zend_Registry::set('sql', $mock_sql);
+		Class_Profil::getCurrentProfil()
+			->setCfgModules(['recherche' =>
+											 ['resultatsimple' =>
+												['liste_format' =>	Class_Systeme_ModulesAppli::LISTE_FORMAT_MUR]]]);
+
+		$this->dispatch('/recherche/simple/id_catalogue/3/id_module/9/aleatoire/1', true);
+	}
+
+
+	/** @test */
+	public function monLienShouldExistsAsANotice() {
+		$this->assertEquals('MON LIEN', Class_Notice::find(1)->getAlphaTitre());
+	}
+
+
+	/** @test */
+	public function sitoDomainEShouldExists() {
+		$this->assertNotNull(Class_NoticeDomain::find(1));
+	}
+
+
+  /** @test */
+	public function onRechercheSimpleCatalogueSitoShouldBePresent() {
+		$this->assertXPathContentContains('//div[@class="resultat_recherche"]//a', 'Mon lien',											 $this->_response->getBody());
+	}
+}
+
+
+
+class RechercheControlleSimpleActionWithEmptyDomainSettingsAndArticlesLinkedTest extends AbstractControllerTestCase {
+	public function setUp() {
+		parent::setUp();
+		Class_Exemplaire::beVolatile();
+		Class_Notice::beVolatile();
+
+		$this->fixture('Class_Catalogue',
+									 ['id'=>3,
+										'libelle' => 'News']);
+		$this->fixture('Class_Article',
+									 ['id' => 1,
+										'titre' => 'News of the sprint',
+										'id_user' => 1,
+										'indexation' => 1,
+										'contenu' => 'Articles are going to be auto indexed !',
+										'domaine_ids' => '3']);
+
+		Class_Notice::beVolatile();
+		Class_NoticeDomain::beVolatile();
+		Class_Article::find(1)->index();
+
+		$mock_sql = $this->mock()
+										 ->whenCalled('fetchAllByColumn')
+										 ->answers([1])
+										 ->whenCalled('fetchOne')
+										 ->answers(1)
+										 ->whenCalled('fetchAll')
+										 ->answers([Class_Notice::find(1)->toArray()]);
+
+		Zend_Registry::set('sql', $mock_sql);
+		Class_Profil::getCurrentProfil()
+			->setCfgModules(['recherche' =>
+											 ['resultatsimple' =>
+												['liste_format' =>	Class_Systeme_ModulesAppli::LISTE_FORMAT_MUR]]]);
+
+		$this->dispatch('/recherche/simple/id_catalogue/3/id_module/9/aleatoire/1', true);
+	}
+
+
+	/** @test */
+	public function newsOfTheSPrintShouldExistsAsANotice() {
+		$this->assertNotNull(Class_Notice::find(1));
+	}
+
+
+	/** @test */
+	public function noticeAuthorShouldBeAsExpected() {
+		$this->assertEquals('', Class_Notice::find(1)->getAuteurPrincipal());
+	}
+
+
+	/** @test */
+	public function newsOfTheSprintDomainShouldExists() {
+		$this->assertNotNull(Class_NoticeDomain::find(1));
+	}
+
+
+  /** @test */
+	public function searchOnNewsShouldContainsNewsOfTheSprint() {
+		$this->assertXPathContentContains('//div[@class="resultat_recherche"]//a', 'News of the sprint');
+	}
+}
+
+?>
diff --git a/tests/application/modules/opac/controllers/SocialNetworkControllerTest.php b/tests/application/modules/opac/controllers/SocialNetworkControllerTest.php
index b1ca8da4ee13eaa43ae6259dded3e0a0e8041a3c..c80a94c93d58a292cc31e5c2662010d0752e6336 100644
--- a/tests/application/modules/opac/controllers/SocialNetworkControllerTest.php
+++ b/tests/application/modules/opac/controllers/SocialNetworkControllerTest.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
  */
 require_once 'AbstractControllerTestCase.php';
 
@@ -56,23 +56,6 @@ class SocialNetworkControllerShareActionTest extends AbstractControllerTestCase
 	}
 
 
-	/** @test */
-	public function shareOnFacebookShouldInjectShortUrlTitreMessageImageUrl() {
-		$this->_expectClientOpenUrlWithLongUrlAndAnswer('http://localhost'. BASE_URL .'/recherche/viewnotice/id/2',
-																										'http://is.gd/PkdNg2');
-		$this->dispatch('/social-network/share/on/facebook/url/'
-										.urlencode('/recherche/viewnotice/id/2')
-										.'/titre/'
-										.urlencode('titre notice')
-										.'/message/'
-										.urlencode('message notice')
-										.'/img_url/'
-										.urlencode('www.uneimagedenotice.fr/notice/2'), true);
-
-		$this->assertEquals("window.open('https://www.facebook.com/sharer/sharer.php?m2w&s=100&p%5Btitle%5D=titre+notice&p%5Bsummary%5D=message+notice&p%5Burl%5D=http%3A%2F%2Fis.gd%2FPkdNg2&p%5Bimages%5D%5B0%5D=www.uneimagedenotice.fr%2Fnotice%2F2','_blank','toolbar=0,status=0,width=800, height=410');",
-												$this->_response->getBody());
-	}
-
 }
 
 
diff --git a/tests/application/modules/telephone/controllers/CasServerControllerTest.php b/tests/application/modules/telephone/controllers/CasServerControllerTest.php
index 7e98fe2249a4d067c0d422282afb0f53efc54a5b..745f4700baf2bef1aaea66d9972ab37177046a5f 100644
--- a/tests/application/modules/telephone/controllers/CasServerControllerTest.php
+++ b/tests/application/modules/telephone/controllers/CasServerControllerTest.php
@@ -24,14 +24,14 @@ require_once 'TelephoneAbstractControllerTestCase.php';
 class Telephone_CasServerControllerLoggedTest extends TelephoneAbstractControllerTestCase {
 	public function setUp() {
 		parent::setUp();
+		Storm_Cache::beVolatile();
 		$user = new StdClass();
 		$user->ID_USER=300;
 		Class_Users::newInstanceWithId(300,
 		                               ['login' => '87364',
 																	  'pseudo' => 'georges']);
-		Zend_Registry::get('cache')->save('300',
-																			md5(Zend_Session::getId().'300'));
-
+		(new Storm_Cache())->save('300',
+															md5(Zend_Session::getId().'300'));
 	}
 
 	/** @test */
diff --git a/tests/application/modules/telephone/controllers/IndexControllerTest.php b/tests/application/modules/telephone/controllers/IndexControllerTest.php
index eebcbfeefe8991079919f641452f48cb348cc936..2b0c1b0a8b98ac7c308f43ebd605db46f72d0a57 100644
--- a/tests/application/modules/telephone/controllers/IndexControllerTest.php
+++ b/tests/application/modules/telephone/controllers/IndexControllerTest.php
@@ -541,8 +541,7 @@ class IndexControllerTelephoneWithNoProfilTelephoneTest extends Zend_Test_PHPUni
 class IndexControllerTelephoneWithForceModuleOPACTest extends AbstractIndexControllerTelephoneWithModulesTest {
 	public function setUp() {
 		parent::setUp();
-		Class_Profil::setCurrentProfil(Class_Profil::getLoader()
-																	 ->newInstanceWithId(1)
+		Class_Profil::setCurrentProfil($this->fixture('Class_Profil', ['id' => 1])
 																	 ->setBrowser('opac')
 																	 ->setLibelle('desktop'));
 
diff --git a/tests/application/modules/telephone/controllers/RechercheControllerHarryPotterTest.php b/tests/application/modules/telephone/controllers/RechercheControllerHarryPotterTest.php
index 47515e385ca8191000e144253580d96bf4762fd5..d75fd24847a3a4e702aa7520bc19df577580e26f 100644
--- a/tests/application/modules/telephone/controllers/RechercheControllerHarryPotterTest.php
+++ b/tests/application/modules/telephone/controllers/RechercheControllerHarryPotterTest.php
@@ -48,6 +48,7 @@ abstract class Telephone_RechercheControllerHarryPotterTestCase extends Telephon
 			->setUrlVignette('http://amazon.fr/potter.jpg')
 			->setUrlImage('http://amazon.fr/potter_grand.jpg')
 			->beLivre();
+		$potter->set_subfield('461','t','Harry Potter');
 	}
 }
 
diff --git a/tests/application/modules/telephone/controllers/RechercheControllerTest.php b/tests/application/modules/telephone/controllers/RechercheControllerTest.php
index 710e47c65e4e4134f394ea2b1b6a201036f56085..0aa2a41ff3b10bfad3b4e072365a0cb2e0e0bb3e 100644
--- a/tests/application/modules/telephone/controllers/RechercheControllerTest.php
+++ b/tests/application/modules/telephone/controllers/RechercheControllerTest.php
@@ -172,58 +172,48 @@ class Telephone_RechercheControllerFrbrWithLinksTest extends TelephoneAbstractCo
     $this->_tintinPicaros = 'TINTINEIPICAROS--HERGE--LIZARD-2001-1';
     $this->_tintinAventure = 'THEADVENTUREOFTINTIN-CIGARSOFTHEPHARAOH-HERGE--USBORNE-2009-1';
 
-    $type = Class_FRBR_LinkType::newInstanceWithId(1)
-      ->setLibelle('Suite')
-      ->setFromSource('a pour suite')
-      ->setFromTarget('est une suite de');
-
-    Storm_Test_ObjectWrapper::onLoaderOfModel('Class_FRBR_Link')
-      ->whenCalled('getLinksForSource')
-      ->with($tintinReporter)
-      ->answers([Class_FRBR_Link::newInstanceWithId(1)
-		 ->setType($type)
-		 ->setSource($this->_getUrlForKey($this->$tintinReporter))
-		 ->setSourceType(Class_FRBR_Link::TYPE_NOTICE)
-		 ->setTarget($this->_getUrlForKey($this->_tintinPicaros))
-		 ->setTargetType(Class_FRBR_Link::TYPE_NOTICE)])
-
-      ->whenCalled('getLinksForTarget')
-      ->with($tintinReporter)
-      ->answers([Class_FRBR_Link::newInstanceWithId(2)
-		 ->setType($type)
-		 ->setSource($this->_getUrlForKey($this->_tintinAventure))
-		 ->setSourceType(Class_FRBR_Link::TYPE_NOTICE)
-		 ->setTarget($this->_getUrlForKey($this->$tintinReporter))
-		 ->setTargetType(Class_FRBR_Link::TYPE_NOTICE)]);
-
-    Storm_Test_objectWrapper::onLoaderOfModel('Class_Notice')
-      ->whenCalled('getNoticeByClefAlpha')
-      ->with($this->_tintinPicaros)
-      ->answers(Class_Notice::newInstanceWithId(555)
-								->setTitrePrincipal('Tintin e i picaros')
-								->setAuteurPrincipal('Hergé')
-								->setClefAlpha($this->_tintinPicaros)
-								->setUrlVignette('picaros_small.png')
-								->setUrlImage('picaros.png'))
-
-      ->whenCalled('getNoticeByClefAlpha')
-      ->with($this->_tintinAventure)
-      ->answers(Class_Notice::newInstanceWithId(777)
-								->setTitrePrincipal('The adventure of Tintin')
-								->setClefAlpha($this->_tintinAventure)
-								->setUrlVignette('tintin_small.png')
-								->setUrlImage('tintin.png'))
-
-      ->whenCalled('find')
-      ->with(333)
-      ->answers(Class_Notice::newInstanceWithId(333)
-		->setClefAlpha($tintinReporter));
+		$type = $this->fixture('Class_FRBR_LinkType',
+													 ['id' => 1,
+														'libelle' => 'Suite',
+														'from_source' => 'a pour suite',
+														'from_target' => 'est une suite de']);
+
+		$this->fixture('Class_FRBR_Link',
+									 ['id' => 1,
+										'type' => $type,
+										'source' => $this->_getUrlForKey($tintinReporter),
+										'target' => $this->_getUrlForKey($this->_tintinPicaros)]);
+
+		$this->fixture('Class_FRBR_Link',
+									 ['id' => 2,
+										'type' => $type,
+										'source' => $this->_getUrlForKey($this->_tintinAventure),
+										'target' => $this->_getUrlForKey($tintinReporter)]);
+
+		$this->fixture('Class_Notice',
+									 ['id' => 555,
+										'titre_principal' => 'Tintin e i picaros',
+										'clef_alpha' => $this->_tintinPicaros,
+										'auteur_principal' => 'Hergé',
+										'url_vignette' => 'picaros_small.png',
+										'url_image' => 'picaros.png']);
+
+		$this->fixture('Class_Notice',
+									 ['id' => 777,
+										'titre_principal' => 'The adventure of Tintin',
+										'clef_alpha' => $this->_tintinAventure,
+										'url_vignette' => 'tintin_small.png',
+										'url_image' => 'tintin.png']);
+
+		$this->fixture('Class_Notice',
+									 ['id' => 333,
+										'clef_alpha' => $tintinReporter]);
 
     $this->dispatch('/telephone/recherche/frbr?id=333', true);
   }
 
   protected function _getUrlForKey($key) {
-    return 'http://localhost' . BASE_URL . '/recherche/viewnotice/clef/' . $key . '?Id_profil=1&type_doc=1';
+    return ROOT_URL . BASE_URL . '/recherche/viewnotice/clef/' . $key . '?Id_profil=1&type_doc=1';
   }
 
 
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
index fe6b9427f662c64aa5b7a249d920e454daaac460..866127bfa7c979d9bf28e436b8aca67c84ec0a93 100644
--- a/tests/bootstrap.php
+++ b/tests/bootstrap.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
  */
 chdir(realpath(dirname(__FILE__)).'/../');
 
@@ -50,12 +50,13 @@ $parts = explode(DIRECTORY_SEPARATOR, $path);
 $parts = array_reverse($parts);
 
 defineConstant("BASE_URL", "/" . $parts[1]);
+defineConstant('ROOT_URL', 'http://localhost');
 defineConstant("URL_IMG", BASE_URL . "/public/opac/skins/original/images/");
 defineConstant("URL_SHARED_IMG", BASE_URL . "/public/opac/images");
 
 setupOpac();
 
-Zend_Registry::get('cache')->setOption('caching', true);
+(new Storm_Cache())->getCache()->setOption('caching', true);
 
 $db_cache = Zend_Cache::factory('Core',
 																'File',
@@ -65,7 +66,6 @@ $db_cache = Zend_Cache::factory('Core',
 
 Zend_Db_Table_Abstract::setDefaultMetadataCache($db_cache);
 
-
 Storm_Cache::setSeed('local');
 
 $cfg = new Zend_Config(Zend_Registry::get('cfg')->toArray(), true);
diff --git a/tests/library/Class/AdminVarTest.php b/tests/library/Class/AdminVarTest.php
index 104c9f65ddd695935b7a0046311a8b3fd24b1c89..2f005e87a91d0f1de4f75ea22e33d72ee20abde6 100644
--- a/tests/library/Class/AdminVarTest.php
+++ b/tests/library/Class/AdminVarTest.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
  */
 require_once 'ModelTestCase.php';
 
@@ -102,6 +102,7 @@ class AdminVarTestGet extends AdminVarTestCase {
 		$this->assertFalse(Class_AdminVar::isWorkflowEnabled());
 	}
 
+
 	/** @test */
 	public function withWorkflowDefinedToOneIsWorkflowEnabledShouldReturnTrue() {
 		Class_AdminVar::getLoader()
@@ -110,15 +111,34 @@ class AdminVarTestGet extends AdminVarTestCase {
 		$this->assertTrue(Class_AdminVar::isWorkflowEnabled());
 	}
 
+
+	/** @test */
+	public function withWorkflowDefinedToEmptyValueIsWorklowEnabledShouldReturnFalse() {
+		Class_AdminVar::getLoader()
+			->newInstanceWithId('WORKFLOW')
+			->setValeur('');
+		$this->assertFalse(Class_AdminVar::isWorkflowEnabled());
+	}
+
+
 	/** @test */
-	public function withWorkflowDefinedToNonOneValueIsWorklowEnabledShouldReturnFalse() {
+	public function withWorkflowDefinedToZeroValueIsWorklowEnabledShouldReturnFalse() {
 		Class_AdminVar::getLoader()
 			->newInstanceWithId('WORKFLOW')
-			->setValeur('A sample non 1 value');
+			->setValeur('0');
 		$this->assertFalse(Class_AdminVar::isWorkflowEnabled());
 	}
 
 
+	/** @test */
+	public function withWorkflowDefinedToJsonArrayValueWorkflowShoudBeEnabled() {
+		Class_AdminVar::getLoader()
+			->newInstanceWithId('WORKFLOW')
+			->setValeur('[{"id":"10", "label":"testing status"}]');
+		$this->assertTrue(Class_AdminVar::isWorkflowEnabled());
+	}
+
+
 	/** @test */
 	public function workflowTextMailArticlePendingShouldHaveDefaultTextNouvelArticleAValider() {
 		$this->assertEquals('Un nouvel article est à valider. TITRE_ARTICLE URL_ARTICLE',Class_AdminVar::getWorkflowTextMailArticlePending());
@@ -129,7 +149,7 @@ class AdminVarTestGet extends AdminVarTestCase {
 	public function workflowTextMailArticleRefusedShouldHaveDefaultTextArticleRefuse() {
 		$this->assertEquals('L\'article a été refusé.',Class_AdminVar::getWorkflowTextMailArticleRefused());
 	}
-	
+
 
 
 	/** @test */
diff --git a/tests/library/Class/ArticleTest.php b/tests/library/Class/ArticleTest.php
index d0cc8301a78044c0bb5bf86a1103f0f435b17728..e292af1f14ca8ac50194417ad985a93cc664197f 100644
--- a/tests/library/Class/ArticleTest.php
+++ b/tests/library/Class/ArticleTest.php
@@ -1043,4 +1043,111 @@ class EventsByMonthWithArticleNextYearTest extends EventsByMonthWithArticleTestC
 		Class_Article::setTimeSource(new TimeSourceForTest('2012-05-19 09:00:00'));
 	}
 }
-?>
\ No newline at end of file
+
+
+
+class ArticleIndexAllTest extends Storm_Test_ModelTestCase {
+	public function setUp() {
+		parent::setUp();
+		Class_Exemplaire::beVolatile();
+		Class_Notice::beVolatile();
+		Class_NoticeDomain::beVolatile();
+		$this->fixture('Class_Catalogue',
+									 ['id' => 1,
+										'libelle' => 'My domain']);
+
+		$this->fixture('Class_Article',
+									 ['id' => 1,
+										'titre' => 'My Cms First Step',
+										'contenu' => 'My Cms First Step',
+										'indexation' => 1]);
+
+		$this->fixture('Class_Article',
+									 ['id' => 2,
+										'titre' => 'My Cms article',
+										'indexation' => 1,
+										'contenu' => 'My Cms First Step',
+										'domaine_ids' => '1']);
+
+		Class_Article::indexAll();
+	}
+
+
+	/** @test */
+	public function articleAlphaKeyShouldBeAsMYCMSFIRSTSTEP_8() {
+		$this->assertEquals('MYCMSFIRSTSTEP------8', Class_Article::find(1)->getAlphaKey());
+	}
+
+
+	/** @test */
+	public function twoRecordsShouildBePresent() {
+		$this->assertCount(2, Class_Notice::findAll());
+	}
+
+
+	/** @test */
+	public function recordShouldHaveExpectedFacette() {
+		$this->assertContains(Class_Catalogue::find(1)->getFacette(), Class_Notice::find(2)->getFacettes());
+	}
+
+
+	/** @test */
+	public function myDomainShouldHaveANoticeLineked() {
+		$this->assertEquals('My Cms article', Class_NoticeDomain::findFirstBy(['domain_id' => 1])->getNotice()->getTitrePrincipal());
+	}
+}
+
+
+
+class ArticleUnindexTest extends Storm_Test_ModelTestCase {
+	public function setUp() {
+		parent::setUp();
+		Class_Exemplaire::beVolatile();
+		Class_Notice::beVolatile();
+		Class_NoticeDomain::beVolatile();
+
+		$this->fixture('Class_Catalogue',
+									 ['id' => 1,
+										'libelle' => 'My domain']);
+
+		$this->fixture('Class_Catalogue',
+									 ['id' => 2,
+										'libelle' => 'My domain2']);
+
+		$this->fixture('Class_Article',
+									 ['id' => 1,
+										'titre' => 'My Cms First Step',
+										'contenu' => 'My Cms First Step',
+										'indexation' => 1,
+										'domaine_ids' => '1;2']);
+
+		$this->fixture('Class_Article',
+									 ['id' => 2,
+										'titre' => 'My Cms second Step',
+										'contenu' => 'My Cms second Step',
+										'indexation' => 1,
+										'domaine_ids' => '1;2']);
+
+		Class_Article::find(1)->index();
+		Class_Article::find(2)->index();
+		Class_Article::find(1)->setDomaineIds(1)->save();
+		Class_Article::find(2)->setDomaineIds('')->save();
+	}
+
+
+	/** @test */
+	public function secondSteprecordShouldNotHaveNoticeDomain() {
+		$this->assertNull(Class_NoticeDomain::findFirstBy(['record_alpha_key' => Class_Notice::find(2)->getAlphaKey()]));
+	}
+
+
+	/** @test */
+	public function recordShouldHaveBeenUnindexedFromDomain2() {
+		$this->assertNull(Class_NoticeDomain::findFirstBy(['domain_id' => 2]));
+	}
+
+	/** @test */
+	public function recordShouldNotHaveDomainFacet() {
+		$this->assertNotContains('H2', Class_Notice::find(1)->getFacettes());
+	}
+}
\ No newline at end of file
diff --git a/tests/library/Class/BatchTest.php b/tests/library/Class/BatchTest.php
index d49a372259bd9ff995fd644cfcd85e73cbf1e143..0fb348a3652c68f897c0c86a503fda324926b6c7 100644
--- a/tests/library/Class/BatchTest.php
+++ b/tests/library/Class/BatchTest.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
  */
 
 
@@ -28,13 +28,13 @@ class BatchModelTest extends Storm_Test_ModelTestCase {
 																								 'type' => 'MOISSONNAGE_VODECLIC',
 																								 'last_run' => '2012-05-01 10:23:00']);
 
-		$this->batch_artevod = $this->fixture('Class_Batch', 
-																					['id' => 3, 
+		$this->batch_artevod = $this->fixture('Class_Batch',
+																					['id' => 3,
 																					 'type' => 'MOISSONNAGE_ARTEVOD',
 																					 'last_run' => '2012-05-01 10:23:00']);
 
-		$this->batch_IndexRessourcesNumeriques = $this->fixture('Class_Batch', 
-																					['id' => 3, 
+		$this->batch_IndexRessourcesNumeriques = $this->fixture('Class_Batch',
+																					['id' => 3,
 																					 'type' => 'INDEX_RESSOURCES_NUMERIQUES',
 																					 'last_run' => '2014-04-02 17:11:00']);
 	}
@@ -89,7 +89,7 @@ class BatchLoaderWithoutRessourcesNumeriquesTest extends Storm_Test_ModelTestCas
 	public function availableTypeShouldNotContainRessourcesNumeriques(){
 		$types = Class_Batch::getAvailableType();
 		foreach(array_keys($types) as $type) {
-			if (0 === strpos($type, 'MOISSONNAGE_')) 
+			if (0 === strpos($type, 'MOISSONNAGE_'))
 				$this->fail();
 		}
 	}
@@ -102,7 +102,7 @@ class BatchLoaderWithRessourcesNumeriquesTest extends Storm_Test_ModelTestCase {
 
 	public function setUp() {
 		parent::setUp();
-		
+
 		Class_Batch::beVolatile();
 		Class_AdminVar::beVolatile();
 		RessourcesNumeriquesFixtures::activate();
@@ -140,4 +140,70 @@ class BatchIndexRessourcesNumeriquesTest extends Storm_Test_ModelTestCase {
 		$this->assertTrue($this->cache->methodHasBeenCalled('clean'));
 	}
 }
+
+
+
+class BatchBuildSitemapTest extends Storm_Test_ModelTestCase {
+	protected $_file_system;
+	protected $_profiles;
+
+	public function setUp() {
+		$this->_profiles[1] = $this->fixture('Class_Profil', ['id' => 1,
+																		'libelle' => 'Portail',
+																		'cfg_accueil' => ZendAfi_Filters_Serialize::serialize(['sitemap' => 1])]);
+		$this->_profiles[2] = $this->fixture('Class_Profil', ['id' => 2,
+																		'libelle' => 'Médiathèque foo',
+																		'cfg_accueil' => ZendAfi_Filters_Serialize::serialize(['sitemap' => 1])]);
+		$this->_profiles[3] = $this->fixture('Class_Profil', ['id' => 3,
+																		'libelle' => 'Services',
+																		'parent_id' => '2',
+																		'cfg_accueil' => ZendAfi_Filters_Serialize::serialize(['sitemap' => 0])]);
+		Class_Profil::beVolatile();
+
+		$this->handle = new stdClass;
+		$this->_file_system = $this->mock()
+			->whenCalled('fopen')->with(ROOT_PATH.'temp/sitemap.xml', 'w')->answers($this->handle)
+			->whenCalled('fwrite')->answers(null)
+			->whenCalled('unlink')->answers(null);
+
+		Class_Sitemap::setFileSystem($this->_file_system);
+
+	}
+
+
+	/** @test */
+	public function sitemapShouldBeHasExpected() {
+		(new Class_Batch_BuildSiteMap())->run();
+		$expected = '<?xml version="1.0" encoding="UTF-8"?>
+<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
+  <url>
+    <loc>' . ROOT_URL . BASE_URL . '?id_profil=1</loc>
+    <priority>1.0</priority>
+    <changefreq>weekly</changefreq>
+  </url>
+  <url>
+    <loc>' . ROOT_URL . BASE_URL . '?id_profil=2</loc>
+    <priority>0.8</priority>
+    <changefreq>weekly</changefreq>
+  </url>
+</urlset>
+';
+
+		$this->assertTrue($this->_file_system
+											->methodHasBeenCalledWithParams('fwrite',
+																											[$this->handle, $expected]));
+	}
+
+	/** @test */
+	public function sitemapFileShouldBeRemoved() {
+		$this->_profiles[1]->setCfgAccueilParam('sitemap', 0);
+		$this->_profiles[2]->setCfgAccueilParam('sitemap', 0);
+		(new Class_Batch_BuildSiteMap())->run();
+		$this->assertTrue($this->_file_system
+											->methodHasBeenCalledWithParams('unlink',
+																											[PATH_TEMP .'sitemap.xml']));
+	}
+
+}
+
 ?>
diff --git a/tests/library/Class/BibTest.php b/tests/library/Class/BibTest.php
index 4f6f83e0d8826d117f9b042ffea43b86cd81bc23..ab26a1f39ab3a611f41dc7ac077c3c363aca500b 100644
--- a/tests/library/Class/BibTest.php
+++ b/tests/library/Class/BibTest.php
@@ -16,26 +16,26 @@
  *
  * 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 BibTest extends Storm_Test_ModelTestCase {
 	/** @test */
 	function loaderFindAllWithPortailShouldIncludePortail() {
-		$this->assertEquals(0, 
+		$this->assertEquals(0,
 												array_first(Class_Bib::getLoader()->findAllWithPortail())->getId());
 	}
 
 	/** @test */
 	function loaderFindAllByWithPortailShouldIncludePortail() {
-		$this->assertEquals(0, 
+		$this->assertEquals(0,
 												array_first(Class_Bib::getLoader()->findAllByWithPortail(array()))->getId());
 	}
 
 
-	/** 
+	/**
 	 * Non régression bug n'affiche aucune bib sur sélection territoire "toutes" admin bib
-	 * @test 
+	 * @test
 	 */
 	public function findAllByIdZoneShouldForceIntForRequest() {
 		Storm_Test_ObjectWrapper::onLoaderOfModel('Class_Bib')
@@ -44,7 +44,7 @@ class BibTest extends Storm_Test_ModelTestCase {
 			->answers('ALL')
 
 			->whenCalled('findAllBy')
-			->with(['id_zone' => 3, 
+			->with(['id_zone' => 3,
 							'order' => 'ville'])
 			->answers('ZONE');
 
@@ -52,6 +52,31 @@ class BibTest extends Storm_Test_ModelTestCase {
 		$this->assertEquals('ZONE', Class_Bib::findAllByIdZone(3));
 
 	}
+
+
+	/** @test */
+	public function withAffZoneContainingIdProfilGetUrlShouldAnswerProfilRewriteUrl() {
+		$this->fixture('Class_Profil',
+									 ['id' => 23,
+										'libelle' => 'Annecy',
+										'rewrite_url' => 'annecy']);
+
+		$bib = $this->fixture('Class_Bib',
+													['id' => 2,
+													 'aff_zone' => serialize(['profilID' => 23,
+																										'libelle' => 'Annecy'])]);
+
+		$this->assertContains('/annecy', $bib->getUrl());
+	}
+
+
+	/** @test */
+	public function withEmptyAffZoneContainingIdProfilGetUrlShouldAnswerBibViewId23() {
+		$bib = $this->fixture('Class_Bib',
+													['id' => 2]);
+
+		$this->assertContains('/bib/bibview/id/2', $bib->getUrl());
+	}
 }
 
 ?>
\ No newline at end of file
diff --git a/tests/library/Class/CVSLinkTest.php b/tests/library/Class/CVSLinkTest.php
index 9e8c0e86096a190c474a75938db6ca46f56cef09..bd265bc8c5c20544072c20c5664f9c966cbcff4e 100644
--- a/tests/library/Class/CVSLinkTest.php
+++ b/tests/library/Class/CVSLinkTest.php
@@ -178,6 +178,15 @@ class CVSLinkWithAbonTest extends CVSLinkTestCase {
 			     <body>
 						 <querystring><![CDATA[]]></querystring>
 						 <affichage>complet</affichage>
+						 <login>34</login>
+						 <nom>Khan</nom>
+						 <prenom>Jerry</prenom>
+						 <pseudo>JKhan</pseudo>
+						 <password>secret</password>
+						 <email>feu@essence.fr</email>
+						 <dnaiss>1977-06-27</dnaiss>
+						 <datout>2023-09-02</datout>
+						 <bibliotheque>Annecy</bibliotheque>
 					 </body>
 			   </albums>";
 		return $this->_cvs->formatXML($xml);
diff --git a/tests/library/Class/CatalogueTest.php b/tests/library/Class/CatalogueTest.php
index fe1da1eec780dae31f31e3e23ed3621e844d4f77..2750dfb485477a71e80e683a4d217dee2b300174 100644
--- a/tests/library/Class/CatalogueTest.php
+++ b/tests/library/Class/CatalogueTest.php
@@ -443,19 +443,19 @@ class CatalogueTestGetRequetesWithFacettesAndNoCatalogue extends ModelTestCase {
 
 	/** @test */
 	public function requeteListeShouldEqualsSelectStarWhereFacettesFromNotices() {
-		$this->assertEquals('select * from notices where MATCH(facettes) AGAINST(\' +(T1, Y1)\' IN BOOLEAN MODE) order by alpha_titre  LIMIT 5000', $this->_requetes['req_liste']);
+		$this->assertEquals('select * from notices Where MATCH(facettes) AGAINST(\' +(T1, Y1)\' IN BOOLEAN MODE) order by alpha_titre  LIMIT 5000', $this->_requetes['req_liste']);
 	}
 
 
 	/** @test */
 	public function requeteComptageShouldBeSelectCount() {
-		$this->assertEquals('select count(*) from notices where MATCH(facettes) AGAINST(\' +(T1, Y1)\' IN BOOLEAN MODE)', $this->_requetes['req_comptage']);
+		$this->assertEquals('select count(*) from notices Where MATCH(facettes) AGAINST(\' +(T1, Y1)\' IN BOOLEAN MODE)', $this->_requetes['req_comptage']);
 	}
 
 
 	/** @test */
 	public function requeteFacettesShouldBeSelectIdNoticeTypeDocFacet() {
-		$this->assertEquals('select notices.id_notice, type_doc, facettes from notices where MATCH(facettes) AGAINST(\' +(T1, Y1)\' IN BOOLEAN MODE) LIMIT 5000', $this->_requetes['req_facettes']);
+		$this->assertEquals('select notices.id_notice, type_doc, facettes from notices Where MATCH(facettes) AGAINST(\' +(T1, Y1)\' IN BOOLEAN MODE) LIMIT 5000', $this->_requetes['req_facettes']);
 	}
 }
 
@@ -505,6 +505,12 @@ class CatalogueTestGetNoticesByPreferences extends ModelTestCase {
 			->newInstanceWithId('CACHE_ACTIF')
 			->setValeur(1);
 
+		$this->_catalogue = $this->fixture('Class_Catalogue',
+																			 ['id' => 666,
+																				'libelle' => 'my cata',
+																				'type_doc' => '1']);
+
+
 		$this->mock_cache = Storm_Test_ObjectWrapper::mock();
 		Storm_Cache::setDefaultZendCache($this->mock_cache);
 		$this->mock_cache
@@ -517,10 +523,13 @@ class CatalogueTestGetNoticesByPreferences extends ModelTestCase {
 
 		$this->mock_sql
 			->whenCalled('fetchAll')
-			->with("select notices.id_notice, notices.editeur, notices.annee, notices.date_creation, notices.date_maj, notices.facettes, notices.clef_oeuvre from notices order by alpha_titre  LIMIT 0,25",
+			->with("select notices.id_notice, notices.editeur, notices.annee, notices.date_creation, notices.date_maj, notices.facettes, notices.clef_oeuvre from notices Where type_doc=1 order by alpha_titre  LIMIT 0,25",
 						 false)
 			->answers([['id_notice' => 23, 'editeur' => 'dargaud', 'annee' => 1975, 'date_creation' => '2011-02-23',
 					'date_maj' => '2011-02-25', 'facettes' => '', 'clef_oeuvre' => 'JEUNE FILLE'],])
+			->whenCalled('query')
+			->with([0 => 'update notices set facettes = clean_spaces(replace(facettes,"HCCCC0030","")) where type_doc not in (8,9,10) and match(facettes) against("+HCCCC0030" in boolean mode)'])
+			->answers(true)
 			->beStrict();
 
 		$this->fixture('Class_Notice', ['id' => 23,
@@ -533,12 +542,10 @@ class CatalogueTestGetNoticesByPreferences extends ModelTestCase {
 																		'clef_alpha' => 'DOU-TEST',
 																		'unimarc' => "01328ngm0 2200265   450 0010007000001000041000071010013000481020007000611150025000682000071000932100022001642150053001863000035002393000045002743300454003193450027007735100018008006060027008186060039008457000042008847020043009267020033009697020032010028010028010342247456  a20021213i20041975u  y0frey0103    ba0 abamjfre  aFR  ac086baz|zba    zz  c1 aLa jeune fillebDVDdDen MusofSouleymane Cisse, réal., scénario  cPathédcop. 2004  a1 DVD vidéo monoface zone 2 (1 h 26 min)ccoul.  aDate de sortie du film : 1975.  aFilm en bambara sous-titré en français  aSékou est renvoyé de l'usine parce qu'il a osé demander une augmentation. Chômeur, il sort avec Ténin, une jeune fille muette ; il ignore qu'elle est la fille de son ancien patron. Ténin, qui sera violée par Sékou lors d'une sortie entre jeunes, se retrouve enceinte et subit la colère de ses parents. Elle se trouve alors confrontée brutalement à la morale de sa famille et à la lâcheté de Sékou, qui refuse de reconnaiîre l'enfant.  b3388334509824d14.00 ?1 aDen Musozbam| 31070135aCinémayMali| 32243367aCinéma30076549yAfrique 131070144aCissébSouleymane43704690 132247457aCoulibalibDounamba Dani4590 132247458aDiabatebFanta4590 132247459aDiarrabOumou4590 0aFRbBNc20011120gAFNOR"]);
 
-
-		$this->_catalogue = Class_Catalogue::getLoader()->newInstanceWithId(666);
 		$this->_notices = Class_Catalogue::getLoader()->getNoticesByPreferences(['id_catalogue' => 666,
-																																	'aleatoire' => '1',
-																																	'nb_analyse' => 25,
-																																	'nb_notices' => 40]);
+																																						 'aleatoire' => '1',
+																																						 'nb_analyse' => 25,
+																																						 'nb_notices' => 40]);
 	}
 
 
@@ -583,8 +590,8 @@ class CatalogueTestGetNoticesByPreferences extends ModelTestCase {
 		/* this test will fail when Class_Catalogue::_default_attribute_values change */
 		$this->mock_cache
 			->whenCalled('load')
-			->with('ab95a4588a206a1b8f977d509d5cf8bc')
-			->answers(serialize(array('test')))
+			->with('274bd8a6a2b9f86411a728fbf579b26e')
+			->answers(serialize(['test']))
 			->beStrict();
 
 		$notices = Class_Catalogue::getLoader()->getNoticesByPreferences($preferences);
@@ -827,7 +834,9 @@ class CatalogueAddOrCreateTest extends CatalogueParentTest {
 			->whenCalled('findThesaurusForCatalogue')
 			->with(100)
 			->answers(null);
-		$this->assertEquals('YYYY0001', $this->_histoire->saveThesaurus()->getIdThesaurus());
+		$this->assertEquals('YYYY0001',
+												Class_Catalogue::saveThesaurus($this->_histoire)
+												->getIdThesaurus());
 	}
 
 
@@ -845,7 +854,9 @@ class CatalogueAddOrCreateTest extends CatalogueParentTest {
 			->whenCalled('findThesaurusForCatalogue')
 			->with(100)
 			->answers(Class_CodifThesaurus::newInstanceWithId(300)->setIdThesaurus('YYYY0001'));
-		$this->assertEquals('YYYY00010001',$this->_politique->saveThesaurus()->getIdThesaurus());
+		$this->assertEquals('YYYY00010001',
+												Class_Catalogue::saveThesaurus($this->_politique)
+												->getIdThesaurus());
 	}
 
 
@@ -858,7 +869,9 @@ class CatalogueAddOrCreateTest extends CatalogueParentTest {
 
 			->whenCalled('findThesaurusForCatalogue')
 			->answers(null);
-		$this->assertEquals('YYYY00010001',$this->_moyenage->saveThesaurus()->getIdThesaurus());
+		$this->assertEquals('YYYY00010001',
+												Class_Catalogue::saveThesaurus($this->_moyenage)
+												->getIdThesaurus());
 	}
 
 
@@ -872,7 +885,9 @@ class CatalogueAddOrCreateTest extends CatalogueParentTest {
 			->whenCalled('findThesaurusForCatalogue')
 			->answers(null);
 		$this->_politique->setParentId($this->_moyenage->getId());
-		$this->assertEquals('YYYY000100010001',$this->_politique->saveThesaurus()->getIdThesaurus());
+		$this->assertEquals('YYYY000100010001',
+												Class_Catalogue::saveThesaurus($this->_politique)
+												->getIdThesaurus());
 	}
 
 
@@ -894,7 +909,10 @@ class CatalogueAddOrCreateTest extends CatalogueParentTest {
 			->with(200)
 			->answers(Class_CodifThesaurus::newInstanceWithId(200)->setIdThesaurus('YYYYHIST0002'));
 		$this->_politique->setParentId($this->_moyenage->getId());
-		$this->assertEquals('YYYYHIST00030001',$this->_politique->saveThesaurus()->getIdThesaurus());
+
+		$this->assertEquals('YYYYHIST00030001',
+												Class_Catalogue::saveThesaurus($this->_politique)
+												->getIdThesaurus());
 	}
 
 
@@ -920,7 +938,9 @@ class CatalogueAddOrCreateTest extends CatalogueParentTest {
 			->with(200)
 			->answers(Class_CodifThesaurus::newInstanceWithId(200)->setIdThesaurus('YYYYPOLI001'));
 		$this->_politique->setParentId($this->_jeux->getId());
-		$this->assertEquals('YYYY00010005',$this->_politique->saveThesaurus()->getIdThesaurus());
+		$this->assertEquals('YYYY00010005',
+												Class_Catalogue::saveThesaurus($this->_politique)
+												->getIdThesaurus());
 	}
 
 
@@ -946,7 +966,9 @@ class CatalogueAddOrCreateTest extends CatalogueParentTest {
 			->with(200)
 			->answers(Class_CodifThesaurus::newInstanceWithId(200)->setIdThesaurus('YYYYPOLI0005'));
 		$this->_politique->setParentId(0);
-		$this->assertEquals('YYYY0001',$this->_politique->saveThesaurus()->getIdThesaurus());
+		$this->assertEquals('YYYY0001',
+												Class_Catalogue::saveThesaurus($this->_politique)
+												->getIdThesaurus());
 	}
 }
 
@@ -973,7 +995,7 @@ class CatalogueGetAllNoticesIdsForDomaineTest extends Storm_Test_ModelTestCase {
 	public function queryWithNb5Page10() {
 		$this->mock_sql
 			->whenCalled('fetchAll')
-			->with('select id_notice from notices where MATCH(facettes) AGAINST(\' +(T1)\' IN BOOLEAN MODE) order by alpha_titre  limit 50,5')
+			->with('select id_notice from notices Where MATCH(facettes) AGAINST(\' +(T1)\' IN BOOLEAN MODE) order by alpha_titre  limit 50,5')
 			->answers([ ['id_notice' => 23, 'titre' => 'POTTER'],
 									['id_notice' => 45, 'titre' => 'POTTER2'],
 									])
@@ -986,10 +1008,30 @@ class CatalogueGetAllNoticesIdsForDomaineTest extends Storm_Test_ModelTestCase {
 	public function queryWithNb1Page2() {
 		$this->mock_sql
 			->whenCalled('fetchAll')
-			->with('select id_notice from notices where MATCH(facettes) AGAINST(\' +(T1)\' IN BOOLEAN MODE) order by alpha_titre  limit 2,1')
+			->with('select id_notice from notices Where MATCH(facettes) AGAINST(\' +(T1)\' IN BOOLEAN MODE) order by alpha_titre  limit 2,1')
 			->answers([])
 			->beStrict();
 		$this->assertEquals([], $this->_catalogue->getAllNoticeIdsForDomaine(1, 2));
 	}
 }
-?>
\ No newline at end of file
+
+
+
+class CatalogueGetRequetesWithCatalogueTest extends ModelTestCase {
+	protected $_catalogue;
+
+	public function setUp() {
+		parent::setUp();
+		$this->fixture('Class_Catalogue',
+									 ['id' => 5,
+										'libelle' => 'My catalogue']);
+
+		$this->_requetes = Class_Catalogue::getRequetes(['id_catalogue' => 5]);
+	}
+
+
+	/** @test */
+	public function requetesShouldBeEmpty() {
+		$this->assertEquals([], $this->_requetes);
+	}
+}
\ No newline at end of file
diff --git a/tests/library/Class/DynamicUserGroupTest.php b/tests/library/Class/DynamicUserGroupTest.php
index 1410554830dfe75948418581e97001c0a9baffd6..bc8a49f0be022a082739d10511e607051f775359 100644
--- a/tests/library/Class/DynamicUserGroupTest.php
+++ b/tests/library/Class/DynamicUserGroupTest.php
@@ -16,70 +16,151 @@
  *
  * 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 DynamicUserGroupAbonneSIGBTest extends Storm_Test_ModelTestCase {
+abstract class DynamicUserGroupTestCase extends Storm_Test_ModelTestCase {
 	public function setUp() {
 		parent::setUp();
+		Storm_Model_Loader::defaultToVolatile();
+
+		$this->_annecy = $this->fixture('Class_Bib',
+																		['id' => 5, 'libelle' => 'Annecy']);
+
+		$this->_marseille = $this->fixture('Class_Bib',
+																			 ['id' => 6, 'libelle' => 'Marseille']);
+
+		$this->_baptiste = $this->fixture('Class_Users',
+																			['id' => 3,
+																			 'prenom' => 'Baptiste',
+																			 'login' => 'B',
+																			 'password' => 'B']);
+
+		$this->_xavier = $this->fixture('Class_Users',
+																		['id' => 8,
+																		 'prenom' => 'Xavier',
+																		 'login' => 'X',
+																		 'password' => 'X']);
+	}
+
+
+	public function tearDown() {
+		Storm_Model_Loader::defaultToDb();
+		parent::tearDown();
+	}
+}
+
 
-		Storm_Test_ObjectWrapper::onLoaderOfModel('Class_Users')
-			->whenCalled('findAllBy')
-			->with(['role_level' => 2,
-							'limit' => 50])
-			->answers([$this->_baptiste = Class_Users::newInstanceWithId(3)
-								 ->setPrenom('Baptiste')
-								 ->setUserGroups([])
-								 ->beAbonneSIGB(),
 
-								 $this->_xavier = Class_Users::newInstanceWithId(4)
-								 ->setPrenom('Xavier')
-								 ->setUserGroups([$this->_group_multimedia = Class_UserGroup::newInstanceWithId(9)
-																	                           ->setLibelle('Multimédia')])
-								 ->beAbonneSIGB()]);
+class DynamicUserGroupAbonneSIGBTest extends DynamicUserGroupTestCase {
+	public function setUp() {
+		parent::setUp();
 
+		$this->_baptiste->setBib($this->_annecy)
+										->setIdabon('B007')
+										->beAbonneSIGB()
+										->assertSave();
 
-		$this->_abonnnes_sigb = Class_UserGroup::newInstanceWithId(3)
-			->beDynamic()
-			->setRoleLevel(ZendAfi_Acl_AdminControllerRoles::ABONNE_SIGB);
+		$this->_multimedia = $this->fixture('Class_UserGroup',
+																				['id' => 9, 'libelle' => 'Multimédia']);
 
-		Storm_Test_ObjectWrapper::onLoaderOfModel('Class_UserGroup')
-			->whenCalled('findAllBy')
-			->with(['role_level' => ZendAfi_Acl_AdminControllerRoles::ABONNE_SIGB,
-							'group_type' => Class_UserGroup::TYPE_DYNAMIC])
-			->answers([$this->_abonnnes_sigb]);
+		$this->_xavier->setBib($this->_annecy)
+									->setIdabon('X008')
+									->addUserGroup($this->_multimedia)
+									->beAbonneSIGB()
+									->assertSave();
 
+		$this->_abonnes = $this->fixture('Class_UserGroup',
+																		 ['id' => 3,
+																			'role_level' => ZendAfi_Acl_AdminControllerRoles::ABONNE_SIGB]);
+		$this->_abonnes->beDynamic()->save();
 	}
 
-	
+
 	/** @test */
 	public function groupShouldBeDynamic() {
-		$this->assertTrue($this->_abonnnes_sigb->isDynamic());
+		$this->assertTrue($this->_abonnes->isDynamic());
 	}
 
-	
+
 	/** @test */
 	public function usersShouldContainsBaptisteAndXavier() {
-		$this->assertEquals(['Baptiste', 'Xavier'],
-												array_map(function($user) { return $user->getPrenom(); },
-																	$this->_abonnnes_sigb->getUsers()));
+		$this->assertEquals([$this->_baptiste, $this->_xavier],
+												$this->_abonnes->getUsers());
 	}
 
-	
+
 	/** @test */
-	public function baptisteShouldBeAbonneSIGB() {
-		$this->assertEquals(ZendAfi_Acl_AdminControllerRoles::ABONNE_SIGB, 
-												$this->_baptiste->getRoleLevel());
+	public function baptisteGroupsShouldBeOnlyAbonneSIGB() {
+		$this->assertEquals([$this->_abonnes], $this->_baptiste->getUserGroups());
 	}
 
 
 	/** @test */
-	public function baptisteGroupsShouldBeOnlyAbonneSIGB() {
-		$this->assertEquals([$this->_abonnnes_sigb], $this->_baptiste->getUserGroups());
+	public function xavierGroupsShouldContainsDynamicAbonneSIGB() {
+		$this->assertContains($this->_abonnes, $this->_xavier->getUserGroups());
+	}
+
+
+	/** @test */
+	public function xavierGroupsShouldContainsManualMultimedia() {
+		$this->assertContains($this->_multimedia, $this->_xavier->getUserGroups());
 	}
 }
 
 
-?>
\ No newline at end of file
+
+class DynamicUserGroupModoBibTest extends DynamicUserGroupTestCase {
+	public function setUp() {
+		parent::setUp();
+
+		$this->_marseillais = $this
+			->fixture('Class_UserGroup',
+								['id' => 3,
+								 'libelle' => 'Rédacteurs marseillais',
+								 'role_level' => ZendAfi_Acl_AdminControllerRoles::MODO_BIB,
+								 'library' => $this->_marseille]);
+		$this->_marseillais->beDynamic()->assertSave();
+
+		$this->_xavier->setBib($this->_marseille)
+									->beModoBib()
+									->assertSave();
+
+		$this->_anneciens = $this
+			->fixture('Class_UserGroup',
+								['id' => 12,
+								 'libelle' => 'Rédacteurs annéciens',
+								 'role_level' => ZendAfi_Acl_AdminControllerRoles::MODO_BIB,
+								 'library' => $this->_annecy]);
+		$this->_anneciens->beDynamic()->assertSave();
+
+		$this->_baptiste->setBib($this->_annecy)
+										->beModoBib()
+										->assertSave();
+	}
+
+
+	/** @test */
+	public function marseillaisShouldHaveOneUser() {
+		$this->assertEquals([$this->_xavier], $this->_marseillais->getUsers());
+	}
+
+
+	/** @test */
+	public function xavierShouldHaveMarseillaisGroup() {
+		$this->assertContains($this->_marseillais, $this->_xavier->getUserGroups());
+	}
+
+
+	/** @test */
+	public function anneciensShouldHaveOneUser() {
+		$this->assertEquals([$this->_baptiste], $this->_anneciens->getUsers());
+	}
+
+
+	/** @test */
+	public function baptisteShouldHaveAnneciensGroup() {
+		$this->assertContains($this->_anneciens, $this->_baptiste->getUserGroups());
+	}
+}
\ No newline at end of file
diff --git a/tests/library/Class/FRBR/LinkTest.php b/tests/library/Class/FRBR/LinkTest.php
index d040a7de268d4c60e4990b704dde1b41ac89b97d..e9d5b7599557072fa3d19c39a5b28ddd3508ed78 100644
--- a/tests/library/Class/FRBR/LinkTest.php
+++ b/tests/library/Class/FRBR/LinkTest.php
@@ -16,35 +16,30 @@
  *
  * 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 FRBR_LinkWrongAttributesTest extends Storm_Test_ModelTestCase {
 	public function setUp() {
 		parent::setUp();
-		
-		$this->link = Class_FRBR_Link::newInstanceWithId(34)
-			->setSource('zork')
-			->setTarget('http://localhost/notice/view/clef/TINTIN')
-			->setSourceType(Class_FRBR_Link::TYPE_NOTICE)
-			->setTargetType('inexistant')
-			->setLinkType(Class_FRBR_LinkType::newInstanceWithId(4)
-										->setLibelle('synonyme')
-										->setFromSource('est un synonyme de')
-										->setFromTarget('est un synonyme de'));
+
+		$this->link = $this->fixture('Class_FRBR_Link',
+																 ['id' => 34,
+																	'source' => 'http://zork.org/id/12',
+																	'target' => 'http://localhost/notice/view/clef/TINTIN']);
 
 	}
 
 
 	/** @test */
 	public function getSourceNoticeShouldReturnNullWithInvalidScheme() {
-		$this->assertEquals(null, $this->link->getSourceNotice());
+		$this->assertEquals(null, $this->link->getSourceEntity());
 	}
 
 
 	/** @test */
 	public function getTargetNoticeShouldReturnNullWithInvalidType() {
-		$this->assertEquals(null, $this->link->getTargetNotice());
+		$this->assertEquals(null, $this->link->getTargetEntity());
 	}
 
 
diff --git a/tests/library/Class/Import/Typo3Fixture.php b/tests/library/Class/Import/Typo3Fixture.php
index cae82a1de63aa264c1799e2aa90d8d4b2c9353f8..7c2e00d7bbda184c800d844dbf99f1243c1a69e1 100644
--- a/tests/library/Class/Import/Typo3Fixture.php
+++ b/tests/library/Class/Import/Typo3Fixture.php
@@ -22,33 +22,46 @@
 class MockTypo3DB {
 
 	public function findAllUsers() {
-		return [ ['uid' => 20,
-							'username' => 'toto',
-							'realName' => 'toto',
-							'email' => 'toto@null.com',
-							'admin' => 1
-			],
+		return [['uid' => 20,
+						 'username' => 'toto',
+						 'realName' => 'toto',
+						 'email' => 'toto@null.com',
+						 'admin' => 1],
+
 						['uid' => 21,
 						 'username' => 'jean',
 						 'realName' => 'Jean Jean',
 						 'email' => 'jean-jeantoto@null.com',
-						 'admin' => 0
-						],
+						 'admin' => 0],
+
 						['uid' => 43,
 						 'username' => 'sserge',
 						 'realName' => 'Serge Serge',
 						 'email' => 'serge@null.com',
-						 'admin' => 0
-						],
+						 'admin' => 0],
+
 						['uid' => 123456789,
 						 'username' => null,
 						 'realName' => 'Tom',
 						 'email' => null,
-						 'admin' => 0
-						]
+						 'admin' => 0]];
+	}
+
+	public function findAllNewsCatSince($update_date) {
+		return [ ['title' => 'Jeux',
+						 'uid' => 777,
+						 'parent_category' => 99
+			],
+				['title' => 'd\'hiver',
+						 'uid' => 81,
+						 'parent_category' => 99
+						],
+
 		];
+
 	}
 
+
 	public function findAllNewsCat() {
 		return [
 						['title' => 'My category',
@@ -82,10 +95,29 @@ class MockTypo3DB {
 						['title' => 'NOS TOPS 5',
 						 'uid' => 43,
 						 'parent_category' => 0
-						]
-		];
+						],
+						['title' => 'Action Culturelle',
+						 'uid' => 867,
+						 'parent_category' => 0
+						],
+						['title' => 'Nos dossiers documentaires',
+						 'uid' => 36,
+						 'parent_category' => 0
+						]];
 	}
 
+	public function findAllEventCatSince($update_date) {
+		return [
+						['uid' => 30,
+						 'title' => 'New Category',
+						 'parent_category' => 0
+						],
+];
+
+
+	}
+
+
 	public function findAllEventCat() {
 		return [
 						['uid' => 6,
@@ -103,6 +135,7 @@ class MockTypo3DB {
 		];
 	}
 
+
 	public function findAllSites() {
 		return [
 						['crdate' => '1412781027',
@@ -135,6 +168,20 @@ class MockTypo3DB {
 	}
 
 
+	public function findAllArticlesSince($last_import_date) {
+		$articles = $this->findAllArticles();
+		$articles[5]['uid']=666;
+		$articles[5]['short']='Crash';
+		$articles[5]['title']='Crash Test';
+		$articles[1]['deleted']=1;
+		$articles[0]['title']='Tous à bord du bateau pirate';
+		$articles[0]['category']=777;
+		$articles[0]['bodytext']="MODIFY".$articles[0]['bodytext'];
+		$articles[4]['bodytext']="MODIFIED".$articles[4]['bodytext'];
+		return [$articles[0],$articles[1], $articles[5], $articles[4]];
+	}
+
+
 	public function findAllArticles() {
 		return [ [
 							'uid' => 14474,
@@ -142,7 +189,7 @@ class MockTypo3DB {
 							'cruser_id' => 43,
 							'starttime' => 1415110980,
 							'endtime' => 1422973380,
-							'hidden' => 0,
+							'deleted' => 0,'hidden' => 0,
 							'short' => 'A bord.',
 							'category' => 10,
 							'image' =>  'bateau.jpg',
@@ -176,7 +223,7 @@ class MockTypo3DB {
 						[ 'uid' => 14476,
 						 'starttime' => 0,
 						 'endtime' => 0,
-						 'hidden' => 0,
+						 'deleted' => 0,'hidden' => 0,
 						 'crdate' => 1359562198,
 						 'cruser_id' => 22,
 						 'short' => null,
@@ -199,10 +246,11 @@ La collection<span style="text-decoration: none"><span style="font-style: normal
  La collection, dans son ensemble, s\'adresse à un large public, désireux de mieux comprendre cet &quot;art de bâtir&quot; qui l\'environne et façonne de plus en plus son rapport à l\'espace, tant public que privé.
 '],
 						[ 'uid' => 14477,
-						 'starttime' => 0,
-						 'endtime' => 0,
-						 'hidden' => 0,
-						 'crdate' => 1359562198,
+						 'starttime' => 1419844200,
+						 'endtime' => 1420017000,
+						 'deleted' => 0,'hidden' => 0,
+						 'datetime' =>1419933840,
+						 'crdate' => 1419933840,
 						 'cruser_id' => 22,
 						 'short' => null,
 						 'category' => 1,
@@ -215,8 +263,8 @@ La collection<span style="text-decoration: none"><span style="font-style: normal
 						[ 'uid' => 14478,
 						 'starttime' => 0,
 						 'endtime' => 0,
-						 'hidden' => 0,
-						 'image' => '',
+						 'deleted' => 0,'hidden' => 0,
+						 'image' => 'onyx.png',
 						 'crdate' => 1359562208,
 						 'cruser_id' => 22,
 						 'short' => null,
@@ -227,8 +275,9 @@ La collection<span style="text-decoration: none"><span style="font-style: normal
 					[ 'uid' => 14488,
 						 'starttime' => 0,
 						 'endtime' => 0,
-						 'hidden' => 0,
+						 'deleted' => 0,'hidden' => 0,
 						 'crdate' => 1357562208,
+						 'datetime' => 1395409080,
 						 'cruser_id' => 22,
 						 'image' => '',
 						 'short' => null,
@@ -239,7 +288,7 @@ La collection<span style="text-decoration: none"><span style="font-style: normal
 						[ 'uid' => 14888,
 						 'starttime' => 0,
 						 'endtime' => 0,
-						 'hidden' => 0,
+						 'deleted' => 0,'hidden' => 0,
 						 'crdate' => 1357562208,
 						 'cruser_id' => 22,
 						 'short' => null,
@@ -251,7 +300,7 @@ La collection<span style="text-decoration: none"><span style="font-style: normal
 						['uid' => 84888,
 						 'starttime' => 0,
 						 'endtime' => 0,
-						 'hidden' => 0,
+						 'deleted' => 0,'hidden' => 0,
 						 'image' => '',
 						 'crdate' => 1357592208,
 						 'cruser_id' => 22,
@@ -265,7 +314,7 @@ La collection<span style="text-decoration: none"><span style="font-style: normal
 						['uid' => 123,
 						 'starttime' => 0,
 						 'endtime' => 0,
-						 'hidden' => 0,
+						 'deleted' => 0,'hidden' => 0,
 						 'crdate' => 1357592258,
 						 'cruser_id' => 22,
 						 'short' => null,
@@ -274,10 +323,11 @@ La collection<span style="text-decoration: none"><span style="font-style: normal
 						 'tx_danpextendnews_tags' => '',
 						 'title' => 'Deezer ?',
 						 'bodytext' => '<link http://www.deezer.com/fr/nneka.html>Nneka</link>'],
-						['uid' => 13973,
+
+						['uid' => 139735,
 						 'starttime' => 0,
 						 'endtime' => 0,
-						 'hidden' => 0,
+						 'deleted' => 0,'hidden' => 0,
 						 'crdate' => 1357592258,
 						 'cruser_id' => 22,
 						 'short' => null,
@@ -286,11 +336,32 @@ La collection<span style="text-decoration: none"><span style="font-style: normal
 						 'tx_danpextendnews_tags' => '',
 						 'title' => 'L\'artothèque',
 						 'bodytext' => '<p style="color: rgb(0, 0, 0); font-family: Verdana, sans-serif; font-size: 12px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px;">L\'artothèque de la Médiathèque Intercommunale propose au public sur les sites du réseau, une collection de 1 825 œuvres d\'art contemporain constituant un large panorama de la création plastique et photographique des quarante dernières années.</p>
-<p style="color: rgb(0, 0, 0); font-family: Verdana, sans-serif; font-size: 12px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px;"><link http://koha.mediathequeouestprovence.fr/cgi-bin/koha/opac-search.pl?q=test&idx=kw&idx=kw&idx=kw&limit=mc-itemtype%3AOART&sort_by=relevance&do=Rechercher _blank external-link-new-window>Un catalogue iconographique</link>&nbsp;est spécialement dédié à cette collection; il est également consultable dans tous les pôles&nbsp;<span style="font-style: italic;">Art</span>,<span style="font-style: italic;">&nbsp;</span>Musique, Cinéma&nbsp;du réseau.<br /><br />Outre le prêt d\'œuvres d\'art, l\'artothèque organise aussi des expositions, réalise des animations, accueille des groupes, édite des catalogues..., favorisant ainsi la diffusion et la promotion de l\'art et des artistes d\'aujourd\'hui.</p>']
+<p style="color: rgb(0, 0, 0); font-family: Verdana, sans-serif; font-size: 12px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px;"><link http://koha.mediathequeouestprovence.fr/cgi-bin/koha/opac-search.pl?q=test&idx=kw&idx=kw&idx=kw&limit=mc-itemtype%3AOART&sort_by=relevance&do=Rechercher _blank external-link-new-window>Un catalogue iconographique</link>&nbsp;est spécialement dédié à cette collection; il est également consultable dans tous les pôles&nbsp;<span style="font-style: italic;">Art</span>,<span style="font-style: italic;">&nbsp;</span>Musique, Cinéma&nbsp;du réseau.<br /><br />Outre le prêt d\'œuvres d\'art, l\'artothèque organise aussi des expositions, réalise des animations, accueille des groupes, édite des catalogues..., favorisant ainsi la diffusion et la promotion de l\'art et des artistes d\'aujourd\'hui.</p>'],
+
+
+						['uid' => 14418,
+						 'starttime' => 1412632800,
+						 'endtime' => 1412683132,
+						 'datetime' => 1412805600,
+						 'hidden' => 0,
+						 'crdate' => 1412683132,
+						 'cruser_id' => 22,
+						 'short' => null,
+	 					 'image' => '',
+						 'category' => 1,
+						 'tx_danpextendnews_tags' => '',
+						 'title' => 'La fete de la science',
+						 'bodytext' => 'on y découvre plein de choses']
 ];
 	}
 
 	public function findAllForeignUidForNewsCat($uid) {
+		if (in_array($uid , [14488, 14418]))
+			return [[ 'uid_foreign' => 867 ]];
+
+		if ($uid==14477)
+			return [[ 'uid_foreign' => 36 ]];
+
 		return [
 						['uid_foreign' => 81],
 						['uid_foreign' => 43],
@@ -372,11 +443,30 @@ La collection<span style="text-decoration: none"><span style="font-style: normal
 
 		];
 	}
+	public function findAllContentsSince($update_date) {
+			$contents=$this->findAllContents();
+			$contents[0]['bodytext']='Modification de contenu';
+			$contents[0]['header']='A haute voix... modif';
 
-	public function findAllContents() {
+			$contents[]=[
+							 'uid' => 188,
+						 'crdate' => 1298390490,
+						 'starttime' => 1298389541,
+						 'endtime' => 0,
+						 'hidden' => 0,
+						 'cruser_id' => 1,
+						 'image' => '',
+						 'title' => '',
+							 'bodytext' => "<ul><li>Internet est consultable dans tous les pôles thématiques du réseau : <link typo3/fileadmin/fichiers/fichiers_joints/reglement_interieur/REGLEMENT-INTERIEUR-2013-annexe3.pdf _blank download>voir la charte d'utilisation.<br /><br /></link></li></ul>",
+							 'header'=> "Internet & Wi-Fi"
+			];
+			return $contents;
+
+	}
+	public function findAllContents($page_id=false) {
 		return [
 						[
-						 'uid' => 32,
+						 'uid' => 13973,
 						 'crdate' => 1298390490,
 						 'starttime' => 1298389541,
 						 'endtime' => 0,
@@ -391,6 +481,13 @@ La collection<span style="text-decoration: none"><span style="font-style: normal
 	}
 
 
+	public function findAllPagesWithPid($pid) {
+
+		if ($pid==309){
+			return [ [ 'uid' => 13973,
+								'title' => 'BLOGS AND SITES' ]];}
+		return [];
+	}
 	public function findPageTitle($uid) {
 		if ($uid == 49)
 			return '2014';
diff --git a/tests/library/Class/Import/Typo3Test.php b/tests/library/Class/Import/Typo3Test.php
index 3de28d20fd73828d737fa74d2c14c47831a06244..5eb12398020729f53a9b9597921ab434ec368feb 100644
--- a/tests/library/Class/Import/Typo3Test.php
+++ b/tests/library/Class/Import/Typo3Test.php
@@ -30,31 +30,47 @@ abstract class Import_Typo3TestCase extends ModelTestCase {
 	public function setUp() {
 		parent::setUp();
 		Class_Import_Typo3_Logs::getInstance()->cleans();
+		Storm_Model_Loader::defaultToVolatile();
+		Class_CustomField_Model::registerAll([
+																					new Class_CustomField_ModelConfiguration_Article(),
+ 																					new Class_CustomField_ModelConfiguration_Sitotheque(),
+ 																					new Class_CustomField_ModelConfiguration_SitothequeCategorie(),
+ 																					new Class_CustomField_ModelConfiguration_Catalogue(),
+																					new Class_CustomField_ModelConfiguration_ArticleCategorie()
+
+																					])
+			;
 
-    Class_Article::beVolatile();
-    Class_Users::beVolatile();
-    Class_ArticleCategorie::beVolatile();
-    Class_Catalogue::beVolatile();
-    Class_CodifThesaurus::beVolatile();
-    Class_Sitotheque::beVolatile();
-    Class_SitothequeCategorie::beVolatile();
 		$this->mock_sql = Storm_Test_ObjectWrapper::mock();
 		$this->old_sql = Zend_Registry::get('sql');
 		Zend_Registry::set('sql', $this->mock_sql);
 
 		$this->mock_sql->whenCalled('execute')
 									 ->answers(true);
+
+		$this->fixture('Class_CustomField_Meta',
+									 ['id' => 1,
+										'label' => 'uid_typo3',
+										'field_type' => Class_CustomField_Meta::TEXT_INPUT,
+										'options_list' => '']);
+
+
+		$this->fixture('Class_CustomField',
+									 ['id' => 1,
+										'meta_id' => 1,
+										'priority' => 1,
+										'model' => 'Article']);
+
 		$this->migration = new Class_Import_Typo3();
 		$this->migration->setTypo3DB(new MockTypo3DB());
-
 	}
 
 
 	public function tearDown() {
 		Zend_Registry::set('sql', $this->old_sql);
+		Storm_Model_Loader::defaultToDb();
 		parent::tearDown();
 	}
-
 }
 
 
@@ -62,25 +78,63 @@ abstract class Import_Typo3TestCase extends ModelTestCase {
 class Import_Typo3UsersTest extends Import_Typo3TestCase {
 	public function setUp() {
 		parent::setUp();
-		$this->migration->run('users');
+		$this->fixture('Class_Users',
+									 ['id' => 12,
+										'login' => 'toto',
+										'password' => 'XXzsinaded',
+										'nom' => 'Un nom']);
+
+		$this->onLoaderOfModel('Class_Users')
+				 ->whenCalled('deleteBy')
+				 ->with(['where' => 'ROLE_LEVEL < 6 AND ROLE_LEVEL <> 2'])
+				 ->answers(null);
+
+		$this->report = $this->migration->run('users');
 	}
 
 
 	/** @test */
-	public function userShouldBeInsertedInDB() {
-		$this->assertEquals('toto', Class_Users::findFirstBy(['login' => 'toto'])->getLogin());
+	public function shouldHave3Users() {
+		$this->assertEquals(3, Class_Users::count(), $this->report);
 	}
 
 
 	/** @test */
-	public function totoShouldBeAdmin() {
-		$this->assertTrue(Class_Users::findFirstBy(['login' => 'toto'])->isAdmin());
+	public function totoShouldBeImported() {
+		$this->_userShouldBeImported(Class_Users::findFirstBy(['login' => 'toto']),
+																 ZendAfi_Acl_AdminControllerRoles::ADMIN_PORTAIL,
+																 'toto@null.com', 'toto');
 	}
 
 
 	/** @test */
-	public function jeanShouldBeModoPortail() {
-		$this->assertEquals(ZendAfi_Acl_AdminControllerRoles::MODO_PORTAIL, Class_Users::findFirstBy(['login' => 'jean'])->getRoleLevel());
+	public function totoShouldHaveBeenUpdated() {
+		$this->assertEquals(12, Class_Users::findFirstBy(['login' => 'toto'])->getId());
+	}
+
+
+	/** @test */
+	public function jeanShouldBeImported() {
+		$this->_userShouldBeImported(Class_Users::findFirstBy(['login' => 'jean']),
+																 ZendAfi_Acl_AdminControllerRoles::MODO_PORTAIL,
+																 'jean-jeantoto@null.com', 'Jean Jean');
+	}
+
+
+	/** @test */
+	public function sergeShouldBeImported() {
+		$this->_userShouldBeImported(Class_Users::findFirstBy(['login' => 'sserge']),
+																 ZendAfi_Acl_AdminControllerRoles::MODO_PORTAIL,
+																 'serge@null.com', 'Serge Serge');
+	}
+
+
+	protected function _userShouldBeImported($user, $role, $mail, $name) {
+		$this->assertNotNull($user);
+		$this->assertEquals($role, $user->getRoleLevel());
+		$this->assertEquals('achanger', $user->getPassword());
+		$this->assertEquals($mail, $user->getMail());
+		$this->assertEquals($name, $user->getNom());
 	}
 }
 
@@ -106,6 +160,16 @@ class Import_Typo3CategoryTest extends Import_Typo3TestCase {
 		$this->assertNotNull($parent);
 	}
 
+	/** @test */
+	public function sitoCategorieShouldHaveUidTypo3() {
+		$this->assertEquals('80',Class_SitothequeCategorie::findFirstBy(['libelle' => 'Musique & Cinéma'])->getCustomField('uid_typo3'));
+	}
+
+	/** @test */
+	public function categoryShouldHaveUidTypo3() {
+		$this->assertEquals(99,Class_ArticleCategorie::findFirstBy(['libelle' => 'Musique & Cinéma'])->getParent()->getCustomField('uid_typo3'));
+
+	}
 
 	/** @test */
 	public function categoryMusiqueCinemaShouldBeChildOfArt() {
@@ -217,7 +281,7 @@ class Import_Typo3ArticleTest extends Import_Typo3TestCase {
 
 	/** @test */
 	public function articleABordDuBateauPirateShouldBeInDomainAutres() {
-		$this->assertEquals(['Société & Civilisation', 'Divers', 'NOS TOPS 5', 'Autres'],
+		$this->assertEquals(['Divers', 'NOS TOPS 5', 'Autres'],
 												$this->bateau_pirate->getDomaineLibelles());
 	}
 
@@ -228,18 +292,18 @@ class Import_Typo3ArticleTest extends Import_Typo3TestCase {
 												$this->bateau_pirate->getCategorieLibelle());
 	}
 
- /** @test */
+	/** @test */
 	public function articleABordDuBateauPirateShouldContainsImageWithBateau() {
 		$this->assertContains('<img src="'.Class_Import_Typo3::BOKEH_IMG_URL.'pics/bateau.jpg" alt=""/>',$this->bateau_pirate->getContenu());
 	}
 
- /** @test */
+	/** @test */
 	public function articleABordDuBateauPirateShouldContainsImageWithBateauInSummary() {
 		$this->assertContains('<img src="'.Class_Import_Typo3::BOKEH_IMG_URL.'pics/bateau.jpg" alt=""/>',$this->bateau_pirate->getDescription());
 	}
 
 
- /** @test */
+	/** @test */
 	public function articleABordDuBateauPirateShouldConvertImageWithUploads() {
 		$this->assertContains('<img style="padding-right: 10px; padding-bottom: 10px; float: left;" src="'.Class_Import_Typo3::BOKEH_IMG_URL.'RTEmagicC_5_37.jpg.jpg" height="76" width="133" alt="" />',$this->bateau_pirate->getContenu());
 	}
@@ -256,12 +320,12 @@ class Import_Typo3ArticleTest extends Import_Typo3TestCase {
 	}
 
 
- /** @test */
+	/** @test */
 	public function articleABordDuBateauPirateShouldConvertFileWithAttachmentsWithHttpLink() {
 		$this->assertContains('<a href="'.Class_Import_Typo3::BOKEH_ATTACHMENT_URL.'fichiers_joints/formulaires/memento_communique_sans_danger.pdf">',$this->bateau_pirate->getContenu());
 	}
 
- /** @test */
+	/** @test */
 	public function articleABordDuBateauPirateShouldConvertUserUploadWithFileAdmin() {
 		$this->assertContains('<a href="'.Class_Import_Typo3::BOKEH_FILEADMIN_URL.'jeunesse/formulaire_inscription_atelier_internet_espace_J.pdf">',$this->bateau_pirate->getContenu());
 	}
@@ -287,6 +351,12 @@ class Import_Typo3ArticleTest extends Import_Typo3TestCase {
 	}
 
 
+	/** @test */
+	public function discoverTowerEventSummaryShouldContainsOnyxImage() {
+		$this->assertContains('<img src="http://www.mediathequeouestprovence.fr/uploads/pics/onyx.png" alt=""/>', Class_Article::findFirstBy(['titre' => 'Onyx tower event'])->getDescription());
+	}
+
+
 	/** @test */
 	public function bokehWikiContentShouldContainsAnchorWithExpectedAttributs() {
 		$this->assertEquals('<p>Welcome to <a href="http://wiki.bokeh-library-portal.org">Wiki Bokeh</a></p>', Class_Article::findFirstBy(['titre' => 'Wiki Bokeh'])->getContenu());
@@ -336,6 +406,63 @@ class Import_Typo3ArticleTest extends Import_Typo3TestCase {
 	public function lartothequeArticleShouldBeTranformed() {
 		$this->assertContains('<a href="http://koha.mediathequeouestprovence.fr/cgi-bin/koha/opac-search.pl?q=test&idx=kw&idx=kw&idx=kw&limit=mc-itemtype%3AOART&sort_by=relevance&do=Rechercher">Un catalogue iconographique</a>', Class_Article::findFirstBy(['titre' => 'L\'artothèque'])->getContenu());
 	}
+
+
+  /** @test */
+	public function welcomeShouldHaveStartEventDate() {
+		$this->assertEquals('2014-03-21 14:38',Class_Article::findFirstBy(['titre' => 'Wiki Bokeh'])->getEventsDebut());
+	}
+
+  /** @test */
+	public function welcomeShouldHaveEndEventDate() {
+		$this->assertEquals('2014-03-21 14:38',Class_Article::findFirstBy(['titre' => 'Wiki Bokeh'])->getEventsFin());
+	}
+
+  /** @test */
+	public function architectureShouldHaveStartEventDate() {
+		$this->assertEquals('2014-12-30 11:04',Class_Article::findFirstBy(['titre' => 'Architecture summary'])->getEventsDebut());
+	}
+
+  /** @test */
+	public function architectureShouldHaveEndEventDate() {
+		$this->assertEquals('2014-12-30 11:04',Class_Article::findFirstBy(['titre' => 'Architecture summary'])->getEventsFin());
+	}
+
+
+  /** @test */
+	public function architectureShouldHaveDateCreation() {
+		$this->assertEquals('2014-12-30 11:04:00',
+												Class_Article::findFirstBy(['titre' => 'Architecture summary'])->getDateCreation());
+	}
+
+  /** @test */
+	public function architectureShouldHaveDateDebut() {
+		$this->assertEquals('2014-12-29 10:10',
+												Class_Article::findFirstBy(['titre' => 'Architecture summary'])->getDebut());
+	}
+
+
+  /** @test */
+	public function architectureShouldHaveDateFin() {
+		$this->assertEquals('2014-12-31 10:10',
+												Class_Article::findFirstBy(['titre' => 'Architecture summary'])->getFin());
+	}
+
+
+
+  /** @test */
+	public function feteScienceShouldHaveDateFin() {
+		$this->assertEquals('2014-10-09 00:00',
+												Class_Article::findFirstBy(['titre' => 'La fete de la science'])->getEventsFin());
+	}
+
+
+  /** @test */
+	public function architectureCategoryShouldBeNosDossiersDocumentaires() {
+		$this->assertEquals('Nos dossiers documentaires',Class_Article::findFirstBy(['titre' => 'Architecture summary'])->getCategorie()->getLibelle());
+	}
+
+
 }
 
 
@@ -380,7 +507,7 @@ class Import_Typo3CalendarTest extends Import_Typo3TestCase {
 
 	/** @test */
 	public function decouvrirBridgeShouldBeInDomainsAtelierPont() {
-	$this->assertEquals(['Atelier', 'Pont'],
+		$this->assertEquals(['Pont'],
 												$this->event_bridge->getDomaineLibelles());
 	}
 
@@ -391,6 +518,12 @@ class Import_Typo3CalendarTest extends Import_Typo3TestCase {
 	}
 
 
+	/** @test */
+	public function discoverMuseumEventsDebutShouldBe() {
+		$this->assertEquals('2007-11-02 18:30', Class_Article::findFirstBy(['titre' => 'Discover Museum'])->getEventsDebut());
+	}
+
+
 	/** @test */
 	public function discoverSpaceContentShouldContainsExpectedTitleWithHTML() {
 		$this->assertEquals('<p>Discover Space</p>', Class_Article::findFirstBy(['titre' => 'Discover Space'])->getContenu());
@@ -445,7 +578,7 @@ class Import_Typo3SitothequeTest extends Import_Typo3TestCase {
 
 	/** @test */
 	public function museoParcShouldBeInDomainAutres() {
-		$this->assertEquals(['My category', 'Divers', 'NOS TOPS 5', 'Autres'],
+		$this->assertEquals(['Divers', 'NOS TOPS 5', 'Autres'],
 												$this->sito->getDomaineLibelles());
 	}
 
@@ -456,15 +589,202 @@ class Import_Typo3SitothequeTest extends Import_Typo3TestCase {
 												$this->sito->getCategorie()->getPath());
 	}
 
-/** @test */
+  /** @test */
 	public function ouestCanadienShouldContainsUrlBokehSearch() {
 		$this->assertEquals('/miop-test.net/recherche/viewnotice/clef/OUEST_CANADIEN', Class_Sitotheque::findFirstBy(['titre' => 'L\'ouest canadien'])->getUrl());
 
 	}
+
+
+  /** @test */
+	public function ouestCanadienShouldStoreUidTypo3() {
+		$this->assertEquals(14479, Class_Sitotheque::findFirstBy(['titre' => 'L\'ouest canadien'])->getCustomField('uid_typo3'));
+
+	}
+
 }
 
+class Import_Typo3UpdateArticleTest extends Import_Typo3TestCase {
+	protected $article_voix;
+	public function setUp() {
+		parent::setUp();
+		$this->migration->import_categories();
+		$this->migration->importArticles();
+
+		$this->migration->importArticlesPages();
+		Class_Article::setTimeSource(new TimeSourceForTest('2015-02-03 10:00:00'));
+
+		$this->article_voix=Class_Article::findFirstBy(['titre' => 'A haute voix... le roman se fait entendre...']);
+		$this->migration->updateCategories(888);
+
+		Class_CustomField_Value::clearCache();
+		Class_ArticleCategorie::clearCache();
+
+
+		$this->migration->updateArticlePages(888);
+		$this->migration->updateArticles(888);
+	}
+
+	public function expectedUpdateArticle() {
+		$articles=MockTypo3DB::findAllArticles();
+		return [
+						[
+						 'title' => 'Tous à bord du bateau pirate',
+							['titre' =>'Tous à bord du bateau pirate',
+							 'description' => '<img src="http://www.mediathequeouestprovence.fr/uploads/pics/bateau.jpg" alt=""/><p>'.$articles[0]['short'].'</p>',
+							 'debut' => '2014-11-04 15:23',
+							 'fin' => '2015-02-03 15:23',
+							 'avis' => false,
+							 'tags' => 'pirate;bateau',
+							 'events_debut' => null,
+							 'events_fin' => null,
+							 'indexation' => 1,
+							 'cacher_titre' => 0,
+							 'date_maj' => '2015-02-03 10:00:00',
+							 'date_creation' => '1974-06-24 04:44:21',
+							 'status' => 3,
+							 'id_lieu' => 0,
+							 'domaine_ids' => '5;7;6',
+							 'id_origine' => 0,
+							 'all_day' => false,
+							 'langue' => 'fr',
+							 'id' => 1,
+							 'id_user' => 0,
+							 'id_article' => 1
+							]],
+
+						['title' => 'A bord du bateau pirate',
+							[]], //updated
+
+						['title' => 'Wiki Bokeh',
+						 ['contenu' => '<p>MODIFIEDWelcome to <a href="http://wiki.bokeh-library-portal.org">Wiki Bokeh</a></p>']], //updated
+
+						['title' => 'Architecture', //deleted
+						 []],
+
+						['title' => 'Internet & Wi-Fi',
+						 ['contenu' => "<ul><li>Internet est consultable dans tous les pôles thématiques du réseau : <a href=\"typo3/fileadmin/fichiers/fichiers_joints/reglement_interieur/REGLEMENT-INTERIEUR-2013-annexe3.pdf\">voir la charte d'utilisation.<br /><br /></a></li></ul>"]],
+						['title' => 'A haute voix... le roman se fait entendre...',
+						 [
+						 ]],
+						['title' => 'A haute voix... modif',
+						 ['contenu' =>'<p>Modification de contenu</p>',
+						 ]],
+						['title' => 'Crash Test',
+						 ['description' => '<p>Crash</p>'
+						 ]]
+
+
+
+		];
+	}
+
+	/**
+	 * @dataProvider expectedUpdateArticle
+	 * @test
+	 */
+	public function updateArticleShouldBeUpdated($title,$article) {
+		$article_class_found=Class_Article::findFirstBy(['titre' => $title]);
+		if (empty($article))
+			return $this->assertNull($article_class_found);
+
+		$article_found=$article_class_found->attributesToArray();
+		foreach ($article  as $key => $value) {
+			$this->assertEquals($value,$article_found[$key]);
+		}
+
+	}
+
+	/** @test */
+	public function categoryForBateauPirateShouldBeJeux() {
+		$article=Class_Article::findFirstBy(['titre' => 'Tous à bord du bateau pirate']);
+		$this->assertEquals(Class_ArticleCategorie::findFirstBy(['libelle' => 'Jeux'])->getId(),$article->getCategorie()->getId());
+	}
+
+  /** @test */
+	public function parentCategoryForJeuxShouldBeArt() {
+		Class_ArticleCategorie::clearCache();
+		$this->assertEquals("Art",Class_ArticleCategorie::findFirstBy(['libelle' => 'Jeux'])->getParent()->getLibelle());
+	}
+
+  /** @test */
+	public function categoryDiversShouldBeRenamedDhiver() {
+		$this->assertEquals("d'hiver",Class_ArticleCategorie::findFirstByCustomFieldValue(Class_Import_Typo3::UID_TYPO3_CF,81)->getLibelle());
+
+	}
+
+	/** @test */
+	public function articleShouldBeModified() {
+		$this->assertEquals(14474, Class_Article::findFirstBy(['titre' => 'Tous à bord du bateau pirate'])->getCustomField('uid_typo3'));
+	}
+
+	/** @test */
+	public function AhauteVoixShouldKeepSameArticleId() {
+		$article=Class_Article::findFirstBy(['titre' => 'A haute voix... modif']);
+		$this->assertEquals(13973,$article->getCustomField('uid_typo3'));
+		$this->assertEquals($article->getId(), $this->article_voix->getId());
+	}
+
+	/** @test */
+	public function articleReallyYouCrashShouldNotBeModified() {
+		$this->assertEquals(84888,Class_Article::findFirstBy(['titre' => 'Realy ? you crash ?'])->getCustomField('uid_typo3'));
+		$this->assertEquals(666,Class_Article::findFirstBy(['titre' => 'Crash Test'])->getCustomField('uid_typo3'));
+	}
+
+
+
+
+}
+
+
+class Import_Typo3CustomFieldTest extends Import_Typo3TestCase {
+	public function setUp() {
+		parent::setUp();
+		$this->migration->import_categories();
+		$this->migration->importArticles();
+
+		$this->migration->importArticlesPages();
+  	Class_CustomField_Value::clearCache();
+		$this->migration->updateCategoriesForDossiersDocumentaires();
 
 
+	}
+	/** @test */
+	public function museoShouldHaveCustomField() {
+   	Class_CustomField_Value::clearCache();
+		$article=Class_Article::findFirstBy(['titre' => 'A haute voix... le roman se fait entendre...']);
+ 		$this->custom_field_values = Class_CustomField_Value::findAllByInstance($article);
+		$this->assertEquals(13973,$this->custom_field_values[0]->getValue());
+	}
+
+
+  /** @test */
+	public function customfieldShouldbeUnique() {
+		$custom_fields=Class_CustomField::findAllBy(['meta_id' => 1,
+																								 'model' => 'Article']);
+		foreach ($custom_fields as $custom_field) {
+			$value = Class_CustomField_Value::findFirstBy(['custom_field_id' => $custom_field->getId(),
+																										 'value' => 13973]);
+		}
+
+	}
+
+	/** @test */
+	public function updateCategoriesShouldUpdateCategorie() {
+		Class_CustomField_Value::clearCache();
+		$article=Class_Article::findFirstBy(['titre' => 'A haute voix... le roman se fait entendre...']);
+		$this->assertEquals('BLOGS AND SITES',$article->getCategorie()->getLibelle());
+	}
+
+
+	/** @test */
+	public function updateCategorieShouldUpdateParentCategorie() {
+		Class_CustomField_Value::clearCache();
+		$article=Class_Article::findFirstBy(['titre' => 'A haute voix... le roman se fait entendre...']);
+		$this->assertEquals('Nos dossiers documentaires',$article->getCategorie()->getParent()->getLibelle());
+	}
+}
+
 class Import_Typo3ContentTest extends Import_Typo3TestCase {
 	public function setUp() {
 		parent::setUp();
@@ -503,14 +823,14 @@ class Import_Typo3LogsTest extends Import_Typo3TestCase {
 
 	/** @test */
 	public function logsShouldContainsUrlKohaNotFoundForArticle23() {
-		$this->assertContains('Class_Article id:1 , typo3 uid: 32, unknown URL: http://koha.mediathequeouestprovence.fr/cgi-bin/koha/opac-shelves.pl?viewshelf=1556&sortfield=title%20%3Ccid:part1.08080603.08080008@ouestprovence.fr',
+		$this->assertContains('Class_Article id:1 , typo3 uid: 13973, unknown URL: http://koha.mediathequeouestprovence.fr/cgi-bin/koha/opac-shelves.pl?viewshelf=1556&sortfield=title%20%3Ccid:part1.08080603.08080008@ouestprovence.fr',
 													Class_Import_Typo3_Logs::getInstance()->getLogs());
 	}
 
 
 	/** @test */
 	public function logsShouldContainsUrlKohaNotFoundForArticle9() {
-		$this->assertContains('Class_Article id:9 , typo3 uid: 13973, unknown URL: http://koha.mediathequeouestprovence.fr/cgi-bin/koha/opac-search.pl?q=test&idx=kw&idx=kw&idx=kw',
+		$this->assertContains('Class_Article id:9 , typo3 uid: 139735, unknown URL: http://koha.mediathequeouestprovence.fr/cgi-bin/koha/opac-search.pl?q=test&idx=kw&idx=kw&idx=kw',
 													Class_Import_Typo3_Logs::getInstance()->getLogs());
 	}
 
@@ -561,20 +881,20 @@ class Import_Typo3LogsTest extends Import_Typo3TestCase {
 
 	/** @test */
 	public function logsShouldContains8T3Categories() {
-		$this->assertContains("Typo3 categories found: 8", Class_Import_Typo3_Logs::getInstance()->getLogs());
+		$this->assertContains("Typo3 categories found: 10", Class_Import_Typo3_Logs::getInstance()->getLogs());
 	}
 
 
 	/** @test */
 	public function logsShouldContainsSavedSitoCategories() {
-		$this->assertContains("Typo3 Sito categories saved: 7", Class_Import_Typo3_Logs::getInstance()->getLogs());
+		$this->assertContains("Typo3 Sito categories saved: 9", Class_Import_Typo3_Logs::getInstance()->getLogs());
 	}
 
 
 	/** @test */
 	public function logsShouldContainsSavedArticleCategories() {
-		$this->assertContains("Typo3 Article categories saved: 10",
-													 Class_Import_Typo3_Logs::getInstance()->getLogs());
+		$this->assertContains("Typo3 Article categories saved: 12",
+													Class_Import_Typo3_Logs::getInstance()->getLogs());
 	}
 
 
@@ -592,13 +912,13 @@ class Import_Typo3LogsTest extends Import_Typo3TestCase {
 
 	/** @test */
 	public function logsShouldContainsSavedDomains() {
-		$this->assertContains("Typo3 Domains saved: 11", Class_Import_Typo3_Logs::getInstance()->getLogs());
+		$this->assertContains("Typo3 Domains saved: 13", Class_Import_Typo3_Logs::getInstance()->getLogs());
 	}
 
 
 	/** @test */
-	public function logsShouldContains8SavedArticles() {
-		$this->assertContains("Typo3 Articles saved: 8", Class_Import_Typo3_Logs::getInstance()->getLogs());
+	public function logsShouldContains9SavedArticles() {
+		$this->assertContains("Typo3 Articles saved: 9", Class_Import_Typo3_Logs::getInstance()->getLogs());
 	}
 
 
diff --git a/tests/library/Class/Indexation/PseudoNoticeTest.php b/tests/library/Class/Indexation/PseudoNoticeTest.php
index c732a8bd68a14d8db3722e53833e43be50c6b043..8f1c5d0059101f975fa93a8a3798c7c470a94ee9 100644
--- a/tests/library/Class/Indexation/PseudoNoticeTest.php
+++ b/tests/library/Class/Indexation/PseudoNoticeTest.php
@@ -16,13 +16,14 @@
  *
  * 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 Class_Indexation_PseudoNoticeTestCase extends Storm_Test_ModelTestCase {
 	public function setUp() {
 		parent::setUp();
 		Class_Notice::beVolatile();
+		Class_Exemplaire::beVolatile();
 	}
 }
 
@@ -30,7 +31,7 @@ abstract class Class_Indexation_PseudoNoticeTestCase extends Storm_Test_ModelTes
 class Class_Indexation_PseudoNoticeAlbumTest extends Class_Indexation_PseudoNoticeTestCase {
 	public function setUp() {
 		parent::setUp();
-		$this->fixture('Class_Album', 
+		$this->fixture('Class_Album',
 									 ['id' => 896,
 									 'fichier' => '123_file.png',
 									 'notes' => 'a:3:{i:1;a:2:{s:5:"field";s:3:"856";s:4:"data";a:2:{s:1:"x";s:12:"external_uri";s:1:"a";N;}}s:5:"215$a";s:0:"";i:2;a:2:{s:5:"field";s:3:"701";s:4:"data";a:2:{s:1:"a";s:16:"Gustave Flaubert";i:4;s:0:"";}}}',
@@ -40,7 +41,7 @@ class Class_Indexation_PseudoNoticeAlbumTest extends Class_Indexation_PseudoNoti
 		$this->_notice = Class_Notice::find(1);
 	}
 
-	
+
 	/** @test */
 	public function vignetteURLShouldBeUserfile123file() {
 		$this->assertEquals(BASE_URL.'/userfiles/album/896/thumb_123_file.png', $this->_notice->getUrlVignette());
@@ -69,7 +70,7 @@ class Class_Indexation_PseudoNoticeAlbumTest extends Class_Indexation_PseudoNoti
 
 	/** @test */
 	public function noticeShouldHave856aWithPermalink() {
-		$this->assertContains('/bib-numerique/notice/ido/778997987', 
+		$this->assertContains('/bib-numerique/notice/ido/778997987',
 													$this->_notice->get_subfield('856', 'a')[0]);
 	}
 }
@@ -90,7 +91,7 @@ class Class_Indexation_PseudoNoticeSitothequeFromRawSQLTest extends Class_Indexa
 																				'tags' => 'VOD']);
 
     /** as done in Cosmogramme */
-		Class_Indexation_PseudoNotice::newWith(Class_Indexation_PseudoNotice::TYPE_SITO,
+		Class_Indexation_PseudoNotice::newWith(Class_TypeDoc::SITE,
 																					 ['ID_SITO' => 12,
 																						'TITRE' => 'Thot cursus',
 																						'URL' => 'http://cursus.edu/',
@@ -122,16 +123,16 @@ class Class_Indexation_PseudoNoticeSitothequeTest extends Class_Indexation_Pseud
 																											 'description' => 'Top notch site',
 																											 'tags' => 'VOD']);
 
-		Class_Indexation_PseudoNotice::newWith(Class_Indexation_PseudoNotice::TYPE_SITO,
+		Class_Indexation_PseudoNotice::newWith(Class_TypeDoc::SITE,
 																					 $this->_sito->toArray() + ['ID_SITO' => $this->_sito->getId()])
 			->save();
 
 		$this->_notice = $this->_sito->getNotice();
 	}
 
-	
+
 	/** @test */
-  public function shouldIndex() { 
+  public function shouldIndex() {
 		$this->assertNotNull($this->_notice);
 	}
 
@@ -144,7 +145,7 @@ class Class_Indexation_PseudoNoticeSitothequeTest extends Class_Indexation_Pseud
 
 	/** @test */
 	public function typeShouldBeSite() {
-		$this->assertEquals(Class_Indexation_PseudoNotice::TYPE_SITO, 
+		$this->assertEquals(Class_TypeDoc::SITE,
 												$this->_notice->getTypeDoc());
 	}
 
@@ -156,4 +157,39 @@ class Class_Indexation_PseudoNoticeSitothequeTest extends Class_Indexation_Pseud
 }
 
 
-?>
\ No newline at end of file
+
+class Class_Indexation_PseudoNoticeArticleTest extends Storm_Test_ModelTestCase {
+	public function setUp() {
+		parent::setUp();
+		Class_Exemplaire::beVolatile();
+		$this->fixture('Class_NoticeDomain',
+									 ['id' => 189,
+										'domain_id' => 17,
+										'record_alpha_key' => 'MYARTCILE------8']);
+
+		$this->fixture('Class_Catalogue',
+									 ['id' => 17,
+										'libelle' => 'my cata']);
+
+		Class_Notice::beVolatile();
+
+		$this->fixture('Class_Article',
+									 ['id' => 1,
+										'titre' => 'My Artcile',
+										'contenu' => 'article is about...',
+										'domaine_ids' => '17']);
+
+		$this->fixture('Class_Article',
+									 ['id' => 5,
+										'titre' => 'My Artcile bis',
+										'contenu' => 'article is about bis...']);
+
+		Class_Article::indexAll();
+	}
+
+
+	/** @test */
+	public function noticeDomainShouldContainsOnlyOneEnreg() {
+		$this->assertCount(1, Class_NoticeDomain::findAll());
+	}
+}
diff --git a/tests/library/Class/LocalisationTest.php b/tests/library/Class/LocalisationTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..d814c2aeaf7a44d5a9ebb4e34d67e768b5012e36
--- /dev/null
+++ b/tests/library/Class/LocalisationTest.php
@@ -0,0 +1,181 @@
+<?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 LocalisationTest extends Storm_Test_ModelTestCase {
+	public function setUp() {
+		parent::setUp();
+
+		$this->_bib = $this->fixture('Class_Bib',
+																 ['id' => 1]);
+
+		$this->fixture('Class_Localisation',
+									 ['id' => 1,
+										'id_bib' => 1,
+										'annexe' => 1,
+										'libelle' => 'romans',
+										'section' => 'A',
+										'type_doc' => 1]);
+
+
+		$this->fixture('Class_Localisation',
+									 ['id' => 2,
+										'id_bib' => 1,
+										'annexe' => 1,
+										'libelle' => 'Mangas',
+										'section' => 'J',
+										'genre' => 'M',
+										'type_doc' => 1,
+										'cote_debut' => 'BD A',
+										'cote_fin' => 'BD Z']);
+
+
+		$this->fixture('Class_Localisation',
+									 ['id' => 3,
+										'id_bib' => 1,
+										'annexe' => 1,
+										'libelle' => 'BD',
+										'section' => 'J',
+										'genre' => 'B',
+										'type_doc' => 1,
+										'cote_debut' => 'BD A',
+										'cote_fin' => 'BD Z']);
+
+
+		$this->fixture('Class_Localisation',
+									 ['id' => 4,
+										'id_bib' => 1,
+										'annexe' => 1,
+										'libelle' => 'Compact discs',
+										'cote_debut' => 'CDA',
+										'cote_fin' => 'CDZ']);
+
+		$this->fixture('Class_Localisation',
+									 ['id' => 5,
+										'id_bib' => 1,
+										'annexe' => 1,
+										'libelle' => 'CD Jazz',
+										'cote_debut' => 'CDJazz A',
+										'cote_fin' => 'CDJazz Z']);
+	}
+
+
+	/** @test */
+	public function bdByCoteAndGenreLocalisationShouldBeBD() {
+		$this->fixture('Class_Exemplaire',
+									 ['id' => 4,
+										'id_bib' => 1,
+										'code_barres' => 'abc',
+										'cote' => 'BD E',
+										'genre' => 'B',
+										'notice' => $this->fixture('Class_Notice',
+																							 ['id' => 2,
+																								'type_doc' => 1])]);
+
+		$this->assertEquals('BD',
+												Class_Localisation::getLocFromExemplaire($this->_bib,
+																																 'BD E',
+																																 'abc')['libelle']);
+	}
+
+
+	/** @test */
+	public function coteMatchingShoulHavePriority() {
+		$this->fixture('Class_Exemplaire',
+									 ['id' => 4,
+										'id_bib' => 1,
+										'code_barres' => 'abc',
+										'cote' => 'CDE',
+										'genre' => 'B',
+										'section' => 'J',
+										'annexe' => 1,
+										'notice' => $this->fixture('Class_Notice',
+																							 ['id' => 2,
+																								'type_doc' => 1])]);
+
+		$this->assertEquals('Compact discs',
+												Class_Localisation::getLocFromExemplaire($this->_bib,
+																																 'CDE',
+																																 'abc')['libelle']);
+	}
+
+
+		/** @test */
+	public function coteWithMostCharactersMatchingShoulHavePriority() {
+		$this->fixture('Class_Exemplaire',
+									 ['id' => 4,
+										'id_bib' => 1,
+										'code_barres' => 'abc',
+										'cote' => 'CDJazz E',
+										'genre' => 'B',
+										'section' => 'J',
+										'annexe' => 1,
+										'notice' => $this->fixture('Class_Notice',
+																							 ['id' => 2,
+																								'type_doc' => 1])]);
+
+		$this->assertEquals('CD Jazz',
+												Class_Localisation::getLocFromExemplaire($this->_bib,
+																																 'CDJazz E',
+																																 'abc')['libelle']);
+	}
+
+
+	/** @test */
+	public function bdByGenreLocalisationShouldBeMangas() {
+		$this->fixture('Class_Exemplaire',
+									 ['id' => 4,
+										'id_bib' => 1,
+										'code_barres' => 'abc',
+										'cote' => 'BD E',
+										'genre' => 'M',
+										'notice' => $this->fixture('Class_Notice',
+																							 ['id' => 2,
+																								'type_doc' => 1])]);
+
+		$this->assertEquals('Mangas',
+												Class_Localisation::getLocFromExemplaire($this->_bib,
+																																 'B',
+																																 'abc')['libelle']);
+	}
+
+
+	/** @test */
+	public function romanBySectionLocalisationShouldBeRoman() {
+		$this->fixture('Class_Exemplaire',
+									 ['id' => 4,
+										'id_bib' => 1,
+										'code_barres' => 'abc',
+										'section' => 'A',
+										'cote' => 'ROM',
+										'notice' => $this->fixture('Class_Notice',
+																							 ['id' => 2,
+																								'type_doc' => 1])]);
+
+		$this->assertEquals('romans',
+												Class_Localisation::getLocFromExemplaire($this->_bib,
+																																 'ROM',
+																																 'abc')['libelle']);
+	}
+
+}
+
+?>
\ No newline at end of file
diff --git a/tests/library/Class/MigrationFixture.php b/tests/library/Class/MigrationFixture.php
index 70653d119b070d21d4811730f1ac7e366a86b899..ab8c0be6608b792ae56f2da3f0b1bbfa19982b83 100644
--- a/tests/library/Class/MigrationFixture.php
+++ b/tests/library/Class/MigrationFixture.php
@@ -52,7 +52,11 @@ class MigrationFixture {
 																					->whenCalled('file')
 																					->with('./cosmogramme/sql/patch/patch_12.sql')
 																					->answers([ 'update working;'])
-			);
+
+																					->whenCalled('sha1_file')
+																					->willDo(function() { return uniqid();} )
+		);
+
 		$_mock_system_include = Storm_Test_ObjectWrapper::mock()
 			->whenCalled('include_file')
 			->answers(true);
@@ -63,7 +67,10 @@ class MigrationFixture {
 			->willDo(function() {throw new CustomException('database error' , '666');})
 			->whenCalled('execute')
 			->with('update working;')
-			->answers(true);
+			->answers(true)
+			->whenCalled('query')
+			->with("SHOW TABLES LIKE 'patch_hash'")
+			->answers(1);
 
 		Zend_Registry::set('sql', $mock_sql);
 		Class_Systeme_Include::setInstance($_mock_system_include);
@@ -78,13 +85,31 @@ class MigrationFixture {
 																										 './cosmogramme/sql/patch/patch_10.sql',
 																										 './cosmogramme/sql/patch/patch_14.php',
 																										 './cosmogramme/sql/patch/patch_12.sql',
+																										 './cosmogramme/sql/patch/patch_15.sql',
 																										 './cosmogramme/sql/patch/patch_5.sql'
 																											])
 
 																					->whenCalled('file')
 																					->with('./cosmogramme/sql/patch/patch_12.sql')
 																					->answers([ 'update working;'])
-																						);
+
+																					->whenCalled('sha1_file')
+																					->with('./cosmogramme/sql/patch/patch_12.sql')
+																					->answers('bidonquiapaslatetedunchathein')
+
+																					->whenCalled('sha1_file')
+																					->with('./cosmogramme/sql/patch/patch_13.php')
+																					->answers('patch_13_php_sum')
+
+																					->whenCalled('sha1_file')
+																					->with('./cosmogramme/sql/patch/patch_14.php')
+																					->answers('patch_14_php_sum')
+
+																					->whenCalled('sha1_file')
+																					->with('./cosmogramme/sql/patch/patch_15.sql')
+																					->answers('bidonquiapaslatetedunchathein')
+		);
+
 		$_mock_system_include = Storm_Test_ObjectWrapper::mock()
 			->whenCalled('include_file')
 			->with('./cosmogramme/sql/patch/patch_13.php')
@@ -100,10 +125,12 @@ class MigrationFixture {
 		$mock_sql = Storm_Test_ObjectWrapper::mock();
 		$mock_sql->whenCalled('execute')
 			->with('update working;')
-			->answers(true);
+			->answers(true)
+			->whenCalled('query')
+			->with("SHOW TABLES LIKE 'patch_hash'")
+			->answers(1);
 
 		Zend_Registry::set('sql', $mock_sql);
-
 	}
 
 
@@ -125,13 +152,18 @@ class MigrationFixture {
 																											'END'
 																											])
 
+																					->whenCalled('sha1_file')
+																					->answers('bidonquiapaslatetedunchathein')
+
 			);
 
 		$mock_sql = Storm_Test_ObjectWrapper::mock();
 		$mock_sql->whenCalled('execute')
 			->with("CREATE FUNCTION clean_spaces(str VARCHAR(255)) RETURNS VARCHAR(255) DETERMINISTIC BEGIN while instr(str, '  ') > 0 do set str := replace(str, '  ', ' '); end while; return trim(str); END")
 			->answers(true)
-			->whenCalled('execute')
+			->whenCalled('query')
+			->with("SHOW TABLES LIKE 'patch_hash'")
+			->answers(1)
 			->beStrict();
 
 		Zend_Registry::set('sql', $mock_sql);
diff --git a/tests/library/Class/MigrationTest.php b/tests/library/Class/MigrationTest.php
index 832f990ca1c13ba549523e68540642e2cca4461c..e05d6dd3baee9eb3e933ab85566fe51de060268e 100644
--- a/tests/library/Class/MigrationTest.php
+++ b/tests/library/Class/MigrationTest.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 MigrationTestCase extends Storm_Test_ModelTestCase {
@@ -24,9 +24,9 @@ abstract class MigrationTestCase extends Storm_Test_ModelTestCase {
 
 	public function setUp() {
 		parent::setUp();
-		
+		Storm_Model_Loader::defaultToVolatile();
 		Class_Migration_ScriptPatchs::setEcho('none');
-		
+
 		$this->fixture('Class_CosmoVar', ['id' => 'patch_level',
 																			'valeur' => 10 ] );
 		$this->_old_sql = Zend_Registry::get('sql');
@@ -35,6 +35,7 @@ abstract class MigrationTestCase extends Storm_Test_ModelTestCase {
 
 	public function tearDown() {
 		Zend_Registry::set('sql', $this->_old_sql);
+		Storm_Model_Loader::defaultToDb();
 		parent::tearDown();
 	}
 }
@@ -48,7 +49,7 @@ class MigrationPatchsWithEmptyDirTest extends MigrationTestCase {
 		MigrationFixture::mockEmptyFileSystem();
 	}
 
-	
+
 	/** @test */
 	public function lastPatchNumberShouldReturn0() {
 		$this->assertEquals('0', $this->migration->getLastPatchNumber());
@@ -63,8 +64,8 @@ class Class_MigrationPatchsTest extends MigrationTestCase {
 		$this->migration = new Class_Migration_Patchs();
 		MigrationFixture::mockFileSystemWithSql();
 	}
-	
-	
+
+
 	/** @test */
 	public function lastPatchNumberShouldReturn14() {
 		$this->assertEquals('14',$this->migration->getLastPatchNumber());
@@ -82,7 +83,7 @@ class Class_MigrationPatchsTest extends MigrationTestCase {
 
 
 
-class Class_MigrationScriptPatchsTest extends MigrationTestCase {	
+class Class_MigrationScriptPatchsTest extends MigrationTestCase {
 	public function setup() {
 		parent::setup();
 		MigrationFixture::mockFileSystemWithSql();
@@ -105,7 +106,7 @@ class Class_MigrationScriptPatchsTest extends MigrationTestCase {
 
 
 
-class MigrationScriptPatchsPhpTest extends MigrationTestCase {	
+class MigrationScriptPatchsPhpTest extends MigrationTestCase {
 	public function setup() {
 		parent::setup();
 		MigrationFixture::mockFileSystemWithPhpScripts();
@@ -122,7 +123,22 @@ class MigrationScriptPatchsPhpTest extends MigrationTestCase {
   /** @test */
 	public function runForcePatchShouldUpgradePatchLevelTo14() {
 		(new Class_Migration_ScriptPatchs())->run(true);
-		$this->assertEquals(14, Class_CosmoVar::find('patch_level')->getValeur());
+		$this->assertEquals(15, Class_CosmoVar::find('patch_level')->getValeur());
+	}
+
+
+	/** @test */
+	public function runShouldSaveSha1() {
+		(new Class_Migration_ScriptPatchs())->run();
+		$this->assertEquals('bidonquiapaslatetedunchathein',
+												Class_Migration_PatchHash::findFirstBy([])->getValue());
+	}
+
+
+	/** @test */
+	public function runForcedShouldSaveTwoSha1() {
+		(new Class_Migration_ScriptPatchs())->run(true);
+		$this->assertEquals(2, Class_Migration_PatchHash::count());
 	}
 }
 
diff --git a/tests/library/Class/MoteurRecherche/MoteurRechercheFacettesTest.php b/tests/library/Class/MoteurRecherche/MoteurRechercheFacettesTest.php
index eba7123ca7f76f8d4f4ade43d053bb195cd1a3a4..b0a41ecf88ebe092dd9c97a0aba2a88ecbdbeb4c 100644
--- a/tests/library/Class/MoteurRecherche/MoteurRechercheFacettesTest.php
+++ b/tests/library/Class/MoteurRecherche/MoteurRechercheFacettesTest.php
@@ -194,7 +194,11 @@ class MoteurRechercheFacettesTest extends Storm_Test_ModelTestCase {
 	public function WithCacheShouldNotExecuteSQL(){
 		$cache = Storm_Test_ObjectWrapper::mock();
 		Storm_Cache::setDefaultZendCache($cache);
-		$cache->whenCalled('load')->answers(serialize($this->facettes));
+		$cache
+			->whenCalled('save')
+			->answers(true)
+			->whenCalled('load')
+			->answers(serialize($this->facettes));
 		$this->mock_sql->clearAllRedirections();
 		$this->assertEquals( $this->facettes,
 												 $this->moteur_recherche->getFacettes($this->_requete,
diff --git a/tests/library/Class/MoteurRechercheTest.php b/tests/library/Class/MoteurRechercheTest.php
index d7b942d24e9de5e18d2e6cb83619d45dcc15573c..f6490b17c25679fe9705f7462f4b21edb32af66c 100644
--- a/tests/library/Class/MoteurRechercheTest.php
+++ b/tests/library/Class/MoteurRechercheTest.php
@@ -23,7 +23,7 @@ abstract class MoteurRechercheTestCase extends Storm_Test_ModelTestCase {
 	protected
 		$mock_sql,
 		$_original_sql,
-		$expected_date = '2012-05-03';
+		$expected_date = '2011-05-03';
 
 
 	public function setUp() {
@@ -34,12 +34,7 @@ abstract class MoteurRechercheTestCase extends Storm_Test_ModelTestCase {
 
 		Zend_Registry::set('sql', $this->mock_sql);
 
-		Class_MoteurRecherche::setTimeSource($this->mock()
-																				 ->whenCalled('mktime')
-																				 ->with()
-																				 ->answers(mktime(0, 0, 0, 5, 3, 2012)));
-		$this->criteres_recherche= new Class_CriteresRecherche();
-		$this->moteur_recherche = new Class_MoteurRecherche();
+		Class_MoteurRecherche::setTimeSource(new TimeSourceForTest('2012-05-03 09:00:00'));
 	}
 
 
@@ -58,10 +53,16 @@ abstract class MoteurRechercheTestCase extends Storm_Test_ModelTestCase {
 
 		Class_Zone::newInstanceWithId(2)
 			->setBibs([ Class_Bib::newInstanceWithId(3),
-									Class_Bib::newInstanceWithId(4)
-									]);
-		$profil = Class_Profil::getLoader()->newInstanceWithId(26)
-			->setTypeDoc('1');
+								 Class_Bib::newInstanceWithId(4)
+								 ]);
+
+		$profil = $this->fixture('Class_Profil',
+														 ['id' => 26])
+									 ->setTypeDoc('1')
+									 ->setSelAnnexe('')
+									 ->setIdSite('')
+									 ->setSelection('');
+		$profil->save();
 
 		if (isset($params['selection_annexe']))
 			$profil->setSelAnnexe($params['selection_annexe']);
@@ -72,6 +73,8 @@ abstract class MoteurRechercheTestCase extends Storm_Test_ModelTestCase {
 		if (isset($params['selection_sections']))
 			$profil->setSelSection($params['selection_sections']);
 
+		$profil->save();
+
 		Class_Profil::setCurrentProfil($profil);
 	}
 
@@ -158,10 +161,10 @@ class MoteurRechercheAvanceeTest extends MoteurRechercheTestCase {
 							'type_recherche' => 'nimportequoi',
 							'pertinence' => false,
 							'tri' => 'alpha_titre'] ,
-						 'req_notices' => $this->listSqlWith("MATCH(titres) AGAINST('+(NOUVEAU NOUVEAUX NOUVO) +(CHIEN CHIENS CHIN) +(GARDE GARDES GARD)' IN BOOLEAN MODE) or MATCH(editeur) AGAINST('+(RAISON RAISONS RAISON) +(D00 D00S ) +(AGIR AGIRS AJIR)' IN BOOLEAN MODE) and date_creation >'".$this->expected_date."' and MATCH(facettes) AGAINST('+B4' IN BOOLEAN MODE)",
+						 'req_notices' => $this->listSqlWith("MATCH(titres) AGAINST('+(NOUVEAU NOUVEAUX NOUVO) +(CHIEN CHIENS CHIN) +(GARDE GARDES GARD)' IN BOOLEAN MODE) or MATCH(editeur) AGAINST('+(RAISON RAISONS RAISON) +(D00 D00S ) +(AGIR AGIRS AJIR)' IN BOOLEAN MODE) and date_creation >'2012-02-03' and MATCH(facettes) AGAINST('+B4' IN BOOLEAN MODE)",
 																								 'alpha_titre'),
-						 'req_comptage' => $this->countSqlWith("MATCH(titres) AGAINST('+(NOUVEAU NOUVEAUX NOUVO) +(CHIEN CHIENS CHIN) +(GARDE GARDES GARD)' IN BOOLEAN MODE) or MATCH(editeur) AGAINST('+(RAISON RAISONS RAISON) +(D00 D00S ) +(AGIR AGIRS AJIR)' IN BOOLEAN MODE) and date_creation >'".$this->expected_date."' and MATCH(facettes) AGAINST('+B4' IN BOOLEAN MODE)"),
-						 'req_facettes' => $this->facetSqlWith("MATCH(titres) AGAINST('+(NOUVEAU NOUVEAUX NOUVO) +(CHIEN CHIENS CHIN) +(GARDE GARDES GARD)' IN BOOLEAN MODE) or MATCH(editeur) AGAINST('+(RAISON RAISONS RAISON) +(D00 D00S ) +(AGIR AGIRS AJIR)' IN BOOLEAN MODE) and date_creation >'".$this->expected_date."' and MATCH(facettes) AGAINST('+B4' IN BOOLEAN MODE)")],
+						 'req_comptage' => $this->countSqlWith("MATCH(titres) AGAINST('+(NOUVEAU NOUVEAUX NOUVO) +(CHIEN CHIENS CHIN) +(GARDE GARDES GARD)' IN BOOLEAN MODE) or MATCH(editeur) AGAINST('+(RAISON RAISONS RAISON) +(D00 D00S ) +(AGIR AGIRS AJIR)' IN BOOLEAN MODE) and date_creation >'2012-02-03' and MATCH(facettes) AGAINST('+B4' IN BOOLEAN MODE)"),
+						 'req_facettes' => $this->facetSqlWith("MATCH(titres) AGAINST('+(NOUVEAU NOUVEAUX NOUVO) +(CHIEN CHIENS CHIN) +(GARDE GARDES GARD)' IN BOOLEAN MODE) or MATCH(editeur) AGAINST('+(RAISON RAISONS RAISON) +(D00 D00S ) +(AGIR AGIRS AJIR)' IN BOOLEAN MODE) and date_creation >'2012-02-03' and MATCH(facettes) AGAINST('+B4' IN BOOLEAN MODE)")],
 
 						[['rech_auteurs' => 'Stiegler',
 							'operateur_auteurs' => 'and',
@@ -217,6 +220,9 @@ class MoteurRechercheAvanceeTest extends MoteurRechercheTestCase {
 	public function lancerRechercheAvanceeShouldBe($params, $req_notices, $req_comptage ,$req_facettes) {
 		$this->mockReqProfilAndZone($req_comptage,$params);
 
+		$this->criteres_recherche= new Class_CriteresRecherche();
+		$this->moteur_recherche = new Class_MoteurRecherche();
+
 		$this->criteres_recherche->setParams($params);
 		$retour = $this->moteur_recherche->lancerRecherche( $this->criteres_recherche);
 		$this->assertEquals(['nombre' => 10,
@@ -237,66 +243,66 @@ class MoteurRechercheSimpleTest extends MoteurRechercheTestCase {
 		$count_sql = 'Select count(*) from notices Where ' . $match_axes . ' AGAINST(%s IN BOOLEAN MODE)';
 		$facet_sql = 'Select id_notice, type_doc, facettes from notices Where %s limit 15000';
 		return [
-			[['expressionRecherche' => 'Bakounine'],
-			 'nb_mots' => 1,
-			 'req_comptage' => sprintf($count_sql, "'+(BAKOUNINE BAKOUNINES BAKOUNIN)'"),
-			 'req_liste' => "Select id_notice, MATCH(alpha_titre) AGAINST(' BAKOUNINE') as rel1, MATCH(alpha_auteur) AGAINST(' BAKOUNINE') as rel2 from notices Where ". $match_axes ." AGAINST('+(BAKOUNINE BAKOUNINES BAKOUNIN)' IN BOOLEAN MODE) order by (rel1 * 1.5) + (rel2) desc",
-			 'req_facettes' => sprintf($facet_sql, $match_axes ." AGAINST('+(BAKOUNINE BAKOUNINES BAKOUNIN)' IN BOOLEAN MODE)")],
-
-			[['expressionRecherche' => 'Slavoj Zizek',
-				'tri' => 'alpha_titre'] ,
-			 'nb_mots' => 2,
-			 'req_comptage' => sprintf($count_sql, "'+(SLAVOJ SLAVOJS SLAVOJ) +(ZIZEK ZIZEKS ZIZEK)'"),
-			 'req_liste' => "Select id_notice from notices Where " . $match_axes . " AGAINST('+(SLAVOJ SLAVOJS SLAVOJ) +(ZIZEK ZIZEKS ZIZEK)' IN BOOLEAN MODE) order by alpha_titre",
-			 'req_facettes' => sprintf($facet_sql, $match_axes . " AGAINST('+(SLAVOJ SLAVOJS SLAVOJ) +(ZIZEK ZIZEKS ZIZEK)' IN BOOLEAN MODE)")],
-
-			[['expressionRecherche' => 'La commune de Paris',
-				'annexe' => 'MED1',
-				'selection_annexe' => 'TUN;TAP',
-				'tri' => 'alpha_titre'] ,
-			 'nb_mots' => 2,
-			 'req_comptage' => sprintf($count_sql, "'+(COMMUNE COMMUNES KOMUN) +(PARI PARIS PARI)' IN BOOLEAN MODE) and MATCH(facettes) AGAINST('+YMED1 +(YTUN YTAP)'"),
-			 'req_liste' => "Select id_notice from notices Where " . $match_axes . " AGAINST('+(COMMUNE COMMUNES KOMUN) +(PARI PARIS PARI)' IN BOOLEAN MODE) and MATCH(facettes) AGAINST('+YMED1 +(YTUN YTAP)' IN BOOLEAN MODE) order by alpha_titre",
-			 'req_facettes' => sprintf($facet_sql, $match_axes . " AGAINST('+(COMMUNE COMMUNES KOMUN) +(PARI PARIS PARI)' IN BOOLEAN MODE) and MATCH(facettes) AGAINST('+YMED1 +(YTUN YTAP)' IN BOOLEAN MODE)")],
-
-			[['expressionRecherche' => '2-7427-3315-9',
-				'geo_zone' => 2,
-				'tri' => 'alpha_auteur'] ,
-			 'nb_mots'=> null,
-			 'req_comptage' => "Select count(*) from notices Where (isbn='2-7427-3315-9' or isbn='978-2-7427-3315-6') and MATCH(facettes) AGAINST('+(B3 B4)' IN BOOLEAN MODE)",
-			 'req_liste' => "Select id_notice from notices Where (isbn='2-7427-3315-9' or isbn='978-2-7427-3315-6') and MATCH(facettes) AGAINST('+(B3 B4)' IN BOOLEAN MODE) order by alpha_auteur",
-			 'req_facettes' => "Select id_notice, type_doc, facettes from notices Where (isbn='2-7427-3315-9' or isbn='978-2-7427-3315-6') and MATCH(facettes) AGAINST('+(B3 B4)' IN BOOLEAN MODE) limit 15000"],
-
-			[['expressionRecherche' => '2-7427-3315-9',
-				'geo_bib' => 3,
-				'tri' => 'alpha_auteur'] ,
-			 'nb_mots'=> null,
-			 'req_comptage' => "Select count(*) from notices Where (isbn='2-7427-3315-9' or isbn='978-2-7427-3315-6') and MATCH(facettes) AGAINST('+(B3)' IN BOOLEAN MODE)",
-			 'req_liste' => "Select id_notice from notices Where (isbn='2-7427-3315-9' or isbn='978-2-7427-3315-6') and MATCH(facettes) AGAINST('+(B3)' IN BOOLEAN MODE) order by alpha_auteur",
-			 'req_facettes' => "Select id_notice, type_doc, facettes from notices Where (isbn='2-7427-3315-9' or isbn='978-2-7427-3315-6') and MATCH(facettes) AGAINST('+(B3)' IN BOOLEAN MODE) limit 15000"]  ,
-
-			[['expressionRecherche' => '9782742761579'],
-			 'nb_mots'=> null,
-			 'req_comptage' => "Select count(*) from notices Where (isbn='2-7427-6157-8' or isbn='978-2-7427-6157-9')",
-			 'req_liste' => "Select id_notice from notices Where (isbn='2-7427-6157-8' or isbn='978-2-7427-6157-9')",
-			 'req_facettes' => "Select id_notice, type_doc, facettes from notices Where (isbn='2-7427-6157-8' or isbn='978-2-7427-6157-9') limit 15000"],
-
-			[['expressionRecherche' => '9782742761579',
-				'selection_bib'=> 1],
-			 'nb_mots'=> null,
-			 'req_comptage' => "Select count(*) from notices Where (isbn='2-7427-6157-8' or isbn='978-2-7427-6157-9') and MATCH(facettes) AGAINST('+B1' IN BOOLEAN MODE)",
-			 'req_liste' => "Select id_notice from notices Where (isbn='2-7427-6157-8' or isbn='978-2-7427-6157-9') and MATCH(facettes) AGAINST('+B1' IN BOOLEAN MODE)",
-			 'req_facettes' => "Select id_notice, type_doc, facettes from notices Where (isbn='2-7427-6157-8' or isbn='978-2-7427-6157-9') and MATCH(facettes) AGAINST('+B1' IN BOOLEAN MODE) limit 15000"],
-
-			[['expressionRecherche' => 'logo',
-				'annexe' => 'MED1',
-				'facettes' => 'M52291-A15067',
-				'selection_annexe' => 'TUN;TAP',
-				'selection_sections' => '1;12;9'],
-			 'nb_mots'=> 1,
-			 'req_comptage' => sprintf($count_sql, "'+(LOGO LOGOS LOGO)' IN BOOLEAN MODE) and MATCH(facettes) AGAINST('+YMED1 +M52291 +A15067 +(YTUN YTAP)  +(S1 S12 S9)'"),
-			 'req_liste' => "Select id_notice, MATCH(alpha_titre) AGAINST(' LOGO') as rel1, MATCH(alpha_auteur) AGAINST(' LOGO') as rel2 from notices Where " . $match_axes . " AGAINST('+(LOGO LOGOS LOGO)' IN BOOLEAN MODE) and MATCH(facettes) AGAINST('+YMED1 +M52291 +A15067 +(YTUN YTAP)  +(S1 S12 S9)' IN BOOLEAN MODE) order by (rel1 * 1.5) + (rel2) desc",
-			 'req_facettes' => sprintf($facet_sql, $match_axes . " AGAINST('+(LOGO LOGOS LOGO)' IN BOOLEAN MODE) and MATCH(facettes) AGAINST('+YMED1 +M52291 +A15067 +(YTUN YTAP)  +(S1 S12 S9)' IN BOOLEAN MODE)")]];
+						[['expressionRecherche' => 'Bakounine'],
+						 'nb_mots' => 1,
+						 'req_comptage' => sprintf($count_sql, "'+(BAKOUNINE BAKOUNINES BAKOUNIN)'"),
+						 'req_liste' => "Select id_notice, MATCH(alpha_titre) AGAINST(' BAKOUNINE') as rel1, MATCH(alpha_auteur) AGAINST(' BAKOUNINE') as rel2 from notices Where ". $match_axes ." AGAINST('+(BAKOUNINE BAKOUNINES BAKOUNIN)' IN BOOLEAN MODE) order by (rel1 * 1.5) + (rel2) desc",
+						 'req_facettes' => sprintf($facet_sql, $match_axes ." AGAINST('+(BAKOUNINE BAKOUNINES BAKOUNIN)' IN BOOLEAN MODE)")],
+
+						[['expressionRecherche' => 'Slavoj Zizek',
+							'tri' => 'alpha_titre'] ,
+						 'nb_mots' => 2,
+						 'req_comptage' => sprintf($count_sql, "'+(SLAVOJ SLAVOJS SLAVOJ) +(ZIZEK ZIZEKS ZIZEK)'"),
+						 'req_liste' => "Select id_notice from notices Where " . $match_axes . " AGAINST('+(SLAVOJ SLAVOJS SLAVOJ) +(ZIZEK ZIZEKS ZIZEK)' IN BOOLEAN MODE) order by alpha_titre",
+						 'req_facettes' => sprintf($facet_sql, $match_axes . " AGAINST('+(SLAVOJ SLAVOJS SLAVOJ) +(ZIZEK ZIZEKS ZIZEK)' IN BOOLEAN MODE)")],
+
+						[['expressionRecherche' => 'La commune de Paris',
+							'annexe' => 'MED1',
+							'selection_annexe' => 'TUN;TAP',
+							'tri' => 'alpha_titre'] ,
+						 'nb_mots' => 2,
+						 'req_comptage' => sprintf($count_sql, "'+(COMMUNE COMMUNES KOMUN) +(PARI PARIS PARI)' IN BOOLEAN MODE) and MATCH(facettes) AGAINST('+YMED1 +(YTUN YTAP)'"),
+						 'req_liste' => "Select id_notice from notices Where " . $match_axes . " AGAINST('+(COMMUNE COMMUNES KOMUN) +(PARI PARIS PARI)' IN BOOLEAN MODE) and MATCH(facettes) AGAINST('+YMED1 +(YTUN YTAP)' IN BOOLEAN MODE) order by alpha_titre",
+						 'req_facettes' => sprintf($facet_sql, $match_axes . " AGAINST('+(COMMUNE COMMUNES KOMUN) +(PARI PARIS PARI)' IN BOOLEAN MODE) and MATCH(facettes) AGAINST('+YMED1 +(YTUN YTAP)' IN BOOLEAN MODE)")],
+
+						[['expressionRecherche' => '2-7427-3315-9',
+							'geo_zone' => 2,
+							'tri' => 'alpha_auteur'] ,
+						 'nb_mots'=> null,
+						 'req_comptage' => "Select count(*) from notices Where (isbn='2-7427-3315-9' or isbn='978-2-7427-3315-6') and MATCH(facettes) AGAINST('+(B3 B4)' IN BOOLEAN MODE)",
+						 'req_liste' => "Select id_notice from notices Where (isbn='2-7427-3315-9' or isbn='978-2-7427-3315-6') and MATCH(facettes) AGAINST('+(B3 B4)' IN BOOLEAN MODE) order by alpha_auteur",
+						 'req_facettes' => "Select id_notice, type_doc, facettes from notices Where (isbn='2-7427-3315-9' or isbn='978-2-7427-3315-6') and MATCH(facettes) AGAINST('+(B3 B4)' IN BOOLEAN MODE) limit 15000"],
+
+						[['expressionRecherche' => '2-7427-3315-9',
+							'geo_bib' => 3,
+							'tri' => 'alpha_auteur'] ,
+						 'nb_mots'=> null,
+						 'req_comptage' => "Select count(*) from notices Where (isbn='2-7427-3315-9' or isbn='978-2-7427-3315-6') and MATCH(facettes) AGAINST('+(B3)' IN BOOLEAN MODE)",
+						 'req_liste' => "Select id_notice from notices Where (isbn='2-7427-3315-9' or isbn='978-2-7427-3315-6') and MATCH(facettes) AGAINST('+(B3)' IN BOOLEAN MODE) order by alpha_auteur",
+						 'req_facettes' => "Select id_notice, type_doc, facettes from notices Where (isbn='2-7427-3315-9' or isbn='978-2-7427-3315-6') and MATCH(facettes) AGAINST('+(B3)' IN BOOLEAN MODE) limit 15000"]  ,
+
+						[['expressionRecherche' => '9782742761579'],
+						 'nb_mots'=> null,
+						 'req_comptage' => "Select count(*) from notices Where (isbn='2-7427-6157-8' or isbn='978-2-7427-6157-9')",
+						 'req_liste' => "Select id_notice from notices Where (isbn='2-7427-6157-8' or isbn='978-2-7427-6157-9')",
+						 'req_facettes' => "Select id_notice, type_doc, facettes from notices Where (isbn='2-7427-6157-8' or isbn='978-2-7427-6157-9') limit 15000"],
+
+						[['expressionRecherche' => '9782742761579',
+							'selection_bib'=> 1],
+						 'nb_mots'=> null,
+						 'req_comptage' => "Select count(*) from notices Where (isbn='2-7427-6157-8' or isbn='978-2-7427-6157-9') and MATCH(facettes) AGAINST('+B1' IN BOOLEAN MODE)",
+						 'req_liste' => "Select id_notice from notices Where (isbn='2-7427-6157-8' or isbn='978-2-7427-6157-9') and MATCH(facettes) AGAINST('+B1' IN BOOLEAN MODE)",
+						 'req_facettes' => "Select id_notice, type_doc, facettes from notices Where (isbn='2-7427-6157-8' or isbn='978-2-7427-6157-9') and MATCH(facettes) AGAINST('+B1' IN BOOLEAN MODE) limit 15000"],
+
+						[['expressionRecherche' => 'logo',
+							'annexe' => 'MED1',
+							'facettes' => 'M52291-A15067',
+							'selection_annexe' => 'TUN;TAP',
+							'selection_sections' => '1;12;9'],
+						 'nb_mots'=> 1,
+						 'req_comptage' => sprintf($count_sql, "'+(LOGO LOGOS LOGO)' IN BOOLEAN MODE) and MATCH(facettes) AGAINST('+YMED1 +M52291 +A15067 +(YTUN YTAP)  +(S1 S12 S9)'"),
+						 'req_liste' => "Select id_notice, MATCH(alpha_titre) AGAINST(' LOGO') as rel1, MATCH(alpha_auteur) AGAINST(' LOGO') as rel2 from notices Where " . $match_axes . " AGAINST('+(LOGO LOGOS LOGO)' IN BOOLEAN MODE) and MATCH(facettes) AGAINST('+YMED1 +M52291 +A15067 +(YTUN YTAP)  +(S1 S12 S9)' IN BOOLEAN MODE) order by (rel1 * 1.5) + (rel2) desc",
+						 'req_facettes' => sprintf($facet_sql, $match_axes . " AGAINST('+(LOGO LOGOS LOGO)' IN BOOLEAN MODE) and MATCH(facettes) AGAINST('+YMED1 +M52291 +A15067 +(YTUN YTAP)  +(S1 S12 S9)' IN BOOLEAN MODE)")]];
 	}
 
 
@@ -306,6 +312,9 @@ class MoteurRechercheSimpleTest extends MoteurRechercheTestCase {
 	 */
 	public function lancerRechercheSimpleShouldBe($params, $nb_mots,$req_comptage ,$req_notices ,$req_facettes) {
 		$this->mockReqProfilAndZone($req_comptage,$params);
+		$this->criteres_recherche= new Class_CriteresRecherche();
+		$this->moteur_recherche = new Class_MoteurRecherche();
+
 		$this->criteres_recherche->setParams($params);
 		$retour = $this->moteur_recherche->lancerRecherche( $this->criteres_recherche);
 		$this->assertEquals(['nombre' => 10,
@@ -330,7 +339,7 @@ class MoteurRechercheSerieTest extends MoteurRechercheTestCase {
 						 'req_liste' => 'Select id_notice from notices Where clef_chapeau="GEO"  order by tome_alpha desc',
 						 'req_facettes' => 'Select id_notice, type_doc, facettes from notices Where clef_chapeau="GEO"  limit 15000']];
 
-			}
+	}
 
 	/**
 	 * @dataProvider expectedSql
@@ -338,6 +347,9 @@ class MoteurRechercheSerieTest extends MoteurRechercheTestCase {
 	 */
 	public function lancerRechercheSerieShouldBe($params, $req_comptage ,$req_notices ,$req_facettes) {
 		$this->mockReqProfilAndZone($req_comptage,$params);
+		$this->criteres_recherche= new Class_CriteresRecherche();
+		$this->moteur_recherche = new Class_MoteurRecherche();
+
 		$this->criteres_recherche->setParams($params);
 		$retour = $this->moteur_recherche->lancerRecherche($this->criteres_recherche);
 		$this->assertEquals(['nombre' => 10,
@@ -450,6 +462,9 @@ class MoteurRechercheRebondTest extends MoteurRechercheTestCase {
 	 */
 	public function lancerRechercheRebondShouldBe($params, $req_comptage ,$req_notices ,$req_facettes) {
 		$this->mockReqProfilAndZone($req_comptage,$params);
+		$this->criteres_recherche= new Class_CriteresRecherche();
+		$this->moteur_recherche = new Class_MoteurRecherche();
+
 		$this->criteres_recherche->setParams($params);
 		$retour = $this->moteur_recherche->lancerRecherche($this->criteres_recherche);
 		$this->assertEquals(['nombre' => 10,
@@ -495,6 +510,8 @@ class MoteurRechercheGuideeWithOneSubCategorieTest extends MoteurRechercheTestCa
 	 */
 	public function lancerRechercheGuideeShouldBe($params,$fil_ariane, $req_comptage, $req_liste,$req_facettes,$req_allcodif) {
 		$this->mockReqProfilAndZone($req_comptage,$params);
+		$this->criteres_recherche= new Class_CriteresRecherche();
+		$this->moteur_recherche = new Class_MoteurRecherche();
 
 		Class_CodifDewey::newInstanceWithId($params['rubrique'][1], ['libelle' => 'un libelle']);
 
@@ -595,6 +612,8 @@ class MoteurRechercheGuideeWithTwoSubCategorieTest extends MoteurRechercheTestCa
 			->with($req_allcodif)
 			->answers([])
 			->beStrict();
+		$this->criteres_recherche= new Class_CriteresRecherche();
+		$this->moteur_recherche = new Class_MoteurRecherche();
 
 		$this->criteres_recherche->setParams($params);
 
@@ -651,56 +670,57 @@ class MoteurRechercheCatalogueTest extends MoteurRechercheTestCase {
 
 
 	public function createCatalogueWithNouveauteAndTypeDoc() {
-		$this->_politique = Class_Catalogue::newInstanceWithId(8, [ 'libelle' => 'Dernières news',
-																																'nouveaute' => 1,
-																																'type_doc' => '1;4'
-																																]);
+		Class_Catalogue::beVolatile();
+		$this->_politique = Class_Catalogue::newInstanceWithId(88, [ 'libelle' => 'Dernières news',
+																															 'nouveaute' => 1,
+																															 'type_doc' => '1;4'
+																															 ]);
 
 		Storm_Test_ObjectWrapper::onLoaderOfModel('Class_Catalogue')
 			->whenCalled('findAllBy')
-			->with(['id' => 8])
+			->with(['id' => 88])
 			->answers( $this->_politique);
 
 	}
 
 
 	public function createCatalogueWithCoteAndAnneesParutions() {
-		$this->_parutions = Class_Catalogue::newInstanceWithId(9, [ 'libelle' => 'Dernières parutions',
-																																'annee_debut' => '1997',
-																																'cote_debut' => 'N222.77',
-																																'cote_fin' => 'V222.77']);
+		$this->_parutions = Class_Catalogue::newInstanceWithId(99, [ 'libelle' => 'Dernières parutions',
+																															 'annee_debut' => '1997',
+																															 'cote_debut' => 'N222.77',
+																															 'cote_fin' => 'V222.77']);
 
 		Storm_Test_ObjectWrapper::onLoaderOfModel('Class_Catalogue')
 			->whenCalled('findAllBy')
-			->with(['id' => 9])
+			->with(['id' => 99])
 			->answers( $this->_politique);
 	}
 
 
 	public function expectedSql() {
 		return [
-			[
-				[ 'id_catalogue' => 8	],
+						[
+						 [ 'id_catalogue' => 88	],
 
-				'req_comptage' =>  "Select count(*) from notices Where date_creation >'". $this->expected_date."' and MATCH(facettes) AGAINST('+(T1 T4)' IN BOOLEAN MODE)",
-				'req_libelle' => "select libelle from codif_dewey where id_dewey='3'",
-				'req_liste' => "Select id_notice from notices Where date_creation >'". $this->expected_date ."' and MATCH(facettes) AGAINST('+(T1 T4)' IN BOOLEAN MODE)",
+						 'req_comptage' =>  "Select count(*) from notices Where date_creation >'2012-05-03' and MATCH(facettes) AGAINST('+(T1 T4)' IN BOOLEAN MODE)",
+						 'req_libelle' => "select libelle from codif_dewey where id_dewey='3'",
+						 'req_liste' => "Select id_notice from notices Where date_creation >'2012-05-03' and MATCH(facettes) AGAINST('+(T1 T4)' IN BOOLEAN MODE)",
 
-				'req_facettes' => "Select id_notice, type_doc, facettes from notices Where date_creation >'". $this->expected_date ."' and MATCH(facettes) AGAINST('+(T1 T4)' IN BOOLEAN MODE) limit 15000"
+						 'req_facettes' => "Select id_notice, type_doc, facettes from notices Where date_creation >'2012-05-03' and MATCH(facettes) AGAINST('+(T1 T4)' IN BOOLEAN MODE) limit 15000"
 
-				],
+						],
 
-			[
-				[ 'id_catalogue' => 9 ],
+						[
+						 [ 'id_catalogue' => 99 ],
 
-				'req_comptage' =>  "Select count(*) from notices Where  cote >='N222.77' and  cote <= 'V222.77' and annee >='1997'",
-				'req_libelle' => "select libelle from codif_dewey where id_dewey='3'",
-				'req_liste' => "Select id_notice from notices Where  cote >='N222.77' and  cote <= 'V222.77' and annee >='1997'",
+						 'req_comptage' =>  "Select count(*) from notices Where  cote >='N222.77' and  cote <= 'V222.77' and annee >='1997'",
+						 'req_libelle' => "select libelle from codif_dewey where id_dewey='3'",
+						 'req_liste' => "Select id_notice from notices Where  cote >='N222.77' and  cote <= 'V222.77' and annee >='1997'",
 
-				'req_facettes' => "Select id_notice, type_doc, facettes from notices Where  cote >='N222.77' and  cote <= 'V222.77' and annee >='1997' limit 15000"
+						 'req_facettes' => "Select id_notice, type_doc, facettes from notices Where  cote >='N222.77' and  cote <= 'V222.77' and annee >='1997' limit 15000"
 
-				]
-			];
+						]
+		];
 	}
 
 
@@ -710,6 +730,9 @@ class MoteurRechercheCatalogueTest extends MoteurRechercheTestCase {
 	 */
 	public function lancerRechercheCatalogueWithNouveauteShouldBe($params,$req_comptage ,$req_libelle,$req_liste,$req_facettes) {
 		$this->mockReqProfilAndZone($req_comptage,$params);
+		$this->criteres_recherche= new Class_CriteresRecherche();
+		$this->moteur_recherche = new Class_MoteurRecherche();
+
 		$this->mock_sql
 			->whenCalled('fetchOne')
 			->with($req_libelle)
@@ -752,7 +775,155 @@ class MoteurRecherchePhonetixCollisionTest extends MoteurRechercheTestCase {
 			->with("Select count(*) from notices Where MATCH(titres, auteurs, editeur, collection, matieres, dewey) AGAINST('+(JEAN JEANS) +(GENET GENETS) +(PARCOUR PARCOURS PARKOUR)' IN BOOLEAN MODE)")
 			->answers(5000)
 			->beStrict();
+		$this->criteres_recherche= new Class_CriteresRecherche();
+		$this->moteur_recherche = new Class_MoteurRecherche();
+
 		$this->criteres_recherche->setParams(['expressionRecherche' => 'jean genet parcours']);
 		$retour = $this->moteur_recherche->lancerRecherche($this->criteres_recherche);
 	}
+}
+
+
+
+abstract class MoteurRechercheWithCatalogueTestCase extends Storm_Test_ModelTestCase {
+
+	protected $request;
+
+	public function setUp() {
+		parent::setUp();
+		Class_Exemplaire::beVolatile();
+		Class_NoticeDomain::beVolatile();
+		Class_Notice::beVolatile();
+
+		$this->fixture('Class_Catalogue',
+									 ['id' => 5,
+										'libelle' => 'My cata']);
+
+		$this->fixture('Class_Sitotheque',
+									 ['id' => 1,
+										'domaine_ids' => 5,
+										'titre' => 'mon lien',
+										'url' => 'http://monlien.com'
+									 ]);
+
+		Class_Sitotheque::find(1)->index();
+	}
+}
+
+
+
+class MoteurRechercheWithCatalogueTest extends MoteurRechercheWithCatalogueTestCase {
+
+	public function setUp() {
+		parent::setUp();
+		Class_Profil::getCurrentProfil()->setSelAnnexe('2,4,1');
+		$criteres_recherche = (new Class_CriteresRecherche())->setParams(['id_catalogue' => 5]);
+		$this->request = (new Class_MoteurRecherche())->lancerRecherche($criteres_recherche);
+	}
+
+
+  /** @test */
+	public function requestShouldContainsProfilSettings() {
+		$this->assertEquals('Select id_notice from notices Where (MATCH(facettes) AGAINST(\'Q5\' IN BOOLEAN MODE)) and MATCH(facettes) AGAINST(\'+(Y2,4,1)\' IN BOOLEAN MODE)', $this->request['req_liste']);
+	}
+}
+
+
+
+class MoteurRechercheWithCatalogueAndParamsTest extends MoteurRechercheWithCatalogueTestCase {
+	public function setUp() {
+		parent::setUp();
+
+		Class_MoteurRecherche::setTimeSource(new TimeSourceForTest('2014-12-23 09:00:00'));
+
+		Class_Profil::getCurrentProfil()->setSelAnnexe('2,4,1');
+		$this->fixture('Class_Catalogue',
+									 ['id' => 5,
+										'libelle' => 'Dernières news',
+										'nouveaute' => 1,
+										'type_doc' => '1;4' ]);
+
+		$criteres_recherche = (new Class_CriteresRecherche())
+			->setParams(['id_catalogue' => 5,
+									 'aleatoire' => 1,
+									 'tri' => 'date_creation+desc',
+									 'fil' => ';X1',
+									 'rubrique' =>'P4',
+									 'selection_annexe' => ' ',
+									 'style_liste' => '']);
+
+		$this->request = (new Class_MoteurRecherche())->lancerRecherche($criteres_recherche);
+	}
+
+
+  /** @test */
+	public function requestShouldUseProfilSettings() {
+		$this->assertEquals('Select id_notice from notices Where ((date_creation >\'2014-12-23\' and MATCH(facettes) AGAINST(\'+(T1 T4)\' IN BOOLEAN MODE)) or MATCH(facettes) AGAINST(\'Q5\' IN BOOLEAN MODE)) and MATCH(facettes) AGAINST(\'+P4* +(Y2,4,1)\' IN BOOLEAN MODE)', $this->request['req_liste']);
+	}
+}
+
+
+
+class MoteurRechercheWithCatalogueAndUrlParamsTest extends MoteurRechercheWithCatalogueTestCase {
+	public function setUp() {
+		parent::setUp();
+		Class_Profil::getCurrentProfil()->setSelAnnexe('');
+		Class_MoteurRecherche::setTimeSource(new TimeSourceForTest('2014-12-23 09:00:00'));
+
+		$this->fixture('Class_Catalogue',
+									 ['id' => 5,
+										'libelle' => 'Our domain',
+										'type_doc' => '1;4' ]);
+
+		$criteres_recherche =
+			(new Class_CriteresRecherche())->setParams(['id_catalogue' => 5,
+																									'tri' => 'date_creation+desc',
+																									'facettes' =>'T1-T10',
+																									'facette' => 'T5']);
+
+		$this->request = (new Class_MoteurRecherche())->lancerRecherche($criteres_recherche);
+	}
+
+
+  /** @test */
+	public function requestShouldUseProfilSettings() {
+		$this->assertEquals('Select id_notice from notices Where ((MATCH(facettes) AGAINST(\'+(T1 T4)\' IN BOOLEAN MODE)) or MATCH(facettes) AGAINST(\'Q5\' IN BOOLEAN MODE)) and MATCH(facettes) AGAINST(\'+T1 +T10 +T5\' IN BOOLEAN MODE)', $this->request['req_liste']);
+	}
+}
+
+
+
+class MoteurRechercheWithCatalogueWithNoSettingsAndUrlParamsTest extends MoteurRechercheWithCatalogueTestCase {
+	public function setUp() {
+		parent::setUp();
+		Class_Profil::getCurrentProfil()->setSelAnnexe('');
+		Class_MoteurRecherche::setTimeSource(new TimeSourceForTest('2014-12-23 09:00:00'));
+
+		$this->fixture('Class_Catalogue',
+									 ['id' => 5,
+										'libelle' => 'Our domain',
+										'type_doc' => '' ]);
+
+		$this->fixture('Class_Sitotheque',
+									 ['id' => 1,
+										'domaine_ids' => 5,
+										'titre' => 'mon lien',
+										'url' => 'http://monlien.com'
+									 ]);
+
+		Class_Sitotheque::find(1)->index();
+
+		$criteres_recherche =
+			(new Class_CriteresRecherche())->setParams(['id_catalogue' => 5,
+																									'tri' => 'date_creation+desc',
+																									'facettes' =>'T4-T8']);
+
+		$this->request = (new Class_MoteurRecherche())->lancerRecherche($criteres_recherche);
+	}
+
+
+  /** @test */
+	public function requestShouldUseProfilSettings() {
+		$this->assertEquals('Select id_notice from notices Where (MATCH(facettes) AGAINST(\'Q5\' IN BOOLEAN MODE)) and MATCH(facettes) AGAINST(\'+T4 +T8\' IN BOOLEAN MODE)', $this->request['req_liste']);
+	}
 }
\ No newline at end of file
diff --git a/tests/library/Class/MultiUploadTest.php b/tests/library/Class/MultiUploadTest.php
index 0262fe00f8e2afa1271b5abc98254d3f1be16b24..c3eb1ca8050ee0abcbba81dcd49f251943f13826 100644
--- a/tests/library/Class/MultiUploadTest.php
+++ b/tests/library/Class/MultiUploadTest.php
@@ -28,6 +28,7 @@ abstract class MultiUploadTestCase extends PHPUnit_Framework_TestCase {
 	protected function setUp() {
 		parent::setUp();
 		$this->_request = new Zend_Controller_Request_Http();
+		$this->_request->setParam('qqfile', 'foo');
 		$this->_tmpBasePath = realpath(sys_get_temp_dir()) . '/testAlbum';
 	}
 
@@ -36,25 +37,6 @@ abstract class MultiUploadTestCase extends PHPUnit_Framework_TestCase {
 
 
 
-class MultiUploadInitializationTest extends MultiUploadTestCase {
-	/** @test */
-	public function withIncompatibleSettingsShouldHaveError() {
-		$upload = Class_MultiUpload::newInstanceWith($this->_request)
-								->setSettingsReader(
-									Storm_Test_ObjectWrapper::on(new MultiUploadSettingReader())
-										->whenCalled('get')->with('post_max_size')
-										->answers('1K')->getWrapper()
-										->whenCalled('get')->with('upload_max_filesize')
-										->answers('1K')->getWrapper()
-								);
-
-		$this->assertFalse($upload->handleUpload('', ''));
-		$this->assertRegExp('/monter le post/', $upload->getError());
-	}
-}
-
-
-
 class MultiUploadXHRTest extends MultiUploadTestCase {
 	/** @var Class_MultiUpload */
 	protected $_upload;
@@ -122,7 +104,7 @@ class MultiUploadXHRTest extends MultiUploadTestCase {
 												->whenCalled('getHandlerFor')
 												->answers(
 													Storm_Test_ObjectWrapper::on(new Class_MultiUpload_Handler(null))
-														->whenCalled('getSize')->answers(100 * 1024 * 1024)
+														->whenCalled('getSize')->answers(600 * 1024 * 1024)
 														->getWrapper()
 												)
 												->getWrapper();
@@ -130,7 +112,7 @@ class MultiUploadXHRTest extends MultiUploadTestCase {
 		$this->_upload->setHandlerFactory($factoryWrapper);
 
 		$this->assertFalse($this->_upload->handleUpload('', ''));
-		$this->assertEquals('Fichier trop volumineux', $this->_upload->getError());
+		$this->assertContains('Fichier trop volumineux. Paramétrage du serveur : monter le post_max_size et le upload_max_filesize', $this->_upload->getError());
 	}
 
 
diff --git a/tests/library/Class/Notice/ClefAlphaTest.php b/tests/library/Class/Notice/ClefAlphaTest.php
index 2a310e02ed56cb3c3a5fbf48d31e2bea8edc21a4..91118a5c271f430ac64f846eca7e8b44f7e60d52 100644
--- a/tests/library/Class/Notice/ClefAlphaTest.php
+++ b/tests/library/Class/Notice/ClefAlphaTest.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 Notice_ClefAlphaSusAImprevuTest extends Storm_Test_ModelTestCase {
@@ -25,7 +25,7 @@ class Notice_ClefAlphaSusAImprevuTest extends Storm_Test_ModelTestCase {
 		$this->_clef = new Class_Notice_ClefAlpha('SUSALIMPREVU--BOUCQF-2-LELOMBARD-2012-1');
 
 
-		$this->_sus_imprevu = Class_Notice::newInstanceWithId(5, 
+		$this->_sus_imprevu = Class_Notice::newInstanceWithId(5,
 																													['clef_alpha' => 'SUSALIMPREVU--BOUCQF-2-LELOMBARD-2010-1']);
 
 		Storm_Test_ObjectWrapper::onLoaderOfModel('Class_Notice')
@@ -57,17 +57,17 @@ class Notice_ClefAlphaSusAImprevuTest extends Storm_Test_ModelTestCase {
 
 	/** @test */
 	public function getUniqueNoticeWithSameTitleTomeTypeDocShouldReturnSusALimprevu() {
-		$this->assertEquals($this->_sus_imprevu, 
+		$this->assertEquals($this->_sus_imprevu,
 												$this->_clef->getUniqueNoticeWithSameTitleTomeTypeDoc());
 	}
 
 
 	/** @test */
 	public function withTwoNoticesWithSameTitleShouldReturnOneWithHighestNumberOfMatchingElements() {
-		$sus_imprevu2 = Class_Notice::newInstanceWithId(5, 
+		$sus_imprevu2 = Class_Notice::newInstanceWithId(5,
 																										 ['clef_alpha' => 'SUSALIMPREVU--BOUCQF-3-LELOMBARD-2010-1']);
 
-		$sus_imprevu3 = Class_Notice::newInstanceWithId(6, 
+		$sus_imprevu3 = Class_Notice::newInstanceWithId(6,
 																										 ['clef_alpha' => 'SUSALIMPREVU--BOUCQF-3-LELOMBARD-2012-1']);
 
 		Storm_Test_ObjectWrapper::onLoaderOfModel('Class_Notice')
@@ -79,4 +79,70 @@ class Notice_ClefAlphaSusAImprevuTest extends Storm_Test_ModelTestCase {
 	}
 }
 
-?>
\ No newline at end of file
+
+
+class Notice_ClefAlphaOnPseudoNoticeTest extends Storm_Test_ModelTestCase {
+	public function expectedAlphaKeysForModels() {
+		return
+			[
+			 ['ILNEIGE------8',
+
+				'Class_Article',
+
+				['id' => 1,
+				 'titre' => 'il neige',
+				 'contenu' => 'c\'est tout blanc']
+			 ],
+
+
+			 ['MYSITO------10',
+
+				'Class_Sitotheque',
+
+				['id' => 2,
+				 'url' => 'http://web.afi-sa.net',
+				 'titre' => 'My sito',
+				 'description' => 'My sito'
+				]
+			 ],
+
+
+			 ['LEMONDE------9',
+
+				'Class_Rss',
+
+				['id' => 4,
+				 'titre' => 'Le monde',
+				 'description' => 'A la Une',
+				 'url' => 'http://rss.lemonde.fr/c/205/f/3050/index.rss']
+			 ]
+			];
+	}
+
+
+	/**
+	 * @test
+	 * @dataProvider expectedAlphaKeysForModels
+	 */
+	public function onModelAlphaKeyShouldBe($alpha_key, $classname, $data) {
+		$this->assertEquals($alpha_key, (new Class_Notice_ClefAlpha($this->fixture($classname, $data)))->keyString());
+	}
+
+
+	/** @test */
+	public function ironMaidenAlbumAlphaKeyShouldBeIRON_MAIDEN() {
+		$album = $this->fixture('Class_Album',
+														['id' => 4,
+														 'type_doc_id' => Class_TypeDoc::AUDIO_RECORD,
+														 'titre' => 'Seventh Son of a Seventh Son',
+														 'sous_titre' => 'Evil that men do',
+														 'annee' =>  '1988'
+														])
+									->addAuthor('Iron Maiden')
+									->addAuthor('Bruce Dickinson', 'Chanteur')
+									->addEditor('EMI');
+
+		$this->assertEquals('SEVENTHSONOFASEVENTHSON-EVILTHATMENDO-IRONMAIDENBRUCEDICKINSON--EMI-1988-109',
+												(new Class_Notice_ClefAlpha($album))->keyString());
+	}
+}
diff --git a/tests/library/Class/PanierNoticeTest.php b/tests/library/Class/PanierNoticeTest.php
index 5169ad7730ee6fe5105e1d4ee16a6232d4bdf825..cc4fc133e863582517771745abc30e0a5086487e 100644
--- a/tests/library/Class/PanierNoticeTest.php
+++ b/tests/library/Class/PanierNoticeTest.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
  */
 require_once 'ModelTestCase.php';
 
@@ -99,11 +99,11 @@ abstract class PanierNoticeWithThreeNoticesTestCase extends ModelTestCase {
 																													 'notices' => ';STARWARS;;INDIANAJONES;SPIDERMAN;INDIANAJONES;TEMPLEPERDU---3--1985-1;ZORK']);
 
 
-		$this->star_wars = $this->fixture('Class_Notice',['id' => 3, 
+		$this->star_wars = $this->fixture('Class_Notice',['id' => 3,
 																											'url_vignette' => 'http://premiere.com/star_wars.png',
 																											'clef_alpha' => 'STARWARS']);
 
-		$this->indiana_jones = $this->fixture('Class_Notice',['id' => 5, 
+		$this->indiana_jones = $this->fixture('Class_Notice',['id' => 5,
 																													'url_vignette' => 'NO',
 																													'clef_alpha' => 'INDIANAJONES']);
 
@@ -115,109 +115,132 @@ abstract class PanierNoticeWithThreeNoticesTestCase extends ModelTestCase {
 }
 
 
-class PanierNoticeWithThreeNoticesTest extends PanierNoticeWithThreeNoticesTestCase {
+class PanierNoticeWithThreeNoticesTest extends ModelTestCase {
 	public function setUp() {
 		parent::setUp();
 
-		Storm_Test_ObjectWrapper::onLoaderOfModel('Class_Notice')
-			->whenCalled('findAllBy')
-			->with(['clef_alpha' => ['STARWARS', 'INDIANAJONES', 'SPIDERMAN', 'TEMPLEPERDU---3--1985-1', 'ZORK'],
-							'order' => 'FIELD(clef_alpha, "STARWARS","INDIANAJONES","SPIDERMAN","TEMPLEPERDU---3--1985-1","ZORK")'])
-			->answers([$this->star_wars, $this->indiana_jones, $this->spiderman])
+		$this->fixture('Class_Notice',
+									 ['id' => 1,
+										'clef_alpha' => 'STARWARS',
+										'url_vignette' => 'http://bokeh.fr/img.png']);
+
+		$this->fixture('Class_Notice',
+									 ['id' => 2,
+										'clef_alpha' => 'INDIANAJONES']);
+
+		$this->fixture('Class_Notice',
+									 ['id' => 3,
+										'clef_alpha' => 'SPIDERMAN',
+										'url_vignette' => 'http://bokeh.fr/img.png']);
+
+		$this->fixture('Class_Notice',
+									 ['id' => 4,
+										'clef_alpha' => 'TEMPLEPERDU',
+										'type_doc' => 1]);
+
+		$this->fixture('Class_Notice',
+									 ['id' => 6,
+										'clef_alpha' => 'TEMPLEPERDU',
+										'type_doc' => 3]);
 
-			->whenCalled('findAllBy')
-			->with(['where' => 'clef_alpha like "ZORK-%"'])
-			->answers([]);
 
+		$this->fixture('Class_Notice',
+									 ['id' => 5,
+										'clef_alpha' => 'ZORK']);
+
+		$this->fixture('Class_PanierNotice',
+									 ['id' => 4,
+										'libelle' => 'Fictions',
+										'notices' => ';STARWARS;;INDIANAJONES;SPIDERMAN;INDIANAJONES;TEMPLEPERDU---3--1985-1;ZORK-456456']);
 	}
 
 
-	public function testGetAllNotices() {
-		$notices = $this->fictions->getNoticesAsArray();
-		$this->assertEquals(array($this->star_wars, $this->indiana_jones, $this->spiderman), 
-												$notices);
+	/** @test */
+	public function fictionsShouldContainsExpectedRecords() {
+		$this->assertEquals([Class_Notice::find(3),
+												 Class_Notice::find(2),
+												 Class_Notice::find(1)],
+												 Class_PanierNotice::find(4)->getNoticesAsArray());
 	}
 
 
-	public function testGetNoticesWithVignettesTrue() {
-		$notices = $this->fictions->getNoticesOnlyVignettes(true);
-		$this->assertEquals([$this->star_wars, '2' => $this->spiderman],
-												$notices);
+	/** @test */
+	public function fictionRecordsWithImgShouldReturnExpectedRecords() {
+		$this->assertEquals([Class_Notice::find(3),
+												 Class_Notice::find(1)],
+												Class_PanierNotice::find(4)->getNoticesOnlyVignettes(true));
 	}
 
 
-	public function testGetNoticesWithVignettesFalse() {
-		$notices = $this->fictions->getNoticesOnlyVignettes(false);
-		$this->assertEquals(array($this->star_wars, $this->indiana_jones, $this->spiderman),
-												$notices);
+	/** @test */
+	public function fictionsRecordsWithoutImgShouldReturnAllRecords() {
+		$this->assertEquals(Class_PanierNotice::find(4)->getNoticesAsArray(),
+												Class_PanierNotice::find(4)->getNoticesOnlyVignettes(false));
 	}
 
 
+	/** @test */
+	public function numberOfNoticesShouldBeThree() {
+		$this->assertEquals(3, Class_PanierNotice::Find(4)->numberOfNotices());
+	}
+
 	/** @test */
 	public function numberOfLostNoticesShouldBeTwo() {
-		$this->assertEquals(2, $this->fictions->numberOfLostNotices());
+		$this->assertEquals(2, Class_PanierNotice::find(4)->numberOfLostNotices());
 	}
 
 
 	/** @test */
 	public function getLostClesNoticesShouldBeTEMPLEPERDUAndZORK() {
-		$this->assertEquals(['TEMPLEPERDU---3--1985-1', 'ZORK'], $this->fictions->getLostClesNotices());
+		$this->assertEquals(['TEMPLEPERDU---3--1985-1', 'ZORK-456456'], Class_PanierNotice::find(4)->getLostClesNotices());
 	}
 
 
 	/** @test */
-	public function afterTryToFindLostClesNoticesShouldUpdateClesNoticeWithTEMPLERETROUVE() {
-		Class_Notice::getLoader()
+	public function fixLostClesNoticeShouldUpadteFictionRecordsKeyWithExpectedDatas() {
+		Storm_Test_ObjectWrapper::onLoaderOfModel('Class_Notice')
+			->whenCalled('findAllBy')
+			->with(['where' => 'clef_alpha like "ZORK-%"'])
+			->answers([Class_Notice::find(5)])
 			->whenCalled('findAllBy')
 			->with(['where' => 'clef_alpha like "TEMPLEPERDU-%"'])
-			->answers([Class_Notice::newInstanceWithId(54, ['clef_alpha' => 'TEMPLERETROUVE'])])
-			->beStrict();
-		
-		
+			->answers([Class_Notice::find(4)]);
+
 		Class_PanierNotice::fixLostClesNoticesForAll();
-		Class_PanierNotice::clearCache();
-		$this->assertEquals(['STARWARS', 'INDIANAJONES', 'SPIDERMAN', 'TEMPLERETROUVE', 'ZORK'],
+		$this->assertEquals(['STARWARS', 'INDIANAJONES', 'SPIDERMAN', 'TEMPLEPERDU', 'ZORK'],
 												Class_PanierNotice::find(4)->getClesNotices());
-
-		return Class_PanierNotice::getLoader();
 	}
 
 
 	/** @test */
 	public function afterTryToFindLostClesNoticesShouldUpdateClesNoticeWithTEMPLERETROUVEWithDoublons() {
-		$notices_found = [Class_Notice::newInstanceWithId(54, ['clef_alpha' => 'TEMPLERETROUVE','type_doc' => 1]),
-											Class_Notice::newInstanceWithId(56, ['clef_alpha' => 'TEMPLERETROUVE','type_doc' => 3])];
-		Class_Notice::getLoader()
+		Storm_Test_ObjectWrapper::onLoaderOfModel('Class_Notice')
 			->whenCalled('findAllBy')
 			->with(['where' => 'clef_alpha like "TEMPLEPERDU-%"'])
-			->answers($notices_found)
+			->answers([Class_Notice::find(6), Class_Notice::find(4)])
 
 			->whenCalled('findAllBy')
 			->with(['where' => 'clef_alpha like "TEMPLEPERDU-%"',
 							'tome_alpha' => '3'])
-			->answers($notices_found)
+			->answers([Class_Notice::find(6), Class_Notice::find(4)])
 
 			->whenCalled('findAllBy')
 			->with(['where' => 'clef_alpha like "TEMPLEPERDU-%"',
 							'tome_alpha' => '3',
 							'type_doc' => '1'])
-			->answers([$notices_found[0]])
-			->beStrict();
-				
-		Class_PanierNotice::fixLostClesNoticesForAll();
-		$this->assertEquals(['STARWARS', 'INDIANAJONES', 'SPIDERMAN', 'TEMPLERETROUVE', 'ZORK'],
-												$this->fictions->getClesNotices());
+			->answers([Class_Notice::find(4)]);
 
-		return Class_PanierNotice::getLoader();
+		Class_PanierNotice::fixLostClesNoticesForAll();
+		$this->assertEquals(['STARWARS', 'INDIANAJONES', 'SPIDERMAN', 'TEMPLEPERDU', 'ZORK'],
+												Class_PanierNotice::find(4)->getClesNotices());
 	}
-
 }
 
 
 
 
 class PanierNoticeAssociatedToCatalogueTest extends Storm_Test_ModelTestCase {
-	protected 
+	protected
 		$_fictions,
 		$_catalogue_films,
 		$_assoc_fictions_films,
@@ -227,7 +250,7 @@ class PanierNoticeAssociatedToCatalogueTest extends Storm_Test_ModelTestCase {
 	public function setUp() {
 		parent::setUp();
 		$this->_old_sql = Zend_Registry::get('sql');
-		Zend_Registry::set('sql', 
+		Zend_Registry::set('sql',
 											 $this->mock()
 											 ->whenCalled('query')->answers(true));
 
@@ -238,11 +261,13 @@ class PanierNoticeAssociatedToCatalogueTest extends Storm_Test_ModelTestCase {
 
 		$this->_catalogue_films = $this->fixture('Class_Catalogue', ['id' =>6, 'libelle' => 'Films']);
 
+		$this->_fictions->setCatalogues([$this->_catalogue_films])->save();
+
 		$this->_catalogue_cinema->setSousDomaines([$this->_catalogue_films]);
 
 		$this->_assoc_fictions_films = $this->fixture('Class_PanierNoticeCatalogue', ['id' =>19,
 																																									'id_panier' => 4,
-																																									 'id_catalogue' => 6]);
+																																									'id_catalogue' => 6]);
 	}
 
 
@@ -263,8 +288,8 @@ class PanierNoticeAssociatedToCatalogueTest extends Storm_Test_ModelTestCase {
 		$this->assertEquals([$this->_fictions], $this->_catalogue_films->getPanierNotices());
 	}
 
-	
-		/** @test */
+
+	/** @test */
 	public function catalogueCinemaShouldHaveSousDomaineFilms() {
 		$this->assertEquals([$this->_catalogue_films], $this->_catalogue_cinema->getSousDomaines());
 	}
@@ -312,10 +337,15 @@ class NewPanierNoticeForUserTest extends Storm_Test_ModelTestCase {
 	}
 }
 
-class PanierNoticeWithWrongUserIdTest extends PanierNoticeWithThreeNoticesTestCase {
+
+
+class PanierNoticeWithWrongUserIdTest extends AbstractControllerTestCase {
 
 	public function setUp() {
 		parent::setUp();
+		ZendAfi_Auth::setInstance(null);
+		Class_PanierNoticeCatalogue::beVolatile();
+
 		$this->fixture('Class_Users', ['id'=>78,
 																	 'idabon' => 888,
 																	 'login' => 'tom',
@@ -326,10 +356,15 @@ class PanierNoticeWithWrongUserIdTest extends PanierNoticeWithThreeNoticesTestCa
 																	 'login' => 'jerry',
 																	 'password' => 'toto']);
 
+		$this->fixture('Class_PanierNotice',
+									 ['id' =>4,
+										'libelle' => 'Fictions',
+										'notices' => ';STARWARS;INDIANAJONES;SPIDERMAN']);
+
+		Class_PanierNotice::find(4)->setIdabon(888)
+															 ->setIdUser(777)
+															 ->save();
 
-		$this->fictions->setIdabon(888)
-			->setIdUser(777)
-			->save();
 		$this->fixture('Class_PanierNotice', ['id' => 99,
 																					'id_user' => 78,
 																					'idabon' => 877,
@@ -341,17 +376,17 @@ class PanierNoticeWithWrongUserIdTest extends PanierNoticeWithThreeNoticesTestCa
 																					'libelle' => 'Demon']);
 
 		$voyage=$this->fixture('Class_PanierNotice', ['id' => 28,
-																						'id_user' => 78,
-																					'idabon' => 111,
-																					'libelle' => 'Voyage']);
-		
+																									'id_user' => 78,
+																									'idabon' => 111,
+																									'libelle' => 'Voyage']);
+
 		$voyage->setDateMaj('2013-12-12');
 		Class_PanierNotice::getLoader()->save($voyage);
-
 		Class_PanierNotice::fixLostUserIdForAll();
 		Class_PanierNotice::clearCache();
 	}
 
+
 	/** @test */
 	public function cartFictionsShouldChangeUserIdForIdabon() {
 		$this->assertEquals(78, Class_PanierNotice::find(4)->getIdUser());
@@ -369,16 +404,102 @@ class PanierNoticeWithWrongUserIdTest extends PanierNoticeWithThreeNoticesTestCa
 		$this->assertEquals(666, Class_PanierNotice::find(990)->getIdUser());
 	}
 
-	
+
 	/** @test */
 	public function cartVoyageShouldChangeIdUser() {
 		$this->assertEquals(11, Class_PanierNotice::find(28)->getIdUser());
 	}
 
+
   /** @test */
 	public function dateMajCartVoyageShouldNotChange() {
 		$this->assertEquals('2013-12-12', Class_PanierNotice::find(28)->getDateMaj());
 	}
+}
 
+
+
+class PanierNoticeIndexAllTest extends ModelTestCase {
+	public function setUp() {
+		parent::setUp();
+		$this->fixture('Class_Notice', ['id' => 4,
+																		'titre_principal' => 'Le Montespan',
+																		'auteur_principal' => 'Jean Teulテδゥ',
+																		'clef_alpha' => 'MONTESPAN']);
+
+		$this->fixture('Class_Catalogue',
+									 ['id' => 1,
+										'id_catalogue' => 1,
+										'libelle' => 'art']);
+
+		$this->fixture('Class_Catalogue',
+									 ['id' => 2,
+										'id_catalogue' => 2,
+										'libelle' => 'musique']);
+
+		$this->fixture('Class_PanierNotice', ['id' => 2,
+																					'id_panier' => 2,
+																					'libelle' => 'Mes BD',
+																					'date_maj' => '10/02/2011',
+																					'notices' => 'MONTESPAN',
+																					'user' => Class_Users::getIdentity()])
+				 ->setCatalogues([Class_Catalogue::find(1), Class_Catalogue::find(2)]);
+
+		$this->fixture('Class_PanierNotice', ['id' => 9,
+																					'id_panier' => 9,
+																					'libelle' => 'Orphelin',
+																					'date_maj' => '25/05/2010',
+																					'notices' => 'MONTESPAN',
+																					'user' => Class_Users::getIdentity()])
+				 ->setCatalogues([Class_Catalogue::find(1)]);
+
+		$this->fixture('Class_PanierNotice', ['id' => 15,
+																					'id_panier' => 15,
+																					'libelle' => 'Mes Romans',
+																					'date_maj' => '25/05/2010',
+																					'notices' => 'MONTESPAN',
+																					'user' => Class_Users::getIdentity()]);
+
+		$this->fixture('Class_PanierNoticeCatalogue',
+									 ['id' => 1,
+										'id_panier' => 2,
+										'id_catalogue' => 1]);
+
+		$this->fixture('Class_PanierNoticeCatalogue',
+									 ['id' => 2,
+										'id_panier' => 2,
+										'id_catalogue' => 2]);
+
+		$this->fixture('Class_PanierNoticeCatalogue',
+									 ['id' => 3,
+										'id_panier' => 9,
+										'id_catalogue' => 1]);
+
+		$this->fixture('Class_NoticeDomain',
+									 ['id' => 1,
+										'domain_id' => 1,
+										'record_alpha_key' => 'MONTESPAN',
+										'panier_id' => 15]);
+
+		Class_PanierNotice::indexAll();
+		Class_PanierNotice::find(2)->setCatalogues([Class_Catalogue::find(1)])->save();
+	}
+
+
+	/** @test */
+	public function facetOfRecordMontespanShouldContainsFacetQ1() {
+		$this->assertContains('Q1', Class_Notice::findFirstBy(['clef_alpha' => 'MONTESPAN'])->getFacettes());
+	}
+
+
+	/** @test */
+	public function allCartsShouldBeIndexed() {
+		$this->assertCount(2, Class_NoticeDomain::findAll());
+	}
+
+
+	/** @test */
+	public function recordMonstespanShouldNotHaveFacetQ2() {
+		$this->assertNotContains('Q2', Class_Notice::findFirstBy(['clef_alpha' => 'MONTESPAN'])->getFacettes());
+	}
 }
-?>
\ No newline at end of file
diff --git a/tests/library/Class/ProfilTest.php b/tests/library/Class/ProfilTest.php
index 23f8a0811e3c1a8a635cf31d8801b472660a7d42..16465d7d7d0a1ba4896ed3348340633b90b3c779 100644
--- a/tests/library/Class/ProfilTest.php
+++ b/tests/library/Class/ProfilTest.php
@@ -30,7 +30,8 @@ class ProfilVideTest extends ModelTestCase {
 	public function cfgAccueilShouldReturnArrayWithModules() {
 		$this->assertEquals(['page_css' => null,
 												 'use_parent_css' => true,
-												 'modules' => []],
+												 'modules' => [],
+												 'sitemap' => 1],
 												$this->profil_vide->getCfgAccueilAsArray());
 	}
 
@@ -1276,10 +1277,12 @@ class ProfilWithPagesCopyTest extends Storm_Test_ModelTestCase {
 
 		$profil = $this->fixture('Class_Profil',
 														 ['id' => 1,
-															'nb_divisions' => 2])
+															'nb_divisions' => 2,
+															'rewrite_url' => 'adulte'])
 			->setSubProfils([ $this->fixture('Class_Profil', ['id' => 2]),
 												$this->fixture('Class_Profil', ['id' => 3]),
 												$this->fixture('Class_Profil', ['id' => 4,
+																												'rewrite_url' => 'CD',
 																												'libelle' => 'CD']),
 
 												$this->fixture('Class_Profil', ['id' => 5])
@@ -1298,6 +1301,11 @@ class ProfilWithPagesCopyTest extends Storm_Test_ModelTestCase {
 		$this->assertEquals(4, $this->_clone->numberOfSubProfils());
 	}
 
+	/** @test */
+	public function cloneRewriteUrlShouldBeEmpty() {
+		$this->assertEmpty($this->_clone->getRewriteUrl());
+	}
+
 
 	/** @test */
 	public function cloneFirstPageLibelleShouldBeIndexedAtNouveauProfil() {
@@ -1319,6 +1327,12 @@ class ProfilWithPagesCopyTest extends Storm_Test_ModelTestCase {
 	}
 
 
+	/** @test */
+	public function clonedPageCDRewriteUrlShouldBeEmpty() {
+		$this->assertEmpty($this->_clone->getSubProfils()['CD']->getRewriteUrl());
+	}
+
+
 	/** @test */
 	public function pageCdParentShouldBeClonedProfil() {
 		$this->assertSame($this->_clone, $this->_clone->getSubProfils()['CD']->getParentProfil());
diff --git a/tests/library/Class/SitothequeTest.php b/tests/library/Class/SitothequeTest.php
index 1470ff091d8bbcfcf41466188aa4ecec05c283f1..b813c58152d76845c42288eade1578bc132a8713 100644
--- a/tests/library/Class/SitothequeTest.php
+++ b/tests/library/Class/SitothequeTest.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 SitothequeSitesFromIdsAndCategoriesTest extends Storm_Test_ModelTestCase {
 	/** @test */
@@ -29,7 +29,7 @@ class SitothequeSitesFromIdsAndCategoriesTest extends Storm_Test_ModelTestCase {
 		$cat_cinema
 			->setSousCategories(array())
 			->setSitotheques(array($site_telerama));
-		
+
 		$sites = Class_Sitotheque::getLoader()->getSitesFromIdsAndCategories(array(26,27), array(4));
 
 		$this->assertEquals(array($site_premiere, $site_allocine, $site_telerama),
@@ -38,4 +38,96 @@ class SitothequeSitesFromIdsAndCategoriesTest extends Storm_Test_ModelTestCase {
 }
 
 
-?>
\ No newline at end of file
+
+
+class SitothequeIndexAllTest extends Storm_Test_ModelTestCase {
+	public function setUp() {
+		parent::setUp();
+		Class_Notice::beVolatile();
+		Class_NoticeDomain::beVolatile();
+		Class_Exemplaire::beVolatile();
+		$this->fixture('Class_Catalogue',
+									 ['id' => 1,
+										'libelle' => 'My domain']);
+
+		$this->fixture('Class_Sitotheque',
+									 ['id' => 1,
+										'url' => 'http://web.afi-sa.net',
+										'titre' => 'My website',
+										'description' => 'My Cms First Step']);
+
+		$this->fixture('Class_Sitotheque',
+									 ['id' => 2,
+										'url' => 'http://web.afi-sa.net',
+										'titre' => 'My sito',
+										'description' => 'My sito',
+										'domaine_ids' => '1']);
+
+		$this->fixture('Class_Sitotheque',
+									 ['id' => 3,
+										'url' => 'http://web.afi-sa.net',
+										'titre' => 'My sito2',
+										'description' => 'My sito2',
+										'domaine_ids' => '1']);
+
+		Class_Sitotheque::indexAll();
+		Class_Sitotheque::find(3)->setDomaineIds('1')->save();
+	}
+
+
+	/** @test */
+	public function sitoAlphaKeyShouldBeAsExpected() {
+		$this->assertEquals('MYWEBSITE------10', Class_Sitotheque::find(1)->getAlphaKey());
+	}
+
+
+	/** @test */
+	public function threeRecordsShouldBePresent() {
+		$this->assertCount(3, Class_Notice::findAll());
+	}
+
+
+	/** @test */
+	public function domainShouldHaveMySitoRecord() {
+		$this->assertEquals('My sito', Class_NoticeDomain::findFirstBy(['domain_id' => 1])->getNotice()->getTitrePrincipal());
+	}
+
+
+	/** @test */
+	public function domainShouldHaveMySito2Record() {
+		$this->assertNotNull(Class_NoticeDomain::findFirstBy(['record_alpha_key' => 'MYSITO2------10']));
+	}
+}
+
+
+class SitothequeUnindexTest extends Storm_Test_ModelTestCase {
+	public function setUp() {
+		parent::setUp();
+		Class_Exemplaire::beVolatile();
+		Class_Notice::beVolatile();
+		Class_NoticeDomain::beVolatile();
+		$this->fixture('Class_Catalogue',
+									 ['id' => 1,
+										'libelle' => 'My domain']);
+
+		$this->fixture('Class_Catalogue',
+									 ['id' => 2,
+										'libelle' => 'My domain news']);
+
+
+		$this->fixture('Class_Sitotheque',
+									 ['id' => 1,
+										'url' => 'http://web.afi-sa.net',
+										'titre' => 'My website',
+										'description' => 'My Cms First Step',
+										'domaine_ids' => '1;2']);
+
+		Class_Sitotheque::find(1)->index();
+		Class_Sitotheque::find(1)->setDomaineIds('1')->save();
+	}
+
+	/** @test */
+	public function sitoRecordShouldNotBeLinkedToMyDomainNews() {
+		$this->assertNull(Class_NoticeDomain::findFirstBy(['domain_id' => 2]));
+	}
+}
\ No newline at end of file
diff --git a/tests/library/Class/SuggestionAchatTest.php b/tests/library/Class/SuggestionAchatTest.php
index 59c19ca4b4d0477af75c0c59ac805cb6f87e637a..07cb64b3fe436b61ada8183c7f4b52c2163718ca 100644
--- a/tests/library/Class/SuggestionAchatTest.php
+++ b/tests/library/Class/SuggestionAchatTest.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
  */
 
 
@@ -43,7 +43,7 @@ abstract class SuggestionAchatTestCase extends Storm_Test_ModelTestCase {
 		'annexes' => '4;8',
 		'sections' => '9;10']);
 
-		$type_doc=$this->fixture('Class_TypeDoc', ['id'=>Class_TypeDoc::LIVRE, 
+		$type_doc=$this->fixture('Class_TypeDoc', ['id'=>Class_TypeDoc::LIVRE,
 		'codif_type_doc' =>  $codif_type_doc,
 		'label'=> 'Livres Numériques']);
 
@@ -71,21 +71,20 @@ class SuggestionAchatMailTest extends SuggestionAchatTestCase {
 
 	public function setUp() {
 		parent::setUp();
-		
 		$this->_suggestion->sendMail('noreply@astromelun.fr');
 		$this->_sent_mail = $this->_mock_transport->sent_mail;
 	}
 
 
 	protected function assertBodyContains($text) {
-		$this->assertContains($text, 
+		$this->assertContains($text,
 													quoted_printable_decode($this->_sent_mail->getBodyText()->getContent()));
 	}
 
 
 	/** @test */
 	public function mailSubjectShouldBeSuggestionAchatHarryPotter() {
-		$this->assertEquals('Suggestion d\'achat: Harry emPotté', 			
+		$this->assertEquals('Suggestion d\'achat: Harry emPotté',
 												quoted_printable_decode($this->_sent_mail->getSubject()));
 	}
 
@@ -174,7 +173,7 @@ class SuggestionAchatMailErrorsTest extends SuggestionAchatTestCase {
 	public function withoutMailSiteShouldSendMailOnlyToUser() {
 		Class_Profil::getCurrentProfil()->setMailSuggestionAchat('');
 		$this->_suggestion->sendMail('noreply@astromelun.fr');
-		$this->assertEquals(['sbelle@gmail.com'], 
+		$this->assertEquals(['sbelle@gmail.com'],
 												$this->_mock_transport->sent_mail->getRecipients());
 	}
 
@@ -183,7 +182,7 @@ class SuggestionAchatMailErrorsTest extends SuggestionAchatTestCase {
 	public function withoutMailUserShouldSendMailOnlyToMailProfil() {
 		$this->_suggestion->getUser()->setMail('');
 		$this->_suggestion->sendMail('noreply@astromelun.fr');
-		$this->assertEquals(['laurent@afi-sa.fr', 'patrick@afi-sa.fr', 'estelle@afi-sa.fr'], 
+		$this->assertEquals(['laurent@afi-sa.fr', 'patrick@afi-sa.fr', 'estelle@afi-sa.fr'],
 												$this->_mock_transport->sent_mail->getRecipients());
 	}
 }
diff --git a/tests/library/Class/Systeme/ModulesMenu/NumilogTest.php b/tests/library/Class/Systeme/ModulesMenu/NumilogTest.php
index 4d10c49a7ce708f2ee8a8c0209663cc6c2f1d10b..c9d408c86a8afc128a0a67bbb3ed217571ad8a19 100644
--- a/tests/library/Class/Systeme/ModulesMenu/NumilogTest.php
+++ b/tests/library/Class/Systeme/ModulesMenu/NumilogTest.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
  */
 
 
@@ -33,7 +33,7 @@ class Class_Systeme_ModulesMenu_NumilogTest extends Storm_Test_ModelTestCase {
 			'date_debut' => '1999-02-10',
 			'date_fin' => '2025-09-12',
 			'user_groups' => [$this->fixture('Class_UserGroup', [
-				'id' => 20, 
+				'id' => 20,
 				'libelle' => 'Numilog'
 			])->addRight(Class_UserGroup::RIGHT_ACCES_NUMILOG)]
 		])->beAbonneSIGB();
@@ -53,4 +53,3 @@ class Class_Systeme_ModulesMenu_NumilogTest extends Storm_Test_ModelTestCase {
 		$this->assertContains('numilog', $this->menu->getDynamiqueUrl());
 	}
 }
-?>
\ No newline at end of file
diff --git a/tests/library/Class/Testing/FileSystemTest.php b/tests/library/Class/Testing/FileSystemTest.php
index b5f28db7f361cb1476bba53b42efe62aaa8fa3c5..a60586f05c177387d87daa591a9d1ebcad333b08 100644
--- a/tests/library/Class/Testing/FileSystemTest.php
+++ b/tests/library/Class/Testing/FileSystemTest.php
@@ -93,6 +93,11 @@ class Class_Testing_FileSystemTest extends PHPUnit_Framework_TestCase {
 		$this->shouldSupport('feof');
 	}
 
+	/** @test */
+	public function shouldSupportFileGetContents() {
+		$this->shouldSupport('file_get_contents');
+	}
+
 
 	protected function shouldSupport($method) {
 		try {
diff --git a/tests/library/Class/UserGroupTest.php b/tests/library/Class/UserGroupTest.php
index 3b9bf57543c5438131303089b4472164be44b720..81f3ee78c1fe159c7ecacc6505a95c5489e452b8 100644
--- a/tests/library/Class/UserGroupTest.php
+++ b/tests/library/Class/UserGroupTest.php
@@ -250,4 +250,39 @@ class UserGroupWithCategorieAssociationTest extends UserGroupsTestCase {
 }
 
 
+
+/** @see http://forge.afi-sa.fr/issues/21501 */
+class UserGroupPermissionDeletionTest extends UserGroupsTestCase {
+	public function setUp() {
+		parent::setUp();
+		$permission = $this->fixture('Class_Permission',
+																 ['id' => 23,
+																	'code' => 'VALIDATED',
+																	'module' => 'ARTICLE',
+																	'type' => 'New status',
+																	'sorting' => 101,
+																	'description' => 'Validated']);
+
+		$bib = $this->fixture('Class_Bib',
+													['id' => 0, 'libelle' => 'Portal']);
+
+		$permission->permitTo($this->_stagiaires, $bib);
+		$this->_stagiaires->delete();
+	}
+
+
+	/** @test */
+	public function groupPermissionShouldBeDeleted() {
+		$this->assertNull(Class_UserGroup_Permission::findFirstBy(['id_permission' => 23,
+																															 'id_group' => $this->_stagiaires->getId()]));
+	}
+
+
+	/** @test */
+	public function permissionShouldNotBeDelete() {
+		$this->assertNotNull(Class_Permission::find(23));
+	}
+}
+
+
 ?>
\ No newline at end of file
diff --git a/tests/library/Class/UsersTest.php b/tests/library/Class/UsersTest.php
index 9dea2a48af6abbcbbfe76b6deb956b45f0186ba1..ebf2df944b981b960b5894bc5d371c90c7687a72 100644
--- a/tests/library/Class/UsersTest.php
+++ b/tests/library/Class/UsersTest.php
@@ -327,49 +327,99 @@ class UsersTestDelete extends ModelTestCase {
 
 
 class UsersTestAssociations extends ModelTestCase {
-
 	public function setUp() {
-		$this->haute_savoie = Class_Zone::getLoader()
-			->newInstanceWithId(74)
-			->setLibelle('Haute-Savoie');
-
-		$this->annecy = Class_Bib::getLoader()
-			->newInstanceWithId(23)
-			->setIdZone(74)
-			->setLibelle('Annecy');
-
-		$this->cran = Class_Bib::getLoader()
-			->newInstanceWithId(74960)
-			->setIdZone(74)
-			->setLibelle('Cran');
-
-		$this->fanfoue = Class_Users::getLoader()
-			->newInstanceWithId(98)
-			->setIdSite(23)
-			->setRoleLevel(ZendAfi_Acl_AdminControllerRoles::ADMIN_BIB)
-			->setPseudo('Fanfoue');
-
-		$this->robert = Class_Users::getLoader()
-			->newInstanceWithId(43)
-			->setIdSite(74960)
-			->setRoleLevel(ZendAfi_Acl_AdminControllerRoles::ADMIN_BIB)
-			->setPseudo('Robert');
-
-		$this->marcel = Class_Users::getLoader()
-			->newInstanceWithId(47)
-			->setRoleLevel(ZendAfi_Acl_AdminControllerRoles::ADMIN_PORTAIL)
-			->setPseudo('Marcel');
-
-		$this->calimero = Class_Users::getLoader()
-			->newInstanceWithId(97)
-			->setRoleLevel(ZendAfi_Acl_AdminControllerRoles::ABONNE_SIGB)
-			->setPseudo('Calimero');
-
-		$this->article_concert = Class_Article::getLoader()
-			->newInstanceWithId(29)
-			->setCategorie(Class_ArticleCategorie::getLoader()
-										 ->newInstanceWithId(70)
-										 ->setIdSite(23));
+		Storm_Model_Loader::defaultToVolatile();
+		$this->setupPermissions();
+
+		$this->haute_savoie = $this->fixture('Class_Zone',
+																				 ['id' => 74, 'libelle' => 'Haute-Savoie']);
+
+		$this->annecy = $this->fixture('Class_Bib',
+																	 ['id' => 23,
+																		'libelle' => 'Annecy',
+																		'zone' => $this->haute_savoie]);
+
+		$this->cran = $this->fixture('Class_Bib',
+																	 ['id' => 74960,
+																		'libelle' => 'Cran',
+																		'zone' => $this->haute_savoie]);
+
+		$this->fanfoue_group = $this->fixture('Class_UserGroup',
+																					['id' => 34, 'libelle' => 'Testing group']);
+
+		$this->fanfoue = $this
+			->fixture('Class_Users',
+								['id' => 98,
+								 'bib' => $this->annecy,
+								 'role_level' => ZendAfi_Acl_AdminControllerRoles::ADMIN_BIB,
+								 'pseudo' => 'Fanfoue',
+								 'login' => 'Fanfoue',
+								 'password' => 'Fanfoue',
+								 'user_groups' => [$this->fanfoue_group]]);
+
+		$this->robert = $this
+			->fixture('Class_Users',
+								['id' => 43,
+								 'bib' => $this->cran,
+								 'role_level' => ZendAfi_Acl_AdminControllerRoles::ADMIN_BIB,
+								 'pseudo' => 'Robert',
+								 'login' => 'Robert',
+								 'password' => 'Robert']);
+
+		$this->marcel = $this
+			->fixture('Class_Users',
+								['id' => 47,
+								 'role_level' => ZendAfi_Acl_AdminControllerRoles::ADMIN_PORTAIL,
+								 'pseudo' => 'Marcel',
+								 'login' => 'Marcel',
+								 'password' => 'Marcel']);
+
+		$this->calimero = $this
+			->fixture('Class_Users',
+								['id' => 97,
+								 'bib' => $this->cran,
+								 'idabon' => 'X03339238',
+								 'role_level' => ZendAfi_Acl_AdminControllerRoles::ABONNE_SIGB,
+								 'pseudo' => 'Calimero',
+								 'login' => 'Calimero',
+								 'password' => 'Calimero']);
+
+		$this->concert_categorie = $this->fixture('Class_ArticleCategorie',
+																							['id' => 70, 'bib' => $this->annecy,
+																							 'libelle' => 'Testing category']);
+
+		$this->article_concert = $this
+			->fixture('Class_Article',
+								['id' => 29,
+								 'categorie' => $this->concert_categorie,
+								 'titre' => 'Ibrahim Aslouff',
+								 'contenu' => 'Un concert top']);
+
+		Class_Permission::createArticle()
+			->permitTo($this->fanfoue_group, $this->concert_categorie);
+	}
+
+
+	protected function setupPermissions() {
+		foreach([[1, 'CATEGORY', 'ARTICLE', 'Sur la catégorie', 1, 'Créer des sous-catégories'],
+						 [2, 'ARTICLE', 'ARTICLE', 'Sur la catégorie', 2, 'Créer des articles'],
+						 [3, 'PENDING', 'ARTICLE', 'Workflow', 1, 'À valider'],
+						 [4, 'VALIDATED', 'ARTICLE', 'Workflow', 101, 'Validé'],
+						 [5, 'REFUSED', 'ARTICLE', 'Workflow', 102, 'Refusé'],
+						 [6, 'ARCHIVED', 'ARTICLE', 'Workflow', 103, 'Archivé']] as $fixture)
+			$this->fixture('Class_Permission',
+										 ['id' => $fixture[0],
+											'code' => $fixture[1],
+											'module' => $fixture[2],
+											'type' => $fixture[3],
+											'sorting' => $fixture[4],
+											'description' => $fixture[5]]);
+	}
+
+
+	public function tearDown() {
+		Storm_Model_Loader::defaultToDb();
+		parent::tearDown();
 	}
 
 	/** @test */
diff --git a/tests/library/Class/WebService/CyberlibrisTest.php b/tests/library/Class/WebService/CyberlibrisTest.php
index e96f935614b279240edfcb155d9733cbddb4a0bc..7bcd79c6188df9d0c91bccc77e6e52e5280c731d 100644
--- a/tests/library/Class/WebService/CyberlibrisTest.php
+++ b/tests/library/Class/WebService/CyberlibrisTest.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
  */
 
 
@@ -24,8 +24,9 @@ class CyberlibrisHarvestSaveTest extends  Storm_Test_ModelTestCase {
 	public function setUp() {
 		parent::setUp();
 
-		Class_AdminVar::newInstanceWithId('CYBERLIBRIS_URL', ['valeur' => 'http://oai-bibliovox.cyberlibris.fr/oai.aspx']); 
-
+		Class_AdminVar::newInstanceWithId('CYBERLIBRIS_URL', ['valeur' => 'http://oai-bibliovox.cyberlibris.fr/oai.aspx']);
+		Class_Notice::beVolatile();
+		Class_Exemplaire::beVolatile();
 		Class_Album::beVolatile();
 
 		$catalogue_xml = file_get_contents(realpath(dirname(__FILE__)). '/../../../fixtures/cyberlibris_oai.xml');
diff --git a/tests/library/Class/WebService/SIGB/CarthameTest.php b/tests/library/Class/WebService/SIGB/CarthameTest.php
index 4161e563e170f2bdd897998a9f327dc4cb467d6d..3f2dc89cac4e4e6eb66a537af038fd2dfe7ac6ed 100644
--- a/tests/library/Class/WebService/SIGB/CarthameTest.php
+++ b/tests/library/Class/WebService/SIGB/CarthameTest.php
@@ -199,9 +199,9 @@ class CarthameAnonymousNoticeTest extends CarthameOperationTestCase {
 	}
 
 	/** @test */
-	public function firstCopyShouldBeNotBeReservable() {
+	public function firstCopyShouldBeBeReservable() {
 		$copies = $this->anonymous->getExemplaires();
-		$this->assertFalse($copies[0]->isReservable());
+		$this->assertTrue($copies[0]->isReservable());
 	}
 
 	/** @test */
diff --git a/tests/library/Class/WebService/SIGB/ExemplaireTest.php b/tests/library/Class/WebService/SIGB/ExemplaireTest.php
index 27357a2f11974bcf5d87edaf74441030eb24733a..1894f05fff25e2bd4b7e6acfa8515173ba709554 100644
--- a/tests/library/Class/WebService/SIGB/ExemplaireTest.php
+++ b/tests/library/Class/WebService/SIGB/ExemplaireTest.php
@@ -16,27 +16,19 @@
  *
  * 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 ExemplaireSIGBTest extends Storm_Test_ModelTestCase {
 	public function setUp() {
 		parent::setUp();
-
-		$this->profil = new Class_Profil();
-		$this->profil
-			->setId(4)
-			->setCfgNotice(array("exemplaires" => array()));
-		Class_Profil::setCurrentProfil($this->profil);
+		Class_Profil::setCurrentProfil($this->fixture('Class_Profil',
+																									['id' => 4,
+																									 'libelle' => 'default profil']));
 
 		$this->ex = new Class_WebService_SIGB_Exemplaire(2);
 	}
 
-	public function tearDown() {
-		$this->profil->setCfgNotice($this->profil->getDefaultValue('cfg_notice'));
-		parent::tearDown();
-	}
-
 
 	/** @test */
 	public function dispoEnPretShouldBeEnPretByDefault() {
@@ -47,8 +39,10 @@ class ExemplaireSIGBTest extends Storm_Test_ModelTestCase {
 
 	/** @test */
 	public function dispoEnPretShouldBeEmprunteAsInProfilParams() {
-		$this->profil
-			->setCfgNotice(array("exemplaires" => array('en_pret' => 'Emprunté')));
+		$updated_cfg = array_merge(Class_Profil::getCurrentProfil()->getCfgNoticeAsArray(),
+															 ['exemplaires' => ['en_pret' => 'Emprunté']]);
+
+		Class_Profil::getCurrentProfil()->setCfgNotice($updated_cfg);
 
 		$this->ex->setDisponibiliteEnPret();
 		$this->assertEquals('Emprunté', $this->ex->getDisponibilite());
diff --git a/tests/library/Class/WebService/SIGB/KohaTest.php b/tests/library/Class/WebService/SIGB/KohaTest.php
index 1ba70fcda0e87b9d9be21d2192fbb224c5769a5c..7ba56e354542c5befe7424f73c94876861eb6d87 100644
--- a/tests/library/Class/WebService/SIGB/KohaTest.php
+++ b/tests/library/Class/WebService/SIGB/KohaTest.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
  */
 include_once('KohaFixtures.php');
 
@@ -58,8 +58,10 @@ abstract class KohaTestCase extends PHPUnit_Framework_TestCase {
 	protected $service;
 
 	public function setUp() {
-		//Pour avoir les textes de prets par defaut
-		Class_Profil::getCurrentProfil()->setCfgNotice(array('exemplaires' => array()));
+		Class_Profil::setCurrentProfil($this->fixture('Class_Profil',
+																									['id' => 1,
+																									 'libelle' => 'Actu']));
+
 		Class_AdminVar::newInstanceWithId('KOHA_MULTI_SITES', ['valeur' => '' ]);
 		$this->mock_web_client = $this->getMock('Class_WebService_SimpleWebClient');
 
@@ -371,18 +373,18 @@ class KohaGetEmprunteurLaureAfondTest extends KohaTestCase {
 	}
 
 
-	/** 
-	 * @test 
+	/**
+	 * @test
 	 * @depends firstReservationShouldExists
 	 */
 	public function reservationTitleShouldBeHarryPotter($hold) {
-		$this->assertEquals("Harry Potter et la chambre des secrets", 
+		$this->assertEquals("Harry Potter et la chambre des secrets",
 												$hold->getTitre());
 	}
 
 
-	/** 
-	 * @test 
+	/**
+	 * @test
 	 * @depends firstReservationShouldExists
 	 */
 	public function reservationAuteurShouldBeJKRowling($hold) {
@@ -403,7 +405,7 @@ class KohaGetEmprunteurLaureAfondTest extends KohaTestCase {
 
 	/** @test */
 	public function firstReservationPickupLocationLabelShouldBeBiblioMeuse() {
-		$this->assertEquals('Biblio Meuse', 
+		$this->assertEquals('Biblio Meuse',
 												$this->laurent->getReservationAt(0)->getPickupLocationLabel());
 	}
 
@@ -440,14 +442,14 @@ class KohaGetEmprunteurLaureAfondTest extends KohaTestCase {
 
 	/** @test */
 	public function secondReservationPickupLocationLabelShouldBeMontmedy() {
-		$this->assertEquals('Montmedy', 
+		$this->assertEquals('Montmedy',
 												$this->laurent->getReservationAt(1)->getPickupLocationLabel());
 	}
 
 
 	/** @test */
 	public function secondReservationGetEtatShouldBeEnAttente() {
-		$this->assertEquals('En attente', 
+		$this->assertEquals('En attente',
 												$this->laurent->getReservationAt(1)->getEtat());
 	}
 
@@ -466,7 +468,7 @@ class KohaGetEmprunteurLaureAfondTest extends KohaTestCase {
 
 	/** @test */
 	function thirdReservationEtatShouldBeExemplaireMisDeCote() {
-		$this->assertEquals('Exemplaire mis de côté', 
+		$this->assertEquals('Exemplaire mis de côté',
 												$this->laurent->getReservationAt(2)->getEtat());
 	}
 }
@@ -492,7 +494,7 @@ class KohaGetEmprunteurJeanAndreWithIdSIGBTest extends KohaTestCase {
 			->will($this->returnValue(KohaFixtures::xmlGetPatronInfoJeanAndre()));
 
 		$this->jean = $this->service->getEmprunteur(
-			$this->fixture('Class_Users', ['id' => 43, 
+			$this->fixture('Class_Users', ['id' => 43,
 																		 'login' => 'JEAN',
 																		 'password' => 'zork',
 																		 'id_sigb' => '01234']));
@@ -532,7 +534,7 @@ class KohaGetEmprunteurJeanAndreWithIdSIGBTest extends KohaTestCase {
 
 	/** @test */
 	public function firstEmpruntTitreShouldBeLaGuitareEn10Lecons() {
-		$this->assertEquals('La guitare en 10 leçons', 
+		$this->assertEquals('La guitare en 10 leçons',
 												$this->jean->getEmpruntAt(0)->getTitre());
 	}
 
@@ -551,7 +553,7 @@ class KohaGetEmprunteurJeanAndreWithIdSIGBTest extends KohaTestCase {
 
 	/** @test */
 	public function firstEmpruntLibraryShouldBeTestingBranch() {
-		$this->assertEquals('Testing branch', 
+		$this->assertEquals('Testing branch',
 												$this->jean->getEmpruntAt(0)->getBibliotheque());
 	}
 
@@ -576,7 +578,7 @@ class KohaGetEmprunteurJeanAndreWithIdSIGBTest extends KohaTestCase {
 
 	/** @test */
 	public function secondEmpruntAuteurShouldBeRobertLouisStevenson() {
-		$this->assertEquals('Robert Louis Stevenson', 
+		$this->assertEquals('Robert Louis Stevenson',
 												$this->jean->getEmpruntAt(1)->getAuteur());
 	}
 
diff --git a/tests/library/Class/WebService/SIGB/NanookTest.php b/tests/library/Class/WebService/SIGB/NanookTest.php
index 15e2e1cfc17891c6eb68a8d14ac671a516ec5cbd..dbe120805e5812bc3993e18c83f72e4af141c843 100644
--- a/tests/library/Class/WebService/SIGB/NanookTest.php
+++ b/tests/library/Class/WebService/SIGB/NanookTest.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
  */
 
 include_once 'NanookFixtures.php';
@@ -171,24 +171,24 @@ class NanookHtmlResponseErrorTest extends NanookServiceErrorTestCase {
 
 	/** @test */
 	public function reservationsShouldBeEmpty() {
-		$this->assertEmpty($this->_emprunteur->getReservations());		
+		$this->assertEmpty($this->_emprunteur->getReservations());
 	}
 
 
 	/** @test */
 	public function pretsRetardShouldBeEmpty() {
-		$this->assertEmpty($this->_emprunteur->getPretsEnRetard());		
+		$this->assertEmpty($this->_emprunteur->getPretsEnRetard());
 	}
 
 
 	/** @test */
 	public function empruntsShouldBeEmpty() {
-		$this->assertEmpty($this->_emprunteur->getEmprunts());		
+		$this->assertEmpty($this->_emprunteur->getEmprunts());
 	}
 
 	/** @test */
 	public function validShouldBeFalse() {
-		$this->assertFalse($this->_emprunteur->isValid());		
+		$this->assertFalse($this->_emprunteur->isValid());
 	}
 }
 
@@ -203,7 +203,9 @@ class NanookGetNoticeLiliGrisbiAndCoTest extends NanookTestCase {
 	public function setUp() {
 		parent::setUp();
 		//Pour avoir les textes de prets par defaut
-		Class_Profil::getCurrentProfil()->setCfgNotice(array('exemplaires' => array()));
+		Class_Profil::setCurrentProfil($this->fixture('Class_Profil',
+																									['id' => 1,
+																									 'libelle' => 'default profil']));
 
 		$this->_mock_web_client
 			->whenCalled('open_url')
@@ -408,13 +410,13 @@ class NanookGetEmprunteurChristelDelpeyrouxTest extends NanookTestCase {
 
 	/** @test */
 	public function emprunteurShouldBeValid() {
-		$this->assertTrue($this->_emprunteur->isValid());		
+		$this->assertTrue($this->_emprunteur->isValid());
 	}
 
 
 	/** @test */
 	public function emprunteurPasswordShouldBe2002() {
-		$this->assertEquals('2002', $this->_emprunteur->getPassword());		
+		$this->assertEquals('2002', $this->_emprunteur->getPassword());
 	}
 
 
@@ -459,7 +461,7 @@ class NanookGetEmprunteurChristelDelpeyrouxTest extends NanookTestCase {
 		$this->assertEquals('1 rue des fleurs', $this->_emprunteur->getAdresse());
 	}
 
-	
+
 	/** @test */
 	public function codePostalShouldBe74000() {
 		$this->assertEquals('74000', $this->_emprunteur->getCodePostal());
@@ -488,7 +490,7 @@ class NanookGetEmprunteurChristelDelpeyrouxTest extends NanookTestCase {
 		$this->assertEquals('01 23 45 67 89', $this->_chrystel->getTelephone());
 		$this->assertEquals('1978-11-10', $this->_chrystel->getNaissance());
 	}
-		
+
 
 	/** @test */
 	public function nbEmpruntsShouldBeThree() {
@@ -631,7 +633,7 @@ class NanookGetEmprunteurChristelDelpeyrouxTest extends NanookTestCase {
 
 	/** @test */
 	public function firstReservationEtatShouldBePasDisponibleAvantLe15Juin2012() {
-		$this->assertEquals('Pas disponible avant le 15/06/2012', 
+		$this->assertEquals('Pas disponible avant le 15/06/2012',
 												$this->_emprunteur->getReservationAt(0)->getEtat());
 	}
 
@@ -744,7 +746,7 @@ class NanookGetEmprunteurAuthenticateTest extends NanookTestCase {
 			->whenCalled('open_url')
 			->with('http://localhost:8080/afi_Nanook/ilsdi/service/GetPatronInfo/patronId/1')
 			->answers(NanookFixtures::xmlGetPatronError());
-				
+
 		$emprunteur = $this->_service->getEmprunteur($user = Class_Users::newInstance()
 																								 ->setLogin('90175000410218')
 																								 ->setPassword('1989'));
@@ -755,7 +757,7 @@ class NanookGetEmprunteurAuthenticateTest extends NanookTestCase {
 	}
 
 
-    
+
 	/** @test */
 	public function withWrongPasswordEmprunteurShouldNotBeValid() {
 		$this->_mock_web_client
@@ -766,7 +768,7 @@ class NanookGetEmprunteurAuthenticateTest extends NanookTestCase {
 			->whenCalled('open_url')
 			->with('http://localhost:8080/afi_Nanook/ilsdi/service/GetPatronInfo/patronId/1')
 			->answers(NanookFixtures::xmlGetPatronChristelDelpeyroux());
-				
+
 		$emprunteur = $this->_service->getEmprunteur($user = Class_Users::newInstance()
 																								 ->setLogin('90175000410218')
 																								 ->setPassword('1989')
@@ -777,7 +779,7 @@ class NanookGetEmprunteurAuthenticateTest extends NanookTestCase {
 		$this->assertFalse($emprunteur->isValid());
 		$this->assertEmpty($emprunteur->getPassword());
 	}
-    
+
 }
 
 
@@ -947,13 +949,13 @@ class NanookSaveEmprunteurPostTest extends NanookTestCase {
 		parent::setup();
 		$this->_mock_web_client
 			->whenCalled('postData')
-			->with('http://localhost:8080/afi_Nanook/ilsdi/service/UpdatePatronInfo/patronId/1', 
+			->with('http://localhost:8080/afi_Nanook/ilsdi/service/UpdatePatronInfo/patronId/1',
 						 ['password' => '987654@afi-sa.fr',
 							'mail' => 'gloas@afi-sa.fr',
 							'phoneNumber' => '0123456789'])
 			->answers(NanookFixtures::xmlGetPatronChristelDelpeyrouxAfterPost())
 			->beStrict();
-		
+
 		$this->_crystel = Class_WebService_SIGB_Emprunteur::newInstance(1)
 			->setPassword('987654@afi-sa.fr')
 			->setEmail('gloas@afi-sa.fr')
@@ -966,7 +968,7 @@ class NanookSaveEmprunteurPostTest extends NanookTestCase {
 	public function christelDelPeyrouxShoudlHaveGloasAsEmail() {
 		$this->assertEquals('gloas@afi-sa.fr', $this->_emprunteur->getEmail());
 	}
-	
+
 	 /** @test **/
 	public function christelDelPeyrouxShoudlHave0123456789AsTelpehone() {
 		$this->assertEquals('0123456789', $this->_emprunteur->getTelephone());
@@ -992,13 +994,13 @@ class NanookSaveEmprunteurRecordNotFoundTest extends NanookTestCase {
 		parent::setup();
 		$this->_mock_web_client
 			->whenCalled('postData')
-			->with('http://localhost:8080/afi_Nanook/ilsdi/service/UpdatePatronInfo/patronId/1', 
+			->with('http://localhost:8080/afi_Nanook/ilsdi/service/UpdatePatronInfo/patronId/1',
 						 ['password' => '987654@afi-sa.fr',
 							'mail' => 'gloas@afi-sa.fr',
 							'phoneNumber' => '0123456789'])
 			->answers(NanookFixtures::xmlRecordNotFound())
 			->beStrict();
-		
+
 		$this->_crystel = Class_WebService_SIGB_Emprunteur::newInstance(1)
 			->setPassword('987654@afi-sa.fr')
 			->setEmail('gloas@afi-sa.fr')
@@ -1012,7 +1014,7 @@ class NanookSaveEmprunteurRecordNotFoundTest extends NanookTestCase {
 	public function christelDelPeyrouxShoudlNotBeValidWithRecordNotFoundError() {
 		$this->assertFalse($this->_emprunteur->isValid());
 	}
-	
+
 }
 
 
@@ -1023,13 +1025,13 @@ class NanookSaveEmprunteurUpdatePatronErrorTest extends NanookTestCase {
 		parent::setup();
 		$this->_mock_web_client
 			->whenCalled('postData')
-			->with('http://localhost:8080/afi_Nanook/ilsdi/service/UpdatePatronInfo/patronId/1', 
+			->with('http://localhost:8080/afi_Nanook/ilsdi/service/UpdatePatronInfo/patronId/1',
 						 ['password' => '987654@afi-sa.fr',
 							'mail' => 'gloas@afi-sa.fr',
 							'phoneNumber' => '0123456789'])
 			->answers(NanookFixtures::xmlUpdatePatronError())
 			->beStrict();
-		
+
 		$this->_crystel = Class_WebService_SIGB_Emprunteur::newInstance(1)
 			->setPassword('987654@afi-sa.fr')
 			->setEmail('gloas@afi-sa.fr')
@@ -1042,7 +1044,5 @@ class NanookSaveEmprunteurUpdatePatronErrorTest extends NanookTestCase {
 	public function christelDelPeyrouxShoudlNotBeValidWithUpdatePatron() {
 		$this->assertFalse($this->_emprunteur->isValid());
 	}
-	
-}
 
-?>
+}
diff --git a/tests/library/Class/WebService/SIGB/PergameTest.php b/tests/library/Class/WebService/SIGB/PergameTest.php
index 4f217a8d1e58826311b18d32542c78d6e2aa9054..bf02aaf64610899a66f1b5cfc739cfd11eadaed6 100644
--- a/tests/library/Class/WebService/SIGB/PergameTest.php
+++ b/tests/library/Class/WebService/SIGB/PergameTest.php
@@ -16,131 +16,137 @@
  *
  * 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 PergameServiceTestCase extends Storm_Test_ModelTestCase {
 	public function setUp() {
 		parent::setUp();
 
-		$this->notice_potter = Class_Notice::getLoader()
-			->newInstanceWithId(1)
-			->setTitrePrincipal('Harry Potter')
-			->setAuteurPrincipal('JK Rowling');
-	
-		$this->potter_annecy = Class_Exemplaire::getLoader()
-			->newInstanceWithId(23)
-			->setIdNotice(1)
-			->setCodeBarres('A-23')
-			->setIdOrigine('1HP')
-			->setActivite('En rayon - Discotheque')
-			->setIdBib(1);
-
-		$this->potter_cran_prete = Class_Exemplaire::getLoader()
-			->newInstanceWithId(24)
-			->setIdNotice(1)
-			->setCodeBarres('C-24')
-			->setIdOrigine('1HP')
-			->setActivite('En rayon')
-			->setIdBib(2);
-
-		$this->potter_cran_reserve = Class_Exemplaire::getLoader()
-			->newInstanceWithId(25)
-			->setIdNotice(1)
-			->setCodeBarres('C-25')
-			->setIdOrigine('1HP')
-			->setActivite('En rayon')
-			->setIdBib(2);
-
-		$this->potter_cran_dispo = Class_Exemplaire::getLoader()
-			->newInstanceWithId(26)
-			->setIdNotice(1)
-			->setCodeBarres('C-26')
-			->setIdOrigine('1HP')
-			->setActivite('En rayon')
-			->setIdBib(2);
-
-
-		Class_IntBib::getLoader()
-			->newInstanceWithId(1)
-			->setCommParams(serialize(array("Autoriser_docs_disponibles" => 1)));
-
-		Class_IntBib::getLoader()
-			->newInstanceWithId(2)
-			->setCommParams(serialize(array("Autoriser_docs_disponibles" => 0)));
-
-		Class_Bib::getLoader()
-			->newInstanceWithId(2)
-			->setLibelle('Cran-Gevrier');
-
-
-		Storm_Test_ObjectWrapper::onLoaderOfModel('Class_Exemplaire')
-			->whenCalled('findFirstBy')
-			->with(array('id_bib' => 2, 'code_barres' => 'C-24'))
-			->answers($this->potter_cran_prete)
-
-			->whenCalled('findFirstBy')
-			->with(array('id_origine' => '1HP'))
-			->answers($this->potter_annecy);
-
+		Class_Profil::setCurrentProfil($this->fixture('Class_Profil',
+																									['id' => 1,
+																									 'libelle' => 'Pergame FTW']));
+
+
+		$this->fixture('Class_Notice',
+									 ['id' => 5,
+										'titre_principal' => 'Trouble maker',
+										'auteur_princiapl' => 'pergame']);
+
+		$this->notice_potter = $this->fixture('Class_Notice',
+																					['id' => 1,
+																					 'titre_principal' => 'Harry Potter',
+																					 'auteur_principal' => 'JK Rowling']);
+
+		$this->potter_annecy = $this->fixture('Class_Exemplaire',
+																					['id' => 23,
+																					 'id_notice' => 1,
+																					 'code_barres' => 'A-23',
+																					 'id_origine' => '1HP',
+																					 'activite' => 'En rayon - Discotheque',
+																					 'id_bib' => 1]);
+
+		$this->potter_cran_prete = $this->fixture('Class_Exemplaire',
+																							['id' => 24,
+																							 'id_notice' => 1,
+																							 'code_barres' => 'C-24',
+																							 'id_origine' => '1HP',
+																							 'activite' => 'En prêt',
+																							 'id_bib' => 2]);
+
+		$this->potter_cran_reserve = $this->fixture('Class_Exemplaire',
+																								['id' => 25,
+																								 'id_notice' => 1,
+																								 'code_barres' => 'C-25',
+																								 'id_origine' => '1HP',
+																								 'activite' => 'En rayon',
+																								 'zone995' => 'I\' from sigb',
+																								 'id_bib' => 2]);
+
+		$this->potter_cran_dispo = $this->fixture('Class_Exemplaire',
+																							['id' => 26,
+																							 'id_notice' => 1,
+																							 'code_barres' => 'C-26',
+																							 'id_origine' => '1HP',
+																							 'activite' => 'En rayon',
+																							 'id_bib' => 2]);
+
+		$this->fixture('Class_Exemplaire',
+									 ['id' => 27,
+										'id_notice' => 5,
+										'code_barres' => 'C-24',
+										'id_origine' => '1HP',
+										'activite' => 'En rayon',
+										'id_bib' => 2]);
+
+
+		$this->fixture('Class_IntBib',
+									 ['id' => 1])
+				 ->setCommParams(serialize(["Autoriser_docs_disponibles" => 1]))
+				 ->save();
+
+		$this->fixture('Class_IntBib',
+									 ['id' => 2])
+				 ->setCommParams(serialize(['Autoriser_docs_disponibles' => 0]))
+				 ->save();
+
+		$this->fixture('Class_Bib',
+									 ['id' => 2,
+										'libelle' => 'Cran-Gevrier']);
 
 		$this->_service_cran = Class_WebService_SIGB_Pergame_Service::getService(2);
 	}
 }
 
 
+
 class PergameServiceGetEmprunteurTest extends PergameServiceTestCase {
 	public function setUp() {
 		parent::setUp();
 
-		Storm_Test_ObjectWrapper::onLoaderOfModel('Class_Pret')
-			->whenCalled('findAllBy')
-			->with(array('IDABON' => 23, 'ORDREABON' => 2, 'EN_COURS' => 1))
-			->answers(array(Class_Pret::getLoader()
-											->newInstanceWithId(59)
-											->setCodeBarres('C-24')
-											->setIdNoticeOrigine(1)
-											->setIdPergame('1HP')
-											->setEnCours(1)
-											->setDateRetour('2010-09-07')
-											->setIdSite(2)
-											->setIdabon(23)
-											->setOrdreabon(2)))
-
-			->whenCalled('countBy')
-			->with(array('ID_NOTICE_ORIGINE' => 1, 
-									 'EN_COURS' => 1))
-			->answers(2);
+		Class_Notice::beVolatile();
+
+		$this->fixture('Class_Pret',
+									 ['id' => 59,
+										'code_barres' => 'C-24',
+										'id_notice_origine' => 1,
+										'id_pergame' => '1HP',
+										'en_cours' => 1,
+										'date_retour' => '2010-09-07',
+										'id_site' => 2,
+										'idabon' => 23,
+										'ordreabon' => 2]);
+
+		$this->fixture('Class_Reservation',
+									 ['id' => 76,
+										'id_notice_origine' => '1HP',
+										'id_pergame' => '1HP',
+										'date_resa' => '2011-12-25',
+										'id_site' => 2,
+										'idabon' => 23,
+										'ordreabon' => 2]);
+
+		$this->fixture('Class_Reservation',
+									 ['id' => 77,
+										'id_notice_origine' => '3HP',
+										'id_pergame' => '3HP',
+										'date_resa' => '2011-12-25',
+										'id_site' => 0,
+										'idabon' => 23,
+										'ordreabon' => 2]);
 
 		Storm_Test_ObjectWrapper::onLoaderOfModel('Class_Reservation')
-			->whenCalled('findAllBy')
-			->with(['IDABON' => 23, 'ORDREABON' => 2])
-			->answers([
-									Class_Reservation::newInstanceWithId(76,
-																											 ['id_notice_origine' => '1HP',
-																												'id_pergame' => '1HP',
-																												'date_resa' => '2011-12-25',
-																												'id_site' => 2,
-																												'idabon' => 23,
-																												'ordreabon' => 2]),
-
-									Class_Reservation::newInstanceWithId(77,
-																											 ['id_notice_origine' => '3HP',
-																												'id_pergame' => '3HP',
-																												'date_resa' => '2011-12-25',
-																												'id_site' => 0,
-																												'idabon' => 23,
-																												'ordreabon' => 2]),
-									])
 			->whenCalled('countBy')
-			->with(['ID_NOTICE_ORIGINE' => 1, 
+			->with(['ID_NOTICE_ORIGINE' => 1,
 							'where' => sprintf('DATE_RESA<"%s"', '2011-12-25')])
 			->answers(2);
 
-		$jc = Class_Users::newInstanceWithId(23, 
-																				 ['login' => 'jc',
-																					'idabon' => 23,
-																					'ordreabon' => 2]);
+		$jc = $this->fixture('Class_Users',
+												 ['id' => 23,
+													'login' => 'jc',
+													'password' => 'jc',
+													'idabon' => 23,
+													'ordreabon' => 2]);
 
 		$this->emprunteur_jc = $this->_service_cran->getEmprunteur($jc);
 		$this->_first_emprunt = array_first($this->emprunteur_jc->getEmprunts());
@@ -199,7 +205,7 @@ class PergameServiceGetEmprunteurTest extends PergameServiceTestCase {
 	/** @test */
 	public function firstReservationIdShouldBe76() {
 		$this->assertEquals(76, $this->_first_reservation->getId());
-	}	
+	}
 
 
 	/** @test */
@@ -231,11 +237,18 @@ class PergameServiceGetEmprunteurTest extends PergameServiceTestCase {
 		$this->assertEquals('Cran-Gevrier', $this->_first_reservation->getBibliotheque());
 	}
 
+
 	/** @test */
 	public function secondeReservationBibliothequeShouldBeEmpty() {
 		$resa = $this->emprunteur_jc->getReservations()[1];
 		$this->assertEmpty($resa->getBibliotheque());
 	}
+
+
+	/** @test */
+	public function firstReservationRecordShouldBeHarryPotter() {
+		$this->assertEquals('Harry Potter', $this->_first_reservation->getNoticeOPAC()->getTitrePrincipal());
+	}
 }
 
 
@@ -245,57 +258,28 @@ class PergameServiceGetExemplairePotterTest extends PergameServiceTestCase {
 	public function setUp() {
 		parent::setUp();
 
-		Storm_Test_ObjectWrapper::onLoaderOfModel('Class_Exemplaire')
-			->whenCalled('findAllBy')
-			->with(array('role' => 'notice', 
-									 'model' => $this->notice_potter))
-			->answers(array($this->potter_annecy, 
-											$this->potter_cran_prete, 
-											$this->potter_cran_reserve, $this->potter_cran_dispo))
-
-			->whenCalled('findFirstBy')
-			->with(array('id_origine' => '1HP', 
-									 'id_bib' => 1))
-			->answers($this->potter_annecy)
-
-			->whenCalled('findFirstBy')
-			->with(array('id_origine' => '1HP', 
-									 'id_bib' => 2))
-			->answers($this->potter_cran_prete);
-
-
-		Storm_Test_ObjectWrapper::onLoaderOfModel('Class_Pret')
-			->whenCalled('findFirstBy')
-			->answers(null)
-
-			->whenCalled('findFirstBy')
-			->with(array('id_site' => 2, 
-									 'id_notice_origine' => '1HP', 
-									 'code_barres' => 'C-24', 
-									 'EN_COURS' => 1))
-			->answers(Class_Pret::getLoader()
-								->newInstanceWithId(59)
-								->setCodeBarres('C-24')
-								->setIdNoticeOrigine(1)
-								->setIdPergame('1HP')
-								->setEnCours(1)
-								->setDateRetour('2010-09-07')
-								->setIdSite(2));
+		$this->fixture('Class_Pret',
+									 ['id' => 59,
+										'code_barres' => 'C-24',
+										'id_notice_origine' => 1,
+										'id_pergame' => '1HP',
+										'en_cours' => 1,
+										'date_retour' => '2010-09-07',
+										'id_site' => 2]);
 
+		$this->fixture('Class_Reservation',
+									 ['id' => 34,
+										'id_site' => 2,
+										'id_notice_origine' => 1]);
+
+		$this->_exemplaire_annecy =
+			Class_WebService_SIGB_Pergame::getService(['id_bib' => 1])
+			->getExemplaire('1HP', 'A-23');
+
+		$this->_exemplaire_cran_prete =
+			Class_WebService_SIGB_Pergame::getService(['id_bib' => 2])
+			->getExemplaire('1HP', 'C-24');
 
-		Storm_Test_ObjectWrapper::onLoaderOfModel('Class_Reservation')
-			->whenCalled('findAllBy')
-			->answers(array())
-
-			->whenCalled('findAllBy')
-			->with(array('id_site' => 2, 'id_notice_origine' => '1HP'))
-			->answers(array(Class_Reservation::getLoader()
-											->newInstanceWithId(34)
-											->setIdSite(2)
-											->setIdNoticeOrigine(1)));
-
-		$this->_exemplaire_annecy = Class_WebService_SIGB_Pergame::getService(array('id_bib' => 1))->getExemplaire('1HP', 'A-23');
-		$this->_exemplaire_cran_prete = Class_WebService_SIGB_Pergame::getService(array('id_bib' => 2))->getExemplaire('1HP', 'C-24');
 		$this->_exemplaire_cran_reserve = $this->_service_cran->getExemplaire('1HP', 'C-25');
 		$this->_exemplaire_cran_dispo = $this->_service_cran->getExemplaire('1HP', 'C-26');
 	}
@@ -414,8 +398,8 @@ class PergameServiceDelegateLegacyTest extends PergameServiceTestCase {
 			->answers(['statut' => 0, 'erreur' => ''])
 			->beStrict();
 
-		$result = $this->_service_cran->reserverExemplaire(Class_Users::getIdentity(), 
-																											 $this->potter_cran_prete, 
+		$result = $this->_service_cran->reserverExemplaire(Class_Users::getIdentity(),
+																											 $this->potter_cran_prete,
 																											 'CRAN');
 		$this->assertEquals(['statut' => 0, 'erreur' => ''], $result);
 	}
diff --git a/tests/library/ZendAfi/AuthTest.php b/tests/library/ZendAfi/AuthTest.php
index d7611236cd1ad6758fe4b5cf52f7460e7bc0b53b..89c44b9cfb306e7eccf4be74ac3d2aedd94c0d8c 100644
--- a/tests/library/ZendAfi/AuthTest.php
+++ b/tests/library/ZendAfi/AuthTest.php
@@ -16,22 +16,21 @@
  *
  * 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 AuthSessionNamespaceTest extends Storm_Test_ModelTestCase {
 	public function setUp() {
 		parent::setup();
 
-		$this->old_cache=Zend_Registry::get('cache');
 		$this->cache_mock=Storm_Test_ObjectWrapper::mock();
 		$this->cache_mock->whenCalled('save')
 			->answers(true)
 			->whenCalled('remove')
 			->answers(true);
-		 
-			
-		Zend_Registry::set('cache',$this->cache_mock);
+
+
+		Storm_Cache::setDefaultZendCache($this->cache_mock);
 
 		$this->fixture('Class_Users', ['id' => 25,
 																	 'login' => 'gigi',
@@ -41,12 +40,13 @@ class AuthSessionNamespaceTest extends Storm_Test_ModelTestCase {
 		$this->islogged=$this->zendAuth->authenticateLoginPassword('gigi',
 																															 'amoroso',
 																															 [new Mock_ZendAfi_Auth_MD5_BASE64_Adapter()]);
-		
+
 	}
 
 
 	public function tearDown()  {
-		Zend_Registry::set('cache',$this->old_cache);
+		Storm_Cache::setDefaultZendCache(null);
+		parent::tearDown();
 	}
 
 
@@ -58,8 +58,8 @@ class AuthSessionNamespaceTest extends Storm_Test_ModelTestCase {
 	}
 
 
-	/** 
-	 * @test 
+	/**
+	 * @test
 	 */
 	public function validAuthenticationInMd5ShouldRedirect()	{
 		$this->assertTrue($this->islogged);
@@ -68,7 +68,7 @@ class AuthSessionNamespaceTest extends Storm_Test_ModelTestCase {
 
   /** @test */
 	public function validAuthenticationUserIdShouldBeStoredInCache() {
-		$this->assertEquals(25, $this->cache_mock->getFirstAttributeForLastCallOn('save'));
+		$this->assertEquals(25, unserialize($this->cache_mock->getFirstAttributeForLastCallOn('save')));
 	}
 
 
@@ -98,7 +98,7 @@ class Mock_ZendAfi_Auth_MD5_BASE64_Adapter  implements Zend_Auth_Adapter_Interfa
 	}
 
 
-	
+
 	public function authenticate() {
 		if ($this->_credential == 'KxhJ6qNkU3d4A8U5rqJvCw')
 			return new Zend_Auth_Result(Zend_Auth_Result::SUCCESS, $this->getResultObject());
@@ -106,7 +106,7 @@ class Mock_ZendAfi_Auth_MD5_BASE64_Adapter  implements Zend_Auth_Adapter_Interfa
 		return new Zend_Auth_Result(Zend_Auth_Result::FAILURE_IDENTITY_NOT_FOUND, $this->_identity);
 	}
 
-	
+
 
 	public function getResultObject() {
 		$object = new StdClass;
@@ -115,5 +115,5 @@ class Mock_ZendAfi_Auth_MD5_BASE64_Adapter  implements Zend_Auth_Adapter_Interfa
 		return $object;
 	}
 }
-	
+
 ?>
\ No newline at end of file
diff --git a/tests/library/ZendAfi/View/Helper/Accueil/CacheTest.php b/tests/library/ZendAfi/View/Helper/Accueil/CacheTest.php
index 10b0e75ea76b9c2467f6282eea55d1dfdba3f455..f9fba49b7af1bd2459ff11fa7580a62e1b502723 100644
--- a/tests/library/ZendAfi/View/Helper/Accueil/CacheTest.php
+++ b/tests/library/ZendAfi/View/Helper/Accueil/CacheTest.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
  */
 require_once 'library/ZendAfi/View/Helper/ViewHelperTestCase.php';
 
@@ -26,18 +26,17 @@ class CacheWithCritiquesTest extends ViewHelperTestCase {
 	public function setUp() {
 		parent::setUp();
 
-		$cfg = new Zend_Config(['sgbd'=>['config'=>['dbname'=>'dbTest']]]);
-		setupCache($cfg);
-		
-		$params = array('type_module' => 'CRITIQUES',
-										'division' => 2,
-										'preferences' => array('boite' => 'boite_vide',
-																					 'titre' => 'Critiques',
-																					 'rss_avis' => false,
-																					 'display_order' => 'Random',
-																					 'only_img' => true));
+		Storm_Cache::beVolatile();
 
-		$this->avis_loader = $this->getMock('MockLoader', array('getAvisFromPreferences'));
+		$params = ['type_module' => 'CRITIQUES',
+							 'division' => 2,
+							 'preferences' => ['boite' => 'boite_vide',
+																 'titre' => 'Critiques',
+																 'rss_avis' => false,
+																 'display_order' => 'Random',
+																 'only_img' => true]];
+
+		$this->avis_loader = $this->getMock('MockLoader', ['getAvisFromPreferences']);
 		Storm_Model_Abstract::setLoaderFor('Class_AvisNotice', $this->avis_loader);
 
 		$this->critiques_helper = new ZendAfi_View_Helper_Accueil_Critiques(2, $params);
diff --git a/tests/library/ZendAfi/View/Helper/Accueil/CalendarTest.php b/tests/library/ZendAfi/View/Helper/Accueil/CalendarTest.php
index 7e6bb1b879d15715a8778598847aef5dc3b642ae..957c13f79ac3a513728271370c4d6e94d9bca8f4 100644
--- a/tests/library/ZendAfi/View/Helper/Accueil/CalendarTest.php
+++ b/tests/library/ZendAfi/View/Helper/Accueil/CalendarTest.php
@@ -895,7 +895,7 @@ class CalendarOnJanuaryTest extends CalendarViewHelperTestCase {
 	function calendarEventDateShouldContains5JanDot2012() {
 		$this->assertXPathContentContains($this->html,
 																			'//span[@class="calendar_event_date"]',
-																			'Du 05 janvier au 01 mars 2012',
+																			'Du jeudi 05 janvier au jeudi 01 mars 2012',
 																			$this->html);
 	}
 
diff --git a/tests/library/ZendAfi/View/Helper/Accueil/KiosqueTest.php b/tests/library/ZendAfi/View/Helper/Accueil/KiosqueTest.php
index 0e83f0d85b693f60a3ccfab42f205aa65bc54cb9..a1c8269e52c6d85ac2e606535bc007a1b3afe946 100644
--- a/tests/library/ZendAfi/View/Helper/Accueil/KiosqueTest.php
+++ b/tests/library/ZendAfi/View/Helper/Accueil/KiosqueTest.php
@@ -49,7 +49,7 @@ abstract class ZendAfi_View_Helper_Accueil_KiosqueTestCase extends ViewHelperTes
 																												'clef_alpha' => 'JF',
 																												'exemplaires' => []]),
 
-			];
+		];
 	}
 }
 
@@ -317,7 +317,7 @@ class ZendAfi_View_Helper_Accueil_KiosqueRequetesAsRedacteurTest extends ZendAfi
 	/** @test */
 	public function linkToEditCatalogueShouldNotBeVisible() {
 		$this->assertNotXPath($this->_html,
-											 '//a[contains(@href, "admin/catalogue/edit/")]');
+													'//a[contains(@href, "admin/catalogue/edit/")]');
 	}
 }
 
@@ -487,7 +487,7 @@ class ZendAfi_View_Helper_Accueil_KiosqueTitle extends ViewHelperTestCase {
 						'division' => 3,
 						'preferences' => ['style_liste' => 'mur',
 															'titre' =>"Title with / slash"]
-			];
+		];
 	}
 
 
@@ -551,4 +551,73 @@ class ZendAfi_View_Helper_Accueil_KiosqueProfileRedirectTest extends ZendAfi_Vie
 		$this->_html = $this->_helper->getHtml()['CONTENU'];
 		$this->assertXPath($this->_html, $xpath, $this->_html);
 	}
-}
\ No newline at end of file
+}
+
+
+abstract class ZendAfi_View_Helper_Accueil_KiosqueWithDomainSetTestCase extends ViewHelperTestCase {
+	public function setUp() {
+		parent::setUp();
+		Class_Notice::beVolatile();
+
+		$this->fixture('Class_Catalogue',
+									 ['id' => 5,
+										'libelle' => 'My cata']);
+
+		$params = ['preferences' => ['id_catalogue' => 5,
+																 'style_liste' => 'vignettes'],
+							 'division' => '2',
+							 'type_module' => 'KIOSQUE',];
+
+		Class_Profil::getCurrentProfil()->setSelAnnexe('2,4,1');
+		$this->_helper = new ZendAfi_View_Helper_Accueil_Kiosque(3, $params);
+		$this->_helper->setView(new ZendAfi_Controller_Action_Helper_View());
+	}
+}
+
+
+
+class ZendAfi_View_Helper_Accueil_KiosqueWithEmptyDomainSetTest extends ZendAfi_View_Helper_Accueil_KiosqueWithDomainSetTestCase {
+	protected $_html;
+
+	public function setUp() {
+		parent::setUp();
+
+		$this->_html = $this->_helper->getKiosqueHtml();
+	}
+
+
+  /** @test */
+	public function kiosqueShouldBeEmpty() {
+		$this->assertXpathContentContains($this->_html, '//p', utf8_encode('Aucun résultat'), $this->_html);
+	}
+}
+
+
+
+class ZendAfi_View_Helper_Accueil_KiosqueWithLinkedRecordsInDomainSetTest extends ZendAfi_View_Helper_Accueil_KiosqueWithDomainSetTestCase {
+	protected $_html;
+
+	public function setUp() {
+		parent::setUp();
+		Class_Exemplaire::beVolatile();
+		Class_NoticeDomain::beVolatile();
+
+		$this->fixture('Class_Sitotheque',
+									 ['id' => 1,
+										'domaine_ids' => 5,
+										'titre' => 'mon lien',
+										'url' => 'http://monlien.com'
+									 ]);
+
+		Class_Sitotheque::find(1)->index();
+
+		$this->_request = Class_Catalogue::getRequetes($this->_helper->getPreferences(), ['id_notice']);
+
+	}
+
+
+	/** @test */
+	public function requestShouldBeAsExpected() {
+		$this->assertEquals('select id_notice from notices Where MATCH(facettes) AGAINST(\'Q5\' IN BOOLEAN MODE) order by date_creation DESC  LIMIT 0,50', $this->_request['req_liste']);
+	}
+}
diff --git a/tests/library/ZendAfi/View/Helper/Accueil/NewsTest.php b/tests/library/ZendAfi/View/Helper/Accueil/NewsTest.php
index cf24f17f6e49353d6195f70ab5b097c0cf41de28..facb71d25d93ab8809ec8415c41853d97d223037 100644
--- a/tests/library/ZendAfi/View/Helper/Accueil/NewsTest.php
+++ b/tests/library/ZendAfi/View/Helper/Accueil/NewsTest.php
@@ -24,9 +24,10 @@ require_once 'library/ZendAfi/View/Helper/ViewHelperTestCase.php';
 abstract class NewsHelperTestCase extends ViewHelperTestCase {
 	public function setUp() {
 		parent::setUp();
-		$this->article_wrapper = Storm_Test_ObjectWrapper::onLoaderOfModel('Class_Article');
-		Class_Profil::setCurrentProfil(Class_Profil::getLoader()->newInstanceWithId(5));
+		Storm_Model_Loader::defaultToVolatile();
 
+		$this->article_wrapper = Storm_Test_ObjectWrapper::onLoaderOfModel('Class_Article');
+		Class_Profil::setCurrentProfil(Class_Profil::newInstanceWithId(5));
 
 		Zend_Registry::get('translate')->setLocale('fr');
 
@@ -37,6 +38,31 @@ abstract class NewsHelperTestCase extends ViewHelperTestCase {
 		Class_AdminVar::getLoader()
 			->newInstanceWithId('WORKFLOW')
 			->setValeur(0);
+
+		$this->setupPermissions();
+	}
+
+
+	protected function setupPermissions() {
+		foreach([[1, 'CATEGORY', 'ARTICLE', 'Droits', 1, 'Créer des sous-catégories et des articles'],
+						 [2, 'ARTICLE', 'ARTICLE', 'Droits', 2, 'Créer des articles'],
+						 [3, 'PENDING', 'ARTICLE', 'Nouveaux statuts autorisés', 1, 'À valider'],
+						 [4, 'VALIDATED', 'ARTICLE', 'Nouveaux statuts autorisés', 101, 'Validé'],
+						 [5, 'REFUSED', 'ARTICLE', 'Nouveaux statuts autorisés', 102, 'Refusé'],
+						 [6, 'ARCHIVED', 'ARTICLE', 'Nouveaux statuts autorisés', 103, 'Archivé']] as $fixture)
+			$this->fixture('Class_Permission',
+										 ['id' => $fixture[0],
+											'code' => $fixture[1],
+											'module' => $fixture[2],
+											'type' => $fixture[3],
+											'sorting' => $fixture[4],
+											'description' => $fixture[5]]);
+	}
+
+
+	public function tearDown() {
+		Storm_Model_Loader::defaultToDb();
+		parent::tearDown();
 	}
 }
 
@@ -106,7 +132,7 @@ class EmptyNewsHelperTest extends NewsHelperTestCase {
 
 	/** @test **/
 	public function addNewsButtonShouldNotBeDisplay() {
-		$this->assertNotXPath($this->html,'//a[contains(@href,"admin/cms/add")]');
+		$this->assertNotXPath($this->html, '//a[contains(@href,"admin/cms/add")]');
 	}
 }
 
@@ -147,10 +173,8 @@ abstract class NewsHelperWithThreeArticlesTestCase extends NewsHelperTestCase {
 								 $this->fete_poire,
 								 $this->fete_nashi];
 
-
-		Class_ArticleCategorie::newInstanceWithId(12,['libelle'=>'Animations',
-																									'articles'=>$articles]);
-
+		$this->fixture('Class_ArticleCategorie',
+									 ['id' => 12, 'libelle'=>'Animations', 'articles' => $articles]);
 
 		$this->article_wrapper
 			->whenCalled('getArticlesByPreferences')
@@ -224,6 +248,82 @@ class NewsHelperWithThreeArticlesAndRomaniaRequestedTest extends NewsHelperWithT
 
 
 
+abstract class NewsHelperWithThreeArticlesPermissionsTestCase extends NewsHelperWithThreeArticlesTestCase {
+	public function setUp() {
+		parent::setUp();
+		$this->login(ZendAfi_Acl_AdminControllerRoles::MODO_PORTAIL);
+
+		$params = ['type_module' => 'NEWS',
+							 'division' => 2,
+							 'preferences' => ['titre' => 'En Hiver',
+																 'rss_avis' => false,
+																 'display_titles_only' => false,
+																 'style_liste' => 'diaporama',
+																 'op_largeur_img' => 200,
+																 'op_transition' => 'zork']];
+
+		$this->helper = new ZendAfi_View_Helper_Accueil_News(12, $params);
+		$this->helper->setView(new ZendAfi_Controller_Action_Helper_View());
+	}
+}
+
+
+
+
+class NewsHelperWithThreeArticlesWithoutPermissionUserModoTest
+	extends NewsHelperWithThreeArticlesPermissionsTestCase {
+	public function setUp() {
+		parent::setUp();
+		$this->html = $this->helper->getBoite();
+	}
+
+
+	/** @test **/
+	public function editNewsButtonShouldNotBeDisplay() {
+		$this->assertNotXPath($this->html, '//a[contains(@href,"admin/cms/edit")]');
+	}
+
+
+	/** @test **/
+	public function addNewsButtonShouldNotBeDisplay() {
+		$this->assertNotXPath($this->html, '//a[contains(@href,"admin/cms/add")]');
+	}
+}
+
+
+
+
+class NewsHelperWithThreeArticlesWithPermissionUserModoTest extends NewsHelperWithThreeArticlesPermissionsTestCase {
+	public function setUp() {
+		parent::setUp();
+		$group = $this->fixture('Class_UserGroup',
+														['id' => 45, 'libelle' => 'Testing group']);
+
+		Class_Users::getIdentity()
+			->setUserGroups([$group]);
+
+		Class_Permission::createArticle()
+			->permitTo($group, Class_ArticleCategorie::find(12));
+
+		$this->html = $this->helper->getBoite();
+	}
+
+
+	/** @test **/
+	public function editNewsButtonShouldNotBeDisplay() {
+		$this->assertXPath($this->html, '//a[contains(@href,"admin/cms/edit")]');
+	}
+
+
+	/** @test **/
+	public function addNewsButtonShouldBeDisplay() {
+		$this->assertXPath($this->html, '//a[contains(@href,"admin/cms/add")]');
+	}
+}
+
+
+
+
 
 class NewsHelperWithThreeArticlesWorkflowActivatedUserAdminTest extends NewsHelperWithThreeArticlesTestCase {
 	public function setUp() {
@@ -239,14 +339,14 @@ class NewsHelperWithThreeArticlesWorkflowActivatedUserAdminTest extends NewsHelp
 		$this->fete_poire->beDraft();
 		$this->fete_nashi->beValidationPending();
 
-		$params = array('type_module' => 'NEWS',
-										'division' => 2,
-										'preferences' => array('titre' => 'En Hiver',
-																					 'rss_avis' => false,
-																					 'display_titles_only' => false,
-																					 'style_liste' => 'diaporama',
-																					 'op_largeur_img' => 200,
-																					 'op_transition' => 'zork'));
+		$params = ['type_module' => 'NEWS',
+							 'division' => 2,
+							 'preferences' => ['titre' => 'En Hiver',
+																 'rss_avis' => false,
+																 'display_titles_only' => false,
+																 'style_liste' => 'diaporama',
+																 'op_largeur_img' => 200,
+																 'op_transition' => 'zork']];
 
 		$helper = new ZendAfi_View_Helper_Accueil_News(12, $params);
 		$helper->setView(new ZendAfi_Controller_Action_Helper_View());
@@ -301,7 +401,7 @@ class NewsHelperWithThreeArticlesWorkflowActivatedUserAdminTest extends NewsHelp
 
 
 	/** @test */
-	function editArticleLinksShouldNotBePresert() {
+	function editArticleLinksShouldBePresent() {
 		$this->assertXPath($this->html,
 											 '//a[contains(@href, "admin/cms/edit/id/34")]//img[@class="article_edit"]');
 	}
diff --git a/tests/library/ZendAfi/View/Helper/Accueil/SitoTest.php b/tests/library/ZendAfi/View/Helper/Accueil/SitoTest.php
index 7fefa7a1e74de1fa2081a3b415687df3cafb2df8..8026bc4b4cb3ae112966e7c362c9b4b87ee0b204 100644
--- a/tests/library/ZendAfi/View/Helper/Accueil/SitoTest.php
+++ b/tests/library/ZendAfi/View/Helper/Accueil/SitoTest.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
  */
 require_once 'library/ZendAfi/View/Helper/ViewHelperTestCase.php';
 
@@ -49,15 +49,62 @@ abstract class SitoViewHelperTestCase extends ViewHelperTestCase {
 																											'description' => 'du vin du vin!',
 																											'url' => 'http://www.rmll.info',
 																											'categorie' => $france]);
-		
+
 
 		$site_rmll->save();
 
-		$helper = new ZendAfi_View_Helper_Accueil_Sito(2, array('division' => '1',
-																														'type_module' => 'SITO',
-																														'preferences' => $this->_preferences));
-		$helper->setView(new ZendAfi_Controller_Action_Helper_View());
-		$this->html = $helper->getBoite();
+		$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();
+	}
+}
+
+
+
+class SitoViewHelperSelectItemsBySelectionOrderTest extends SitoViewHelperTestCase {
+		protected $_preferences = ['titre' => 'Ma sito',
+															 'type_aff' => 1,
+															 'id_items' => '12-15',
+															 'id_categorie' => '',
+															 'nb_aff' => 0,
+															 'display_order' => 'Selection'];
+
+		/** @test */
+		public function firstSiteShouldBeFOSDEM() {
+			$this->assertXPathContentContains($this->html,
+																				'//div/div[1]//h2//a',
+																				'FOSDEM');
+		}
+
+
+		/** @test */
+		public function secondSiteShouldBeRMLL() {
+			$this->assertXPathContentContains($this->html,
+																				'//div/div[2]//h2//a',
+																				'FOSDEM');
+		}
+}
+
+
+
+
+class SitoViewHelperSelectItemsByRandomOrderTest extends SitoViewHelperTestCase {
+	protected $_preferences = ['titre' => 'Ma sito',
+														 'type_aff' => 1,
+														 'id_items' => '12-15',
+														 'id_categorie' => '',
+														 'nb_aff' => 2,
+														 'display_order' => 'Random'];
+	/** @test */
+	public function itemsShouldBeShuffled() {
+		$htmls = [];
+		for($i=0; $i<10; $i++) {
+			$htmls []= $this->_helper->getBoite();
+		}
+
+		$this->assertEquals(2, count(array_unique($htmls)));
 	}
 }
 
@@ -65,32 +112,33 @@ abstract class SitoViewHelperTestCase extends ViewHelperTestCase {
 
 
 class SitoViewHelperSelectItemsAndCatsTest extends SitoViewHelperTestCase {
-	protected $_preferences = array('titre' => 'Ma sito',
-																	'type_aff' => 1,
-																	'id_items' => 12,
-																	'id_categorie' => 3,
-																	'nb_aff' => 2);
+	protected $_preferences = ['titre' => 'Ma sito',
+														 'type_aff' => 1,
+														 'id_items' => 12,
+														 'id_categorie' => 3,
+														 'nb_aff' => 2,
+														 'display_order' => 'Random'];
 
 	/** @test */
 	public function titleShouldBeMaSito() {
-		$this->assertXPathContentContains($this->html, 
-																			'//div[@class="titre"]//h1', 
+		$this->assertXPathContentContains($this->html,
+																			'//div[@class="titre"]//h1',
 																			'Ma sito');
 	}
 
-	
+
 	/** @test */
 	public function h2ShouldContainsFosdemDotOrg() {
-		$this->assertXPathContentContains($this->html, 
-																			'//h2//a[contains(@href, "fosdem.org")]', 
+		$this->assertXPathContentContains($this->html,
+																			'//h2//a[contains(@href, "fosdem.org")]',
 																			'FOSDEM');
 	}
 
 
 	/** @test */
 	public function divSitothequeShouldContainsPleinDeBieres() {
-		$this->assertXPathContentContains($this->html, 
-																			'//div[@class="sitotheque"]', 
+		$this->assertXPathContentContains($this->html,
+																			'//div[@class="sitotheque"]',
 																			utf8_encode('plein de bières belges'));
 	}
 }
@@ -105,18 +153,29 @@ class SitoViewHelperLastTest extends SitoViewHelperTestCase {
 
 	/** @test */
 	public function titleShouldBeDerniersSites() {
-		$this->assertXPathContentContains($this->html, 
-																			'//div[@class="titre"]//h1', 
+		$this->assertXPathContentContains($this->html,
+																			'//div[@class="titre"]//h1',
 																			'Derniers sites');
 	}
 
-	
+
 	/** @test */
 	public function h2ShouldContainsRmllDotInfo() {
-		$this->assertXPathContentContains($this->html, 
-																			'//h2//a[contains(@href, "rmll.info")]', 
+		$this->assertXPathContentContains($this->html,
+																			'//h2//a[contains(@href, "rmll.info")]',
 																			'RMLL');
 	}
+
+	/** @test */
+	public function itemsShouldBeShuffled() {
+		$htmls = [];
+		for($i=0; $i<10; $i++) {
+			$htmls []= $this->_helper->getBoite();
+		}
+
+		$this->assertEquals(2, count(array_unique($htmls)));
+	}
+
 }
 
 
@@ -131,7 +190,7 @@ class SitoViewHelperGroupByCategorieTest extends SitoViewHelperTestCase {
 
 	/** @test */
 	public function liEnBelgiqueShouldContainsFosdem() {
-		$this->assertXPathContentContains($this->html, 
+		$this->assertXPathContentContains($this->html,
 																			'//ul/li/h2/a[text()="Belgique"]/../../ul/li',
 																			'FOSDEM');
 	}
@@ -139,7 +198,7 @@ class SitoViewHelperGroupByCategorieTest extends SitoViewHelperTestCase {
 
 	/** @test */
 	public function liEnFranceShouldContainsRMLL() {
-		$this->assertXPathContentContains($this->html, 
+		$this->assertXPathContentContains($this->html,
 																			'//ul/li/h2/a[text()="France"]/../../ul/li',
 																			'RMLL');
 	}
@@ -147,7 +206,7 @@ class SitoViewHelperGroupByCategorieTest extends SitoViewHelperTestCase {
 
 	/** @test */
 	public function scriptLoaderShouldContainsCodeToAnimateSitotheque() {
-		$this->assertContains('ul.sitotheque>li>h2>a', 
+		$this->assertContains('ul.sitotheque>li>h2>a',
 													Class_ScriptLoader::getInstance()->html());
 	}
 }
diff --git a/tests/library/ZendAfi/View/Helper/Admin/AdminHelpLinkTest.php b/tests/library/ZendAfi/View/Helper/Admin/AdminHelpLinkTest.php
index 5210ce198a52f4cee0758967bd12e68c80f93d86..9c4a26432cdbcbc63f1c143da78fbfa94e55e957 100644
--- a/tests/library/ZendAfi/View/Helper/Admin/AdminHelpLinkTest.php
+++ b/tests/library/ZendAfi/View/Helper/Admin/AdminHelpLinkTest.php
@@ -40,7 +40,16 @@ class AdminHelpLinkHelperTest extends ViewHelperTestCase {
 	protected function assertHelpLink($help_id) {
 		$html = $this->helper->helpLink();
 		$this->assertXPath($html,
-											 "//a[@href='https://akm.ardans.fr/AFI2/invite/listerFiche.do?idFiche=$help_id']");
+											 "//a[@href='https://akm.ardans.fr/AFI2/invite/listerFiche.do?idFiche=$help_id']",
+											 $html);
+	}
+
+
+	protected function assertWikiLink($page) {
+		$html = $this->helper->helpLink();
+		$path = sprintf('//a[@href="http://wiki.bokeh-library-portal.org/index.php/%s"]',
+										$page);
+		$this->assertXPath($html, $path, $html);
 	}
 
 
@@ -54,28 +63,28 @@ class AdminHelpLinkHelperTest extends ViewHelperTestCase {
 	/** @test */
 	public function helpForProfilMenusIndexShouldReturnFiche3618() {
 		$this->setControllerAction('profil', 'menusindex');
-		$this->assertHelpLink(3618);
+		$this->assertWikiLink('Configurer_un_menu');
 	}
 
 
 	/** @test */
 	public function helpForProfilIndexShouldReturnFiche3612() {
 		$this->setControllerAction('profil', 'index');
-		$this->assertHelpLink(3612);
+		$this->assertWikiLink('Configurer_un_profil');
 	}
 
 
 	/** @test */
 	public function helpForProfilIndexUpperCaseShouldReturnFiche3612() {
 		$this->setControllerAction('ProFil', 'inDex');
-		$this->assertHelpLink(3612);
+		$this->assertWikiLink('Configurer_un_profil');
 	}
 
 
 	/** @test */
 	public function helpForProfilZorkShouldReturnFiche3612() {
 		$this->setControllerAction('profil', 'zork');
-		$this->assertHelpLink(3612);
+		$this->assertWikiLink('Configurer_un_profil');
 	}
 
 	/** @test */
@@ -88,14 +97,14 @@ class AdminHelpLinkHelperTest extends ViewHelperTestCase {
 	/** @test */
 	public function helpForCatalogueShouldReturnFiche3613() {
 		$this->setControllerAction('catalogue');
-		$this->assertHelpLink(3613);
+		$this->assertWikiLink('Gestions_des_domaines');
 	}
 
 
 	/** @test */
 	public function helpForCatalogueEditShouldReturnFiche3613() {
 		$this->setControllerAction('catalogue', 'edit');
-		$this->assertHelpLink(3613);
+		$this->assertWikiLink('Gestions_des_domaines');
 	}
 
 
diff --git a/tests/library/ZendAfi/View/Helper/CkEditorTest.php b/tests/library/ZendAfi/View/Helper/CkEditorTest.php
index e8245223d22263f69b1da4915c9601abd64bcf65..7f445b08b6aa0ab3c2bd3c51475decffc45e9a98 100644
--- a/tests/library/ZendAfi/View/Helper/CkEditorTest.php
+++ b/tests/library/ZendAfi/View/Helper/CkEditorTest.php
@@ -16,19 +16,19 @@
  *
  * 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
  */
 require_once 'ViewHelperTestCase.php';
 require_once 'ZendAfi/View/Helper/CkEditor.php';
 
 
 class CkEditorWithFormulaireEnabledTest extends ViewHelperTestCase {
-	
+
 		public function setUp() {
 			parent::setUp();
 			$this->_helper = new ZendAfi_View_Helper_CkEditor();
 			$this->_helper->setView(new ZendAfi_Controller_Action_Helper_View());
-			if (!defined('URL_CSS')) 
+			if (!defined('URL_CSS'))
 				define('URL_CSS','');
 			Class_AdminVar::newInstanceWithId('CMS_FORMULAIRES')->setValeur(1);
 			$this->_html=$this->_helper->ckeditor('','','');
@@ -45,12 +45,12 @@ class CkEditorWithFormulaireEnabledTest extends ViewHelperTestCase {
 
 
 class CkEditorWithFormulaireDisabledTest extends ViewHelperTestCase {
-	
+
 		public function setUp() {
 			parent::setUp();
 			$this->_helper = new ZendAfi_View_Helper_CkEditor();
 			$this->_helper->setView(new ZendAfi_Controller_Action_Helper_View());
-			if (!defined('URL_CSS')) 
+			if (!defined('URL_CSS'))
 				define('URL_CSS','');
 
 			Class_AdminVar::newInstanceWithId('CMS_FORMULAIRES')->setValeur(0);
@@ -78,6 +78,6 @@ class CkEditorViewHelperTest extends ViewHelperTestCase {
 
 	/** @test */
 	public function cmsFormulaireShouldAllowStyleAttribute() {
-		$this->assertContains('"extraAllowedContent":{"audio video":{"attributes":"*"},"source":{"attributes":["src","type"]},"map":{"attributes":"name"},"area":{"attributes":["shape","coords","href","alt"]},"span":{"classes":"*"},"*":{"styles":"*"}}', $this->_html);
+		$this->assertContains('"extraAllowedContent":{"audio video":{"attributes":"*"},"source":{"attributes":["src","type"]},"map":{"attributes":"name"},"area":{"attributes":["shape","coords","href","alt"]},"span":{"classes":"*"},"*":{"attributes":"id","styles":"*"}}', $this->_html);
 	}
 }
diff --git a/tests/library/ZendAfi/View/Helper/Notice/UnimarcTest.php b/tests/library/ZendAfi/View/Helper/Notice/UnimarcTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..9563d47649c1507938af3461d753ac099bbd6bd4
--- /dev/null
+++ b/tests/library/ZendAfi/View/Helper/Notice/UnimarcTest.php
@@ -0,0 +1,54 @@
+<?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_Notice_UnimarcTest extends ViewHelperTestCase {
+	 public function setUp() {
+		 parent::setUp();
+		 $this->_helper = new ZendAfi_View_Helper_Notice_Unimarc();
+		 $this->_helper->setView(new ZendAfi_Controller_Action_Helper_View());
+		 $this->_html = $this->_helper->notice_Unimarc($this->fixture('Class_Notice',
+																																['id' => 1,
+																																 'titre' => 'Pomme et Ananas',
+																																 'clef_alpha' => 'POMMEETANANAS',
+																																 'facettes' => 'A1 Q9',
+																																 'date_maj' => '01/01/2015',
+																																 'unimarc' => '']));
+	 }
+
+
+	 /** @test */
+	 public function alphaKeyShouldBePresent() {
+		 $this->assertContains('Clé alpha: POMMEETANANAS', $this->_html);
+	 }
+
+
+	 /** @test */
+	 public function dateMajShouldBePresent() {
+		 $this->assertContains('Mis à jour le: 01/01/2015', $this->_html);
+	 }
+
+
+	 /** @test */
+	 public function facetsShouldBePresent() {
+		 $this->assertContains('Facettes: A1 Q9', $this->_html);
+	 }
+ }
diff --git a/tests/library/ZendAfi/View/Helper/ShareUrlTest.php b/tests/library/ZendAfi/View/Helper/ShareUrlTest.php
index ef96d061b5eab50673c3a03864c34ca0a684a9a4..0aa0844ef59c7cba5412f03419033e8a5c8ead11 100644
--- a/tests/library/ZendAfi/View/Helper/ShareUrlTest.php
+++ b/tests/library/ZendAfi/View/Helper/ShareUrlTest.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
  */
 require_once 'ViewHelperTestCase.php';
 
@@ -64,40 +64,23 @@ class ZendAfi_View_Helper_ShareUrlTest extends ViewHelperTestCase {
 	}
 
 
-	/** @test */
-	public function getFacebookUrlViewNoticeShouldReturnShortenUrlWithServerHost() {
-		$_SERVER["HTTP_HOST"] = 'localhost';
-		$this->_expectClientOpenUrlForShortenViewNotice2();
-		$this->assertEquals("https://www.facebook.com/sharer/sharer.php?m2w&s=100&p%5Btitle%5D=&p%5Bsummary%5D=&p%5Burl%5D=http%3A%2F%2Fis.gd%2FPkdNg2&p%5Bimages%5D%5B0%5D=",
-												$this->_helper->shareUrl("facebook", '/recherche/viewnotice/id/2'));
-	}
-
-	
-	/** @test **/
-	public function facebookUrlShouldContainsImgUrlTitleAndComment() {
-		$_SERVER["HTTP_HOST"] = 'localhost';
-		$this->_expectClientOpenUrlForShortenViewNotice2();
-		$this->assertEquals("%5Btitle%5D=&p%5Bsummary%5D=&p%5Burl%5D=%2Frecherche%2Fviewnotice%2Fid%2F2&p%5Bimages%5D%5B0%5D=",$this->_helper->getFacebookUrl('/recherche/viewnotice/id/2','','',''));
-	}
-	
-
 	/** @test */
 	public function shortenUrlWithErrorShouldReturnOriginalUrl() {
-		$this->_expectClientOpenUrlWithLongUrlAndAnswer('http://www.institut-francais.com', 
+		$this->_expectClientOpenUrlWithLongUrlAndAnswer('http://www.institut-francais.com',
 																										'Error');
 
 		$this->assertEquals('http://www.institut-francais.com',
-												$this->_helper->shortenUrl('http://www.institut-francais.com'));	
+												$this->_helper->shortenUrl('http://www.institut-francais.com'));
 	}
 
 
 	/** @test */
 	public function shortenUrlWithNullShouldReturnOriginalUrl() {
-		$this->_expectClientOpenUrlWithLongUrlAndAnswer('http://www.institut-francais.com', 
+		$this->_expectClientOpenUrlWithLongUrlAndAnswer('http://www.institut-francais.com',
 																										null);
 
 		$this->assertEquals('http://www.institut-francais.com',
-												$this->_helper->shortenUrl('http://www.institut-francais.com'));	
+												$this->_helper->shortenUrl('http://www.institut-francais.com'));
 	}
 
 
diff --git a/tests/library/ZendAfi/View/Helper/TagArticleEventTest.php b/tests/library/ZendAfi/View/Helper/TagArticleEventTest.php
index 9a93f97c396419adf6f5d91ea8c033aa3e8ba275..a350e7e3e55252f2b29950f2d3cef7726be92ab2 100644
--- a/tests/library/ZendAfi/View/Helper/TagArticleEventTest.php
+++ b/tests/library/ZendAfi/View/Helper/TagArticleEventTest.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
  */
 require_once realpath(dirname(__FILE__)) . '/ViewHelperTestCase.php';
 
@@ -33,7 +33,7 @@ class TagArticleEventTest extends ViewHelperTestCase {
 
 		$this->_helper = new ZendAfi_View_Helper_TagArticleEvent();
 		$this->_helper->setView(new ZendAfi_Controller_Action_Helper_View());
-		
+
 		$this->article = Class_Article::getLoader()
 			->newInstanceWithId(2)
 			->setTitre('Mediévales Andilly')
@@ -50,7 +50,7 @@ class TagArticleEventTest extends ViewHelperTestCase {
 	/** @test */
 	function withEventDebutShouldAnswerLe05Sept() {
 		$this->article->setAllDay(TRUE)->setEventsDebut('2011-09-05');
-		$this->assertTagContains('Le 05 septembre 2011');
+		$this->assertTagContains('lundi 05 septembre 2011');
 	}
 
 	/** @test */
@@ -58,7 +58,7 @@ class TagArticleEventTest extends ViewHelperTestCase {
 		$this->article
 			->setEventsDebut('2011-09-05')
 			->setEventsFin('2011-09-10');
-		$this->assertTagContains('Du 05 au 10 septembre 2011');
+		$this->assertTagContains('Du lundi 05 au samedi 10 septembre 2011');
 	}
 
 	/** @test */
@@ -66,7 +66,7 @@ class TagArticleEventTest extends ViewHelperTestCase {
 		$this->article
 			->setEventsDebut('2011-09-05')
 			->setEventsFin('2011-10-10');
-		$this->assertTagContains('Du 05 septembre au 10 octobre 2011');
+		$this->assertTagContains('Du lundi 05 septembre au lundi 10 octobre 2011');
 	}
 
 	/** @test */
@@ -74,7 +74,7 @@ class TagArticleEventTest extends ViewHelperTestCase {
 		$this->article
 			->setEventsDebut('2011-09-05')
 			->setEventsFin('2012-09-10');
-		$this->assertTagContains('Du 05 septembre 2011 au 10 septembre 2012');
+		$this->assertTagContains('Du lundi 05 septembre 2011 au lundi 10 septembre 2012');
 	}
 
 	/** @test */
@@ -82,7 +82,7 @@ class TagArticleEventTest extends ViewHelperTestCase {
 		$this->article
 			->setEventsDebut('2011-09-05 08:00')
 			->setEventsFin('2011-09-05 10:00');
-		$this->assertTagContains('Le 05 septembre 2011 de 08:00 à 10:00');
+		$this->assertTagContains('lundi 05 septembre 2011 de 08:00 à 10:00');
 	}
 
 	/** @test */
@@ -90,7 +90,7 @@ class TagArticleEventTest extends ViewHelperTestCase {
 		$this->article
 			->setEventsDebut('2011-09-05 08:00')
 			->setEventsFin('2011-09-05 08:00');
-		$this->assertTagContains('Le 05 septembre 2011 à 08:00');
+		$this->assertTagContains('lundi 05 septembre 2011 à 08:00');
 	}
 
 
@@ -100,13 +100,11 @@ class TagArticleEventTest extends ViewHelperTestCase {
 			->setAllDay(TRUE)
 			->setEventsDebut('2011-09-05 08:00')
 			->setEventsFin('2011-09-05 10:00');
-		$this->assertTagContains('Le 05 septembre 2011');
+		$this->assertTagContains('lundi 05 septembre 2011');
 	}
 
 	protected function assertTagContains($expected) {
-		$this->assertEquals(sprintf('<span class="calendar_event_date">%s</span>', $expected), 
+		$this->assertEquals(sprintf('<span class="calendar_event_date">%s</span>', $expected),
 												$this->_helper->tagArticleEvent($this->article));
 	}
 }
-
-
diff --git a/tests/library/ZendAfi/View/Helper/TreeViewFixtures.php b/tests/library/ZendAfi/View/Helper/TreeViewFixtures.php
index ca14f3973ed2497eb89877c61aed453df3a09d3a..95566b58a52210e8d3a250faabd3fa93dde9a04b 100644
--- a/tests/library/ZendAfi/View/Helper/TreeViewFixtures.php
+++ b/tests/library/ZendAfi/View/Helper/TreeViewFixtures.php
@@ -71,7 +71,7 @@ class TreeViewFixtures {
 				'label'			=> 'Ajouter un article',
 			),
 			array(
-				'url' => '/admin/cms/catadd/id/%s',
+				'url' => '/admin/cms-category/add/id/%s',
 				'icon'			=> 'ico/add_cat.gif',
 				'label'			=> 'Ajouter une sous-catégorie'
 			),
@@ -212,7 +212,7 @@ class TreeViewFixtures {
 														->setLibelle('Actualités')
 														->setSousCategories(array())
 														->setArticles(array())),
-			'add_link' => '<a href="admin/cms/catadd/id_bib/0">Ajouter une categorie</a>'
+			'add_link' => '<a href="admin/cms-category/add/id_bib/0">Ajouter une categorie</a>'
 		));
 	}
 
diff --git a/tests/library/ZendAfi/View/Helper/TreeViewTest.php b/tests/library/ZendAfi/View/Helper/TreeViewTest.php
index 64439507bb91d6b45280338f791f0b4c29e576fc..5f02741666071855e166265e80b788ed94ce9233 100644
--- a/tests/library/ZendAfi/View/Helper/TreeViewTest.php
+++ b/tests/library/ZendAfi/View/Helper/TreeViewTest.php
@@ -107,7 +107,7 @@ class TreeViewContainersWithoutItemsActionsTest extends TreeViewContainersTestCa
 
 	/** @test */
 	public function addToRootLinkShouldBePresent() {
-		$this->assertXpath($this->_html, '//a[contains(@href, "admin/cms/catadd/id_bib/0")]');
+		$this->assertXpath($this->_html, '//a[contains(@href, "admin/cms-category/add/id_bib/0")]');
 	}
 
 
@@ -141,7 +141,7 @@ class TreeViewContainersWithoutItemsActionsTest extends TreeViewContainersTestCa
 	/** @test */
 	public function addSubContainerActionShouldBePresent() {
 		$this->assertXpath($this->_html,
-												'//a[contains(@href, "admin/cms/catadd/id/1")]');
+												'//a[contains(@href, "admin/cms-category/add/id/1")]');
 	}
 
 
diff --git a/tests/library/ZendAfi/View/Helper/ViewHelperTestCase.php b/tests/library/ZendAfi/View/Helper/ViewHelperTestCase.php
index 2872f6def543b969beb472242f1e5424dbf9c37f..a78ce5bbc606fd8db0e38d954ea71d97a3bb1b0d 100644
--- a/tests/library/ZendAfi/View/Helper/ViewHelperTestCase.php
+++ b/tests/library/ZendAfi/View/Helper/ViewHelperTestCase.php
@@ -129,7 +129,7 @@ abstract class ViewHelperTestCase extends PHPUnit_Framework_TestCase {
 
 	protected function tearDown() {
 		Storm_Model_Abstract::unsetLoaders();
-		Zend_Registry::get('cache')->clean();
+		(new Storm_Cache)->clean();
 		$this->logout();
 	}