From 00e562ad513500bcbb43069a0adb87f238a18fad Mon Sep 17 00:00:00 2001
From: Chris Hallberg <crhallberg@gmail.com>
Date: Tue, 22 Jul 2014 12:24:21 -0400
Subject: [PATCH] DPLA recommendation module.

---
 config/vufind/config.ini                      |   4 +
 config/vufind/searches.ini                    |  13 +-
 module/VuFind/config/module.config.php        |   1 +
 .../VuFind/src/VuFind/Recommend/DPLATerms.php | 249 ++++++++++++++++++
 .../VuFind/src/VuFind/Recommend/Factory.php   |  19 ++
 .../templates/Recommend/DPLATerms.phtml       |  17 ++
 .../templates/Recommend/DPLATerms.phtml       |  17 ++
 .../templates/Recommend/DPLATerms.phtml       |  18 ++
 .../templates/Recommend/DPLATerms.phtml       |   1 +
 9 files changed, 334 insertions(+), 5 deletions(-)
 create mode 100644 module/VuFind/src/VuFind/Recommend/DPLATerms.php
 create mode 100644 themes/blueprint/templates/Recommend/DPLATerms.phtml
 create mode 100644 themes/bootstrap/templates/Recommend/DPLATerms.phtml
 create mode 100644 themes/bootstrap3/templates/Recommend/DPLATerms.phtml
 create mode 100644 themes/jquerymobile/templates/Recommend/DPLATerms.phtml

diff --git a/config/vufind/config.ini b/config/vufind/config.ini
index dfbcf642fb5..bfef6086088 100644
--- a/config/vufind/config.ini
+++ b/config/vufind/config.ini
@@ -631,6 +631,10 @@ pw               = "Password"
 ;apiKey          = ApiKey
 ;OCLCCode        = MYCODE
 
+; DPLA
+[DPLA]
+apiKey = http://dp.la/info/developers/codex/policies/#get-a-key
+
 ; These settings affect OpenURL generation and presentation; OpenURLs are used to
 ; help users find resources through your link resolver and to manage citations in
 ; Zotero.
diff --git a/config/vufind/searches.ini b/config/vufind/searches.ini
index 780d27422f9..3b91aa8cde9 100644
--- a/config/vufind/searches.ini
+++ b/config/vufind/searches.ini
@@ -166,6 +166,9 @@ CallNumber = callnumber
 ;       Display catalog search results matching the terms found in the specified
 ;       GET parameter (default = "lookfor"), limited to a specified number of
 ;       matches (default = 5).  This is designed for use with non-catalog modules.
+; DLPATerms:[collapsed]
+;       Display results from the DPLA catalog. Provide a boolean to have the sidebar
+;       collapsed or open on page load.
 ; EuropeanaResults:[url]:[requestParam]:[limit]:[unwanted data providers]
 ;       Display search results from Europeana.eu API. [url] is the base search URL
 ;       default "api.europeana.eu/api/opensearch.rss" [requestParam] parameter name
@@ -206,17 +209,17 @@ CallNumber = callnumber
 ;       which will help with accurate analysis (default = Solr)
 ;       [limit] is the number of records to display (default = 10)
 ;       [display mode] determines how the records are displayed. Valid values are
-;       "standard" (for a basic display including titles and authors), 
+;       "standard" (for a basic display including titles and authors),
 ;       "images" for just images or "mixed" for both. (default = standard)
 ;       [random mode] determines if the records are selected from the entire backend
-;       or from the current result set. Valid values are "retain" to limit results 
-;       to the current result set or "disregard" to use the entire backend. 
+;       or from the current result set. Valid values are "retain" to limit results
+;       to the current result set or "disregard" to use the entire backend.
 ;       (default = retain)
 ;       [minimumset] is the minimum result set count required to display random items,
 ;       0 = no minimum required. This setting can be used to prevent random items
 ;       displaying in a small result set. (default = 0)
 ;       [facet-n] A facet to apply to the random selection
-;       [facetvalue-n] The facet value to apply to the random selection 
+;       [facetvalue-n] The facet value to apply to the random selection
 ; SideFacets:[regular facet section]:[checkbox facet section]:[ini name]
 ;       Display the specified facets, where [ini name] is the name of an ini file
 ;       in your config directory (defaults to "facets" if not supplied),
@@ -371,7 +374,7 @@ method = ils
 ; a load on your ILS.
 ranges = 1,5,30
 ; This setting only applies when using the "ils" method. It controls the maximum
