From 5d8da2fb9e9efa472587da429ba37d12a711688c Mon Sep 17 00:00:00 2001
From: Patrick Barroca <pbarroca@afi-sa.fr>
Date: Fri, 12 Jul 2019 18:35:26 +0200
Subject: [PATCH] hotline #89462 : add new oai_dc_bokeh metadataPrefix

---
 VERSIONS_HOTLINE/89462                        |   1 +
 .../opac/controllers/OaiController.php        |  47 +++---
 .../oai/list-metadata-formats.xml.phtml       |  17 +-
 library/Class/FRBR/Link.php                   |   5 +
 .../Class/Notice/DublinCoreBokehVisitor.php   |  70 ++++++++
 .../Class/WebService/OAI/MetadataFormat.php   |  85 ++++++++++
 .../WebService/OAI/Request/GetRecord.php      |  30 ++--
 .../OAI/Request/ListIdentifiers.php           |  10 +-
 .../OAI/Request/ListMetadataFormats.php       |  24 ++-
 .../WebService/OAI/Request/ListRecords.php    |  10 +-
 .../WebService/OAI/Response/RecordBuilder.php |   3 +-
 library/ZendAfi/Form/Album.php                |   6 +-
 .../OAIControllerGetRecordTest.php            | 154 +++++++++++++++---
 .../OAIControllerListMetadataFormatsTest.php  |  60 ++++---
 .../OAIControllerListRecordsTest.php          | 133 ++++++++++++++-
 15 files changed, 544 insertions(+), 111 deletions(-)
 create mode 100644 VERSIONS_HOTLINE/89462
 create mode 100644 library/Class/Notice/DublinCoreBokehVisitor.php
 create mode 100644 library/Class/WebService/OAI/MetadataFormat.php

diff --git a/VERSIONS_HOTLINE/89462 b/VERSIONS_HOTLINE/89462
new file mode 100644
index 00000000000..95f8e85b92c
--- /dev/null
+++ b/VERSIONS_HOTLINE/89462
@@ -0,0 +1 @@
+ - ticket #89462 : Serveur OAI : Ajout d'un metadataPrefix oai_dc_bokeh plus riche que l'oai_dc pour les notices d'albums de la bibliothèque numérique Bokeh
\ No newline at end of file
diff --git a/application/modules/opac/controllers/OaiController.php b/application/modules/opac/controllers/OaiController.php
index f0249bed025..e74912f79ba 100644
--- a/application/modules/opac/controllers/OaiController.php
+++ b/application/modules/opac/controllers/OaiController.php
@@ -18,17 +18,24 @@
  * along with BOKEH; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
