From 3fa240535cf32c3a5c46aa011824589a532de310 Mon Sep 17 00:00:00 2001
From: Ere Maijala <ere.maijala@helsinki.fi>
Date: Mon, 22 Dec 2014 13:57:31 -0500
Subject: [PATCH] Implemented support for simple container-child record links
 using the hierarchy fields.

---
 config/vufind/config.ini                      |  5 ++
 config/vufind/searchspecs.yaml                |  9 ++-
 languages/en.ini                              |  2 +
 languages/fi.ini                              |  2 +
 languages/sv.ini                              |  2 +
 .../src/VuFind/RecordDriver/Factory.php       |  5 +-
 .../src/VuFind/RecordDriver/SolrDefault.php   | 71 ++++++++++++++++++-
 .../VuFind/View/Helper/Root/RecordLink.php    | 19 +++++
 .../RecordDriver/SolrDefault/core.phtml       | 12 +++-
 .../SolrDefault/result-list.phtml             | 11 +--
 .../RecordDriver/SolrDefault/core.phtml       | 14 +++-
 .../SolrDefault/result-list.phtml             |  5 +-
 .../RecordDriver/SolrDefault/core.phtml       | 10 ++-
 13 files changed, 151 insertions(+), 16 deletions(-)

diff --git a/config/vufind/config.ini b/config/vufind/config.ini
index 67bbc079cc8..13a82d190a4 100644
--- a/config/vufind/config.ini
+++ b/config/vufind/config.ini
@@ -1096,6 +1096,11 @@ search = true
 ; results can cause performance problems.  If treeSearchLimit is -1 or not set,
 ; results will be unlimited.
 treeSearchLimit = 100
+; Whether hierarchy fields are used for linking between container records and their
+; children (default = false). This is an alternative to the full collections support
+; (see the [Collections] section), so only one of them should be enabled
+; at a time e.g. unless custom record drivers are used.
+;simpleContainerLinks = true
 
 ; This section will be used to configure the feedback module.
 ; Set "tab_enabled" to true in order to enable the feedback module.
diff --git a/config/vufind/searchspecs.yaml b/config/vufind/searchspecs.yaml
index d139ad91665..e44e42f5c55 100644
--- a/config/vufind/searchspecs.yaml
+++ b/config/vufind/searchspecs.yaml
@@ -56,8 +56,8 @@
 #      QueryFields: ...
 #    # All the same settings as above, but for exact searches, i.e. search terms
 #    #     enclosed in quotes. Allows different fields or weights for exact
-#    #     searches. See below for commented-out examples.  
-#       
+#    #     searches. See below for commented-out examples.
+#
 # ...etc.
 #
 #-----------------------------------------------------------------------------------
@@ -444,6 +444,11 @@ id:
     id:
       - [onephrase, ~]
 
+ParentID:
+  QueryFields:
+    hierarchy_parent_id:
+      - [onephrase, ~]
+
 # Fields for exact matches originating from alphabetic browse
 ids:
   QueryFields:
diff --git a/languages/en.ini b/languages/en.ini
index 191e30a4ba3..afcd5f34522 100644
--- a/languages/en.ini
+++ b/languages/en.ini
@@ -166,6 +166,8 @@ Checked Out = "Checked Out"
 Checked Out Items = "Checked Out Items"
 Checkedout = "Checked Out"
 Chicago Citation = "Chicago Style Citation"
+child_record_count = "%%count%% records"
+child_records = "Contents/pieces"
 Choose a Category to Begin Browsing = "Choose a Category to Begin Browsing"
 Choose a Column to Begin Browsing = "Choose a Column to Begin Browsing"
 Choose a List = "Choose a List"
diff --git a/languages/fi.ini b/languages/fi.ini
index ec85afeec15..932aa5f6477 100644
--- a/languages/fi.ini
+++ b/languages/fi.ini
@@ -161,6 +161,8 @@ Checked Out = "Lainattu"
 Checked Out Items = "Lainat"
 Checkedout = "Lainat"
 Chicago Citation = "Chicago-tyylinen lähdeviittaus"
+child_record_count = "%%count%% tietuetta"
+child_records = "Sisältö/kappaleet"
 Choose a Category to Begin Browsing = "Valitse kategoria aloittaaksesi selauksen"
 Choose a Column to Begin Browsing = "Valitse sarake aloittaaksesi selaamisen"
 Choose a List = "Valitse lista"