-; number of pages of results that will show up when doing a new item search. 
+; number of pages of results that will show up when doing a new item search.
 ; It is necessary to limit the number of results to avoid getting a "too many boolean
 ; clauses" error from the Solr index (see notes at
 ; http://vufind.org/jira/browse/VUFIND-128 for more details).  However, if you
diff --git a/module/VuFind/config/module.config.php b/module/VuFind/config/module.config.php
index 2ea50f5d222..e6e1dc422d0 100644
--- a/module/VuFind/config/module.config.php
+++ b/module/VuFind/config/module.config.php
@@ -368,6 +368,7 @@ $config = array(
                     'authorityrecommend' => 'VuFind\Recommend\Factory::getAuthorityRecommend',
                     'catalogresults' => 'VuFind\Recommend\Factory::getCatalogResults',
                     'collectionsidefacets' => 'VuFind\Recommend\Factory::getCollectionSideFacets',
+                    'dplaterms' => 'VuFind\Recommend\Factory::getDPLATerms',
                     'europeanaresults' => 'VuFind\Recommend\Factory::getEuropeanaResults',
                     'expandfacets' => 'VuFind\Recommend\Factory::getExpandFacets',
                     'favoritefacets' => 'VuFind\Recommend\Factory::getFavoriteFacets',
diff --git a/module/VuFind/src/VuFind/Recommend/DPLATerms.php b/module/VuFind/src/VuFind/Recommend/DPLATerms.php
new file mode 100644
index 00000000000..76d9d876f9d
--- /dev/null
+++ b/module/VuFind/src/VuFind/Recommend/DPLATerms.php
@@ -0,0 +1,249 @@
+<?php
+/**
+ * DPLATerms Recommendations Module
+ *
+ * PHP version 5
+ *
+ * Copyright (C) Villanova University 2010.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * @category VuFind2
+ * @package  Recommendations
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/vufind2:recommendation_modules Wiki
+ */
+namespace VuFind\Recommend;
+use Zend\Http\Client as HttpClient,
+    Zend\Http\Client\Adapter\Exception\TimeoutException;
+
+/**
+ * DPLATerms Recommendations Module
+ *
+ * This class uses current search terms to query the DPLA API.
+ *
+ * @category VuFind2
+ * @package  Recommendations
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/vufind2:recommendation_modules Wiki
+ */
+class DPLATerms implements RecommendInterface
+{
+    /**
+     * Config
+     *
+     * @var string
+     */
+    protected $apiKey;
+
+    /**
+     * Vufind HTTP Client
+     *
+     * @var HttpClient
+     */
+    protected $client;
+
+    /**
+     * Setting of initial collapsedness
+     *
+     * @var bool
+     */
+    protected $collapsed;
+
+    /**
+     * Search results object
+     *
+     * @var \VuFind\Search\Base\Results
+     */
+    protected $searchObject;
+
+    /**
+     * Map of Solr field names to equivalent API parameters
+     *
+     * @var array
+     */
+    protected $formatMap = array(
+        'authorStr'           => 'sourceResource.creator',
+        'building'            => 'provider.name',
+        'format'              => 'sourceResource.format',
+        'geographic_facet'    => 'sourceResource.spatial.region',
+        'institution'         => 'provider.name',
+        'language'            => 'sourceResource.language.name',
+        'publishDate'         => 'sourceResource.date.begin',
+    );
+
+    /**
+     * List of fields to retrieve from the API
+     *
+     * @var array
+     */
+    protected $returnFields = array(
+        'id',
+        'dataProvider',
+        'sourceResource.title',
+        'sourceResource.description',
+    );
+
+    /**
+     * Constructor
+     *
+     * @param string     $apiKey API key
+     * @param HttpClient $client VuFind HTTP client
+     */
+    public function __construct($apiKey, HttpClient $client)
+    {
+        $this->apiKey = $apiKey;
+        $this->client = $client;
+    }
+
+    /**
+     * setConfig
+     *
+     * Store the configuration of the recommendation module.
+     *
+     * @param string $settings Settings from searches.ini.
+     *
+     * @return void
+     */
+    public function setConfig($settings)
+    {
+        $this->collapsed = filter_var($settings, FILTER_VALIDATE_BOOLEAN);
+    }
+
+    /**
+     * Abstract-required method
+     *
+     * @param \VuFind\Search\Base\Params $params  Search parameter object
+     * @param \Zend\StdLib\Parameters    $request Parameter object representing user
+     * request.
+     *
+     * @return void
+     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+     */
+    public function init($params, $request)
+    {
+        // No action needed.
+    }
+
+    /**
+     * process
+     *
+     * Called after the Search Results object has performed its main search.  This
+     * may be used to extract necessary information from the Search Results object
+     * or to perform completely unrelated processing.
+     *
+     * @param \VuFind\Search\Base\Results $results Search results object
+     *
+     * @return void
+     */
+    public function process($results)
+    {
+        $this->searchObject = $results;
+    }
+
+    /**
+     * Get terms related to the query.
+     *
+     * @return array
+     */
+    public function getResults()
+    {
+        $this->client->setUri('http://api.dp.la/v2/items');
+        $this->client->setMethod('GET');
+        $this->client->setParameterGet($this->getApiInput());
+        try {
+            $response = $this->client->send();
+        } catch (TimeoutException $e) {
+            error_log('DPLA API timeout -- skipping recommendations.');
+            return array();
+        }
+        if (!$response->isSuccess()) {
+            return array();
+        }
+        return $this->processResults($response->getBody());
+    }
+
+    /**
+     * Get input parameters for API call.
+     *
+     * @return array
+     */
+    protected function getApiInput()
+    {
+        // Extract the first search term from the search object:
+        $search = $this->searchObject->getParams()->getQuery();
+        $filters = $this->searchObject->getParams()->getFilters();
+        $lookfor = ($search instanceof \VuFindSearch\Query\Query)
+            ? $search->getString()
+            : '';
+
+        $params = array(
+            'q' => $lookfor,
+            'fields' => implode(',', $this->returnFields),
+            'api_key' => $this->apiKey
+        );
+        foreach ($filters as $field=>$filter) {
+            if (isset($this->formatMap[$field])) {
+                $params[$this->formatMap[$field]] = implode(',', $filter);
+            }
+        }
+        return $params;
+    }
+
+    /**
+     * Process the API response.
+     *
+     * @param string $response API response
+     *
+     * @return array
+     */
+    protected function processResults($response)
+    {
+        $body = json_decode($response);
+        $results = array();
+        if ($body->count > 0) {
+            $title = 'sourceResource.title';
+            $desc = 'sourceResource.description';
+            foreach ($body->docs as $i => $doc) {
+                $results[$i] = array(
+                    'title' => is_array($doc->$title)
+                        ? current($doc->$title)
+                        : $doc->$title,
+                    'provider' => is_array($doc->dataProvider)
+                        ? current($doc->dataProvider)
+                        : $doc->dataProvider,
+                    'link' => 'http://dp.la/item/'.$doc->id
+                );
+                if (isset($doc->$desc)) {
+                    $results[$i]['desc'] = is_array($doc->$desc)
+                        ? current($doc->$desc)
+                        : $doc->$desc;
+                }
+            }
+        }
+        return $results;
+    }
+
+    /**
+     * Return the list of facets configured to be collapsed
+     *
+     * @return array
+     */
+    public function isCollapsed()
+    {
+        return $this->collapsed;
+    }
+}
\ No newline at end of file
diff --git a/module/VuFind/src/VuFind/Recommend/Factory.php b/module/VuFind/src/VuFind/Recommend/Factory.php
index 36d38ba1c66..12f04061064 100644
--- a/module/VuFind/src/VuFind/Recommend/Factory.php
+++ b/module/VuFind/src/VuFind/Recommend/Factory.php
@@ -112,6 +112,25 @@ class Factory
         );
     }
 
