From 17b4f8bd3fc4cfad88a3fe1ef7e10c76d6cdd52d Mon Sep 17 00:00:00 2001 From: Demian Katz <demian.katz@villanova.edu> Date: Wed, 18 Apr 2018 09:03:30 -0400 Subject: [PATCH] Add "recently returned" / "trending items" channels (#1075) - Includes underlying Voyager and Demo ILS driver support. --- config/vufind/channels.ini | 22 ++++ languages/en.ini | 2 + .../VuFind/ChannelProvider/PluginManager.php | 6 ++ .../ChannelProvider/RecentlyReturned.php | 72 +++++++++++++ .../ChannelProvider/TrendingILSItems.php | 88 +++++++++++++++ module/VuFind/src/VuFind/ILS/Driver/Demo.php | 35 ++++++ .../VuFind/src/VuFind/ILS/Driver/Voyager.php | 101 ++++++++++++++++++ 7 files changed, 326 insertions(+) create mode 100644 module/VuFind/src/VuFind/ChannelProvider/RecentlyReturned.php create mode 100644 module/VuFind/src/VuFind/ChannelProvider/TrendingILSItems.php diff --git a/config/vufind/channels.ini b/config/vufind/channels.ini index 221efba4404..3270e332bbf 100644 --- a/config/vufind/channels.ini +++ b/config/vufind/channels.ini @@ -31,14 +31,22 @@ cache_home_channels = true ; random - Pick random results from the result set (in search results) or from ; the search backend from which the record was retrieved (in record results). ; +; recentlyreturned - Display items that were recently returned by patrons; +; requires an ILS driver that supports this feature. +; ; similaritems - Find records similar to a specific record, or to top hits in a ; set of search results. +; +; trendingilsitems - Display items that have displayed a lot of recent activity in +; the ILS. (Exact definition of "trending" may vary from system to system). [source.Solr] ; Providers to use on the home page (these will be populated with a blank search ; by default; order matters!) home[] = "facets:provider.facets.home" ;home[] = "random" ;home[] = "newilsitems" +;home[] = "recentlyreturned" +;home[] = "trendingilsitems" ;home[] = "listitems" ; Providers to use for record-based channels (order matters!) record[] = "similaritems" @@ -145,9 +153,23 @@ channelSize = 20 ; entire backend. mode = "retain" +; This section contains default settings for the RecentlyReturned channel provider +[provider.recentlyreturned] +; Number of results to include in the channel. +channelSize = 20 +; Maximum age of results to return (in days) +maxAge = 30 + ; This section contains default settings for the SimilarItems channel provider [provider.similaritems] ; Number of results to include in each channel. channelSize = 20 ; Maximum number of records to examine for similar results. maxRecordsToExamine = 2 + +; This section contains default settings for the TrendingILSItems channel provider +[provider.trendingilsitems] +; Number of results to include in the channel. +channelSize = 20 +; Maximum age of results to return (in days) +maxAge = 90 diff --git a/languages/en.ini b/languages/en.ini index 88bf2f16919..bd9f5bb6450 100644 --- a/languages/en.ini +++ b/languages/en.ini @@ -799,6 +799,7 @@ Range slider = "Range slider" Read the full review online... = "Read the full review online..." Recall This = "Recall This" recaptcha_not_passed = "CAPTCHA not passed" +recently_returned_channel_title = "Recently Returned" Record Citations = "Record Citations" Record Count = "Record Count" Record Type = "Record Type" @@ -1047,6 +1048,7 @@ total_tags = "Total Tags" total_users = "Total Users" Transliterated Title = "Transliterated Title" tree_search_limit_reached_html = "Your search returned too many results to display in the tree. Showing only the first <b>%%limit%%</b> items. For a full search click <a id="fullSearchLink" href="%%url%%" target="_blank">here.</a>" +trending_items_channel_title = "Trending Items" unique_tags = "Unique Tags" University Library = "University Library" Unknown = "Unknown" diff --git a/module/VuFind/src/VuFind/ChannelProvider/PluginManager.php b/module/VuFind/src/VuFind/ChannelProvider/PluginManager.php index e9c5e523fcd..603b7c1254d 100644 --- a/module/VuFind/src/VuFind/ChannelProvider/PluginManager.php +++ b/module/VuFind/src/VuFind/ChannelProvider/PluginManager.php @@ -49,7 +49,9 @@ class PluginManager extends \VuFind\ServiceManager\AbstractPluginManager 'listitems' => 'VuFind\ChannelProvider\ListItems', 'newilsitems' => 'VuFind\ChannelProvider\NewILSItems', 'random' => 'VuFind\ChannelProvider\Random', + 'recentlyreturned' => 'VuFind\ChannelProvider\RecentlyReturned', 'similaritems' => 'VuFind\ChannelProvider\SimilarItems', + 'trendingilsitems' => 'VuFind\ChannelProvider\TrendingILSItems', ]; /** @@ -68,8 +70,12 @@ class PluginManager extends \VuFind\ServiceManager\AbstractPluginManager 'VuFind\ChannelProvider\AbstractILSChannelProviderFactory', 'VuFind\ChannelProvider\Random' => 'VuFind\ChannelProvider\Factory::getRandom', + 'VuFind\ChannelProvider\RecentlyReturned' => + 'VuFind\ChannelProvider\AbstractILSChannelProviderFactory', 'VuFind\ChannelProvider\SimilarItems' => 'VuFind\ChannelProvider\Factory::getSimilarItems', + 'VuFind\ChannelProvider\TrendingILSItems' => + 'VuFind\ChannelProvider\AbstractILSChannelProviderFactory', ]; /** diff --git a/module/VuFind/src/VuFind/ChannelProvider/RecentlyReturned.php b/module/VuFind/src/VuFind/ChannelProvider/RecentlyReturned.php new file mode 100644 index 00000000000..82d7eebb557 --- /dev/null +++ b/module/VuFind/src/VuFind/ChannelProvider/RecentlyReturned.php @@ -0,0 +1,72 @@ +<?php +/** + * "Recently returned" channel provider. + * + * PHP version 5 + * + * Copyright (C) Villanova University 2017. + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * @category VuFind + * @package Channels + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development Wiki + */ +namespace VuFind\ChannelProvider; + +/** + * "Recently returned" channel provider. + * + * @category VuFind + * @package Channels + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development Wiki + */ +class RecentlyReturned extends AbstractILSChannelProvider +{ + /** + * Channel title (will be run through translator). + * + * @var string + */ + protected $channelTitle = 'recently_returned_channel_title'; + + /** + * Retrieve data from the ILS. + * + * @return array + */ + protected function getIlsResponse() + { + return $this->ils->checkCapability('getRecentlyReturnedBibs') + ? $this->ils->getRecentlyReturnedBibs($this->channelSize, $this->maxAge) + : []; + } + + /** + * Given one element from the ILS function's response array, extract the + * ID value. + * + * @param array $response Response array + * + * @return string + */ + protected function extractIdsFromResponse($response) + { + return $response['id']; + } +} diff --git a/module/VuFind/src/VuFind/ChannelProvider/TrendingILSItems.php b/module/VuFind/src/VuFind/ChannelProvider/TrendingILSItems.php new file mode 100644 index 00000000000..ee1f4795ddb --- /dev/null +++ b/module/VuFind/src/VuFind/ChannelProvider/TrendingILSItems.php @@ -0,0 +1,88 @@ +<?php +/** + * "Trending ILS items" channel provider. + * + * PHP version 5 + * + * Copyright (C) Villanova University 2018. + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * @category VuFind + * @package Channels + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development Wiki + */ +namespace VuFind\ChannelProvider; + +/** + * "Trending ILS items" channel provider. + * + * @category VuFind + * @package Channels + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development Wiki + */ +class TrendingILSItems extends AbstractILSChannelProvider +{ + /** + * Channel title (will be run through translator). + * + * @var string + */ + protected $channelTitle = 'trending_items_channel_title'; + + /** + * Set the options for the provider. + * + * @param array $options Options + * + * @return void + */ + public function setOptions(array $options) + { + // Use higher default age for trending. + if (!isset($options['maxAge'])) { + $options['maxAge'] = 90; + } + return parent::setOptions($options); + } + + /** + * Retrieve data from the ILS. + * + * @return array + */ + protected function getIlsResponse() + { + return $this->ils->checkCapability('getTrendingBibs') + ? $this->ils->getTrendingBibs($this->channelSize, $this->maxAge) + : []; + } + + /** + * Given one element from the ILS function's response array, extract the + * ID value. + * + * @param array $response Response array + * + * @return string + */ + protected function extractIdsFromResponse($response) + { + return $response['id']; + } +} diff --git a/module/VuFind/src/VuFind/ILS/Driver/Demo.php b/module/VuFind/src/VuFind/ILS/Driver/Demo.php index 3ef066c1be9..ded059e02b7 100644 --- a/module/VuFind/src/VuFind/ILS/Driver/Demo.php +++ b/module/VuFind/src/VuFind/ILS/Driver/Demo.php @@ -2194,4 +2194,39 @@ class Demo extends AbstractBase } return []; } + + /** + * Get bib records for recently returned items. + * + * @param int $limit Maximum number of records to retrieve (default = 30) + * @param int $maxage The maximum number of days to consider "recently + * returned." + * @param array $patron Patron Data + * + * @return array + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function getRecentlyReturnedBibs($limit = 30, $maxage = 30, + $patron = null + ) { + // This is similar to getNewItems for demo purposes. + $results = $this->getNewItems(1, $limit, $maxage); + return $results['results']; + } + + /** + * Get bib records for "trending" items (recently returned with high usage). + * + * @param int $limit Maximum number of records to retrieve (default = 30) + * @param int $maxage The maximum number of days' worth of data to examine. + * @param array $patron Patron Data + * + * @return array + */ + public function getTrendingBibs($limit = 30, $maxage = 30, $patron = null) + { + // This is similar to getRecentlyReturnedBibs for demo purposes. + return $this->getRecentlyReturnedBibs($limit, $maxage, $patron); + } } diff --git a/module/VuFind/src/VuFind/ILS/Driver/Voyager.php b/module/VuFind/src/VuFind/ILS/Driver/Voyager.php index 68d8a62a434..34a48256a1e 100644 --- a/module/VuFind/src/VuFind/ILS/Driver/Voyager.php +++ b/module/VuFind/src/VuFind/ILS/Driver/Voyager.php @@ -2454,6 +2454,107 @@ EOT; return $recordList; } + /** + * Get bib records for recently returned items. + * + * @param int $limit Maximum number of records to retrieve (default = 30) + * @param int $maxage The maximum number of days to consider "recently + * returned." + * @param array $patron Patron Data + * + * @return array + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function getRecentlyReturnedBibs($limit = 30, $maxage = 30, + $patron = null + ) { + $recordList = []; + + // Oracle does not support the SQL LIMIT clause before version 12, so + // instead we need to provide an optimizer hint, which requires us to + // ensure that $limit is a valid integer. + $intLimit = intval($limit); + $safeLimit = $intLimit < 1 ? 30 : $intLimit; + + $sql = "select /*+ FIRST_ROWS($safeLimit) */ BIB_MFHD.BIB_ID, " + . "max(CIRC_TRANS_ARCHIVE.DISCHARGE_DATE) as RETURNED " + . "from $this->dbName.CIRC_TRANS_ARCHIVE " + . "join $this->dbName.MFHD_ITEM " + . "on CIRC_TRANS_ARCHIVE.ITEM_ID = MFHD_ITEM.ITEM_ID " + . "join $this->dbName.BIB_MFHD " + . "on BIB_MFHD.MFHD_ID = MFHD_ITEM.MFHD_ID " + . "join $this->dbName.BIB_MASTER " + . "on BIB_MASTER.BIB_ID = BIB_MFHD.BIB_ID " + . "where CIRC_TRANS_ARCHIVE.DISCHARGE_DATE is not null " + . "and CIRC_TRANS_ARCHIVE.DISCHARGE_DATE > SYSDATE - :maxage " + . "and BIB_MASTER.SUPPRESS_IN_OPAC='N' " + . "group by BIB_MFHD.BIB_ID " + . "order by RETURNED desc"; + try { + $sqlStmt = $this->executeSQL($sql, [':maxage' => $maxage]); + while (count($recordList) < $limit + && $row = $sqlStmt->fetch(PDO::FETCH_ASSOC) + ) { + $recordList[] = ['id' => $row['BIB_ID']]; + } + } catch (PDOException $e) { + throw new ILSException($e->getMessage()); + } + return $recordList; + } + + /** + * Get bib records for "trending" items (recently returned with high usage). + * + * @param int $limit Maximum number of records to retrieve (default = 30) + * @param int $maxage The maximum number of days' worth of data to examine. + * @param array $patron Patron Data + * + * @return array + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function getTrendingBibs($limit = 30, $maxage = 30, $patron = null) + { + $recordList = []; + + // Oracle does not support the SQL LIMIT clause before version 12, so + // instead we need to provide an optimizer hint, which requires us to + // ensure that $limit is a valid integer. + $intLimit = intval($limit); + $safeLimit = $intLimit < 1 ? 30 : $intLimit; + + $sql = "select /*+ FIRST_ROWS($safeLimit) */ BIB_MFHD.BIB_ID, " + . "count(CIRC_TRANS_ARCHIVE.DISCHARGE_DATE) as RECENT, " + . "sum(ITEM.HISTORICAL_CHARGES) as OVERALL " + . "from $this->dbName.CIRC_TRANS_ARCHIVE " + . "join $this->dbName.MFHD_ITEM " + . "on CIRC_TRANS_ARCHIVE.ITEM_ID = MFHD_ITEM.ITEM_ID " + . "join $this->dbName.BIB_MFHD " + . "on BIB_MFHD.MFHD_ID = MFHD_ITEM.MFHD_ID " + . "join $this->dbName.ITEM " + . "on CIRC_TRANS_ARCHIVE.ITEM_ID = ITEM.ITEM_ID " + . "join $this->dbName.BIB_MASTER " + . "on BIB_MASTER.BIB_ID = BIB_MFHD.BIB_ID " + . "where CIRC_TRANS_ARCHIVE.DISCHARGE_DATE is not null " + . "and CIRC_TRANS_ARCHIVE.DISCHARGE_DATE > SYSDATE - :maxage " + . "and BIB_MASTER.SUPPRESS_IN_OPAC='N' " + . "group by BIB_MFHD.BIB_ID " + . "order by RECENT desc, OVERALL desc"; + try { + $sqlStmt = $this->executeSQL($sql, [':maxage' => $maxage]); + while (count($recordList) < $limit + && $row = $sqlStmt->fetch(PDO::FETCH_ASSOC) + ) { + $recordList[] = ['id' => $row['BIB_ID']]; + } + } catch (PDOException $e) { + throw new ILSException($e->getMessage()); + } + return $recordList; + } + /** * Get suppressed records. * -- GitLab