diff --git a/languages/sv.ini b/languages/sv.ini
index e816f7e7fde..0f56f95b07b 100644
--- a/languages/sv.ini
+++ b/languages/sv.ini
@@ -161,6 +161,8 @@ Checked Out = "Utlånad"
 Checked Out Items = "Utlånade exemplar"
 Checkedout = "Utlånade exemplar"
 Chicago Citation = "Chicago-stil citat"
+child_record_count = "%%count%% poster"
+child_records = "Innehåll/delar"
 Choose a Category to Begin Browsing = "Choose a Category to Begin Browsing"
 Choose a Column to Begin Browsing = "Välj en kolumn för att börja bläddra"
 Choose a List = "Välj en lista"
diff --git a/module/VuFind/src/VuFind/RecordDriver/Factory.php b/module/VuFind/src/VuFind/RecordDriver/Factory.php
index f16ffe51c4a..5cb0e78f792 100644
--- a/module/VuFind/src/VuFind/RecordDriver/Factory.php
+++ b/module/VuFind/src/VuFind/RecordDriver/Factory.php
@@ -141,11 +141,13 @@ class Factory
      */
     public static function getSolrDefault(ServiceManager $sm)
     {
-        return new SolrDefault(
+        $driver = new SolrDefault(
             $sm->getServiceLocator()->get('VuFind\Config')->get('config'),
             null,
             $sm->getServiceLocator()->get('VuFind\Config')->get('searches')
         );
+        $driver->attachSearchService($sm->getServiceLocator()->get('VuFind\Search'));
+        return $driver;
     }
 
     /**
@@ -167,6 +169,7 @@ class Factory
             $sm->getServiceLocator()->get('VuFind\ILSHoldLogic'),
             $sm->getServiceLocator()->get('VuFind\ILSTitleHoldLogic')
         );
+        $driver->attachSearchService($sm->getServiceLocator()->get('VuFind\Search'));
         return $driver;
     }
 
diff --git a/module/VuFind/src/VuFind/RecordDriver/SolrDefault.php b/module/VuFind/src/VuFind/RecordDriver/SolrDefault.php
index 65380c3da88..fa4a88e37fd 100644
--- a/module/VuFind/src/VuFind/RecordDriver/SolrDefault.php
+++ b/module/VuFind/src/VuFind/RecordDriver/SolrDefault.php
@@ -111,6 +111,20 @@ class SolrDefault extends AbstractBase
      */
     protected $highlightDetails = array();
 
+    /**
+     * Search results plugin manager
+     *
+     * @var \VuFindSearch\Service
+     */
+    protected $searchService = null;
+
+    /**
+     * Should we use hierarchy fields for simple container-child records linking?
+     *
+     * @var bool
+     */
+    protected $containerLinking = false;
+
     /**
      * Constructor
      *
@@ -137,6 +151,12 @@ class SolrDefault extends AbstractBase
                 $this->snippetCaptions[$key] = $value;
             }
         }
+
+        // Container-contents linking
+        $this->containerLinking
+            = !isset($mainConfig->Hierarchy->simpleContainerLinks)
+            ? false : $mainConfig->Hierarchy->simpleContainerLinks;
+
         parent::__construct($mainConfig, $recordConfig);
     }
 
@@ -1307,8 +1327,7 @@ class SolrDefault extends AbstractBase
     }
 
     /**
-     * Get the absolute parent title(s) associated with this item
-     * (empty if none).
+     * Get the absolute parent title(s) associated with this item (empty if none).
      *
      * @return array
      */
@@ -1745,4 +1764,52 @@ class SolrDefault extends AbstractBase
             ? $this->fields['dedup_data']
             : array();
     }