+
 class OaiController extends Zend_Controller_Action {
   public function init() {
     $this->_helper->getHelper('contextSwitch')
-      ->addActionContext('list-identifiers', 'xml')
-      ->addActionContext('identify', 'xml')
-      ->addActionContext('list-metadata-formats', 'xml')
-      ->addActionContext('list-records', 'xml')
-      ->addActionContext('list-sets', 'xml')
-      ->addActionContext('get-record', 'xml')
-      ->addActionContext('bad-verb', 'xml')
-      ->initContext();
+                  ->addActionContext('list-identifiers', 'xml')
+                  ->addActionContext('identify', 'xml')
+                  ->addActionContext('list-metadata-formats', 'xml')
+                  ->addActionContext('list-records', 'xml')
+                  ->addActionContext('list-sets', 'xml')
+                  ->addActionContext('get-record', 'xml')
+                  ->addActionContext('bad-verb', 'xml')
+                  ->initContext();
+  }
+
+
+  public function preDispatch() {
+    parent::preDispatch();
+    $this->getHelper('ViewRenderer')->setLayoutScript('empty.phtml');
   }
 
 
@@ -62,7 +69,6 @@ class OaiController extends Zend_Controller_Action {
 
 
   public function listIdentifiersAction() {
-    $this->getHelper('ViewRenderer')->setLayoutScript('empty.phtml');
     $request = new Class_WebService_OAI_Request_ListIdentifiers($this->_request->getParams(),
                                                                 $this->buildBaseUrl());
     $builder = new Class_Xml_Builder();
@@ -70,7 +76,7 @@ class OaiController extends Zend_Controller_Action {
     $this->view->error = $request->getErrorOn($builder);
 
     if ($notices = $request->getNotices()) {
-      $visitor = new Class_Notice_DublinCoreVisitor();
+      $visitor = $request->getRecordVisitor();
       $visitor->setGlobalSetSpec($request->getSet());
       $recordBuilder = new Class_WebService_OAI_Response_RecordHeadersBuilder();
       $headers = '';
@@ -80,13 +86,13 @@ class OaiController extends Zend_Controller_Action {
       }
       $this->view->headers = $headers;
     }
+
     $this->view->builder = $builder;
     $this->view->token = $request->getToken();
   }
 
 
   public function listRecordsAction() {
-    $this->getHelper('ViewRenderer')->setLayoutScript('empty.phtml');
     $request = new Class_WebService_OAI_Request_ListRecords($this->_request->getParams(),
                                                             $this->buildBaseUrl());
     $builder = new Class_Xml_Builder();
@@ -94,7 +100,7 @@ class OaiController extends Zend_Controller_Action {
     $this->view->error = $request->getErrorOn($builder);
 
     if ($notices = $request->getNotices()) {
-      $visitor = new Class_Notice_DublinCoreVisitor();
+      $visitor = $request->getRecordVisitor();
       $visitor->setGlobalSetSpec($request->getSet());
       $recordBuilder = new Class_WebService_OAI_Response_RecordBuilder();
       $records = '';
@@ -104,13 +110,13 @@ class OaiController extends Zend_Controller_Action {
       }
       $this->view->records = $records;
     }
+
     $this->view->builder = $builder;
     $this->view->token = $request->getToken();
   }
 
 
   public function identifyAction() {
-    $this->getHelper('ViewRenderer')->setLayoutScript('empty.phtml');
     $this->view->baseUrl = $baseUrl = $this->buildBaseUrl();
 
     $request = new Class_WebService_OAI_Request_Identify($this->_request->getParams(),
@@ -134,19 +140,16 @@ class OaiController extends Zend_Controller_Action {
 
 
   public function listMetadataFormatsAction() {
-    $this->getHelper('ViewRenderer')->setLayoutScript('empty.phtml');
     $baseUrl = $this->buildBaseUrl();
     $request = new Class_WebService_OAI_Request_ListMetadataFormats($this->_request->getParams(),
                                                                     $baseUrl);
-    $this->view->request = $request;
-    $this->view->builder = $builder = new Class_Xml_Builder();
-    $this->view->error = $request->getErrorOn($builder);
-
+    $builder = new Class_Xml_Builder();
+    $this->view->request = $request->renderOn($builder);
+    $this->view->response = $request->getResponseOn($builder);
   }
 
 
   public function listSetsAction() {
-    $this->getHelper('ViewRenderer')->setLayoutScript('empty.phtml');
     $baseUrl = $this->buildBaseUrl();
     $request = new Class_WebService_OAI_Request_ListSets($this->_request->getParams(),
                                                          $baseUrl);
@@ -167,7 +170,6 @@ class OaiController extends Zend_Controller_Action {
 
 
   public function getRecordAction() {
-    $this->getHelper('ViewRenderer')->setLayoutScript('empty.phtml');
     $baseUrl = $this->buildBaseUrl();
     $request = new Class_WebService_OAI_Request_GetRecord($this->_request->getParams(),
                                                           $baseUrl);
@@ -177,7 +179,7 @@ class OaiController extends Zend_Controller_Action {
     $this->view->error = $request->getErrorOn($builder);
 
     if ($notice = $request->getNotice()) {
-      $visitor = new Class_Notice_DublinCoreVisitor();
+      $visitor = $request->getRecordVisitor();
       $visitor->visit($notice);
       $recordBuilder = new Class_WebService_OAI_Response_RecordBuilder();
       $this->view->record = $recordBuilder->xml($builder, $visitor);
@@ -187,9 +189,6 @@ class OaiController extends Zend_Controller_Action {
 
 
   public function badVerbAction() {
-    $this->getHelper('ViewRenderer')->setLayoutScript('empty.phtml');
     $this->view->baseUrl = $this->buildBaseUrl();
   }
 }
-
-?>
\ No newline at end of file
diff --git a/application/modules/opac/views/scripts/oai/list-metadata-formats.xml.phtml b/application/modules/opac/views/scripts/oai/list-metadata-formats.xml.phtml
index 3b1d10037f9..99244c72caf 100644
--- a/application/modules/opac/views/scripts/oai/list-metadata-formats.xml.phtml
+++ b/application/modules/opac/views/scripts/oai/list-metadata-formats.xml.phtml
@@ -3,17 +3,6 @@
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://www.openarchives.org/OAI/2.0/ http://www.openarchives.org/OAI/2.0/OAI-PMH.xsd">
   <responseDate><?php echo date('c');?></responseDate>
-  <?php echo $this->request->renderOn($this->builder); ?>
-  <?php 
-  if ($this->error) 
-    echo $this->error;
-  else { ?>
-    <ListMetadataFormats>
-      <metadataFormat>
-        <metadataPrefix>oai_dc</metadataPrefix>
-        <schema>http://www.openarchives.org/OAI/2.0/oai_dc.xsd</schema>
-        <metadataNamespace>http://www.openarchives.org/OAI/2.0/oai_dc/</metadataNamespace>
-      </metadataFormat>
-    </ListMetadataFormats>
-  <?php } ?>
-</OAI-PMH>
\ No newline at end of file
+  <?php echo $this->request; ?>
+  <?php echo $this->response; ?>
+</OAI-PMH>
diff --git a/library/Class/FRBR/Link.php b/library/Class/FRBR/Link.php
index 0048ababcc5..8f6b3406e92 100644
--- a/library/Class/FRBR/Link.php
+++ b/library/Class/FRBR/Link.php
@@ -348,4 +348,9 @@ class Class_FRBR_Link extends Storm_Model_Abstract {
   public function forJson() {
     return $this->getRawAttributes();
   }
+
+
+  public function isSourceRecord() {
+    return static::TYPE_NOTICE == $this->getSourceType();
+  }
 }
diff --git a/library/Class/Notice/DublinCoreBokehVisitor.php b/library/Class/Notice/DublinCoreBokehVisitor.php
new file mode 100644
index 00000000000..e5817b5a0d1
--- /dev/null
+++ b/library/Class/Notice/DublinCoreBokehVisitor.php
@@ -0,0 +1,70 @@
+<?php
+/**
+ * Copyright (c) 2019, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+class Class_Notice_DublinCoreBokehVisitor extends Class_Notice_DublinCoreVisitor {
+  /**
+   * @param $album Class_Album
+   */
+  public function visitAlbum($album) {
+    if (!$album)
+      return;
+
+    parent::visitAlbum($album);
+
+    foreach($album->getRessources() as $resource)
+      $this->_addResourceRelationTo($resource);
+
+    $this->_addRecordsRelationTo($album);
+
+    $this->_xml .= $this->_builder
+      ->relation($this->cdata('categorie : '
+                              . implode(' > ', $album->getCategorie()->getPathParts())));
+  }
+
+
+  protected function _addResourceRelationTo($resource) {
+    $this->_xml .= $this->_builder
+        ->relation($this->cdata('ressource : ' . Class_Url::absolute($resource->getOriginalUrl())));
+  }
+
+
+  protected function _addRecordsRelationTo($album) {
+    foreach(Class_FRBR_Link::findAllRecordLinksForAlbum($album) as $link)
+      $this->_addRecordRelationThrough($link);
+  }
+
+
+  protected function _addRecordRelationThrough($link) {
+    $record = $link->isSourceRecord()
+      ? $link->getSourceEntity()
+      : $link->getTargetEntity();
+
+    if (!$record || (!$item = $record->getFirstExemplaire()))
+      return;
+
+    $label = $link->isSourceRecord()
+      ? $link->getTypeLabelFromTarget()
+      : $link->getTypeLabelFromSource();
+
+    $this->_xml .= $this->_builder
+      ->relation($this->cdata($label . ' : ' . $item->getIdOrigine()));
+  }
+}
diff --git a/library/Class/WebService/OAI/MetadataFormat.php b/library/Class/WebService/OAI/MetadataFormat.php
new file mode 100644
index 00000000000..3aa9c921a75
--- /dev/null
+++ b/library/Class/WebService/OAI/MetadataFormat.php
@@ -0,0 +1,85 @@
+<?php
+/**
+ * Copyright (c) 2012-2019, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class Class_WebService_OAI_MetadataFormat {
+  const SCHEMA_OAI_DC = 'http://www.openarchives.org/OAI/2.0/oai_dc.xsd';
+  const NAMESPACE_OAI_DC = 'http://www.openarchives.org/OAI/2.0/oai_dc/';
+
+  protected $_prefix;
+
+  public static function getAll() {
+    return [new Class_WebService_OAI_MetadataFormat_OaiDc(),
+            new Class_WebService_OAI_MetadataFormat_OaiDcBokeh()];
+  }
+
+
+  public static function isKnownPrefix($prefix) {
+    foreach(static::getAll() as $format)
+      if ($format->isPrefix($prefix))
+        return true;
+
+    return false;
+  }
+
+
+  public static function forPrefix($prefix) {
+    foreach(static::getAll() as $format)
+      if ($format->isPrefix($prefix))
+        return $format;
+  }
+
+
+  public function isPrefix($prefix) {
+    return $this->_prefix === $prefix;
+  }
+
+
+  public function renderOn($builder) {
+    $parts = [$builder->metadataPrefix($this->_prefix),
+              $builder->schema(static::SCHEMA_OAI_DC),
+              $builder->metadataNamespace(static::NAMESPACE_OAI_DC)];
+    return $builder->metadataFormat(implode($parts));
+  }
+
+
+  public function getRecordVisitor() {
+    return new Class_Notice_DublinCoreVisitor();
+  }
+}
+
+
+
+
+class Class_WebService_OAI_MetadataFormat_OaiDc extends Class_WebService_OAI_MetadataFormat {
+  protected $_prefix = 'oai_dc';
+}
+
+
+
+
+class Class_WebService_OAI_MetadataFormat_OaiDcBokeh extends Class_WebService_OAI_MetadataFormat {
+  protected $_prefix = 'oai_dc_bokeh';
+
+  public function getRecordVisitor() {
+    return new Class_Notice_DublinCoreBokehVisitor();
+  }
+}
diff --git a/library/Class/WebService/OAI/Request/GetRecord.php b/library/Class/WebService/OAI/Request/GetRecord.php
index ff2c7e371d8..6ac80a07bae 100644
--- a/library/Class/WebService/OAI/Request/GetRecord.php
+++ b/library/Class/WebService/OAI/Request/GetRecord.php
@@ -16,7 +16,7 @@
  *
  * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
  * along with BOKEH; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA 
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
 class Class_WebService_OAI_Request_GetRecord {
   protected $_baseUrl;
@@ -27,8 +27,8 @@ class Class_WebService_OAI_Request_GetRecord {
 
   public function __construct($params, $baseUrl) {
     $this->_baseUrl = $baseUrl;
-    $this->_params = array_merge(array('metadataPrefix' => null,
-                                       'identifier' => null),
+    $this->_params = array_merge(['metadataPrefix' => null,
+                                  'identifier' => null],
                                  $params);
 
     $this->_metadataPrefix = $this->_params['metadataPrefix'];
@@ -37,24 +37,24 @@ class Class_WebService_OAI_Request_GetRecord {
 
 
   public function getErrorOn($builder) {
-    if (!$this->_metadataPrefix) 
-      return $builder->error(array('code' => 'badArgument'), 'Missing metadataPrefix');
+    if (!$this->_metadataPrefix)
+      return $builder->error(['code' => 'badArgument'], 'Missing metadataPrefix');
 
-    if ('oai_dc' != $this->_metadataPrefix) 
-      return $builder->error(array('code' => 'cannotDisseminateFormat'));
+    if (!Class_WebService_OAI_MetadataFormat::isKnownPrefix($this->_metadataPrefix))
+      return $builder->error(['code' => 'cannotDisseminateFormat']);
 
     if (!$this->_identifier)
-      return $builder->error(array('code' => 'badArgument'), 'Missing identifier');
+      return $builder->error(['code' => 'badArgument'], 'Missing identifier');
 
     $this->_notice = Class_Notice::getNoticeByOAIIdentifier($this->_identifier);
 
-    if (!$this->_notice) 
-      return $builder->error(array('code' => 'idDoesNotExist'));
+    if (!$this->_notice)
+      return $builder->error(['code' => 'idDoesNotExist']);
   }
 
 
   public function renderOn($builder) {
-    $attributes = array('verb' => 'GetRecord');
+    $attributes = ['verb' => 'GetRecord'];
     if ($this->_metadataPrefix)
       $attributes['metadataPrefix'] = $this->_metadataPrefix;
 
@@ -68,5 +68,11 @@ class Class_WebService_OAI_Request_GetRecord {
   public function getNotice() {
     return $this->_notice;
   }
+
+
+  public function getRecordVisitor() {
+    return ($format = Class_WebService_OAI_MetadataFormat::forPrefix($this->_metadataPrefix))
+      ? $format->getRecordVisitor()
+      : new Class_Notice_DublinCoreVisitor();
+  }
 }
-?>
\ No newline at end of file
diff --git a/library/Class/WebService/OAI/Request/ListIdentifiers.php b/library/Class/WebService/OAI/Request/ListIdentifiers.php
index f2e7d934a2a..c72da501cc4 100644
--- a/library/Class/WebService/OAI/Request/ListIdentifiers.php
+++ b/library/Class/WebService/OAI/Request/ListIdentifiers.php
@@ -83,7 +83,7 @@ class Class_WebService_OAI_Request_ListIdentifiers {
       $answer .= $builder->error(['code' => 'badArgument'],
                                  'Metadata prefix is not found');
 
-    if ('oai_dc' != $this->_metadataPrefix)
+    if (!Class_WebService_OAI_MetadataFormat::isKnownPrefix($this->_metadataPrefix))
       $answer .= $builder->error(['code' => 'cannotDisseminateFormat']);
 
     if (!$this->_set)
@@ -172,5 +172,9 @@ class Class_WebService_OAI_Request_ListIdentifiers {
   public function getSet() {
     return $this->_set;
   }
-}
-?>
\ No newline at end of file
+
+
+  public function getRecordVisitor() {
+    return new Class_Notice_DublinCoreVisitor();
+  }
+}
\ No newline at end of file
diff --git a/library/Class/WebService/OAI/Request/ListMetadataFormats.php b/library/Class/WebService/OAI/Request/ListMetadataFormats.php
index 2524c5121b2..1be94de46bc 100644
--- a/library/Class/WebService/OAI/Request/ListMetadataFormats.php
+++ b/library/Class/WebService/OAI/Request/ListMetadataFormats.php
@@ -16,12 +16,11 @@
  *
  * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
  * along with BOKEH; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA 
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
 class Class_WebService_OAI_Request_ListMetadataFormats {
-  protected $_baseUrl;
-  protected $_identifier;
-  
+  protected $_baseUrl, $_identifier;
+
   public function __construct($params, $baseUrl) {
     if (isset($params['identifier']))
       $this->_identifier = $params['identifier'];
@@ -29,6 +28,20 @@ class Class_WebService_OAI_Request_ListMetadataFormats {
   }
 
 
+  public function getResponseOn($builder) {
+    if ($error = $this->getErrorOn($builder))
+      return $error;
+
+    $formats = array_map(function($format) use($builder)
+                         {
+                           return $format->renderOn($builder);
+                         },
+                         Class_WebService_OAI_MetadataFormat::getAll());
+
+    return $builder->ListMetadataFormats(implode($formats));
+  }
+
+
   public function getErrorOn($builder) {
     if ($this->_identifier && !Class_Notice::getNoticeByOAIIdentifier($this->_identifier))
       return $builder->error(['code' => 'idDoesNotExist']);
@@ -36,11 +49,10 @@ class Class_WebService_OAI_Request_ListMetadataFormats {
 
 
   public function renderOn($builder) {
-    $attributes = array('verb' => 'ListMetadataFormats');
+    $attributes = ['verb' => 'ListMetadataFormats'];
     if ($this->_identifier)
       $attributes['identifier'] = $this->_identifier;
 
     return $builder->request($attributes, $this->_baseUrl);
   }
 }
-?>
\ No newline at end of file
diff --git a/library/Class/WebService/OAI/Request/ListRecords.php b/library/Class/WebService/OAI/Request/ListRecords.php
index f275601ba7b..aacaf880732 100644
--- a/library/Class/WebService/OAI/Request/ListRecords.php
+++ b/library/Class/WebService/OAI/Request/ListRecords.php
@@ -16,9 +16,15 @@
  *
  * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
  * along with BOKEH; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA 
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
+
 class Class_WebService_OAI_Request_ListRecords extends Class_WebService_OAI_Request_ListIdentifiers {
   protected $_verb = 'ListRecords';
+
+  public function getRecordVisitor() {
+    return ($format = Class_WebService_OAI_MetadataFormat::forPrefix($this->_metadataPrefix))
+      ? $format->getRecordVisitor()
+      : parent::getRecordVisitor();
+  }
 }
-?>
\ No newline at end of file
diff --git a/library/Class/WebService/OAI/Response/RecordBuilder.php b/library/Class/WebService/OAI/Response/RecordBuilder.php
index 39d02ba91e5..58d5167ebe2 100644
--- a/library/Class/WebService/OAI/Response/RecordBuilder.php
+++ b/library/Class/WebService/OAI/Response/RecordBuilder.php
@@ -16,7 +16,7 @@
  *
  * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
  * along with BOKEH; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA 
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
 class Class_WebService_OAI_Response_RecordBuilder {
   public function xml($builder, $visitor) {
@@ -39,4 +39,3 @@ class Class_WebService_OAI_Response_RecordBuilder {
     return $builder->metadata($visitor->xml());
   }
 }
-?>
\ No newline at end of file
diff --git a/library/ZendAfi/Form/Album.php b/library/ZendAfi/Form/Album.php
index 39c1b148002..5652b11289d 100644
--- a/library/ZendAfi/Form/Album.php
+++ b/library/ZendAfi/Form/Album.php
@@ -339,10 +339,10 @@ class ZendAfi_Form_Album extends ZendAfi_Form {
   public function addFrbrLinkFor($album) {
     $descs = $values = ['frbr_type' => [], 'frbr_url' => []];
     foreach (Class_FRBR_Link::findAllRecordLinksForAlbum($album) as $link) {
-      $record_type = (Class_FRBR_Link::TYPE_NOTICE == $link->getSourceType()) ? 'Source' : 'Target';
-      $album_type = 'Source' == $record_type ? 'target' : 'source';
+      $record_type = $link->isSourceRecord() ? 'source' : 'target';
+      $album_type = 'source' == $record_type ? 'target' : 'source';
 
-      $values['frbr_url'][] = $link->{'get' . $record_type}();
+      $values['frbr_url'][] = $link->{'get' . ucfirst($record_type)}();
       $values['frbr_type'][] = $link->getType()->getId() . ':' . $album_type;
       $descs['frbr_type'][] = '';
       $descs['frbr_url'][] = $this->getView()->frbrLabel($link, $record_type);
diff --git a/tests/application/modules/opac/controllers/OAIControllerGetRecordTest.php b/tests/application/modules/opac/controllers/OAIControllerGetRecordTest.php
index eeb086c09af..77492f6051d 100644
--- a/tests/application/modules/opac/controllers/OAIControllerGetRecordTest.php
+++ b/tests/application/modules/opac/controllers/OAIControllerGetRecordTest.php
@@ -26,11 +26,20 @@ abstract class OAIControllerGetRecordTestCase extends AbstractControllerTestCase
 
   protected $_xpath;
   protected $_xml;
+  protected $_storm_default_to_volatile = true;
 
   public function setUp() {
     parent::setUp();
     $this->_xpath = TestXPathFactory::newOaiDc();
   }
+
+
+  protected function assertMetadataContains($path, $content) {
+    $this->_xpath
+      ->assertXPathContentContains($this->_xml,
+                                   '//oai:GetRecord/oai:record/oai:metadata' . $path,
+                                   $content);
+  }
 }
 
 
@@ -90,11 +99,6 @@ class OAIControllerGetRecordNotFoundParamsTest extends OAIControllerGetRecordTes
   public function setUp() {
     parent::setUp();
 
-    Storm_Test_ObjectWrapper::onLoaderOfModel('Class_Notice')
-      ->whenCalled('getNoticeByClefAlpha')
-      ->with('harrypotter-sorciers')
-      ->answers(null);
-
     $this->dispatch('/opac/oai/request?verb=GetRecord&metadataPrefix=oai_dc&identifier=harrypotter-sorciers\"id', true);
     $this->_xml = $this->_response->getBody();
   }
@@ -121,14 +125,11 @@ class OAIControllerGetRecordNotSupportedPrefixTest extends OAIControllerGetRecor
   public function setUp() {
     parent::setUp();
 
-    Storm_Test_ObjectWrapper::onLoaderOfModel('Class_Notice')
-      ->whenCalled('getNoticeByClefAlpha')
-      ->with('harrypotter-sorciers')
-      ->answers(Class_Notice::getLoader()
-                  ->newInstanceWithId(4)
-                  ->setClefAlpha('harrypotter-sorciers')
-                  ->setTitrePrincipal('Harry Potter a l\'ecole des sorciers')
-                  ->setDateMaj('2001-12-14 11:39:44'));
+    $this->fixture('Class_Notice',
+                   ['id' => 4,
+                    'clef_alpha' => 'harrypotter-sorciers',
+                    'titre_principal' => 'Harry Potter a l\'ecole des sorciers',
+                    'date_maj' => '2001-12-14 11:39:44']);
 
     $this->dispatch('/opac/oai/request?verb=GetRecord&metadataPrefix=not_supported&identifier=harrypotter-sorciers');
     $this->_xml = $this->_response->getBody();
@@ -156,14 +157,12 @@ class OAIControllerGetRecordValidParamsTest extends OAIControllerGetRecordTestCa
   public function setUp() {
     parent::setUp();
 
-    $this->onLoaderOfModel('Class_Notice')
-         ->whenCalled('getNoticeByClefAlpha')
-         ->with('harrypotter-sorciers')
-         ->answers(Class_Notice::newInstanceWithId(4)
-                   ->setClefAlpha('harrypotter-sorciers')
-                   ->setTitrePrincipal('Harry Potter a l\'ecole des sorciers')
-                   ->setCreatedAt('2001-12-14')
-                   ->setDateMaj('2017-10-09 11:39:44'));
+    $this->fixture('Class_Notice',
+                   ['id' => 4,
+                    'clef_alpha' => 'harrypotter-sorciers',
+                    'titre_principal' => 'Harry Potter a l\'ecole des sorciers',
+                    'created_at' => '2001-12-14',
+                    'date_maj' => '2001-12-14 11:39:44']);
 
     $this->dispatch('/opac/oai/request?verb=GetRecord&metadataPrefix=oai_dc&identifier=harrypotter-sorciers', true);
     $this->_xml = $this->_response->getBody();
@@ -221,4 +220,117 @@ class OAIControllerGetRecordValidParamsTest extends OAIControllerGetRecordTestCa
                                     self::OAI_RECORD_PATH . 'oai:metadata',
                                     1);
   }
+}
+
+
+
+
+class OAIControllerGetRecordValidParamsOaiDcBokehAlbumTest extends OAIControllerGetRecordTestCase {
+  public function setUp() {
+    parent::setUp();
+
+    $album = $this->fixture('Class_Album',
+                             ['id' => 45,
+                              'titre' => 'Harry Potter a l\'ecole des sorciers',
+                              'visible' => 1,
+                              'status' => Class_Album::STATUS_VALIDATED,
+                              'created_at' => '2001-12-14',
+                              'categorie' => $this->fixture('Class_AlbumCategorie',
+                                                            ['id' => 3,
+                                                             'libelle' => 'Fond Magique']),
+                              'ressources' => [$this->fixture('Class_AlbumRessource',
+                                                              ['id' => 66,
+                                                               'fichier' => '204.jpg'])]]);
+
+    $album->index();
+
+    $linked_record = $this->fixture('Class_Notice',
+                                    ['id' => 111,
+                                     'clef_alpha' => 'LINKEDRECORD-----0',
+                                     'exemplaires' => [$this->fixture('Class_Exemplaire',
+                                                                      ['id' => 9,
+                                                                       'id_origine' => '009992282'])]]);
+
+    $this->fixture('Class_FRBR_Link',
+                   ['id' => 1,
+                    'source' => Class_Url::absolute($album->getPermalink()),
+                    'target' => $linked_record->getAbsoluteUrl(),
+                    'source_type' => Class_FRBR_Link::TYPE_ALBUM,
+                    'target_type' => Class_FRBR_Link::TYPE_NOTICE,
+                    'source_key' => 45,
+                    'target_key' => 'LINKEDRECORD-----0',
+                    'type' => $this->fixture('Class_FRBR_LinkType',
+                                             ['id' => 2,
+                                              'libelle' => 'Version numérique de',
+                                              'from_source' => 'Version originale',
+                                              'from_target' => 'Version numérique',
+                                              ])]);
+
+    $this->dispatch('/opac/oai/request?verb=GetRecord&metadataPrefix=oai_dc_bokeh&identifier=oai:localhost:HARRYPOTTERALECOLEDESSORCIERS------101', true);
+    $this->_xml = $this->_response->getBody();
+  }
+
+
+  /** @test */
+  public function requestShouldBeGetRecordHarryPotterInOaiDcBokeh() {
+    $this->_xpath->assertXpath($this->_xml,
+                               '//oai:request[@verb="GetRecord"][@identifier="oai:localhost:HARRYPOTTERALECOLEDESSORCIERS------101"][@metadataPrefix="oai_dc_bokeh"]');
+  }
+
+
+  /** @test */
+  public function shouldContainOneHeader() {
+    $this->_xpath->assertXpathCount($this->_xml,
+                                    self::OAI_RECORD_PATH . 'oai:header',
+                                    1);
+  }
+
+
+  /** @test */
+  public function headerShouldContainRecordIdentifier() {
+    $this->_xpath->assertXPathContentContains($this->_xml,
+                                              self::OAI_RECORD_PATH . self::OAI_HEADER_PATH . 'oai:identifier',
+                                              'oai:localhost:HARRYPOTTERALECOLEDESSORCIERS------101');
+  }
+
+
+  /** @test */
+  public function recordHeaderDateStampShouldBe2001DecemberFourteen() {
+    $this->_xpath->assertXPathContentContains($this->_xml,
+                                              self::OAI_RECORD_PATH . self::OAI_HEADER_PATH . 'oai:datestamp',
+                                              '2001-12-14');
+  }
+
+
+  /** @test */
+  public function metadataShouldContainOaiDublinCore() {
+    $this->_xpath->assertXPath($this->_xml,
+                               self::OAI_RECORD_PATH . 'oai:metadata/oai_dc:dc');
+  }
+
+
+  /** @test */
+  public function shouldContainOneMetadata() {
+    $this->_xpath->assertXpathCount($this->_xml,
+                                    self::OAI_RECORD_PATH . 'oai:metadata',
+                                    1);
+  }
+
+
+  /** @test */
+  public function shouldContainsCategoryFondMagique() {
+    $this->assertMetadataContains('//dc:relation', 'categorie : Fond Magique');
+  }
+
+
+  /** @test */
+  public function shouldContainsResource204DotJpg() {
+    $this->assertMetadataContains('//dc:relation', '/userfiles/album/45/big/media/204.jpg');
+  }
+
+
+  /** @test */
+  public function shouldContainsLinkedRecord001() {
+    $this->assertMetadataContains('//dc:relation', 'Version originale : 009992282');
+  }
 }
\ No newline at end of file
diff --git a/tests/application/modules/opac/controllers/OAIControllerListMetadataFormatsTest.php b/tests/application/modules/opac/controllers/OAIControllerListMetadataFormatsTest.php
index 0dbf77fc0e8..686198376cc 100644
--- a/tests/application/modules/opac/controllers/OAIControllerListMetadataFormatsTest.php
+++ b/tests/application/modules/opac/controllers/OAIControllerListMetadataFormatsTest.php
@@ -16,7 +16,7 @@
  *
  * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
  * along with BOKEH; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA 
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
 require_once 'AbstractControllerTestCase.php';
 
@@ -55,46 +55,61 @@ class OAIControllerListMetadataFormatsValidTest extends OAIControllerListMetadat
 
 
   /** @test */
-  public function shouldHaveOneMetadataFormat() {
-    $this->_xpath->assertXpathCount($this->_response->getBody(), 
+  public function shouldHaveTwoMetadataFormat() {
+    $this->_xpath->assertXpathCount($this->_response->getBody(),
                                     '//oai:ListMetadataFormats/oai:metadataFormat',
-                                    1);
+                                    2);
   }
 
 
-  /** @test */
-  public function metadataPrefixShouldBeOaiDublinCore() {
-    $this->_assertFormatContentAt('metadataPrefix', 'oai_dc', 1);
+  public function formats() {
+    return [['oai_dc', 1],
+            ['oai_dc_bokeh', 2]];
   }
 
 
-  /** @test */
-  public function schemaShouldbeOaiDublinCore() {
-    $this->_assertFormatContentAt('schema', 
-                                  'http://www.openarchives.org/OAI/2.0/oai_dc.xsd', 
-                                  1);
+  /**
+   * @test
+   * @dataProvider formats
+   */
+  public function prefixShouldBe($prefix, $position) {
+    $this->_assertFormatContentAt('metadataPrefix', $prefix, $position);
   }
 
 
-  /** @test */
-  public function namespaceShouldBeDublinCore() {
-    $this->_assertFormatContentAt('metadataNamespace', 
-                                  'http://www.openarchives.org/OAI/2.0/oai_dc/',
-                                  1);
+  /**
+   * @test
+   * @dataProvider formats
+   */
+  public function schemaShouldbeOaiDublinCoreAt($prefix, $position) {
+    $this->_assertFormatContentAt('schema',
+                                  'http://www.openarchives.org/OAI/2.0/oai_dc.xsd',
+                                  $position);
   }
 
 
-  protected function _assertFormatContentAt($name, $content, $position) {
-    $path = sprintf('//oai:ListMetadataFormats/oai:metadataFormat[%s]/oai:%s',
-                    $position, $name);
-    $this->_xpath->assertXpathContentContains($this->_response->getBody(), $path, $content);
+  /**
+   * @test
+   * @dataProvider formats
+   */
+  public function namespaceShouldBeDublinCoreAt($prefix, $position) {
+    $this->_assertFormatContentAt('metadataNamespace',
+                                  'http://www.openarchives.org/OAI/2.0/oai_dc/',
+                                  $position);
   }
 
 
   /** @test */
-  public function shouldReturnErrorIdDoesNotExist() {
+  public function shouldNotHaveError() {
     $this->_xpath->assertNotXPath($this->_response->getBody(),   '//oai:error');
   }
+
+
+  protected function _assertFormatContentAt($name, $content, $position) {
+    $path = sprintf('//oai:ListMetadataFormats/oai:metadataFormat[%s]/oai:%s',
+                    $position, $name);
+    $this->_xpath->assertXpathContentContains($this->_response->getBody(), $path, $content);
+  }
 }
 
 
@@ -108,4 +123,3 @@ class OAIControllerListMetadataFormatsErrorsTest extends OAIControllerListMetada
                                '//oai:error[@code="idDoesNotExist"]');
   }
 }
-?>
\ No newline at end of file
diff --git a/tests/application/modules/opac/controllers/OAIControllerListRecordsTest.php b/tests/application/modules/opac/controllers/OAIControllerListRecordsTest.php
index 68424057b73..ae56be8f51b 100644
--- a/tests/application/modules/opac/controllers/OAIControllerListRecordsTest.php
+++ b/tests/application/modules/opac/controllers/OAIControllerListRecordsTest.php
@@ -208,4 +208,135 @@ class OAIControllerListRecordsWithoutSetTest
     $this->_xpath->assertNotXPath($this->_body,
                                   '//oai:ListRecords/oai:record');
   }
-}
\ No newline at end of file
+}
+
+
+
+
+class OAIControllerListRecordsInOaiDcBokehFormatTest
+  extends AbstractControllerTestCase {
+
+  protected
+    $_storm_default_to_volatile = true,
+    $_xpath,
+    $_body;
+
+  public function setUp() {
+    parent::setUp();
+    $this->_xpath = TestXPathFactory::newOaiDc();
+
+    $album = $this->fixture('Class_Album',
+                             ['id' => 45,
+                              'titre' => 'Harry Potter a l\'ecole des sorciers',
+                              'visible' => 1,
+                              'status' => Class_Album::STATUS_VALIDATED,
+                              'created_at' => '2001-12-14',
+                              'categorie' => $this->fixture('Class_AlbumCategorie',
+                                                            ['id' => 3,
+                                                             'libelle' => 'Fond Magique']),
+                              'ressources' => [$this->fixture('Class_AlbumRessource',
+                                                              ['id' => 66,
+                                                               'fichier' => '204.jpg'])]]);
+
+    $album->index();
+
+    $linked_record = $this->fixture('Class_Notice',
+                                    ['id' => 111,
+                                     'clef_alpha' => 'LINKEDRECORD-----0',
+                                     'exemplaires' => [$this->fixture('Class_Exemplaire',
+                                                                      ['id' => 9,
+                                                                       'id_origine' => '009992282'])]]);
+
+    $this->fixture('Class_FRBR_Link',
+                   ['id' => 1,
+                    'source' => Class_Url::absolute($album->getPermalink()),
+                    'target' => $linked_record->getAbsoluteUrl(),
+                    'source_type' => Class_FRBR_Link::TYPE_ALBUM,
+                    'target_type' => Class_FRBR_Link::TYPE_NOTICE,
+                    'source_key' => 45,
+                    'target_key' => 'LINKEDRECORD-----0',
+                    'type' => $this->fixture('Class_FRBR_LinkType',
+                                             ['id' => 2,
+                                              'libelle' => 'Version numérique de',
+                                              'from_source' => 'Version originale',
+                                              'from_target' => 'Version numérique',
+                                              ])]);
+
+    $this->fixture('Class_Catalogue',
+                   ['id' => 2,
+                    'libelle' => 'My albums',
+                    'oai_spec' => 'albums']);
+
+    $this
+      ->onLoaderOfModel('Class_Catalogue')
+      ->whenCalled('countNoticesFor')->answers(1)
+      ->whenCalled('loadNoticesFor')->answers([$album->getNotice()]);
+
+    $this->dispatch('/opac/oai/request?verb=ListRecords&metadataPrefix=oai_dc_bokeh&set=albums');
+
+    $this->_body = $this->_response->getBody();
+  }
+
+
+  /** @test */
+  public function requestVerbShouldBeListRecords() {
+    $this->_xpath->assertXPath($this->_body,
+                               '//oai:request[@verb="ListRecords"]');
+  }
+
+
+  /** @test */
+  public function shouldHaveOneRecord() {
+    $this->_xpath->assertXPathCount($this->_body,
+                                    '//oai:ListRecords/oai:record',
+                                    1);
+  }
+
+
+  /** @test */
+  public function firstIdentifierShouldContainSorciers() {
+    $this->_assertHeaderContentAt('identifier', 'HARRYPOTTERALECOLEDESSORCIERS', 1);
+  }
+
+
+  /** @test */
+  public function firstSetSpecShouldBeAlbums() {
+    $this->_assertHeaderContentAt('setSpec', 'albums', 1);
+  }
+
+
+  /** @test */
+  public function shouldContainsCategoryFondMagique() {
+    $this->_assertMetadataContains('//dc:relation', 'categorie : Fond Magique');
+  }
+
+
+  /** @test */
+  public function shouldContainsResource204DotJpg() {
+    $this->_assertMetadataContains('//dc:relation', '/userfiles/album/45/big/media/204.jpg');
+  }
+
+
+  /** @test */
+  public function shouldContainsLinkedRecord001() {
+    $this->_assertMetadataContains('//dc:relation', 'Version originale : 009992282');
+  }
+
+
+  protected function _assertHeaderContentAt($header, $content, $position) {
+    $path = sprintf('//oai:ListRecords/oai:record[%s]/oai:header/oai:%s',
+                    $position, $header);
+
+    $this->_xpath->assertXPathContentContains($this->_body,
+                                              $path,
+                                              $content);
+  }
+
+
+  protected function _assertMetadataContains($path, $content) {
+    $this->_xpath
+      ->assertXPathContentContains($this->_body,
+                                   '//oai:ListRecords/oai:record/oai:metadata' . $path,
+                                   $content);
+  }
+}
-- 
GitLab