diff --git a/VERSIONS_WIP/dev_21987_register_IP_PNB b/VERSIONS_WIP/dev_21987_register_IP_PNB
new file mode 100644
index 0000000000000000000000000000000000000000..cce24935d41cf156ef24950b409578377c6d0bdf
--- /dev/null
+++ b/VERSIONS_WIP/dev_21987_register_IP_PNB
@@ -0,0 +1 @@
+- ticket #21987: implement Dilicom declareIP webservice acces for PNB.
\ No newline at end of file
diff --git a/VERSIONS_WIP/dev_21988_consultation_pnb b/VERSIONS_WIP/dev_21988_consultation_pnb
new file mode 100644
index 0000000000000000000000000000000000000000..58a18836ae2062f76d6f450b9294c295e4637abd
--- /dev/null
+++ b/VERSIONS_WIP/dev_21988_consultation_pnb
@@ -0,0 +1 @@
+- ticket #21988: implement Dilicom consultBook access
\ No newline at end of file
diff --git a/application/modules/admin/controllers/AlbumController.php b/application/modules/admin/controllers/AlbumController.php
index 47c017c2768fa5e3afc81bcd4a18ab8fa3750654..3999e6c1ebe1670dead484ef5067e4ecf78ca2b4 100644
--- a/application/modules/admin/controllers/AlbumController.php
+++ b/application/modules/admin/controllers/AlbumController.php
@@ -25,6 +25,7 @@ class Admin_AlbumController extends ZendAfi_Controller_Action {
 		                            'controller' => 'album'];
 
 	public function init() {
+		parent::init();
 		$this->view->titre = 'Collections';
 
 		Class_ScriptLoader::getInstance()
@@ -37,8 +38,8 @@ class Admin_AlbumController extends ZendAfi_Controller_Action {
 		$categories []= Class_AlbumCategorie::defaultCategory()
 			->setAlbums(Class_Album::findAllBy(['cat_id' => 0]));
 
-		$this->view->categories = [['bib' => Class_Bib::getPortail(),
-																'containers' => $categories]];
+		$this->view->categories = [ [ 'bib' => Class_Bib::getPortail(),
+																  'containers' => $categories]];
 		$this->view->containersActions = $this->_getTreeViewContainerActions();
 		$this->view->itemsActions = $this->_getTreeViewItemActions();
 		$this->view->headScript()->appendScript('var treeViewSelectedCategory = '
@@ -517,7 +518,7 @@ class Admin_AlbumController extends ZendAfi_Controller_Action {
 	 * @return Zend_Form
 	 */
 	protected function _albumForm($album) {
-		$form = ZendAfi_Form_Album::newWith($album);
+		$form = ZendAfi_Form_Album::newWithAlbum($album);
 		$form->addAttribs(['data-backurl' => $this->view->url(['action' => 'index',
 																						               'cat_id' => $album->getCatId()])]);
 		return $form;
@@ -549,6 +550,7 @@ class Admin_AlbumController extends ZendAfi_Controller_Action {
 		$values = $form->getValues();
 		unset($values['fichier']);
 		unset($values['pdf']);
+		unset($values['usage_constraints']);
 
 		$droits_precision = $values['droits_precision'];
 		unset($values['droits_precision']);
diff --git a/application/modules/admin/controllers/IndexController.php b/application/modules/admin/controllers/IndexController.php
index 7d6667bfd5fa389c2250b6d9db1d7386249cefc0..9b28602567a65d30c15d7ffb41506dd4ec463a51 100644
--- a/application/modules/admin/controllers/IndexController.php
+++ b/application/modules/admin/controllers/IndexController.php
@@ -66,46 +66,31 @@ class Admin_IndexController extends ZendAfi_Controller_Action {
 
 
 
-	public function shouldEncodeVar($cle) {
-		return in_array($cle->getId(),
-										['REGISTER_OK', 'RESA_CONDITION', 'TEXTE_MAIL_RESA',
-										 'USER_VALIDATED', 'USER_NON_VALIDATED']);
-	}
-
-
 	public function adminvareditAction() {
 		$id = $this->_getParam('cle');
 		$var = Class_AdminVar::find($id);
 
-		if ($this->_request->isPost()) {
-			$filter = new Zend_Filter_StripTags();
-			$new_valeur = $this->_request->getPost('valeur');
+		$form = ZendAfi_Form_Admin_AdminVar::newWithAdminVar($var);
+		$form->setAction($this->view->url());
+		$form->setAttrib('data-backurl',
+										 $this->view->url(['action' => 'adminvar']));
 
-			if ($this->shouldEncodeVar($var)) {
-				$var->setValeur(urlencode($new_valeur));
+		if ($this->_request->isPost() && $form->isValid($this->_request->getPost())) {
+			$new_valeur = $form->getAdminVarValue();
+			$var->setValeur($new_valeur);
 
-			} else if ($var->getId() == 'JS_STAT') {
-				$var->setValeur(addslashes($new_valeur));
-
-			} else {
-				$var->setValeur(trim($filter->filter($new_valeur)));
+			if ($var->save()) {
+					$this->_helper->notify('Variable '.$id.' sauvegardée', $id);
+					$this->_redirectClose('admin/index/adminvaredit/cle/'.$id);
+					return;
 			}
-
-			$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;
+			$form->getElement('valeur')->addErrors($var->getErrors());
+			$this->_helper->notify('Erreur(s) : ' . implode(', ', $var->getErrors())
+														 . ', variable '.$id.' NON sauvegardée');
 		}
 
-		$this->view->var_valeur	= $this->shouldEncodeVar($var)
-			? urldecode($var->getValeur())
-			: $var->getValeur();
-
-		$this->view->var_cle		= $var->getId();
-		$this->view->var = $var;
-		$this->view->tuto				= Class_AdminVar::helpFor($var->getId());
+		$this->view->form = $form;
+		$this->view->admin_var = $var;
 		$this->view->titre			= 'Modifier la variable: ' . $var->getId();
 	}
 
diff --git a/application/modules/admin/views/scripts/head.phtml b/application/modules/admin/views/scripts/head.phtml
index 139ce62c853bd418b66b7de1a20a2c37091dbc06..3534eec35e0518a2b12da0e1860405db57618c55 100644
--- a/application/modules/admin/views/scripts/head.phtml
+++ b/application/modules/admin/views/scripts/head.phtml
@@ -16,11 +16,11 @@
 																							 BASE_URL, URL_ADMIN_IMG, URL_ADMIN_CSS, USERFILESURL))
 										 ->addAdminScripts(['onload_utils',
 																				'global',
-																				'toolbar'])			 
+																				'toolbar'])
 										 ->addOPACScript('subModal')
-										 ->loadPrettyPhoto()
+										 ->addJQueryReady('initializePopups();')
 										 ->showNotifications()
-										 ->addJQueryReady('initializePopups()');
+										 ->loadPrettyPhoto();
 
 		$script_loader = Class_ScriptLoader::getInstance();
 		$head_scripts->renderStyleSheets();
@@ -30,7 +30,7 @@
 		$script_loader->renderJavaScripts();
 
 		echo $this->headScript();
-		echo $this->headLink(); 
+		echo $this->headLink();
 		?>
 		<script>
 		 (function heartbeat() {
diff --git a/application/modules/admin/views/scripts/index/adminvar.phtml b/application/modules/admin/views/scripts/index/adminvar.phtml
index 827edf8fd6ccc4fc42c7fde68fc17b61704ed609..657735318869131f043050fc5be7ad27afd69752 100644
--- a/application/modules/admin/views/scripts/index/adminvar.phtml
+++ b/application/modules/admin/views/scripts/index/adminvar.phtml
@@ -33,18 +33,18 @@ hide: {duration: 1000}
 		<?php
 		$ligne = 0;
 		foreach($this->vars as $var) {
-			if ($var->getId() == 'JS_STAT')
+			if ($var->getClef() == 'JS_STAT')
 				$value = $this->escape($var->getValeur());
 			else if(preg_match('^%0D%0A^',$var->getValeur()))
-			$value = urldecode(str_replace('%0D%0A','<br />',$var->getValeur()));
+			  $value = urldecode(str_replace('%0D%0A','<br />',$var->getValeur()));
 			else
 				$value = urldecode($var->getValeur());
 			$ligne ++ ;
 			?>
 			<tr class="<?php echo ($ligne & 1) ? "first" : "second";?>" data-tooltip="">
-				<td style="vertical-align:top;" ><?php echo $var->getId();?>
+				<td style="vertical-align:top;" ><?php echo $var->getClef();?>
 					<span style="display:none;">
-						<?php echo Class_AdminVar::helpFor($var->getId());?>
+						<?php echo Class_AdminVar::helpFor($var->getClef());?>
 					</span>
 				</td>
 				<td>
@@ -61,7 +61,7 @@ hide: {duration: 1000}
 					<?php endif; ?>
 				</td>
 				<td style="width:2%;text-align:center">
-					<a data-popup="true" href="<?php echo $this->url(['action' => 'adminvaredit', 'cle' => $var->getId()])?>"><?php echo $this->boutonIco("type=edit");?></a>
+					<a data-popup="true" href="<?php echo $this->url(['action' => 'adminvaredit', 'cle' => $var->getClef()])?>"><?php echo $this->boutonIco("type=edit");?></a>
 				</td>
 			</tr>
 		<?php	} ?>
diff --git a/application/modules/admin/views/scripts/index/adminvaredit.phtml b/application/modules/admin/views/scripts/index/adminvaredit.phtml
index 75d1cffe71e20d1f15f18794cb79c92e359fe1b4..8bc8d32de3f8bff8a07f69642975587ee085b872 100644
--- a/application/modules/admin/views/scripts/index/adminvaredit.phtml
+++ b/application/modules/admin/views/scripts/index/adminvaredit.phtml
@@ -1,42 +1,4 @@
-<center>
-<div class="form" align="center">
-<form name="form" action="<?php echo BASE_URL ?>/admin/index/adminvaredit" method="post">
-<fieldset>
-<legend><?php echo $this->traduire('Variable'); ?></legend>
-<br />
-<div class="formTable">
-<table cellspacing="2">
-  <tr>
-    <td class="droite"><?php echo $this->var_cle; ?></td>
-    <td class="gauche"><?php echo $this->tuto;?></td>
-  </tr>
-   <tr>
-    <td class="droite">
-      <?php if ($this->var->isOnOff()): ?>
-        <?php echo $this->_('Activé ?'); ?>
-      <?php else: ?>
-        Nouvelle valeur
-      <?php endif; ?>
-    </td>
-    <td class="gauche">
-      <?php if ($this->var->isOnOff()): ?>
-        <?php echo $this->formCheckbox('valeur', null, array('checked' => $this->var_valeur)); ?>
-      <?php else: ?>
-        <textarea rows="10" cols="60" name="valeur"><?php echo $this->var_valeur; ?></textarea>
-      <?php endif; ?>
-    </td>
-  </tr>
-</table><br />
-</div>
-<input type="hidden" name="cle" value="<?php echo $this->var_cle; ?>" />
-</fieldset><br>
-
-<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=29','picto=back.gif','texte=Retour','url='.BASE_URL.'/admin/index/adminvar','largeur=120px'); ?></td>
-</tr>
-</table>
-</form>
-</div>
-</center>
+<p>
+	<?php echo $this->admin_var->getDescription() ?>
+</p>
+<?php echo $this->renderForm($this->form); ?>
diff --git a/application/modules/admin/views/scripts/popup.phtml b/application/modules/admin/views/scripts/popup.phtml
index 586cd1016f44de48564beb2b462b26c3732578c4..d9b54d727c0b33ad26242696221ed338644036f5 100644
--- a/application/modules/admin/views/scripts/popup.phtml
+++ b/application/modules/admin/views/scripts/popup.phtml
@@ -6,7 +6,6 @@ Class_ScriptLoader::getInstance()
 ->renderJavaScripts();
 
 echo $this->headScript();
-echo $this->headLink(); 
+echo $this->headLink();
 
 ?>
-
diff --git a/application/modules/opac/controllers/BibNumeriqueController.php b/application/modules/opac/controllers/BibNumeriqueController.php
index fb6ae672c8e25e564fd334232855ffcdd1a40018..cc7e611071753fa6e31d4f1a8fccef485520912d 100644
--- a/application/modules/opac/controllers/BibNumeriqueController.php
+++ b/application/modules/opac/controllers/BibNumeriqueController.php
@@ -18,7 +18,7 @@
  * along with BOKEH; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
-class BibNumeriqueController extends Zend_Controller_Action {
+class BibNumeriqueController extends ZendAfi_Controller_Action {
 	public function viewAlbumAction() {
 		if (null === ($album = Class_Album::getLoader()->find((int)$this->_getParam('id')))) {
 			$this->_redirect('opac/');
@@ -254,6 +254,23 @@ class BibNumeriqueController extends Zend_Controller_Action {
 		$this->getResponse()->setBody('<!DOCTYPE html><html><body>'.$html.Class_ScriptLoader::getInstance()->html().'</body></html>');
 
 	}
+
+
+	public function consultBookAction() {
+		$album = Class_Album::find($this->_getParam('id'));
+
+		$client_ip = (new Class_RemoteClient($this->_request))->getIpAddress();
+		$response = (new Class_WebService_BibNumerique_Dilicom_Hub())->consultBook($album,
+																																							 $client_ip);
+		$content = json_decode($response);
+		if ($content->returnMessage) {
+			$this->_helper->notify(implode(',', $content->returnMessage));
+			$this->_redirectToReferer();
+			return;
+		}
+
+		$this->_redirect($content->link->url);
+	}
 }
 
 ?>
\ No newline at end of file
diff --git a/application/modules/opac/controllers/ModulesController.php b/application/modules/opac/controllers/ModulesController.php
index ca66ea07cfe69187fa38f4c87ff237f5997be961..54262af62eee6e507bd1b683527ac421c23dd2b7 100644
--- a/application/modules/opac/controllers/ModulesController.php
+++ b/application/modules/opac/controllers/ModulesController.php
@@ -48,10 +48,10 @@ class ModulesController extends Zend_Controller_Action {
 	}
 
 
-  public function numeriquepremiumAction() {
+	public function numeriquepremiumAction() {
 		$url = $this->_getParam('url');
-    $this->checkNotifyMessage(NULL, $url);
-  }
+		$this->checkNotifyMessage(NULL, $url);
+	}
 
 
 	public function vodeclicAction() {
@@ -79,8 +79,8 @@ class ModulesController extends Zend_Controller_Action {
 	public function mycowAction() {
 		$mycow = new Class_Systeme_ModulesMenu_MyCow();
 		$mycow->afterLoginRedirectTo($this->_request->getServer('HTTP_REFERER'));
-		$this->checkNotifyMessage($mycow, $mycow->getDynamiqueUrl());
-	}
+		 $this->checkNotifyMessage($mycow, $mycow->getDynamiqueUrl());
+	 }
 
 
 	public function mycowSsoAction() {
diff --git a/cosmogramme/php/_init.php b/cosmogramme/php/_init.php
index 5206463c4cf4120fa9855f4ac72503da985cdc96..6fd16e323e0bc358b67aa55c9ef1f3b000b19706 100644
--- a/cosmogramme/php/_init.php
+++ b/cosmogramme/php/_init.php
@@ -2,7 +2,7 @@
 // Constantes
 error_reporting(E_ERROR | E_PARSE);
 
-define("PATCH_LEVEL","240");
+define("PATCH_LEVEL","241");
 
 define("APPLI","cosmogramme");
 define("COSMOPATH", "/var/www/html/vhosts/opac2/www/htdocs");
diff --git a/cosmogramme/sql/patch/patch_239.php b/cosmogramme/sql/patch/patch_239.php
index d9fdc007b0ec30fb269e8d416a525f5b039b8b55..69e32aab073cd84083322b1297ddc97cd2d0d76c 100644
--- a/cosmogramme/sql/patch/patch_239.php
+++ b/cosmogramme/sql/patch/patch_239.php
@@ -6,4 +6,4 @@ $adapter->query('CREATE TABLE `harvest_log` ( '
 								. '`end_date` varchar(10) not null,'
 								. 'primary key (id)'
 								. ') engine=MyISAM default charset=utf8');
-?>
\ No newline at end of file
+?>
diff --git a/cosmogramme/sql/patch/patch_240.php b/cosmogramme/sql/patch/patch_240.php
index 6462478f8e86e3f36b4dc8cc6ac50f0e8c265394..a96601ff87eeb03fe2e8a41ff223b5aed3fa829b 100644
--- a/cosmogramme/sql/patch/patch_240.php
+++ b/cosmogramme/sql/patch/patch_240.php
@@ -1,5 +1,4 @@
 <?php
-
 $page_size = 1000;
 $page = 1;
 
diff --git a/cosmogramme/sql/patch/patch_241.php b/cosmogramme/sql/patch/patch_241.php
new file mode 100644
index 0000000000000000000000000000000000000000..19470edc574429997c9ca3e43fe0fc52307d2fd2
--- /dev/null
+++ b/cosmogramme/sql/patch/patch_241.php
@@ -0,0 +1,10 @@
+<?php
+Zend_Registry::get('sql')->query('CREATE TABLE `album_usage_constraints` ( '
+								. 'id int(11) unsigned not null auto_increment,'
+								. 'album_id int(11) unsigned not null,'
+								. 'usage_type varchar(4) not null,'
+								. 'serialized_datas text not null,'
+								. 'primary key (id),'
+								. 'key (`album_id`)'
+								. ') engine=MyISAM default charset=utf8');
+?>
\ No newline at end of file
diff --git a/library/Class/AdminVar.php b/library/Class/AdminVar.php
index 7de72801480a9fb0e2d9e8db11a7b970c32b7844..11d9e011a1450a5aac2289f24e083167a81a9fed 100644
--- a/library/Class/AdminVar.php
+++ b/library/Class/AdminVar.php
@@ -20,7 +20,12 @@
 	*/
 
 class Class_AdminVar extends Storm_Model_Abstract {
-	 const TYPE_ON_OFF = 'on-off';
+	//@see ZendAfi_Form_Admin_AdminVar for a new form type
+	 const
+		 TYPE_DEFAULT = 'default',
+		 TYPE_ENCODED_DATA = 'encoded-data',
+		 TYPE_ON_OFF = 'on-off',
+		 TYPE_MULTI_INPUT = 'multi-input';
 
 	 protected $_table_name = 'bib_admin_var';
 	 protected $_table_primary = 'CLEF';
@@ -127,7 +132,7 @@ class Class_AdminVar extends Storm_Model_Abstract {
 		* @return bool
 		*/
 	 public static function isDilicomPNBEnabled() {
-		 return self::isModuleEnabled('DILICOM_PNB');
+		 return ('' != self::get('DILICOM_PNB_GLN_CONTRACTOR')) && ('' != self::get('DILICOM_PNB_SERVER_URL'));
 	 }
 
 
@@ -144,11 +149,6 @@ class Class_AdminVar extends Storm_Model_Abstract {
 	 }
 
 
-	 public static function beModuleEnabled($module) {
-		 self::set($module,1);
-	 }
-
-
 	 /**
 		* @return bool
 		*/
@@ -357,9 +357,11 @@ class Class_AdminVar extends Storm_Model_Abstract {
 				],
 				'REGISTER_OK' => [
 					'description' => 'Texte visible par l\'internaute après son inscription.',
+					'type' => self::TYPE_ENCODED_DATA,
 				],
 				'RESA_CONDITION' => [
 					'description' => 'Texte visible après l\'envoi d\'e-mail de demande de réservation.',
+					'type' => self::TYPE_ENCODED_DATA,
 				],
 				'SITE_OK' => [
 					'description' => 'Désactiver pour passer le site en maintenance',
@@ -369,7 +371,7 @@ class Class_AdminVar extends Storm_Model_Abstract {
 					'description' => 'Nom de la bibliothèque chez bibliosurf (en minuscules)',
 				],
 				'JS_STAT' => [
-					'description' => 'Javascript code for statistics',
+					'description' => 'Javascript code for statistics'
 				],
 				'ID_READ_SPEAKER' => [
 					'description' => 'Numéro de client Read Speaker <a target="_blank" href="http://webreader.readspeaker.com">http://webreader.readspeaker.com</a>',
@@ -385,6 +387,7 @@ class Class_AdminVar extends Storm_Model_Abstract {
 				],
 				'LANGUES' => [
 					'description' => 'Liste des codes langue utilisées en plus du français séparées par des ;. Exemple: en;ro;es',
+					'type' => self::TYPE_MULTI_INPUT
 				],
 				'CACHE_ACTIF' => [
 					'description' => implode('<br/>', [
@@ -403,6 +406,8 @@ class Class_AdminVar extends Storm_Model_Abstract {
 						'[{"id":10, "label":"À valider niveau 2"}, {"id":11, "label":"À valider niveau 3"}]',
 						'Les identifiants 1 à 5 sont réservés'
 					]),
+				 'validate' => 'ZendAfi_Validate_WorkflowVar',
+				 'after_save' => 'setWorkflowPermissions',
 				],
 				'WORKFLOW_TEXT_MAIL_ARTICLE_PENDING' => [
 					'description' => 'Contenu de l\'email de notification d\'article en attente de validation',
@@ -518,6 +523,7 @@ class Class_AdminVar extends Storm_Model_Abstract {
 				],
 				'CHAMPS_FICHE_UTILISATEUR' => [
 					'description' => '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;',
+					'type' => self::TYPE_MULTI_INPUT
 				],
 				'FACETTE_PCDM4_LIBELLE' => [
 					'description' => 'Libellé pour la PCDM4',
@@ -608,9 +614,22 @@ class Class_AdminVar extends Storm_Model_Abstract {
 					'description' => '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',
 					'type' => self::TYPE_ON_OFF,
 				],
-				'DILICOM_PNB' => [
-					'description' => 'Activation du PNB Dilicom',
-					'type' => self::TYPE_ON_OFF,
+				'DILICOM_PNB_GLN_COLLECTIVITE' => [
+					'description' => 'Gln de la collectivité, il est fourni par Dilicom.',
+				],
+				'DILICOM_PNB_PWD_COLLECTIVITE' => [
+					'description' => 'Mot de passe de la collectivité, il est fourni par Dilicom.',
+				],
+				'DILICOM_PNB_GLN_CONTRACTOR' => [
+					'description' => 'Contracteur du PNB Dilicom',
+				],
+				'DILICOM_PNB_SERVER_URL' => [
+					'description' => 'Url du serveur PNB Dilicom',
+				],
+				'DILICOM_PNB_IP_ADRESSES' => [
+					'description' => 'Liste des adresses IP publiques autorisées pour la consultation des documents',
+					'type' => self::TYPE_MULTI_INPUT,
+					'validate' => 'ZendAfi_Validate_Dilicom_IpAdresses',
 				],
 				'MYCOW_EID' => [
 					'description' => 'Clé d\'identification MyCOW.EU pour le portail. Cette clé doit être fournie par MyCOW.EU. Elle active la ressource numérique dans le portail.',
@@ -652,19 +671,6 @@ class Class_AdminVar extends Storm_Model_Abstract {
 	}
 
 
-	/**
-	 * @param $name string
-	 * @return string
-	 */
-	public static function helpFor($name) {
-		$known_vars = static::getKnownVars();
-		if (!isset($known_vars[$name]['description']))
-			return '';
-		return $known_vars[$name]['description'];
-	}
-
-
-
 	/** @return bool */
 	public static function isCacheEnabled() {
 		return self::isModuleEnabled('CACHE_ACTIF');
@@ -723,19 +729,42 @@ class Class_AdminVar extends Storm_Model_Abstract {
 	}
 
 
-	public function getType() {
+	public function getDescription() {
+		return static::helpFor($this->getId());
+	}
+
+	public function getVarMetaDataIfNone($key, $default_value) {
 		$known_vars = static::getKnownVars();
 		$name = $this->getId();
-		if (!isset($known_vars[$name]['type']))
-			return '';
-		return $known_vars[$name]['type'];
+		if (!isset($known_vars[$name][$key]))
+			return $default_value;
+		return $known_vars[$name][$key];
 	}
 
+	public function getType() {
+		return $this->getVarMetaDataIfNone('type', self::TYPE_DEFAULT);
+	}
+
+
+	/**
+	 * @param $name string
+	 * @return string
+	 */
+	public static function helpFor($name) {
+		return static::find($name)->getVarMetaDataIfNone('description', '');
+	}
+
+
 	public function isOnOff() {
 		return $this->getType() == self::TYPE_ON_OFF;
 	}
 
 
+	public function isMultiInput() {
+		return $this->getType() == self::TYPE_MULTI_INPUT;
+	}
+
+
 	public static function getBabelthequeId() {
 		$mathes = [];
 		if (preg_match('/bw_([^\.]+)\.js/', (string)self::get('BABELTHEQUE_JS'), $matches))
@@ -768,30 +797,34 @@ class Class_AdminVar extends Storm_Model_Abstract {
 
 	public static function getWorkflowTextMailArticleValidated() {
 		if(!static::get('WORKFLOW_TEXT_MAIL_ARTICLE_VALIDATED'))
-			static::set('WORKFLOW_TEXT_MAIL_ARTICLE_VALIDATED',
-									'L\'article TITRE_ARTICLE a été validé. URL_ARTICLE');
+			static::set('WORKFLOW_TEXT_MAIL_ARTICLE_VALIDATED', 'L\'article TITRE_ARTICLE a été validé. URL_ARTICLE');
 
 		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 (!$validator_class = $this->getVarMetaDataIfNone('validate', null))
+			return;
+		$validate = new $validator_class(isset($this->_attributes_in_db['valeur'])
+																		 ? $this->_attributes_in_db['valeur']
+																		 :'');
+		if ($validate->isValid($this->getValeur()))
+			return;
 
-		if (array_key_exists($this->getId(), $mapping)) {
-			if (!$mapping[$this->getId()]->isValid($this->getValeur())) {
-				foreach($mapping[$this->getId()]->getMessages() as $error)
-					$this->addError($error);
-			}
-		}
+		array_map([$this, 'addError'], $validate->getMessages());
 	}
 
 
 	public function afterSave() {
-		if ('WORKFLOW' != $this->getId())
-			return;
+		if ($afterSaveHook = $this->getVarMetaDataIfNone('after_save', null)) {
+			call_user_func([$this, $afterSaveHook]);
+		}
+	}
+
 
-		$statuses = json_decode($this->getValeur());
+	protected function setWorkflowPermissions() {
+			$statuses = json_decode($this->getValeur());
 		if (!is_array($statuses)) {
 			Class_Permission::cleanDynamicWorkflow();
 			return;
diff --git a/library/Class/Album.php b/library/Class/Album.php
index 627295101e1afea959df289cac75081a91b46593..ef39a702a913c098482b4914dee9ae4d64bb1f54 100644
--- a/library/Class/Album.php
+++ b/library/Class/Album.php
@@ -106,7 +106,12 @@ class Class_Album extends Storm_Model_Abstract {
 	protected $_has_many = ['ressources' => ['model' => 'Class_AlbumRessource',
 																					 'role'				=> 'album',
 																					 'dependents'	=> 'delete',
-																					 'order'				=> 'ordre']];
+																					 'order'				=> 'ordre'],
+
+													'usage_constraints' => ['model' => 'Class_Album_UsageConstraint',
+																									'role' => 'album',
+																									'instance_of' => 'Class_Album_UsageConstraints',
+																									'dependents' => 'delete']];
 
 	protected $_default_attribute_values = ['titre' => '',
 																					'sous_titre' => '',
@@ -273,6 +278,12 @@ class Class_Album extends Storm_Model_Abstract {
 	}
 
 
+	public function getISBN() {
+		$values = $this->getMarc()->getSubfield('10', 'a');
+		return end($values);
+	}
+
+
 	/**
 	 * @return array
 	 */
diff --git a/library/Class/Album/Marc.php b/library/Class/Album/Marc.php
index a4e139d91fe10f3c898c18077b8ac4a7b9330225..a0034a8abae0a31c345f445cb700e9e3ac6fd07c 100644
--- a/library/Class/Album/Marc.php
+++ b/library/Class/Album/Marc.php
@@ -100,7 +100,8 @@ class Class_Album_Marc {
 			}
 		}
 
-		return $this->addZone($field, [[ $subfield, $value ]]);
+		return $this->addZone($field,
+													[ [ $subfield, $value ] ] );
 	}
 
 
diff --git a/library/Class/Album/UsageConstraint.php b/library/Class/Album/UsageConstraint.php
new file mode 100644
index 0000000000000000000000000000000000000000..4fb22d5a3ea376a22260b05898c2e47102cd6573
--- /dev/null
+++ b/library/Class/Album/UsageConstraint.php
@@ -0,0 +1,114 @@
+<?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_Album_UsageConstraint extends Storm_Model_Abstract {
+	const
+		DEVICE_SHARE_CONSTRAINT = '04',
+		LOAN_CONSTRAINT = '06',
+		AVAILABILITY_CONSTRAINT = '07',
+		DURATION = 'duration',
+		QUANTITY = 'quantity',
+		ORDER_LINE_ID = 'order_line_id',
+		MAX_NB_OF_USERS = 'max_number_of_users';
+
+	protected
+		$_table_name = 'album_usage_constraints',
+		$_belongs_to = ['album' => ['model' => 'Class_Album']],
+		$_default_attribute_values = ['serialized_datas' => ''],
+		$_datas;
+
+
+	public function beforeSave() {
+		$this->setSerializedDatas($this->getDatas()->serialized());
+	}
+
+
+	public function isLoanConstraint() {
+		return $this->getUsageType() == self::LOAN_CONSTRAINT;
+	}
+
+
+	public function isAvailabilityConstraint() {
+		return $this->getUsageType() == self::AVAILABILITY_CONSTRAINT;
+	}
+
+
+	public function getDatas() {
+		if (!isset($this->_datas))
+			$this->_datas = (new Class_JSONSerializedDatas($this->getSerializedDatas()));
+		return $this->_datas;
+	}
+
+
+	public function getDuration() {
+		return (int)$this->getDatas()->get(self::DURATION);
+	}
+
+
+	public function getOrderLineId() {
+		return $this->getDatas()->get(self::ORDER_LINE_ID);
+	}
+
+	public function setOrderLineId($id) {
+		$this->getDatas()->set(self::ORDER_LINE_ID, $id);
+		return $this;
+	}
+
+	public function setDuration($value) {
+		$this->getDatas()->set(self::DURATION, $value);
+		return $this;
+	}
+
+
+	public function setQuantity($value) {
+		$this->getDatas()->set(self::QUANTITY, $value);
+		return $this;
+	}
+
+
+	public function getQuantity() {
+		return $this->getDatas()->get(self::QUANTITY);
+	}
+
+
+	public function setMaxNumberOfUsers($value) {
+		$this->getDatas()->set(self::MAX_NB_OF_USERS, $value);
+		return $this;
+	}
+
+
+	public function getMaxNumberOfUsers() {
+		return $this->getDatas()->get(self::MAX_NB_OF_USERS);
+	}
+
+
+	public function acceptVisitor($visitor) {
+		$visitor->visitUsageType($this->getUsageType());
+		$this->getDatas()
+				 ->keysAndValuesDo(
+													 function($k, $v) use ($visitor) {
+														 $visitor->visitUsageData($k, $v);
+													 });
+	}
+}
+
+?>
\ No newline at end of file
diff --git a/library/Class/Album/UsageConstraints.php b/library/Class/Album/UsageConstraints.php
new file mode 100644
index 0000000000000000000000000000000000000000..a8b591ac1a85aa7b14e7eb101d12226b147fb695
--- /dev/null
+++ b/library/Class/Album/UsageConstraints.php
@@ -0,0 +1,58 @@
+<?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_Album_UsageConstraints extends Storm_Model_Collection_Abstract {
+	public function getLoanConstraint() {
+		return $this->detect(function($c) {return $c->isLoanConstraint();});
+	}
+
+
+	public function getLoanDuration() {
+		return $this->getLoanConstraint()->getDuration();
+	}
+
+
+	public function getLoanOrderLineId() {
+		return $this->getLoanConstraint()->getOrderLineId();
+	}
+
+
+	public function getLoanMaxNumberOfUsers() {
+		return $this->getLoanConstraint()->getMaxNumberOfUsers();
+	}
+
+
+	public function getLoanQuantity() {
+		return $this->getLoanConstraint()->getQuantity();
+	}
+
+
+	public function getAvailabilityDuration() {
+		return $this->detect(function($c) {return $c->isAvailabilityConstraint();})->getDuration();
+	}
+
+
+	public function acceptVisitor($visitor) {
+		return $this->eachDo(function($c) use ($visitor){ $visitor->visitUsageConstraint($c);});
+	}
+}
+?>
\ No newline at end of file
diff --git a/library/Class/JSONSerializedDatas.php b/library/Class/JSONSerializedDatas.php
new file mode 100644
index 0000000000000000000000000000000000000000..beba702c01f1034216e75f3b61ba69bde79ce7b3
--- /dev/null
+++ b/library/Class/JSONSerializedDatas.php
@@ -0,0 +1,55 @@
+<?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_JSONSerializedDatas {
+	protected $_datas;
+
+	public function __construct($datas) {
+		if (!$this->_datas = json_decode($datas, true))
+			$this->_datas = [];
+	}
+
+
+	public function get($name) {
+		return isset($this->_datas[$name]) ? $this->_datas[$name] : null;
+	}
+
+
+	public function set($name, $value) {
+		$this->_datas[$name] = $value;
+		return $this;
+	}
+
+
+	public function serialized() {
+		return json_encode($this->_datas);
+	}
+
+
+	public function keysAndValuesDo($closure) {
+		foreach($this->_datas as $key => $value)
+			$closure($key, $value);
+		return $this;
+	}
+}
+
+
+?>
\ No newline at end of file
diff --git a/library/Class/RemoteClient.php b/library/Class/RemoteClient.php
new file mode 100644
index 0000000000000000000000000000000000000000..e265e8d7e6e747d05ef7465c77d16456188d6c0f
--- /dev/null
+++ b/library/Class/RemoteClient.php
@@ -0,0 +1,44 @@
+<?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_RemoteClient {
+	protected $_request;
+
+	public function __construct($request) {
+		$this->_request = $request;
+	}
+
+
+	public function getIpAddress() {
+		$keys = ['HTTP_X_FORWARDED_FOR', 'HTTP_CLIENT_IP', 'REMOTE_ADDR'];
+		foreach($keys as $key)
+			if ($ip = $this->_extractIp($this->_request->getServer($key)))
+				return $ip;
+	}
+
+	protected function _extractIp($str) {
+		if (!$ips = array_filter(explode(',', $str)))
+			return null;
+		return trim($ips[0]);
+	}
+}
+
+?>
\ No newline at end of file
diff --git a/library/Class/UserGroup.php b/library/Class/UserGroup.php
index 9781dedf67ffccf6cae202082133e172bda43337..d46fa700b7554bc6f598f052f23849d8ba8e9107 100644
--- a/library/Class/UserGroup.php
+++ b/library/Class/UserGroup.php
@@ -55,6 +55,7 @@ class Class_UserGroup extends Storm_Model_Abstract {
 	const RIGHT_ACCES_CYBERLIBRIS = 22;
 	const RIGHT_ACCES_MYCOW = 24;
 	const RIGHT_ACCES_KIDILANGUES = 25;
+	const RIGHT_ACCES_PNB_DILICOM = 26;
 	const RIGHT_ACCES_PLANETNEMO = 27;
 
 	// droits moderateurs
@@ -95,6 +96,7 @@ class Class_UserGroup extends Storm_Model_Abstract {
 		self::RIGHT_ACCES_MYCOW => 'Bibliothèque numérique: accéder à MyCOW.EU',
 		self::RIGHT_ACCES_PLANETNEMO => 'Bibliothèque numérique: accéder à planetnemo.fr',
 		self::RIGHT_ACCES_KIDILANGUES => 'Bibliothèque numérique: accéder à Kidilangues',
+		self::RIGHT_ACCES_PNB_DILICOM => 'Bibliothèque numérique: autoriser le prêt numérique Dilicom',
 		self::RIGHT_USER_DOMAINES_SUPPRESSION_LIMIT => 'Domaines: accès, modification et suppression limitée au créateur',
 		self::RIGHT_USER_DOMAINES_TOTAL_ACCESS => 'Domaines: accès total en modification et suppression',
 		self::RIGHT_USER_FILE_ACCESS => 'Articles: accès sur les répertoires images et file',
diff --git a/library/Class/Users.php b/library/Class/Users.php
index f1d3f37c58cbda895d930bb776bf8d6c384e167f..9ed4c5ea9c7dcce088a3fdf9e6ead3463b30a969 100644
--- a/library/Class/Users.php
+++ b/library/Class/Users.php
@@ -769,6 +769,11 @@ class Class_Users extends Storm_Model_Abstract {
 	}
 
 
+	public function hasRightAccessDilicom() {
+		return $this->isAbonneAndHasRightToAccess(Class_UserGroup::RIGHT_ACCES_PNB_DILICOM);
+	}
+
+
 	public function getFirstAvisByIdNotice($id_notice) {
 		$notice = Class_Notice::getLoader()->find($id_notice);
 		$avis = $notice->getAvisByUser($this);
diff --git a/library/Class/WebService/BibNumerique/Dilicom/Book.php b/library/Class/WebService/BibNumerique/Dilicom/Book.php
index 193f24f4611f5d96c647cd9cc53a0e2eca6248a9..2fea88928b13e14abc45fae8eabb364498024292 100644
--- a/library/Class/WebService/BibNumerique/Dilicom/Book.php
+++ b/library/Class/WebService/BibNumerique/Dilicom/Book.php
@@ -24,7 +24,9 @@ class Class_WebService_BibNumerique_Dilicom_Book extends Class_WebService_BibNum
 	protected
 		$_isbn,
 		$_subtitle,
-		$_collections = [];
+		$_order_line_id,
+		$_collections = [],
+		$_usage_constraints = [];
 
 
 	public function fillAlbum($album) {
@@ -32,7 +34,14 @@ class Class_WebService_BibNumerique_Dilicom_Book extends Class_WebService_BibNum
 			->setTypeDoc(Class_TypeDoc::find(Class_TypeDoc::DILICOM))
 			->setISBN($this->_isbn)
 			->setSousTitre($this->_subtitle)
-			->addEditor($this->getEditeur());
+			->addEditor($this->getEditeur())
+			->setUsageConstraints($this->_usage_constraints);
+
+		if ($this->_order_line_id)
+			$album
+			->getUsageConstraints()
+			->getLoanConstraint()
+			->setOrderLineId($this->_order_line_id);
 
 		array_map([$album, 'addAuthor'], $this->getAuthors());
 		array_map([$album, 'addCollection'], $this->_collections);
@@ -44,10 +53,21 @@ class Class_WebService_BibNumerique_Dilicom_Book extends Class_WebService_BibNum
 	}
 
 
+	public function setOrderLineId($data) {
+		$this->_order_line_id = $data;
+	}
+
+
 	public function addCollection($collection) {
 		$this->_collections []= $collection;
 	}
 
+
+	public function addUsageConstraint($constraint) {
+		$this->_usage_constraints []= $constraint;
+	}
+
+
 	public function setIsbn($isbn) {
 		$this->_isbn = $isbn;
 		return $this;
diff --git a/library/Class/WebService/BibNumerique/Dilicom/Hub.php b/library/Class/WebService/BibNumerique/Dilicom/Hub.php
new file mode 100644
index 0000000000000000000000000000000000000000..80a29eeff9b7232c5a8722948112af195f16f142
--- /dev/null
+++ b/library/Class/WebService/BibNumerique/Dilicom/Hub.php
@@ -0,0 +1,84 @@
+<?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_WebService_BibNumerique_Dilicom_Hub extends Class_WebService_Abstract {
+	use Trait_TimeSource;
+
+	public function consultBook($album, $ip_address) {
+		$order_line_id = $album->getUsageConstraints()->getLoanOrderLineId();
+
+		return $this->dilicomCall('consultBook',
+															['orderLineId' => $order_line_id,
+															 'accessMedium' => 'STREAMING',
+															 'localization' => 'IN_SITU',
+															 'consultEndDate' => urlencode(date(DATE_ISO8601 ,$this->getCurrentTime() + 3600)),
+															 'ean13' => $album->getISBN(),
+															 'ipAddress' => $ip_address,
+															 'loanId' =>  implode('',
+																										[base_convert($this->getTimeSource()->time(), 10, 36),
+																										 Class_Users::currentUserId(),
+																										 $album->getId()])
+															]);
+	}
+
+
+	public function declareIp($ips = []) {
+		return $this->dilicomCall('declareIp', $this->buildIps($ips));
+	}
+
+
+	protected function dilicomCall($service, $params) {
+		static::getHttpClient()->setAuth(Class_AdminVar::get('DILICOM_PNB_GLN_COLLECTIVITE'),
+																		 Class_AdminVar::get('DILICOM_PNB_PWD_COLLECTIVITE'));
+		return $this->httpGet(
+													$this->buildUrl($service,
+																					$params));
+	}
+
+
+	protected function buildUrl($service, $params) {
+		$params = array_merge(['glnContractor' => Class_AdminVar::get('DILICOM_PNB_GLN_CONTRACTOR')],
+													$params);
+
+		$parts = [];
+		foreach($params as $key => $value) {
+			$parts[] = $key . '=' . $value;
+		}
+
+		return $this->getDilicomUrl() . $service . '?' . implode('&', $parts);
+	}
+
+
+	protected function getDilicomUrl() {
+		return Class_AdminVar::get('DILICOM_PNB_SERVER_URL') . '/v2/pnb-numerique/json/';
+	}
+
+
+	protected function buildIps($ips) {
+		$ips_param = [];
+		for($i = 0; $i < count($ips); $i++) {
+			$ips_param['ips[' . $i . ']'] = $ips[$i];
+		}
+		return $ips_param;
+	}
+}
+?>
\ No newline at end of file
diff --git a/library/Class/WebService/BibNumerique/Dilicom/ONIXFile.php b/library/Class/WebService/BibNumerique/Dilicom/ONIXFile.php
index 8539597c100ac07decc7f6e12b2d97369a980a20..fc6d268b8ee6b651c93af64f28e481a7618edd22 100644
--- a/library/Class/WebService/BibNumerique/Dilicom/ONIXFile.php
+++ b/library/Class/WebService/BibNumerique/Dilicom/ONIXFile.php
@@ -29,7 +29,9 @@ class Class_WebService_BibNumerique_Dilicom_ONIXFile {
 		TEXT_TYPE_DESCRIPTION = '03',
 		LANG_ROLE_TEXT_LANG = '01',
 		RESOURCE_CONTENT_TYPE_FRONT_COVER = '01',
-		RESOURCE_CONTENT_TYPE_WIDGET = '16';
+		RESOURCE_CONTENT_TYPE_WIDGET = '16',
+		USAGE_UNIT_DAYS = '09',
+		USAGE_UNIT_USERS = '07';
 
 	protected
 		$_parser,
@@ -40,6 +42,9 @@ class Class_WebService_BibNumerique_Dilicom_ONIXFile {
 		$_current_product_id_value,
 		$_current_resource_content_type,
 		$_current_language_role,
+		$_current_usage_constraint,
+		$_current_quantity,
+		$_current_usage_unit,
 		$_tag_mapping = ['B203' => 'TitleText',
 										 'B221' => 'ProductIDType',
 										 'B244' => 'IDValue',
@@ -53,7 +58,10 @@ class Class_WebService_BibNumerique_Dilicom_ONIXFile {
 										 'B253' => 'LanguageRole',
 										 'B252' => 'LanguageCode',
 										 'X435' => 'ResourceLink',
-										 'X436' => 'ResourceContentType'];
+										 'X436' => 'ResourceContentType',
+										 'X318' => 'EPubUsageType',
+										 'X320' => 'Quantity',
+										 'X321' => 'EPubUsageUnit'];
 
 
 	public function __construct() {
@@ -77,6 +85,7 @@ class Class_WebService_BibNumerique_Dilicom_ONIXFile {
 
 	public function parseXML($xml) {
 		$this->_book = new Class_WebService_BibNumerique_Dilicom_Book();
+		$this->_resources = [];
 
 		$this->_parser = (new Class_WebService_FasterXMLParser());
 		$this->_parser
@@ -188,5 +197,40 @@ class Class_WebService_BibNumerique_Dilicom_ONIXFile {
 			$this->_book->setExternalURI($content);
 		}
 	}
+
+
+	public function startEPubUsageConstraint() {
+		$this->_book->addUsageConstraint($this->_current_usage_constraint = new Class_Album_UsageConstraint());
+	}
+
+
+	public function endEPubUsageType($content) {
+		$this->_current_usage_constraint->setUsageType($content);
+	}
+
+
+	public function endEPubUsageLimit() {
+		if ($this->_current_usage_unit == self::USAGE_UNIT_DAYS) {
+			$this->_current_usage_constraint->setDuration($this->_current_quantity);
+			return;
+		}
+
+		if ($this->_current_usage_unit == self::USAGE_UNIT_USERS) {
+			$this->_current_usage_constraint->setMaxNumberOfUsers($this->_current_quantity);
+			return;
+		}
+
+		$this->_current_usage_constraint->setQuantity($this->_current_quantity);
+	}
+
+
+	public function endQuantity($content) {
+		$this->_current_quantity = $content;
+	}
+
+
+	public function endEPubUsageUnit($content) {
+		$this->_current_usage_unit = $content;
+	}
 }
 ?>
\ No newline at end of file
diff --git a/library/Class/WebService/BibNumerique/Dilicom/PNBOffersFile.php b/library/Class/WebService/BibNumerique/Dilicom/PNBOffersFile.php
index 031d27c7519aeaee99a5e74a027f2c54ab36300d..d6b90910d6adc7b61529438d3bfddf2a3f56637a 100644
--- a/library/Class/WebService/BibNumerique/Dilicom/PNBOffersFile.php
+++ b/library/Class/WebService/BibNumerique/Dilicom/PNBOffersFile.php
@@ -52,6 +52,14 @@ class Class_WebService_BibNumerique_Dilicom_PNBOffersFile {
 		$this->_books []= Class_WebService_BibNumerique_Dilicom_ONIXFile::bookFromXML(html_entity_decode($data));
 	}
 
+
+	public function endOrderLineId($data) {
+		end($this->_books)->setOrderLineId($data);
+	}
+
+	public function endean13($data) {
+		end($this->_books)->setISBN($data);
+	}
 }
 
 ?>
\ No newline at end of file
diff --git a/library/ZendAfi/Form/Admin/AdminVar.php b/library/ZendAfi/Form/Admin/AdminVar.php
new file mode 100644
index 0000000000000000000000000000000000000000..1d9a09480367669b01ba12272a57cf1558d3b8d7
--- /dev/null
+++ b/library/ZendAfi/Form/Admin/AdminVar.php
@@ -0,0 +1,74 @@
+<?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_Form_Admin_AdminVar extends ZendAfi_Form {
+	protected static $_FORM_CLASSES =
+		[
+		 Class_AdminVar::TYPE_ON_OFF => 'ZendAfi_Form_Admin_AdminVar_Checkbox',
+		 Class_AdminVar::TYPE_MULTI_INPUT => 'ZendAfi_Form_Admin_AdminVar_MultiInput',
+		 Class_AdminVar::TYPE_DEFAULT => 'ZendAfi_Form_Admin_AdminVar',
+		 Class_AdminVar::TYPE_ENCODED_DATA => 'ZendAfi_Form_Admin_AdminVar_EncodedData',
+		];
+
+
+	public static function newWithAdminVar($admin_var) {
+		$form_class = static::$_FORM_CLASSES[$admin_var->getType()];
+
+		$form = (new $form_class());
+		$form->setValue($admin_var->getValeur());
+		return $form;
+	}
+
+
+	public function setValue($value) {
+		$this->populate(['valeur' => $value]);
+	}
+
+
+	public function getAdminVarValue() {
+		$values = $this->getValues();
+		$filter = new Zend_Filter_StripTags();
+		return trim($filter->filter($values['valeur']));
+	}
+
+
+	public function init() {
+		parent::init();
+
+		$this->addVariableEditElement();
+
+		$this->addDisplayGroup([ 'valeur' ],
+													 'variable',
+													 ['legend' => $this->_('Variable')]);
+	}
+
+
+	public function addVariableEditElement() {
+		$this->addElement('textarea',
+											'valeur',
+											['label' => $this->_('Valeur'),
+											 'rows' => 10,
+											 'cols' => 40,
+											 'required' => true,
+											 'allowEmpty' => false]);
+	}
+}
+?>
\ No newline at end of file
diff --git a/library/ZendAfi/Form/Admin/AdminVar/Checkbox.php b/library/ZendAfi/Form/Admin/AdminVar/Checkbox.php
new file mode 100644
index 0000000000000000000000000000000000000000..0b0af3826cb0a860e6ad201e112475ce20ec1604
--- /dev/null
+++ b/library/ZendAfi/Form/Admin/AdminVar/Checkbox.php
@@ -0,0 +1,32 @@
+<?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_Form_Admin_AdminVar_Checkbox extends ZendAfi_Form_Admin_AdminVar {
+	public function addVariableEditElement() {
+		$this->addElement('checkbox',
+											'valeur',
+											['label' => $this->_('Activé ?'),
+											 'required' => true,
+											 'allowEmpty' => false]);
+	}
+}
+?>
\ No newline at end of file
diff --git a/library/ZendAfi/Form/Admin/AdminVar/EncodedData.php b/library/ZendAfi/Form/Admin/AdminVar/EncodedData.php
new file mode 100644
index 0000000000000000000000000000000000000000..a819ed039bf4b47740c80223ab7f6e3caf3f9833
--- /dev/null
+++ b/library/ZendAfi/Form/Admin/AdminVar/EncodedData.php
@@ -0,0 +1,34 @@
+<?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_Form_Admin_AdminVar_EncodedData extends ZendAfi_Form_Admin_AdminVar {
+	public function setValue($value) {
+		parent::setValue(urldecode($value));
+	}
+
+
+	public function getAdminVarValue() {
+		$values = $this->getValues();
+		return urlencode(trim($values['valeur']));
+	}
+}
+?>
\ No newline at end of file
diff --git a/library/ZendAfi/Form/Admin/AdminVar/MultiInput.php b/library/ZendAfi/Form/Admin/AdminVar/MultiInput.php
new file mode 100644
index 0000000000000000000000000000000000000000..25ab4e142a9823c0e27e640351fc67aefbf220fc
--- /dev/null
+++ b/library/ZendAfi/Form/Admin/AdminVar/MultiInput.php
@@ -0,0 +1,45 @@
+<?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_Form_Admin_AdminVar_MultiInput extends ZendAfi_Form_Admin_AdminVar {
+	public function addVariableEditElement() {
+		$this->addElement('multiInput',
+											'valeur',
+											['label' => $this->_('Liste de valeurs'),
+											 'fields' => [
+																		['name' => 'values', 'label' => $this->_('Valeur')]
+											 ]
+											]);
+	}
+
+
+	public function setValue($value) {
+		$this->getElement('valeur')->setValues(['values' => explode(';', $value)]);
+	}
+
+
+	public function getAdminVarValue() {
+		$values = $this->getValues();
+		return implode(';', $values['valeur']['values']);
+	}
+}
+?>
\ No newline at end of file
diff --git a/library/ZendAfi/Form/Album.php b/library/ZendAfi/Form/Album.php
index d5950f5fa420da6915860367791ac8736f164794..140325595ca4cadf3b82a4af26391698665c673e 100644
--- a/library/ZendAfi/Form/Album.php
+++ b/library/ZendAfi/Form/Album.php
@@ -31,7 +31,8 @@ class ZendAfi_Form_Album extends ZendAfi_Form {
 																 'status',
 																 'frbr_multi'];
 
-	public static function newWith($album) {
+
+	public static function newWithAlbum($album) {
 		$form = new self();
 
 		$form
@@ -62,8 +63,7 @@ class ZendAfi_Form_Album extends ZendAfi_Form {
 												 'album_desc',
 				                 ['legend' => $form->_('Description')])
 
-			->addDisplayGroup(['droits',
-					               'droits_precision',
+			->addDisplayGroup([
 												 'annee',
 												 'cote',
 												 'provenance',
@@ -79,6 +79,12 @@ class ZendAfi_Form_Album extends ZendAfi_Form {
 												 'album_metadata',
 				                 ['legend' => $form->_('Metadonnées')])
 
+			->addDisplayGroup(['droits',
+					               'droits_precision',
+												 'usage_constraints'],
+												'album_usage',
+												['legend' => $form->_('Utilisation')])
+
 			->addDisplayGroup(['bibliotheques',
 												 'annexes',
 												 'sections'],
@@ -298,7 +304,9 @@ class ZendAfi_Form_Album extends ZendAfi_Form {
 			->addElement('text', 'droits_precision',
 									 ['label' => '',
 										'value' => $current_precision,
-										'style'	=> 'width:440px;']);
+										'style'	=> 'width:440px;'])
+			->addElement('usageConstraints', 'usage_constraints', ['label' => 'Utilisations',
+																														 'value' => $album->getUsageConstraints()]);
 	}
 
 	public function addAffichageFor($album) {
diff --git a/library/ZendAfi/Form/Decorator/MultiInput.php b/library/ZendAfi/Form/Decorator/MultiInput.php
index 79f31e9d24e4438e1035c47b6d3faf2778e05818..26901b3bdc3a9e0a336b0596e3106678512fcdc8 100644
--- a/library/ZendAfi/Form/Decorator/MultiInput.php
+++ b/library/ZendAfi/Form/Decorator/MultiInput.php
@@ -27,17 +27,16 @@ class ZendAfi_Form_Decorator_MultiInput extends Zend_Form_Decorator_Abstract {
 		Class_ScriptLoader::getInstance()
 			->loadJQuery()
 			->loadJQueryUI()
-			->addAdminScript('multi_inputs/multi_inputs.js');
-
-		return $content
-			. ' <div id="multi_inputs_' . $this->_element->getId() . '"></div>
-<script type="text/javascript">$("#multi_inputs_' . $this->_element->getId()  .'").multi_inputs({
+			->addAdminScript('multi_inputs/multi_inputs.js')
+			->addJQueryReady('$("#multi_inputs_' . $this->_element->getId()  .'").multi_inputs({
 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>';
+"delete_message":'. json_encode($this->_element->getDeleteMessage()) . '});');
+
+		return $content
+			. ' <div id="multi_inputs_' . $this->_element->getId() . '"></div>';
 	}
 }
 ?>
\ No newline at end of file
diff --git a/library/ZendAfi/Form/Decorator/UsageConstraints.php b/library/ZendAfi/Form/Decorator/UsageConstraints.php
new file mode 100644
index 0000000000000000000000000000000000000000..8c5623bac89d4f5555e653d83029917ac221d8e3
--- /dev/null
+++ b/library/ZendAfi/Form/Decorator/UsageConstraints.php
@@ -0,0 +1,33 @@
+<?php
+/**
+ * Copyright (c) 2012, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+class ZendAfi_Form_Decorator_UsageConstraints extends Zend_Form_Decorator_Abstract {
+	/**
+	 * @param  string $content
+	 * @return string
+	 */
+	public function render($content) {
+		return
+			$content.
+			$this->_element->getView()->album_UsageConstraints($this->_element->getValue());
+	}
+}
+?>
\ No newline at end of file
diff --git a/library/ZendAfi/Form/Element/MultiInput.php b/library/ZendAfi/Form/Element/MultiInput.php
index 1bf5eff5218216935efed0c413955c94b297533b..ec91bf7bac8acbb1e00042186654aac14f525f8f 100644
--- a/library/ZendAfi/Form/Element/MultiInput.php
+++ b/library/ZendAfi/Form/Element/MultiInput.php
@@ -32,7 +32,7 @@ class ZendAfi_Form_Element_MultiInput extends Zend_Form_Element {
 		parent::__construct($spec, $options);
 		$decorators = $this->_decorators;
 		$this->_decorators = ['MultiInput' => new ZendAfi_Form_Decorator_MultiInput()];
-
+		$this->addDecorator('Errors');
 		foreach ($decorators as $name => $value)
 			$this->_decorators[$name] = $value;
 		$this->removeDecorator('ViewHelper');
@@ -165,4 +165,9 @@ class ZendAfi_Form_Element_MultiInput extends Zend_Form_Element {
 	public function getDescs() {
 		return $this->_descs;
 	}
+
+
+	protected function _getErrorMessages() {
+		return $this->getErrorMessages();
+	}
 }
\ No newline at end of file
diff --git a/library/ZendAfi/Form/Element/UsageConstraints.php b/library/ZendAfi/Form/Element/UsageConstraints.php
new file mode 100644
index 0000000000000000000000000000000000000000..13aa231eeb5d720eba097e09b2f6f815d227c137
--- /dev/null
+++ b/library/ZendAfi/Form/Element/UsageConstraints.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
+ */
+class ZendAfi_Form_Element_UsageConstraints extends Zend_Form_Element_Xhtml {
+	public function __construct($spec, $options = null) {
+		parent::__construct($spec, $options);
+		$decorators = $this->_decorators;
+		$this->_decorators = ['UsageConstraints' => new ZendAfi_Form_Decorator_UsageConstraints()];
+
+		foreach ($decorators as $name => $value) {
+			$this->_decorators[$name] = $value;
+		}
+
+		$this->removeDecorator('ViewHelper');
+	}
+}
+?>
\ No newline at end of file
diff --git a/library/ZendAfi/Validate/Dilicom/IpAdresses.php b/library/ZendAfi/Validate/Dilicom/IpAdresses.php
new file mode 100644
index 0000000000000000000000000000000000000000..165957eebba6dae0810f27d73f89eb30568ec697
--- /dev/null
+++ b/library/ZendAfi/Validate/Dilicom/IpAdresses.php
@@ -0,0 +1,56 @@
+<?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_Dilicom_IpAdresses extends Zend_Validate_Abstract{
+	const
+		DECLARE_IP_FAIL = 'declareIpFail',
+		CONFIGURATION_INCOMPLETE = 'configurationIncomplete',
+		COMMUNICATION_ERROR = 'communicationError';
+
+	protected $_messageTemplates = [
+																	self::DECLARE_IP_FAIL   => "%value%",
+																	self::CONFIGURATION_INCOMPLETE => 'configuration du PNB Dilicom incomplète',
+																	self::COMMUNICATION_ERROR => 'échec de communication avec le serveur PNB Dilicom'];
+
+	public function isValid($value)	{
+		if (!Class_AdminVar::isDilicomPNBEnabled()) {
+			$this->_error(self::CONFIGURATION_INCOMPLETE);
+			return false;
+		}
+
+		$content = (new Class_WebService_BibNumerique_Dilicom_Hub())->declareIp(explode(';', $value));
+		if (!$result = json_decode($content)) {
+			$this->_error(self::COMMUNICATION_ERROR);
+			return false;
+		}
+
+		$this->_setValue(implode(',', $result->returnMessage));
+
+		if ('OK' !== $result->returnStatus) {
+			$this->_error(self::DECLARE_IP_FAIL);
+			return false;
+		}
+		return true;
+	}
+}
+
+?>
\ No newline at end of file
diff --git a/library/ZendAfi/View/Helper/Album/UsageConstraints.php b/library/ZendAfi/View/Helper/Album/UsageConstraints.php
new file mode 100644
index 0000000000000000000000000000000000000000..4106454f3697d1f03622fb6e24dd2c5f9bfeee75
--- /dev/null
+++ b/library/ZendAfi/View/Helper/Album/UsageConstraints.php
@@ -0,0 +1,100 @@
+<?php
+/**
+ * Copyright (c) 2012, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+class ZendAfi_View_Helper_Album_UsageConstraints extends Zend_View_Helper_HtmlElement {
+	protected
+		$_usage_type_labels,
+		$_data_labels,
+		$_content;
+
+	public function album_UsageConstraints($constraints){
+		if (!$constraints)
+			return '';
+
+		$this->_content = '';
+		$constraints->acceptVisitor($this);
+		return $this->_tag('dl', $this->_content);
+	}
+
+
+	public function visitUsageConstraint($constraint) {
+		$this->_constraint_content = '';
+		$this->_constraint_type = '';
+
+		$constraint->acceptVisitor($this);
+
+		$this->_content .= $this->getDtDd($this->_constraint_type,
+																			$this->getListedConstraints());
+	}
+
+
+	public function visitUsageType($id) {
+		$this->_constraint_type = $this->getUsageTypeLabel($id);
+	}
+
+
+	public function visitUsageData($name, $value) {
+		$this->_constraint_content .= $this->getDtDd($this->getDataLabel($name), $value);
+	}
+
+
+	public function getUsageTypeLabel($id) {
+		if (!isset($this->_usage_type_labels))
+			$this->_usage_type_labels =
+				[
+				 Class_Album_UsageConstraint::DEVICE_SHARE_CONSTRAINT => $this->view->_('Appareils autorisés'),
+				 Class_Album_UsageConstraint::LOAN_CONSTRAINT => $this->view->_('Prêt'),
+				 Class_Album_UsageConstraint::AVAILABILITY_CONSTRAINT => $this->view->_('Mise à disposition')
+				];
+
+		return $this->_usage_type_labels[$id];
+	}
+
+
+	public function getDataLabel($id) {
+		if (!isset($this->_data_labels))
+			$this->_data_labels =
+				[
+				 'duration' => $this->view->_('Durée (j)'),
+				 'quantity' => $this->view->_('Quantité'),
+				 'max_number_of_users' => $this->view->_('Nombre d\'utilisateurs')
+				];
+
+		return $this->_data_labels[$id];
+	}
+
+
+	protected function getListedConstraints() {
+		return $this->_tag('dl', $this->_constraint_content);
+	}
+
+
+	protected function getDtDd($term, $data) {
+		return $this->_tag('dt', $term) .	$this->_tag('dd', $data);
+	}
+
+
+	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/TagDilicomWidget.php b/library/ZendAfi/View/Helper/TagDilicomWidget.php
index 33034d7f847058b556dc8c909403bbae3871dc94..97126d62caf4c071ad2cfeb27cb365e0920d4510 100644
--- a/library/ZendAfi/View/Helper/TagDilicomWidget.php
+++ b/library/ZendAfi/View/Helper/TagDilicomWidget.php
@@ -22,12 +22,22 @@
 
 class ZendAfi_View_Helper_TagDilicomWidget extends Zend_View_Helper_HtmlElement {
 	public function tagDilicomWidget($album) {
-		return $this->view->tag('iframe',
-														null,
-														['src' => $album->getExternalURI(),
-														 'width' => '100%',
-														 'height' => '600px']);
+		$iframe = $this->view->tag('iframe',
+															 null,
+															 ['src' => $album->getExternalURI(),
+																'width' => '100%',
+																'height' => '600px']);
+
+		$link = $this->view->tag('p', $this->view->_('Vous devez vous connecter pour accéder à la consultation en ligne.'));
+
+		if(($user = Class_Users::getIdentity()) && ($user->hasRightAccessDilicom()))
+			$link = 	$this->view->tagAnchor(['controller' => 'bib-numerique',
+																				'action' => 'consult-book',
+																				'id' => $album->getId()],
+																			 $this->view->_('Consulter le livre en ligne'),
+																			 ['target' => '_blank']);
+
+		return $link . $iframe;
 	}
 }
-
 ?>
\ No newline at end of file
diff --git a/public/admin/css/global.css b/public/admin/css/global.css
index cbeeeb642b73d696084738dd1b3eecc1aa044c14..169005624ac3c559b3bbc1419241587842f2797f 100644
--- a/public/admin/css/global.css
+++ b/public/admin/css/global.css
@@ -1006,6 +1006,10 @@ form#album #fieldset-album_display tr>td:first-child {
 		width: 1%;
 }
 
+
+form#album dl dl dt {display:inline-block; width: 180px}
+form#album dl dl dd {display: inline;}
+
 button.switchview {
     float: right;
     border: 0px;
@@ -1228,3 +1232,4 @@ span.ui-dialog-title {
 div#reader {
 		min-width: 700px;
 }
+
diff --git a/scripts/apply_patch.php b/scripts/apply_patch.php
new file mode 100644
index 0000000000000000000000000000000000000000..aae6316b1ecb8f95b28def6e947217f723dd09ab
--- /dev/null
+++ b/scripts/apply_patch.php
@@ -0,0 +1,5 @@
+<?php
+require('console.php');
+require('cosmogramme/sql/patch/patch_'.$argv[1].'.php');
+echo "done\n";
+?>
\ No newline at end of file
diff --git a/scripts/emacs/phafi-mode.el b/scripts/emacs/phafi-mode.el
index 3d0b35b7ff86940a648f6fc9e00d31319b7a4f43..6a3b400ca349e1ea675b4d2e09727bb3c65b688a 100644
--- a/scripts/emacs/phafi-mode.el
+++ b/scripts/emacs/phafi-mode.el
@@ -356,6 +356,17 @@
   )
 
 
+(defun phafi-sql-patch(patch)
+  "Force execution of db migration"
+	(interactive (list (read-number "Enter patch number: ")))
+	
+  (async-shell-command (concat
+												"cd "	(phafi-root-dir) ";"
+												"php scripts/apply_patch.php "(number-to-string patch)
+												))
+  )
+
+
 (defun phafi-root-dir()
   (concat
    (file-name-directory phafi-phpunit-config) "../")
diff --git a/tests/application/modules/admin/controllers/AdminIndexControllerTest.php b/tests/application/modules/admin/controllers/AdminIndexControllerTest.php
index 6e91d1f3a660f03bc82fcc630441ed29b9d5122f..92d2fe4d395f0e453c05a7a1561ce532c3d4604c 100644
--- a/tests/application/modules/admin/controllers/AdminIndexControllerTest.php
+++ b/tests/application/modules/admin/controllers/AdminIndexControllerTest.php
@@ -19,6 +19,7 @@
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
 require_once 'AdminAbstractControllerTestCase.php';
+require_once 'fixtures/RessourcesNumeriquesFixtures.php';
 
 abstract class AdminIndexControllerTestCase extends Admin_AbstractControllerTestCase {
 	public function setUp() {
@@ -153,9 +154,9 @@ class AdminIndexControllerIndexActionTest extends AdminIndexControllerTestCase {
 class AdminIndexControllerAdminVarActionTest extends Admin_AbstractControllerTestCase {
 	public function setUp() {
 		parent::setUp();
-		$this->fixture('Class_AdminVar', ['id' => 'BIBNUM', 'valeur' => '0']);
-		$this->fixture('Class_AdminVar', ['id' => 'CACHE_ACTIF', 'valeur' => '1']);
-		$this->dispatch('/admin/index/adminvar');
+		Class_AdminVar::newInstanceWithId('BIBNUM', ['valeur' => '0']);
+		Class_AdminVar::newInstanceWithId('CACHE_ACTIF', ['valeur' => '1']);
+		$this->dispatch('/admin/index/adminvar', true);
 	}
 
 	/** @test */
@@ -165,7 +166,7 @@ class AdminIndexControllerAdminVarActionTest extends Admin_AbstractControllerTes
 
 	/** @test */
 	public function avisMaxSaisieShouldBePresent() {
-		$this->assertXpathContentContains('//td', 'AVIS_MAX_SAISIE');
+		$this->assertXpathContentContains('//td', 'AVIS_MAX_SAISIE', $this->_response->getBody());
 	}
 
 	/** @test */
@@ -233,37 +234,39 @@ class AdminIndexControllerAdminVarActionTest extends Admin_AbstractControllerTes
 
 
 
-class AdminIndexControllerAdminVarEditModoBlogActionTest extends Admin_AbstractControllerTestCase {
+class AdminIndexControllerAdminVarEditActionTest extends Admin_AbstractControllerTestCase {
 	public function setUp() {
 		parent::setUp();
 
-		$this->modo_blog = Storm_Test_ObjectWrapper::onLoaderOfModel('Class_AdminVar')
+		$this->genre_facet = Storm_Test_ObjectWrapper::onLoaderOfModel('Class_AdminVar')
 			->whenCalled('save')
 			->answers('true')
 			->getWrapper()
-			->newInstanceWithId('MODO_BLOG')
+			->newInstanceWithId('FACETTE_GENRE_LIBELLE')
 			->setValeur('1');
 	}
 
 
 	/** @test */
-	public function editMODO_BLOG() {
-		$this->dispatch('/admin/index/adminvaredit/cle/MODO_BLOG');
-		$this->assertQueryContentContains('td.droite', 'MODO_BLOG');
+	public function editPageShouldContainsTitleFacetteGenreLibelle() {
+		$this->dispatch('/admin/index/adminvaredit/cle/FACETTE_GENRE_LIBELLE');
+		$this->assertQueryContentContains('h1',
+																			'FACETTE_GENRE_LIBELLE',
+																			$this->_response->getBody());
 	}
 
 
 	/** @test */
-	public function postTwoToMODO_BLOG() {
+	public function postTwoToFacetteGenreLibelleShouldRemoveTags() {
 		$this
 			->getRequest()
 			->setMethod('POST')
-			->setPost(array('cle' => 'MODO_BLOG',
-											'valeur' => "<b>2  \n</b>"));
-		$this->dispatch('/admin/index/adminvaredit/cle/MODO_BLOG');
-		$this->assertEquals(2, $this->modo_blog->getValeur());
-		$this->assertRedirectTo('/admin/index/adminvaredit/cle/MODO_BLOG');
-		$this->assertFlashMessengerContains('Variable MODO_BLOG sauvegardée');
+			->setPost(array('cle' => 'FACETTE_GENRE_LIBELLE',
+											'valeur' => "<b>test  \n</b>"));
+		$this->dispatch('/admin/index/adminvaredit/cle/FACETTE_GENRE_LIBELLE');
+		$this->assertEquals('test', $this->genre_facet->getValeur());
+		$this->assertRedirectTo('/admin/index/adminvaredit/cle/FACETTE_GENRE_LIBELLE');
+		$this->assertFlashMessengerContains('Variable FACETTE_GENRE_LIBELLE sauvegardée');
 	}
 }
 
@@ -444,8 +447,8 @@ class AdminIndexControllerAdminVarEditResaConditionActionTest extends Admin_Abst
 
 	/** @test */
 	public function editResaConditionShouldDecodeItsValeur() {
-		$this->dispatch('/admin/index/adminvaredit/cle/RESA_CONDITION');
-		$this->assertXPathContentContains('//textarea', 'Mes conditions de reservation');
+		$this->dispatch('/admin/index/adminvaredit/cle/RESA_CONDITION', true);
+		$this->assertXPathContentContains('//textarea', 'Mes conditions de reservation', $this->_response->getBody());
 	}
 
 
@@ -457,4 +460,122 @@ class AdminIndexControllerAdminVarEditResaConditionActionTest extends Admin_Abst
 		$this->assertEquals('Il+faut+demander', $this->_resa_condition->getValeur());
 	}
 }
+
+
+
+abstract class AdminIndexControllerDilicomPnbIpAdressesTestCase extends Admin_AbstractControllerTestCase {
+	protected $_http;
+
+
+	public function setUp() {
+		parent::setUp();
+		$this->_http = Storm_Test_ObjectWrapper::mock();
+		$this->_http
+			->whenCalled('setAuth')->answers(null)
+			->whenCalled('open_url')->answers(json_encode(['returnStatus' => 'OK',
+																										 'returnMessage' => []]));
+		RessourcesNumeriquesFixtures::activateDilicom();
+
+		Class_WebService_BibNumerique_Dilicom_Hub::setDefaultHttpClient($this->_http);
+	}
+
+	public function tearDown() {
+		Class_WebService_BibNumerique_Dilicom_Hub::setDefaultHttpClient(null);
+		RessourcesNumeriquesFixtures::deactivateDilicom();
+		parent::tearDown();
+	}
+}
+
+
+
+
+class AdminIndexControllerAdminVarEditDilicomPnbIpAdressesTest extends AdminIndexControllerDilicomPnbIpAdressesTestCase {
+	public function setUp() {
+		parent::setUp();
+		$this->fixture('Class_AdminVar', ['id' => 'DILICOM_PNB_IP_ADRESSES',
+																			'valeur' => '178.12.34.56;178.12.34.57']);
+		$this->dispatch('/admin/index/adminvaredit/cle/DILICOM_PNB_IP_ADRESSES',
+										true);
+	}
+
+
+	/** @test */
+	public function varNameDilicomPnbIpAdressesShouldBeDisplayed() {
+		$this->assertQueryContentContains('//h1', 'DILICOM_PNB_IP_ADRESSES');
+	}
+
+
+	/** @test */
+	public function pageShouldContainsMultiInputsValeur() {
+		$this->assertXPath('//div[@id="multi_inputs_valeur"]');
+	}
+
+
+	/** @test */
+	public function multiInputAdressesShouldContains178_12_34_56() {
+		$this->assertXPathContentContains('//script',
+																			'178.12.34.56');
+	}
+
+
+	/** @test */
+	public function descriptionShouldContainsListeDesAdressesIp() {
+		$this->assertXPathContentContains('//p', 'Liste des adresses IP');
+	}
+
+
+	/** @test */
+	public function backUrlShouldBeAdminVar() {
+		$this->assertXPath('//div[contains(@onclick, "/admin/index/adminvar")]',
+											 $this->_response->getBody());
+
+	}
+}
+
+
+
+class AdminIndexControllerAdminVarDilicomDeclareIpPostTest extends AdminIndexControllerDilicomPnbIpAdressesTestCase {
+	/** @test */
+	public function hubErrorMessageShouldAppearInNotification() {
+		$this->_http->whenCalled('open_url')
+								->answers(json_encode(['requestId' => 'xxx',
+																			 'returnStatus' => 'KO',
+																			 'returnMessage' => ['xxx is not a valid address']]));
+
+		$this->postDispatch('/admin/index/adminvaredit/cle/DILICOM_PNB_IP_ADRESSES',
+												['cle' => 'DILICOM_PNB_IP_ADRESSES',
+												 'valeurs' => ['xxx',
+																			 '123.156.15.3']]);
+
+		$this->assertXPathContentContains('//div//ul[@class="errors"]/li', 'xxx is not a valid address');
+	}
+
+
+	/** @test */
+	public function whenDilicomDisabledErrorMessageShouldAppearInNotification() {
+		RessourcesNumeriquesFixtures::deactivateDilicom();
+
+		$this->postDispatch('/admin/index/adminvaredit/cle/DILICOM_PNB_IP_ADRESSES',
+												['cle' => 'DILICOM_PNB_IP_ADRESSES',
+												 'valeurs' => ['xxx',
+																			 '123.156.15.3']]);
+
+		$this->assertXPathContentContains('//div//ul[@class="errors"]/li', 'configuration du PNB Dilicom incomplète');
+	}
+
+
+	/** @test */
+	public function emptyAnswerErrorMessageShouldBePresnt() {
+		$this->_http->whenCalled('open_url')
+								->answers('');
+
+		$this->postDispatch('/admin/index/adminvaredit/cle/DILICOM_PNB_IP_ADRESSES',
+												['cle' => 'DILICOM_PNB_IP_ADRESSES',
+												 'valeur' => ['xxx',
+																			'123.156.15.3']]);
+
+		$this->assertXPathContentContains('//div//ul[@class="errors"]/li', 'échec de communication avec le serveur PNB Dilicom');
+	}
+}
+
 ?>
\ No newline at end of file
diff --git a/tests/application/modules/admin/controllers/AlbumControllerDilicomPNBTest.php b/tests/application/modules/admin/controllers/AlbumControllerDilicomPNBTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..f1f856e93c3fafee946d7c8097d2b024e47f70e5
--- /dev/null
+++ b/tests/application/modules/admin/controllers/AlbumControllerDilicomPNBTest.php
@@ -0,0 +1,117 @@
+<?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 AlbumControllerDilicomPNBEditTest extends Admin_AbstractControllerTestCase {
+	public function setUp() {
+		parent::setUp();
+
+		$this->fixture('Class_Album',
+									 ['id' => 23,
+										'titre' => 'Being human being',
+										'usage_constraints' =>
+
+			 [
+				$this->fixture('Class_Album_UsageConstraint',
+											 ['id' => 1,
+												'usage_type' => Class_Album_UsageConstraint::LOAN_CONSTRAINT,
+												'serialized_datas' => json_encode([Class_Album_UsageConstraint::DURATION => 45,
+																													 Class_Album_UsageConstraint::QUANTITY => 30,
+																													 Class_Album_UsageConstraint::MAX_NB_OF_USERS => 15])]),
+
+				$this->fixture('Class_Album_UsageConstraint',
+											 ['id' => 2,
+												'usage_type' => Class_Album_UsageConstraint::DEVICE_SHARE_CONSTRAINT,
+												'serialized_datas' => json_encode([Class_Album_UsageConstraint::QUANTITY => 6])])
+			 ]
+			 ]);
+
+		$this->dispatch('/admin/album/editalbum/id/23', true);
+	}
+
+
+	/** @test */
+	public function inputTitreShouldContainsBeingHumanBeing() {
+		$this->assertXPath('//input[@name="titre"][@value="Being human being"]');
+	}
+
+
+	/** @test */
+	public function fieldsetUsageShouldBeVisible() {
+		$this->assertXPathContentContains('//fieldset/legend','Utilisation');
+	}
+
+
+	/** @test */
+	public function dtInUsageShouldContainsPret() {
+		$this->assertXPathContentContains('//fieldset//dl//dt', 'Prêt');
+	}
+
+
+	/** @test */
+	public function dddldtShouldContainsDuration() {
+		$this->assertXPathContentContains('//fieldset//dl/dd/dl/dt', 'Durée (j)');
+	}
+
+
+	/** @test */
+	public function dddlddShouldContains45() {
+		$this->assertXPathContentContains('//fieldset//dl/dd/dl/dd', '45');
+	}
+
+
+	/** @test */
+	public function dtInUsageShouldContainsAuthorizedDevices() {
+		$this->assertXPathContentContains('//fieldset//dl//dt', 'Appareils autorisés');
+	}
+}
+
+
+
+class AlbumControllerDilicomPNBEditPostTest extends Admin_AbstractControllerTestCase {
+	protected $_album;
+	public function setUp() {
+		parent::setUp();
+
+		$this->_album = $this->fixture('Class_Album',
+																	 ['id' => 3,
+																		'libelle' => 'Totem et Thora',
+																		'id_origine' => 'Dilicom-88817216',
+																		'external_uri' => 'http://www.edenlivres.fr/p/23416',
+																		'type_doc_id' => Class_TypeDoc::DILICOM,
+																		'isbn' => '435465',
+																		'usage_constraints' => [$this->fixture('Class_Album_UsageConstraint',
+																																					 ['id' => 1,
+																																						'id_album' => 3,
+																																						'titre ' => 'Totem et Thora',
+																																						'usage_type' => Class_Album_UsageConstraint::LOAN_CONSTRAINT,
+																																						'order_line_id' => 'x321'])]]);
+
+		$this->postDispatch('/admin/album/edit_album/id/3',
+												['titre' => 'Tootem']);
+	}
+
+	/** @test */
+	public function constraintsShouldNotHaveBeenDeleted() {
+		$this->assertNotNull($this->_album->getUsageConstraints()->getLoanConstraint());
+	}
+}
+
+?>
\ No newline at end of file
diff --git a/tests/application/modules/admin/controllers/AlbumControllerTest.php b/tests/application/modules/admin/controllers/AlbumControllerTest.php
index c06a2bb8c74510d78f11d079a87daa7937198edb..d9571f188769040ba2e181ad2245b2f14496e5b2 100644
--- a/tests/application/modules/admin/controllers/AlbumControllerTest.php
+++ b/tests/application/modules/admin/controllers/AlbumControllerTest.php
@@ -1555,20 +1555,37 @@ class Admin_AlbumControllerEditAlbumMesBDTest extends Admin_AlbumControllerEditA
 
 
 	/** @test */
+	public function pageShouldContainsMultiInputsAuthors() {
+		$this->assertXPath('//div[@id="multi_inputs_authors"]');
+	}
+
+
+  /** @test */
 	public function pageShouldContainsScriptWithAuthorPba() {
-		$this->assertXPathContentContains('//div[@id="multi_inputs_authors"]/following-sibling::script','Pba',$this->_response->getBody());
+		$this->assertXPathContentContains('//script','Pba');
+	}
+
+
+	/** @test */
+	public function pageShouldContainsMultiInputsEditors() {
+		$this->assertXPath('//div[@id="multi_inputs_editors"]');
 	}
 
 
 	/** @test */
 	public function pageShouldContainsScriptWithEditorGlo() {
-		$this->assertXPathContentContains('//div[@id="multi_inputs_editors"]/following-sibling::script','Glo',$this->_response->getBody());
+		$this->assertXPathContentContains('//script','Glo');
 	}
 
 
+	/** @test */
+	public function pageShouldContainsMultiInputsCollections() {
+		$this->assertXPath('//div[@id="multi_inputs_collections"]');
+	}
+
 	/** @test */
 	public function pageShouldContainsScriptWithCollectionRatm() {
-		$this->assertXPathContentContains('//div[@id="multi_inputs_collections"]/following-sibling::script','Ratm',$this->_response->getBody());
+		$this->assertXPathContentContains('//script','Ratm');
 	}
 
 
diff --git a/tests/application/modules/opac/controllers/BibNumeriqueControllerDilicomTest.php b/tests/application/modules/opac/controllers/BibNumeriqueControllerDilicomTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..81cbc8965ccb25df4ed1e42b3960a995cf179a9f
--- /dev/null
+++ b/tests/application/modules/opac/controllers/BibNumeriqueControllerDilicomTest.php
@@ -0,0 +1,159 @@
+<?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 BibNumeriqueContollerDilicomTestCase extends AbstractControllerTestCase {
+	protected
+		$_http,
+		$_time_source;
+
+	public function setUp() {
+		parent::setUp();
+
+		$logged_user = $this->fixture('Class_Users',
+																	['id' => 6,
+																	 'nom'=>'Pito',
+																	 'login'=>'Chat',
+																	 'password'=>'123456',
+																	 'id_site' => 1,
+																	 'idabon' => '12345',
+																	 'user_groups' => [$this->fixture('Class_UserGroup',
+																																		['id' => '20',
+																																		 'libelle' => 'Multimedia',
+																																		 'rights' => [Class_UserGroup::RIGHT_ACCES_PNB_DILICOM]])]]);
+		$logged_user->beAbonneSIGB()->assertSave();
+		ZendAfi_Auth::getInstance()->logUser($logged_user);
+
+		$this->book = $this->fixture('Class_Album',
+																 ['id' => 3,
+																	'libelle' => 'Totem et Thora',
+																	'id_origine' => 'Dilicom-88817216',
+																	'external_uri' => 'http://www.edenlivres.fr/p/23416',
+																	'type_doc_id' => Class_TypeDoc::DILICOM,
+																	'isbn' => '435465',
+																	'usage_constraints' => [$this->fixture('Class_Album_UsageConstraint',
+																																				 ['id' => 1,
+																																					'id_album' => 3,
+																																					'titre ' => 'Totem et Thora',
+																																					'usage_type' => Class_Album_UsageConstraint::LOAN_CONSTRAINT,
+																																					'order_line_id' => 'x321'])]]);
+
+
+
+		RessourcesNumeriquesFixtures::activateDilicom();
+
+
+		$this->_http = Storm_Test_ObjectWrapper::mock();
+		Class_WebService_BibNumerique_Dilicom_Hub::setDefaultHttpClient($this->_http);
+
+		$this->_http
+			->whenCalled('setAuth')
+			->with('afi-bib', 'secretPassword')
+			->answers(null);
+
+		$this->_time_source = new TimeSourceForTest('2014-05-02 14:14:14');
+		Class_WebService_BibNumerique_Dilicom_Hub::setTimeSource($this->_time_source);
+	}
+}
+
+
+
+
+class BibNumeriqueContollerDilicomConsultBookActionTest extends BibNumeriqueContollerDilicomTestCase {
+	public function setUp() {
+		parent::setUp();
+
+		$this->_http
+			->whenCalled('open_url')
+			->with('https://pnb-test.centprod.com/v2/pnb-numerique/json/consultBook?glnContractor=123456789&orderLineId=x321'
+						 .'&accessMedium=STREAMING'
+						 .'&localization=IN_SITU'
+						 .'&consultEndDate='.urlencode('2014-05-02T15:14:14+0200')
+						 .'&ean13=435465'
+						 .'&ipAddress=195.251.88.223'
+						 .'&loanId='.base_convert($this->_time_source->time(), 10, 36).'63')
+			->answers('{"orderLineId":"54e7473f975a2fa6aa4d3e17","consultEndDate":"2014-05-02T15:14:14+0200","loanId":"3","returnStatus":"OK","returnMessage":[],"requestId":"awvzrcttestpnbv2_000000039_201503051511","link":{"url":"https://pnb-test.centprod.com/v2//link/3056000302801/CONSULT/3/9791023501766-FGR9FJJGJCMXYMB8BO87XR9TPHDN9QNS.do","aformatDescription":"EPUB","mimetype":"application/epub+zip","ean13":"9791023501766","format":"E101"}}')
+			->beStrict();
+
+
+		$_SERVER['HTTP_X_FORWARDED_FOR'] = '195.251.88.223';
+
+		$this->dispatch('/bib-numerique/consult-book/id/3', true);
+	}
+
+
+	public function tearDown() {
+		Class_WebService_BibNumerique_Dilicom_Hub::setDefaultHttpClient(null);
+		RessourcesNumeriquesFixtures::deactivateDilicom();
+		parent::tearDown();
+	}
+
+
+	/** @test */
+	public function openUrlShouldHaveBeenCalled() {
+		$this->assertTrue($this->_http->methodHasBeenCalled('open_url'));
+	}
+
+
+	/** @test */
+	public function setAuthShouldHaveBeenCalled() {
+		$this->assertTrue($this->_http->methodHasBeenCalled('setAuth'));
+	}
+
+
+	/** @test */
+	public function responseShouldRedirectToConsultBookUrl() {
+		$this->assertRedirectTo('https://pnb-test.centprod.com/v2//link/3056000302801/CONSULT/3/9791023501766-FGR9FJJGJCMXYMB8BO87XR9TPHDN9QNS.do');
+	}
+}
+
+
+
+
+class BibNumeriqueContollerDilicomConsultBookWithErrorsActionTest extends BibNumeriqueContollerDilicomTestCase {
+	public function setUp() {
+		parent::setUp();
+
+
+		$this->_http
+			->whenCalled('open_url')
+			->answers('{"orderLineId":"54e7473f975a2fa6aa4d3e17","consultEndDate":"","loanId":"3","returnStatus":"ERROR","returnMessage":["Crash boom"],"requestId":"awvzrcttestpnbv2_000000039_201503051511","link":{}}');
+
+		$_SERVER['HTTP_REFERER'] = '/viewnotice/id/3';
+
+		$this->dispatch('/bib-numerique/consult-book/id/3', true);
+	}
+
+
+	/** @test */
+	public function responseShouldRedirectToReferrer() {
+		$this->assertRedirectTo('/viewnotice/id/3', $this->getResponseLocation());
+	}
+
+
+	/** @test */
+	public function errorMessageShouldBeInNotifications() {
+		$this->assertFlashMessengerContains('Crash boom');
+	}
+}
+
+
+?>
\ No newline at end of file
diff --git a/tests/application/modules/opac/controllers/BibNumeriqueControllerTest.php b/tests/application/modules/opac/controllers/BibNumeriqueControllerTest.php
index 63b92abea1146166e3297ebab519c2123787a885..0f0574acd56f3a794d8e6c7eb7c462b82a9637c1 100644
--- a/tests/application/modules/opac/controllers/BibNumeriqueControllerTest.php
+++ b/tests/application/modules/opac/controllers/BibNumeriqueControllerTest.php
@@ -18,7 +18,6 @@
  * 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';
 
 class BibNumeriqueControllerViewAlbumActionTest extends AbstractControllerTestCase {
 	/** @test */
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
index a27bf92693b5b32744f6b8b897a2a2767806bd17..784c43ec45fd4a429290d7175447959e7e4699a4 100644
--- a/tests/bootstrap.php
+++ b/tests/bootstrap.php
@@ -84,9 +84,9 @@ $_SERVER['HTTP_HOST'] = 'localhost';
 //Initialize the router for tests that do not rely on dispatch
 Zend_Controller_Front::getInstance()->getRouter()->route(new Zend_Controller_Request_Http('http://localhost/'));
 
-require_once "tests/library/ZendAfi/View/Helper/ViewHelperTestCase.php";
+require_once 'tests/library/ZendAfi/View/Helper/ViewHelperTestCase.php';
 require_once 'tests/application/modules/admin/controllers/AdminAbstractControllerTestCase.php';
-require_once('tests/fixtures/RessourcesNumeriquesFixtures.php');
+require_once 'tests/fixtures/RessourcesNumeriquesFixtures.php';
 
 register_shutdown_function(function(){
 	TestSpeedTrap::printSpeedTrappedTests();
diff --git a/tests/bootstrap_frontcontroller.php b/tests/bootstrap_frontcontroller.php
index af4c0de2ba119cfe03914a13ea0b0c348be1318f..8b82fc7e6ce54739b0e280bb473496c85f3eba68 100644
--- a/tests/bootstrap_frontcontroller.php
+++ b/tests/bootstrap_frontcontroller.php
@@ -31,4 +31,6 @@ $controller = newFrontController()->throwExceptions(true);
 Zend_Controller_Action_HelperBroker::addHelper(new ZendAfi_Controller_Action_Helper_ViewRenderer());
 
 setupRoutes($controller, new Zend_Config([]));
+
+$controller->getRouter()->addDefaultRoutes();
 ?>
\ No newline at end of file
diff --git a/tests/fixtures/RessourcesNumeriquesFixtures.php b/tests/fixtures/RessourcesNumeriquesFixtures.php
index bfe882401fd8e1e412877222c0ac976b0a9355cf..3babce9846ec52bcf5ba8fbd626217e0ce517eb5 100644
--- a/tests/fixtures/RessourcesNumeriquesFixtures.php
+++ b/tests/fixtures/RessourcesNumeriquesFixtures.php
@@ -244,5 +244,21 @@ class RessourcesNumeriquesFixtures {
 		Class_AdminVar::newInstanceWithId('KIDILANGUES_LOGIN', ['valeur' => '']);
 		Class_AdminVar::newInstanceWithId('KIDILANGUES_PWD', ['valeur' => '']);
 	}
+
+
+	public static function activateDilicom() {
+		Class_AdminVar::newInstanceWithId('DILICOM_PNB_GLN_COLLECTIVITE', ['valeur' => 'afi-bib']);
+		Class_AdminVar::newInstanceWithId('DILICOM_PNB_PWD_COLLECTIVITE', ['valeur' => 'secretPassword']);
+		Class_AdminVar::newInstanceWithId('DILICOM_PNB_SERVER_URL', ['valeur' => 'https://pnb-test.centprod.com']);
+		Class_AdminVar::newInstanceWithId('DILICOM_PNB_GLN_CONTRACTOR', ['valeur' => 123456789]);
+	}
+
+
+	public static function deactivateDilicom() {
+		Class_AdminVar::newInstanceWithId('DILICOM_PNB_GLN_COLLECTIVITE', ['valeur' => '']);
+		Class_AdminVar::newInstanceWithId('DILICOM_PNB_PWD_COLLECTIVITE', ['valeur' => '']);
+		Class_AdminVar::newInstanceWithId('DILICOM_PNB_SERVER_URL', ['valeur' => '']);
+		Class_AdminVar::newInstanceWithId('DILICOM_PNB_GLN_CONTRACTOR', ['valeur' => '']);
+	}
 }
 ?>
diff --git a/tests/library/Class/AdminVarTest.php b/tests/library/Class/AdminVarTest.php
index 51853ab042d687642bd3e15ea1b850fd0a781206..1a3867071831b5db36e3fbbfaf592321825c8fe9 100644
--- a/tests/library/Class/AdminVarTest.php
+++ b/tests/library/Class/AdminVarTest.php
@@ -201,20 +201,24 @@ class AdminVarBabelthequeTest extends Storm_Test_ModelTestCase {
 
 class AdminVarWorkflowTest extends Storm_Test_ModelTestCase {
 	public function setup() {
+		parent::setup();
 		Class_AdminVar::beVolatile();
 		Class_AdminVar::set('WORKFLOW_TEXT_MAIL_ARTICLE_PENDING','');
 		Class_AdminVar::set('WORKFLOW_TEXT_MAIL_ARTICLE_REFUSED','');
 	}
 
+
 	/** @test */
 	public function workflowTextMailArticlePendingShouldHaveDefaultTextNouvelArticleAValider() {
-		$this->assertEquals('Un nouvel article est à valider. TITRE_ARTICLE URL_ARTICLE',Class_AdminVar::getWorkflowTextMailArticlePending());
+		$this->assertEquals('Un nouvel article est à valider. TITRE_ARTICLE URL_ARTICLE',
+												Class_AdminVar::getWorkflowTextMailArticlePending());
 	}
 
 
 	/** @test */
 	public function workflowTextMailArticleRefusedShouldHaveDefaultTextArticleRefuse() {
-		$this->assertEquals('L\'article a été refusé.',Class_AdminVar::getWorkflowTextMailArticleRefused());
+		$this->assertEquals('L\'article a été refusé.',
+												Class_AdminVar::getWorkflowTextMailArticleRefused());
 	}
 }
 
diff --git a/tests/library/Class/Album/UsageConstraintTest.php b/tests/library/Class/Album/UsageConstraintTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..d7b38d2b462bbbbc5f7603f7488d8091fe8b74d5
--- /dev/null
+++ b/tests/library/Class/Album/UsageConstraintTest.php
@@ -0,0 +1,73 @@
+<?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_Album_UsageConstraintTest extends Storm_Test_ModelTestCase {
+	public function setUp() {
+		parent::setUp();
+
+		Storm_Model_Loader::defaultToVolatile();
+
+		$this->fixture('Class_Album',
+									 ['id' => 23]);
+
+		$this->fixture('Class_Album_UsageConstraint',
+									 ['id' => 2,
+										'album_id' => 23,
+										'usage_type' => Class_Album_UsageConstraint::LOAN_CONSTRAINT,
+										'serialized_datas' => json_encode(['duration' => 45,
+																											 'quantity' => 30,
+																											 'max_number_of_users' => 15])]);
+
+
+
+		Class_Album::clearCache();
+		Class_Album_UsageConstraint::clearCache();
+	}
+
+
+	public function tearDown() {
+		Storm_Model_Loader::defaultToDb();
+		parent::tearDown();
+	}
+
+
+	/** @test */
+	public function albumGetCounstraintsLoanDurationShouldAnswers45() {
+		$this->assertEquals(45, Class_Album::find(23)->getUsageConstraints()->getLoanDuration());
+	}
+
+
+	/** @test */
+	public function savedConstraintShouldSerializeItsDatas() {
+		$constraint = Class_Album_UsageConstraint::newInstance(['usage_type' => Class_Album_Usageconstraint::DEVICE_SHARE_CONSTRAINT,
+																														'quantity' => 4]);
+		Class_Album::find(23)
+			->addUsageConstraint($constraint)
+			->save();
+
+		Class_Album_UsageConstraint::clearCache();
+		$this->assertEquals(['quantity' => 4],
+												json_decode(Class_Album_UsageConstraint::find(3)->getSerializedDatas(),
+																		true));
+	}
+}
+
+
+?>
\ No newline at end of file
diff --git a/tests/library/Class/AlbumTest.php b/tests/library/Class/AlbumTest.php
index e4b05f12d64b4e938ed4942a7cb34aa898e45cd5..27aa464b0ebec137406903595f4ad62db240b9c6 100644
--- a/tests/library/Class/AlbumTest.php
+++ b/tests/library/Class/AlbumTest.php
@@ -77,6 +77,12 @@ class AlbumHarlockBasicTest extends AlbumHarlockTestCase {
 	}
 
 
+	/** @test */
+	public function usageConstraintsShouldBeEmptyArray() {
+		$this->assertEquals(0, count($this->_album->getUsageConstraints()));
+	}
+
+
 	/** @test */
 	public function permalinkShouldBeBibNumNotice999() {
 		$this->assertEquals(array('module' => 'opac',
diff --git a/tests/library/Class/RemoteClientTest.php b/tests/library/Class/RemoteClientTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..4ad9bf1a129f27e254afedf08ab7db16c0e1d787
--- /dev/null
+++ b/tests/library/Class/RemoteClientTest.php
@@ -0,0 +1,93 @@
+<?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 RemoteClientIpAddressTest extends Storm_Test_ModelTestCase {
+	public function setUp() {
+		parent::setUp();
+		$this->_SERVER_BAK = $_SERVER;
+	}
+
+
+	public function tearDown() {
+		$_SERVER = $this->_SERVER_BAK;
+		parent::tearDown();
+	}
+
+
+	public function  serverValues() {
+		return
+			[
+
+			 [ '10.23.45.67',
+				[ 'HTTP_X_FORWARDED_FOR' =>  '10.23.45.67' ]
+			 ],
+
+
+			 [ '10.23.45.67',
+				[ 'REMOTE_ADDR' =>  '10.23.45.67' ]
+			 ],
+
+
+			 [ '10.23.45.67',
+				[
+				 'REMOTE_ADDR' =>  '10.23.45.66',
+				 'HTTP_X_FORWARDED_FOR' =>  '10.23.45.67',
+				]
+			 ],
+
+
+			 [ '10.23.45.67',
+				[
+				 'REMOTE_ADDR' =>  '10.23.45.66',
+				 'HTTP_CLIENT_IP' =>  '10.23.45.67',
+				]
+			 ],
+
+
+			 [ '10.23.45.67',
+				[ 'HTTP_X_FORWARDED_FOR' =>  '10.23.45.67, 192.34.45.34' ]
+			 ],
+
+
+			 [ '10.23.45.67',
+				[
+				 'REMOTE_ADDR' =>  '10.23.45.67',
+				 'HTTP_X_FORWARDED_FOR' =>  ' ',
+				]
+			 ],
+
+			];
+	}
+
+	/**
+	 * @dataProvider serverValues
+	 * @test
+	 */
+	public function ipShouldBeExtractedFromGlobalServer($expected_ip, $server) {
+		$_SERVER = $server;
+		$request = new Zend_Controller_Request_Http();
+		$this->assertEquals($expected_ip,
+												(new Class_RemoteClient($request))->getIpAddress());
+	}
+
+}
+
+?>
\ No newline at end of file
diff --git a/tests/library/Class/WebService/Dilicom/HubTest.php b/tests/library/Class/WebService/Dilicom/HubTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..2655ed4186e58f105fb6035b851df45e1f7718d3
--- /dev/null
+++ b/tests/library/Class/WebService/Dilicom/HubTest.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
+ */
+
+abstract class Class_Webservice_Dilicom_HubTestCase extends Storm_Test_ModelTestCase {
+	public function setUp() {
+		parent::setUp();
+
+		RessourcesNumeriquesFixtures::activateDilicom();
+
+		$this->_http = Storm_Test_ObjectWrapper::mock();
+		Class_WebService_BibNumerique_Dilicom_Hub::setDefaultHttpClient($this->_http);
+
+		$this->_response = json_encode(['requestId' => 'xxx',
+																		'returnStatus' => 'OK',
+																		'returnMessage' => []]);
+	}
+
+	public function tearDown() {
+		Class_WebService_BibNumerique_Dilicom_Hub::setDefaultHttpClient(null);
+		RessourcesNumeriquesFixtures::deactivateDilicom();
+		parent::tearDown();
+	}
+}
+
+
+
+class Class_Webservice_Dilicom_HubDeclareIpTest extends Class_Webservice_Dilicom_HubTestCase {
+	public function setUp() {
+		parent::setUp();
+
+		$this->_http->whenCalled('setAuth')
+								->with('afi-bib', 'secretPassword')
+								->answers(null)
+								->beStrict();
+
+	}
+
+	/** @test */
+	public function savingDilicomPnbIPAddressShouldCallWebserviceDeclareIP() {
+		$this->_http->whenCalled('open_url')
+								->with('https://pnb-test.centprod.com/v2/pnb-numerique/json/declareIp?'
+											 .'glnContractor=123456789&ips[0]=10.12.13.24')
+								->answers($this->_response);
+
+		$this->fixture('Class_AdminVar',
+									 ['id' => 'DILICOM_PNB_IP_ADRESSES',
+										'valeur' => '10.12.13.24']);
+
+		$this->assertTrue($this->_http->methodHasBeenCalled('open_url'));
+		$this->assertTrue($this->_http->methodHasBeenCalled('setAuth'));
+	}
+
+
+	/** @test */
+	public function savingDilicomPnbIpAdressWithMultipleIpsShouldCallWebServiceDeclareIp() {
+		$this->_http->whenCalled('open_url')
+								->with('https://pnb-test.centprod.com/v2/pnb-numerique/json/declareIp?'
+											 .'glnContractor=123456789&ips[0]=10.12.13.24&ips[1]=11.128.16.87')
+								->answers($this->_response);
+
+		$this->fixture('Class_AdminVar',
+									 ['id' => 'DILICOM_PNB_IP_ADRESSES',
+										'valeur' => '10.12.13.24;11.128.16.87']);
+
+		$this->assertTrue($this->_http->methodHasBeenCalled('open_url'));
+	}
+}
+?>
\ No newline at end of file
diff --git a/tests/library/Class/WebService/Dilicom/PNBOffersParserTest.php b/tests/library/Class/WebService/Dilicom/PNBOffersParserTest.php
index 78325df00300951587d7e317ce8ade0bbff5a8eb..cba6a0806fc2957a2cc592ef2db454152f8fefda 100644
--- a/tests/library/Class/WebService/Dilicom/PNBOffersParserTest.php
+++ b/tests/library/Class/WebService/Dilicom/PNBOffersParserTest.php
@@ -86,6 +86,60 @@ class DilicomPNBOfferParserTest extends Storm_Test_ModelTestCase {
 	}
 
 
+	/**
+	 * @depends firstAlbumShouldBePlusJamaisSansElle
+	 * @test
+	 */
+	public function firstAlbumShouldHaveSixUsageConstraints($album) {
+		$this->assertCount(6, $album->getUsageConstraints());
+	}
+
+
+	/**
+	 * @depends firstAlbumShouldBePlusJamaisSansElle
+	 * @test
+	 */
+	public function firstAlbumLoanDurationShouldBe59($album) {
+		$this->assertEquals(59, $album->getUsageConstraints()->getLoanDuration());
+	}
+
+
+	/**
+	 * @depends firstAlbumShouldBePlusJamaisSansElle
+	 * @test
+	 */
+	public function firstAlbumLoanOrderLineIdShouldBe54e748d8975a2fa6aa4d3e25($album) {
+		$this->assertEquals('54e7473f975a2fa6aa4d3e17',
+												$album->getUsageConstraints()->getLoanOrderLineId());
+	}
+
+	/**
+	 * @depends firstAlbumShouldBePlusJamaisSansElle
+	 * @test
+	 */
+	public function firstAlbumLoanQuantityShouldBe40($album) {
+		$this->assertEquals(40, $album->getUsageConstraints()->getLoanQuantity());
+	}
+
+
+	/**
+	 * @depends firstAlbumShouldBePlusJamaisSansElle
+	 * @test
+	 */
+	public function firstAlbumAvailabilityDurationShouldBe1825($album) {
+		$this->assertEquals(1825, $album->getUsageConstraints()->getAvailabilityDuration());
+	}
+
+
+		/**
+	 * @depends firstAlbumShouldBePlusJamaisSansElle
+	 * @test
+	 */
+	public function firstAlbumLoanMaxNumberOfUsersShouldBe15($album) {
+		$this->assertEquals(15,
+												$album->getUsageConstraints()->getLoanMaxNumberOfUsers());
+	}
+
 
 	/** @test */
 	public function secondAlbumShouldBeJournalDunDegonfle() {
@@ -127,6 +181,18 @@ class DilicomPNBOfferParserTest extends Storm_Test_ModelTestCase {
 		$this->assertEquals('Homer et le chien formidable', $album->getTitre());
 		return $album;
 	}
+
+
+
+	/**
+	 * @depends thirdAlbumShouldBeHomerEtLeChienFormidable
+	 * @test
+	 */
+	public function thirdAlbumISBNShouldBe9791023504149($album) {
+		$this->assertEquals('9791023504149', $album->getIsbn());
+	}
+
+
 }
 
 ?>
\ No newline at end of file
diff --git a/tests/library/Class/WebService/Dilicom/fixtures/full_pnb_666_20150220T150017Z.xml b/tests/library/Class/WebService/Dilicom/fixtures/full_pnb_666_20150220T150017Z.xml
index e24f3d9b036349e3f34bd704c9898be20665e419..4acdece34718191b938261431133d8b6d77f0c55 100644
--- a/tests/library/Class/WebService/Dilicom/fixtures/full_pnb_666_20150220T150017Z.xml
+++ b/tests/library/Class/WebService/Dilicom/fixtures/full_pnb_666_20150220T150017Z.xml
@@ -952,7 +952,7 @@ Heureusement, entre ce que dit Greg et ce qu'il fait vraiment, il y a un monde..
 &lt;NotificationType&gt;04&lt;/NotificationType&gt;
 &lt;ProductIdentifier&gt;
 &lt;ProductIDType&gt;15&lt;/ProductIDType&gt;
-&lt;IDValue&gt;9791023504149&lt;/IDValue&gt;
+&lt;IDValue&gt;9791023504148&lt;/IDValue&gt;
 &lt;/ProductIdentifier&gt;
 &lt;ProductIdentifier&gt;
 &lt;ProductIDType&gt;01&lt;/ProductIDType&gt;
@@ -1395,4 +1395,4 @@ Heureusement, entre ce que dit Greg et ce qu'il fait vraiment, il y a un monde..
         <returnStatus>OK</returnStatus>
     </orderLine>
 </offer>
-</pnbOffers>
\ No newline at end of file
+</pnbOffers>
diff --git a/tests/library/ZendAfi/View/Helper/Admin/MenuGaucheAdminTest.php b/tests/library/ZendAfi/View/Helper/Admin/MenuGaucheAdminTest.php
index 6d46c05f11e51228d28e15ac547ddf022f11cc5b..9f1c261935bb97ebd22dac6ff69b0d1a1ef983a0 100644
--- a/tests/library/ZendAfi/View/Helper/Admin/MenuGaucheAdminTest.php
+++ b/tests/library/ZendAfi/View/Helper/Admin/MenuGaucheAdminTest.php
@@ -80,7 +80,6 @@ class ZendAfi_View_Helper_Admin_MenuGaucheAdminVariableAsAdminTest extends ZendA
 
 	/** @test */
 	public function menuImportDilicomShouldNotBeVisibleWhenOptionDILICOM_PNB_isDisabled() {
-		Class_AdminVar::newInstanceWithId('DILICOM_PNB', ['valeur' => 0]);
 		$this->assertNotXPath($this->helper->menuGaucheAdmin(),
 													'//a[contains(@href, "/admin/album/import-dilicom")]');
 	}
@@ -88,7 +87,7 @@ class ZendAfi_View_Helper_Admin_MenuGaucheAdminVariableAsAdminTest extends ZendA
 
 	/** @test */
 	public function menuImportDilicomShouldBeVisibleWhenOptionDILICOM_PNB_isEnabled() {
-		Class_AdminVar::newInstanceWithId('DILICOM_PNB', ['valeur' => 1]);
+		RessourcesNumeriquesFixtures::activateDilicom();
 		$this->assertXPath($this->helper->menuGaucheAdmin(),
 											 '//a[contains(@href, "/admin/album/import-dilicom")]');
 	}
diff --git a/tests/library/ZendAfi/View/Helper/RenderAlbumTest.php b/tests/library/ZendAfi/View/Helper/RenderAlbumTest.php
index a8efcc835f55f38e0b9d0e023fe76889fa675412..0ab59c377db3edde828b8d8f733eb3d8616a584a 100644
--- a/tests/library/ZendAfi/View/Helper/RenderAlbumTest.php
+++ b/tests/library/ZendAfi/View/Helper/RenderAlbumTest.php
@@ -241,23 +241,84 @@ class ZendAfi_View_Helper_RenderAlbumCyberlibrisTest extends ZendAfi_View_Helper
 											 '//a[@href="http://www.bibliovox.com/my_lib?docid=88817216"][@target="_blank"]',
 											 $html);
 	}
-
 }
 
 
 
-class ZendAfi_View_Helper_RenderAlbumDilicomPNBTest extends ZendAfi_View_Helper_RenderAlbumTestCase {
+
+abstract class ZendAfi_View_Helper_RenderAlbumDilicomPNBTestCase extends ZendAfi_View_Helper_RenderAlbumTestCase {
 	public function setUp() {
 		parent::setUp();
-
+		Storm_Model_Loader::defaultToVolatile();
 		$this->book = $this->fixture('Class_Album',
 																 ['id' => 3,
 																	'libelle' => 'Totem et Thora',
 																	'id_origine' => 'Dilicom-88817216',
 																	'external_uri' => 'http://www.edenlivres.fr/p/23416',
-																	'type_doc_id' => Class_TypeDoc::DILICOM]);
+																	'type_doc_id' => Class_TypeDoc::DILICOM,
+																	'usage_constraints' => [$this->fixture('Class_Album_UsageConstraint',
+																																				 ['id' => 1,
+																																					'id_album' => 3,
+																																					'usage_type' => Class_Album_UsageConstraint::LOAN_CONSTRAINT,
+																																					'order_line_id' => 'x321'])]]);
+		RessourcesNumeriquesFixtures::activateDilicom();
+	}
+
+
+	public function tearDown() {
+		Storm_Model_Loader::defaultToDb();
+		ZendAfi_Auth::getInstance()->clearIdentity();
+		parent::tearDown();
+	}
+}
+
+
+
+
+class ZendAfi_View_Helper_RenderAlbumDilicomPNBNotLoggedTest extends ZendAfi_View_Helper_RenderAlbumDilicomPNBTestCase {
+	public function setUp() {
+		parent::setUp();
+		ZendAfi_Auth::getInstance()->clearIdentity();
+		$this->_html = $this->_helper->renderAlbum($this->book);
+	}
 
-			$this->_html = $this->_helper->renderAlbum($this->book);
+
+	/** @test */
+	public function htmlShouldNotContainsLinkToConsultBook() {
+		$this->assertNotXPathContentContains($this->_html,
+																				 '//a[contains(@href, "/bib-numerique/consult-book/id/3")]',
+																				 'Consulter le livre en ligne');
+	}
+
+
+	/** @test */
+	public function htmlShouldContainsMessageToIntroduceLogin() {
+		$this->assertXPathContentContains($this->_html, '//p', utf8_encode('Vous devez vous connecter pour accéder à la consultation en ligne.'));
+	}
+}
+
+
+
+
+class ZendAfi_View_Helper_RenderAlbumDilicomPNBTest extends ZendAfi_View_Helper_RenderAlbumDilicomPNBTestCase {
+	public function setUp() {
+		parent::setUp();
+
+		$logged_user = $this->fixture('Class_Users',
+																	['id' => 6,
+																	 'nom'=>'Pito',
+																	 'login'=>'Chat',
+																	 'password'=>'123456',
+																	 'id_site' => 1,
+																	 'idabon' => '12345',
+																	 'user_groups' => [$this->fixture('Class_UserGroup',
+																																		['id' => '20',
+																																		 'libelle' => 'Multimedia',
+																																		 'rights' => [Class_UserGroup::RIGHT_ACCES_PNB_DILICOM]])]]);
+		$logged_user->beAbonneSIGB()->assertSave();
+		ZendAfi_Auth::getInstance()->logUser($logged_user);
+
+		$this->_html = $this->_helper->renderAlbum($this->book);
 	}
 
 
@@ -265,5 +326,15 @@ class ZendAfi_View_Helper_RenderAlbumDilicomPNBTest extends ZendAfi_View_Helper_
 	public function htmlShouldContainsIFrameOnEdenBook() {
 		$this->assertXPath($this->_html, '//iframe[@src="http://www.edenlivres.fr/p/23416"][@width="100%"][@height="600px"]', $this->_html);
 	}
+
+
+	/** @test */
+	public function htmlShouldContainsLinkToConsultBook() {
+		$this->assertXPathContentContains($this->_html,
+																			'//a[contains(@href, "/bib-numerique/consult-book/id/3")]',
+																			'Consulter le livre en ligne');
+	}
 }
+
+
 ?>
\ No newline at end of file