+
+    /**
+     * Attach a Search Results Plugin Manager connection and related logic to
+     * the driver
+     *
+     * @param \VuFindSearch\Service $service Search Service Manager
+     *
+     * @return void
+     */
+    public function attachSearchService(\VuFindSearch\Service $service)
+    {
+        $this->searchService = $service;
+    }
+
+    /**
+     * Get the number of child records belonging to this record
+     *
+     * @return int Number of records
+     */
+    public function getChildRecordCount()
+    {
+        // Shortcut: if this record is not the top record, let's not find out the
+        // count. This assumes that contained records cannot contain more records.
+        if (!$this->containerLinking
+            || empty($this->fields['is_hierarchy_id'])
+            || null === $this->searchService
+        ) {
+            return 0;
+        }
+
+        $safeId = addcslashes($this->fields['is_hierarchy_id'], '"');
+        $query = new \VuFindSearch\Query\Query(
+            'hierarchy_parent_id:"' . $safeId . '"'
+        );
+        return $this->searchService->search('Solr', $query, 0, 0)->getTotal();
+    }
+
+    /**
+     * Get the container record id.
+     *
+     * @return string Container record id (empty string if none)
+     */
+    public function getContainerRecordID()
+    {
+        return $this->containerLinking
+            && !empty($this->fields['hierarchy_parent_id'])
+            ? $this->fields['hierarchy_parent_id'][0] : '';
+    }
 }
diff --git a/module/VuFind/src/VuFind/View/Helper/Root/RecordLink.php b/module/VuFind/src/VuFind/View/Helper/Root/RecordLink.php
index fe41b07847d..33b387834d2 100644
--- a/module/VuFind/src/VuFind/View/Helper/Root/RecordLink.php
+++ b/module/VuFind/src/VuFind/View/Helper/Root/RecordLink.php
@@ -218,4 +218,23 @@ class RecordLink extends \Zend\View\Helper\AbstractHelper
             $escapeHelper($truncateHelper($driver->getBreadcrumb(), 30))
             . '</a>';
     }
+
+    /**
+     * Given a record driver, generate a URL to fetch all child records for it.
+     *
+     * @param \VuFind\RecordDriver\AbstractBase $driver Host Record.
+     *
+     * @return string
+     */
+    public function getChildRecordSearchUrl($driver)
+    {
+        $urlHelper = $this->getView()->plugin('url');
+        $url = $urlHelper('search-results')
+            . '?lookfor='
+            . urlencode(addcslashes($driver->getUniqueID(), '"'))
+            . '&type=ParentID';
+        // Make sure everything is properly HTML encoded:
+        $escaper = $this->getView()->plugin('escapehtml');
+        return $escaper($url);
+    }
 }
\ No newline at end of file
diff --git a/themes/blueprint/templates/RecordDriver/SolrDefault/core.phtml b/themes/blueprint/templates/RecordDriver/SolrDefault/core.phtml
index 8cffef3f4d1..950d451954c 100644
--- a/themes/blueprint/templates/RecordDriver/SolrDefault/core.phtml
+++ b/themes/blueprint/templates/RecordDriver/SolrDefault/core.phtml
@@ -13,9 +13,10 @@
   <table cellpadding="2" cellspacing="0" border="0" class="citation" summary="<?=$this->transEsc('Bibliographic Details')?>">
     <? $journalTitle = $this->driver->getContainerTitle(); if (!empty($journalTitle)): ?>
     <tr valign="top">
-      <th><?=$this->transEsc('Journal Title')?>:</th>
+      <th><?=$this->transEsc('Published in')?>:</th>
       <td>
-        <a href="<?=$this->record($this->driver)->getLink('journaltitle', $journalTitle)?>"><?=$this->escapeHtml($journalTitle)?></a>
+        <? $containerID = $this->driver->getContainerRecordID(); ?>
+        <a href="<?=($containerID ? $this->recordLink()->getUrl("VuFind|$containerID") : $this->record($this->driver)->getLink('journaltitle', $journalTitle))?>"><?=$this->escapeHtml($journalTitle)?></a>
         <? $ref = $this->driver->getContainerReference(); if (!empty($ref)) { echo $this->escapeHtml($ref); } ?>
       </td>
     </tr>
@@ -154,6 +155,13 @@
     </tr>
     <? endif; ?>
 
+    <? $childRecordCount = $this->driver->tryMethod('getChildRecordCount'); if ($childRecordCount): ?>
+    <tr valign="top">
+      <th><?=$this->transEsc('child_records')?>: </th>
+      <td><a href="<?=$this->recordLink()->getChildRecordSearchUrl($this->driver)?>"><?=$this->transEsc('child_record_count', array('%%count%%' => $childRecordCount))?></a></td>
+    </tr>
+    <? endif; ?>
+
     <?
         $openUrl = $this->driver->openURLActive('record') ? $this->driver->getOpenURL() : false;
         // Account for replace_other_urls setting