+    /**
+     * Factory for DPLA Terms module.
+     *
+     * @param ServiceManager $sm Service manager.
+     *
+     * @return DPLATerms
+     */
+    public static function getDPLATerms(ServiceManager $sm)
+    {
+        $config = $sm->getServiceLocator()->get('VuFind\Config')->get('config');
+        if (!isset($config->DPLA->apiKey)) {
+            throw new \Exception('DPLA API key missing from configuration.');
+        }
+        return new DPLATerms(
+            $config->DPLA->apiKey,
+            $sm->getServiceLocator()->get('VuFind\Http')->createClient()
+        );
+    }
+
     /**
      * Factory for EuropeanaResults module.
      *
diff --git a/themes/blueprint/templates/Recommend/DPLATerms.phtml b/themes/blueprint/templates/Recommend/DPLATerms.phtml
new file mode 100644
index 00000000000..8df5a2cc8d5
--- /dev/null
+++ b/themes/blueprint/templates/Recommend/DPLATerms.phtml
@@ -0,0 +1,17 @@
+<? $results = $this->recommend->getResults(); ?>
+<? if(!empty($results)): ?>
+  <dl class="narrowList navmenu<? if(!$this->recommend->isCollapsed()): ?> open<? endif ?>">
+    <dt class="facet_dpla">DPLA</dt>
+    <? foreach($results as $item): ?>
+      <dd>
+        <a href="<?=$item['link'] ?>" target="new"><?=$this->escapeHtml($item['title']) ?></a>
+        <? if(!empty($item['desc'])): ?>
+          <span title="<?=$item['desc'] ?>"><?=$this->escapeHtml($this->truncate($item['desc'], 50)) ?></span></br>
+        <? endif; ?>
+        <span style="font-size:85%;font-style:italic">
+          (<?=$this->transEsc('Provider') ?>: <?=$this->escapeHtml($item['provider']) ?>)
+        </span>
+      </dd>
+    <? endforeach; ?>
+  </ul>
+<? endif; ?>
diff --git a/themes/bootstrap/templates/Recommend/DPLATerms.phtml b/themes/bootstrap/templates/Recommend/DPLATerms.phtml
new file mode 100644
index 00000000000..16382ddc9ba
--- /dev/null
+++ b/themes/bootstrap/templates/Recommend/DPLATerms.phtml
@@ -0,0 +1,17 @@
+<? $results = $this->recommend->getResults(); ?>
+<? if(!empty($results)): ?>
+  <ul class="nav nav-list collapsed<? if(!$this->recommend->isCollapsed()): ?> open<? endif ?>">
+    <li class="nav-header">DPLA</li>
+    <? foreach($results as $item): ?>
+      <li>
+        <a href="<?=$item['link'] ?>" target="new"><?=$this->escapeHtml($item['title']) ?></a>
+        <? if(!empty($item['desc'])): ?>
+          <span title="<?=$item['desc'] ?>"><?=$this->escapeHtml($this->truncate($item['desc'], 50)) ?></span></br>
+        <? endif; ?>
+        <span style="font-size:85%;font-style:italic">
+          (<?=$this->transEsc('Provider') ?>: <?=$this->escapeHtml($item['provider']) ?>)
+        </span>
+      </li>
+    <? endforeach; ?>
+  </ul>
+<? endif; ?>
diff --git a/themes/bootstrap3/templates/Recommend/DPLATerms.phtml b/themes/bootstrap3/templates/Recommend/DPLATerms.phtml
new file mode 100644
index 00000000000..2ad9cd87ce8
--- /dev/null
+++ b/themes/bootstrap3/templates/Recommend/DPLATerms.phtml
@@ -0,0 +1,18 @@
+<? $results = $this->recommend->getResults(); ?>
+<? if(!empty($results)): ?>
+  <ul class="list-group" id="side-panel-dpla">
+    <li class="list-group-item title<? if($this->recommend->isCollapsed()): ?> collapsed<? endif ?>" data-toggle="collapse" href="#side-collapse-dpla">
+      DPLA
+    </li>
+    <div id="side-collapse-dpla" class="collapse<? if(!$this->recommend->isCollapsed()): ?> in<? endif ?>">
+    <? foreach($results as $item): ?>
+      <li class="list-group-item">
+        <a href="<?=$item['link'] ?>" target="new"><?=$this->escapeHtml($item['title']) ?></a><br/>
+        <? if(!empty($item['desc'])): ?>
+          <span class="desc" title="<?=$item['desc'] ?>"><?=$this->escapeHtml($this->truncate($item['desc'], 50)) ?></span><br/>
+        <? endif; ?>
+        (<span class="from"><?=$this->transEsc('Provider') ?>: <?=$this->escapeHtml($item['provider']) ?></span>)
+      </li>
+    <? endforeach; ?>
+  </ul>
+<? endif; ?>
\ No newline at end of file
diff --git a/themes/jquerymobile/templates/Recommend/DPLATerms.phtml b/themes/jquerymobile/templates/Recommend/DPLATerms.phtml
new file mode 100644
index 00000000000..0df1e74df18
--- /dev/null
+++ b/themes/jquerymobile/templates/Recommend/DPLATerms.phtml
@@ -0,0 +1 @@
+<? /* Not supported in mobile theme. */ ?>
\ No newline at end of file
-- 
GitLab