diff --git a/themes/blueprint/templates/RecordDriver/SolrDefault/result-list.phtml b/themes/blueprint/templates/RecordDriver/SolrDefault/result-list.phtml
index 7a6126e06ad..2d472ca0611 100644
--- a/themes/blueprint/templates/RecordDriver/SolrDefault/result-list.phtml
+++ b/themes/blueprint/templates/RecordDriver/SolrDefault/result-list.phtml
@@ -38,7 +38,10 @@
       <? $journalTitle = $this->driver->getContainerTitle(); $summDate = $this->driver->getPublicationDates(); ?>
       <? if (!empty($journalTitle)): ?>
         <?=!empty($summAuthor) ? '<br />' : ''?>
-        <?=/* TODO: handle highlighting more elegantly here */ $this->transEsc('Published in') . ' <a href="' . $this->record($this->driver)->getLink('journaltitle', str_replace(array('{{{{START_HILITE}}}}', '{{{{END_HILITE}}}}'), '', $journalTitle)) . '">' . $this->highlight($journalTitle) . '</a>';?>
+        <?=$this->transEsc('Published in')?>
+        <? $containerID = $this->driver->getContainerRecordID(); ?>
+        <? /* TODO: handle highlighting more elegantly here: */?>
+        <a href="<?=($containerID ? $this->recordLink()->getUrl("VuFind|$containerID") : $this->record($this->driver)->getLink('journaltitle', str_replace(array('{{{{START_HILITE}}}}', '{{{{END_HILITE}}}}'), '', $journalTitle)))?>"><?=$this->highlight($journalTitle) ?></a>
         <?=!empty($summDate) ? ' (' . $this->escapeHtml($summDate[0]) . ')' : ''?>
       <? elseif (!empty($summDate)): ?>
         <?=!empty($summAuthor) ? '<br />' : ''?>
@@ -70,7 +73,7 @@
       <?
       /* Display information on duplicate records if available */
       $dedupData = $this->driver->getDedupData();
-      if ($dedupData): ?> 
+      if ($dedupData): ?>
       <div class="dedupInformation">
       <?
         $i = 0;
@@ -91,8 +94,8 @@
         }?>
       </div>
       <? endif; ?>
-      
-      
+
+
       <div class="callnumAndLocation">
         <? if ($this->driver->supportsAjaxStatus()): ?>
           <strong class="hideIfDetailed"><?=$this->transEsc('Call Number')?>:</strong>
diff --git a/themes/bootstrap3/templates/RecordDriver/SolrDefault/core.phtml b/themes/bootstrap3/templates/RecordDriver/SolrDefault/core.phtml
index e0f675a3b47..a855698b5cd 100644
--- a/themes/bootstrap3/templates/RecordDriver/SolrDefault/core.phtml
+++ b/themes/bootstrap3/templates/RecordDriver/SolrDefault/core.phtml
@@ -50,9 +50,10 @@
     <table class="table table-striped" summary="<?=$this->transEsc('Bibliographic Details')?>">
       <? $journalTitle = $this->driver->getContainerTitle(); if (!empty($journalTitle)): ?>
       <tr>
-        <th><?=$this->transEsc('Journal Title')?>:</th>
+        <th><?=$this->transEsc('Published in')?>:</th>
         <td>
-          <a href="<?=$this->record($this->driver)->getLink('journaltitle', $journalTitle)?>"><?=$this->escapeHtml($journalTitle)?></a>
+          <? $containerID = $this->driver->getContainerRecordID(); ?>
+          <a href="<?=($containerID ? $this->recordLink()->getUrl("VuFind|$containerID") : $this->record($this->driver)->getLink('journaltitle', $journalTitle))?>"><?=$this->escapeHtml($journalTitle)?></a>
           <? $ref = $this->driver->getContainerReference(); if (!empty($ref)) { echo $this->escapeHtml($ref); } ?>
         </td>
       </tr>
@@ -192,6 +193,15 @@
       </tr>
       <? endif; ?>
 
+      <? $childRecordCount = $this->driver->tryMethod('getChildRecordCount'); if ($childRecordCount): ?>
+      <tr>
+        <th><?=$this->transEsc('child_records')?>: </th>
+        <td>
+          <a href="<?=$this->recordLink()->getChildRecordSearchUrl($this->driver)?>"><?=$this->transEsc('child_record_count', array('%%count%%' => $childRecordCount))?></a>
+        </td>
+      </tr>
+      <? endif; ?>
+
       <?
         $openUrl = $this->driver->openURLActive('record') ? $this->driver->getOpenURL() : false;
         // Account for replace_other_urls setting
diff --git a/themes/bootstrap3/templates/RecordDriver/SolrDefault/result-list.phtml b/themes/bootstrap3/templates/RecordDriver/SolrDefault/result-list.phtml
index 4e1a5afebbf..d40c7d21141 100644
--- a/themes/bootstrap3/templates/RecordDriver/SolrDefault/result-list.phtml
+++ b/themes/bootstrap3/templates/RecordDriver/SolrDefault/result-list.phtml
@@ -45,7 +45,10 @@
           <? $journalTitle = $this->driver->getContainerTitle(); $summDate = $this->driver->getPublicationDates(); ?>
           <? if (!empty($journalTitle)): ?>
             <?=!empty($summAuthor) ? '<br />' : ''?>
-            <?=/* TODO: handle highlighting more elegantly here */ $this->transEsc('Published in') . ' <a href="' . $this->record($this->driver)->getLink('journaltitle', str_replace(array('{{{{START_HILITE}}}}', '{{{{END_HILITE}}}}'), '', $journalTitle)) . '">' . $this->highlight($journalTitle) . '</a>';?>
+            <?=$this->transEsc('Published in')?>
+            <? $containerID = $this->driver->getContainerRecordID(); ?>
+            <? /* TODO: handle highlighting more elegantly here: */?>
+            <a href="<?=($containerID ? $this->recordLink()->getUrl("VuFind|$containerID") : $this->record($this->driver)->getLink('journaltitle', str_replace(array('{{{{START_HILITE}}}}', '{{{{END_HILITE}}}}'), '', $journalTitle)))?>"><?=$this->highlight($journalTitle) ?></a>
             <?=!empty($summDate) ? ' (' . $this->escapeHtml($summDate[0]) . ')' : ''?>
           <? elseif (!empty($summDate)): ?>
             <?=!empty($summAuthor) ? '<br />' : ''?>
diff --git a/themes/jquerymobile/templates/RecordDriver/SolrDefault/core.phtml b/themes/jquerymobile/templates/RecordDriver/SolrDefault/core.phtml
index eb6f0d28506..1be519b07da 100644
--- a/themes/jquerymobile/templates/RecordDriver/SolrDefault/core.phtml
+++ b/themes/jquerymobile/templates/RecordDriver/SolrDefault/core.phtml
@@ -15,9 +15,10 @@
 
 <dl class="biblio" title="<?=$this->transEsc('Bibliographic Details')?>">
   <? $journalTitle = $this->driver->getContainerTitle(); if (!empty($journalTitle)): ?>
-    <dt><?=$this->transEsc('Journal Title')?>:</dt>
+    <dt><?=$this->transEsc('Published in')?>:</dt>
       <dd>
-      <a rel="external" href="<?=$this->record($this->driver)->getLink('journaltitle', $journalTitle)?>"><?=$this->escapeHtml($journalTitle)?></a>
+      <? $containerID = $this->driver->getContainerRecordID(); ?>
+      <a rel="external" href="<?=($containerID ? $this->recordLink()->getUrl("VuFind|$containerID") : $this->record($this->driver)->getLink('journaltitle', $journalTitle))?>"><?=$this->escapeHtml($journalTitle)?></a>
       <? $ref = $this->driver->getContainerReference(); if (!empty($ref)) { echo $this->escapeHtml($ref); } ?>
     </dd>
   <? endif; ?>
@@ -125,6 +126,11 @@
     </dd>
   <? endif; ?>
 
+  <? $childRecordCount = $this->driver->tryMethod('getChildRecordCount'); if ($childRecordCount): ?>
+    <dt><?=$this->transEsc('child_records')?>: </dt>
+    <dd><a rel="external" href="<?=$this->recordLink()->getChildRecordSearchUrl($this->driver)?>"><?=$this->transEsc('child_record_count', array('%%count%%' => $childRecordCount))?></a></dd>
+  <? endif; ?>
+
   <?
       $openUrl = $this->driver->openURLActive('record') ? $this->driver->getOpenURL() : false;
       // Account for replace_other_urls setting
-- 
GitLab