diff --git a/config/vufind/config.ini b/config/vufind/config.ini index 4f8076d50eda381750e8c5116c421fc118e28ade..c1218c8c43895b3438ad4132e51de14056eaf18a 100644 --- a/config/vufind/config.ini +++ b/config/vufind/config.ini @@ -91,6 +91,11 @@ generator = "VuFind 2.2" [Session] type = File lifetime = 3600 ; Session lasts for 1 hour +; Keep-alive interval in seconds. When set to a positive value, the session is kept +; alive with a JavaScript call as long as a VuFind page is open in the browser. +; Default is 0 (disabled). When keep-alive is enabled, session lifetime above can be +; reduced to e.g. 600. +;keepAlive = 60 ;file_save_path = /tmp/vufind_sessions ;memcache_host = localhost ;memcache_port = 11211 diff --git a/config/vufind/searches.ini b/config/vufind/searches.ini index c590ea3b78029fe703e42a481d130507a37f727a..f6fbd8eff1999c09437556dcea46e22b63e2ae18 100644 --- a/config/vufind/searches.ini +++ b/config/vufind/searches.ini @@ -326,14 +326,22 @@ side[] = "ExpandFacets:Author" ; This section controls the "New Items" search. [NewItem] +; New item information can be retrieved from Solr or from the ILS; this setting +; controls which mechanism is used. If using Solr, change tracking must be enabled +; (see https://vufind.org/wiki/tracking_record_changes). If using the ILS, your +; driver must support the getNewItems() method. +; Valid options: ils, solr; default: ils +method = ils ; Comma-separated list of date ranges to offer to the user (i.e. 1,5,30 = one day -; old, or five days old, or thirty days old). Be careful about raising the maximum -; age too high -- searching very long date ranges may put a load on your ILS. +; old, or five days old, or thirty days old). If using the "ils" method, be careful +; about raising the maximum age too high -- searching very long date ranges may put +; a load on your ILS. ranges = 1,5,30 -; This setting controls the maximum 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 +; 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. +; 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 ; set the value too low, you may get the same results no matter which range setting ; is selected! result_pages = 10 diff --git a/module/VuFind/config/module.config.php b/module/VuFind/config/module.config.php index a9d55c809247b3f1693d47c94985441d7dc5297b..083800ed48bf745f64319b1008544940502124c9 100644 --- a/module/VuFind/config/module.config.php +++ b/module/VuFind/config/module.config.php @@ -101,8 +101,9 @@ $config = array( 'controller_plugins' => array( 'factories' => array( 'holds' => array('VuFind\Controller\Plugin\Factory', 'getHolds'), - 'storageRetrievalRequests' => array('VuFind\Controller\Plugin\Factory', 'getStorageRetrievalRequests'), + 'newitems' => array('VuFind\Controller\Plugin\Factory', 'getNewItems'), 'reserves' => array('VuFind\Controller\Plugin\Factory', 'getReserves'), + 'storageRetrievalRequests' => array('VuFind\Controller\Plugin\Factory', 'getStorageRetrievalRequests'), ), 'invokables' => array( 'db-upgrade' => 'VuFind\Controller\Plugin\DbUpgrade', diff --git a/module/VuFind/src/VuFind/Controller/AjaxController.php b/module/VuFind/src/VuFind/Controller/AjaxController.php index 015dc5c2259da7dfcaa41307f4c69fef8908670b..78e995a7c35664d0f010d6067493cc4916581bd2 100644 --- a/module/VuFind/src/VuFind/Controller/AjaxController.php +++ b/module/VuFind/src/VuFind/Controller/AjaxController.php @@ -1348,6 +1348,19 @@ class AjaxController extends AbstractBase return $this->output($html, self::STATUS_OK); } + /** + * Keep Alive + * + * This is responsible for keeping the session alive whenever called + * (via JavaScript) + * + * @return \Zend\Http\Response + */ + protected function keepAliveAjax() + { + return $this->output(true, self::STATUS_OK); + } + /** * Convenience method for accessing results * diff --git a/module/VuFind/src/VuFind/Controller/MyResearchController.php b/module/VuFind/src/VuFind/Controller/MyResearchController.php index d747fecffa83492c32ab99e012bac944065f7509..a038a418e0e546f47ffb3309903002722bbdf9e1 100644 --- a/module/VuFind/src/VuFind/Controller/MyResearchController.php +++ b/module/VuFind/src/VuFind/Controller/MyResearchController.php @@ -504,7 +504,7 @@ class MyResearchController extends AbstractBase $source = $this->params()->fromPost( 'source', $this->params()->fromQuery('source', 'VuFind') ); - $driver = $this->getRecordLoader()->load($id, $source); + $driver = $this->getRecordLoader()->load($id, $source, true); $listID = $this->params()->fromPost( 'list_id', $this->params()->fromQuery('list_id', null) ); @@ -789,21 +789,9 @@ class MyResearchController extends AbstractBase */ protected function getDriverForILSRecord($current) { - try { - if (!isset($current['id'])) { - throw new RecordMissingException(); - } - $record = $this->getServiceLocator()->get('VuFind\RecordLoader') - ->load($current['id']); - } catch (RecordMissingException $e) { - $factory = $this->getServiceLocator() - ->get('VuFind\RecordDriverPluginManager'); - $record = $factory->get('Missing'); - $record->setRawData( - array('id' => isset($current['id']) ? $current['id'] : null) - ); - $record->setSourceIdentifier('Solr'); - } + $id = isset($current['id']) ? $current['id'] : null; + $record = $this->getServiceLocator()->get('VuFind\RecordLoader') + ->load($id, 'VuFind', true); $record->setExtraDetail('ils_details', $current); return $record; } diff --git a/module/VuFind/src/VuFind/Controller/Plugin/Factory.php b/module/VuFind/src/VuFind/Controller/Plugin/Factory.php index aec0729b62662cace51a46bf3e8c2939a28788fb..8f93d7c4dcc242dca4edb2e9b4727135960bdf3c 100644 --- a/module/VuFind/src/VuFind/Controller/Plugin/Factory.php +++ b/module/VuFind/src/VuFind/Controller/Plugin/Factory.php @@ -52,17 +52,18 @@ class Factory } /** - * Construct the StorageRetrievalRequests plugin. + * Construct the NewItems plugin. * * @param ServiceManager $sm Service manager. * - * @return StorageRetrievalRequests + * @return Reserves */ - public static function getStorageRetrievalRequests(ServiceManager $sm) + public static function getNewItems(ServiceManager $sm) { - return new StorageRetrievalRequests( - $sm->getServiceLocator()->get('VuFind\HMAC') - ); + $search = $sm->getServiceLocator()->get('VuFind\Config')->get('searches'); + $config = isset($search->NewItem) + ? $search->NewItem : new \Zend\Config\Config(array()); + return new NewItems($config); } /** @@ -79,4 +80,18 @@ class Factory && $config->Reserves->search_enabled; return new Reserves($useIndex); } + + /** + * Construct the StorageRetrievalRequests plugin. + * + * @param ServiceManager $sm Service manager. + * + * @return StorageRetrievalRequests + */ + public static function getStorageRetrievalRequests(ServiceManager $sm) + { + return new StorageRetrievalRequests( + $sm->getServiceLocator()->get('VuFind\HMAC') + ); + } } \ No newline at end of file diff --git a/module/VuFind/src/VuFind/Controller/Plugin/NewItems.php b/module/VuFind/src/VuFind/Controller/Plugin/NewItems.php new file mode 100644 index 0000000000000000000000000000000000000000..b896624978ff051525c341e6b6a9bd7b1c9c515f --- /dev/null +++ b/module/VuFind/src/VuFind/Controller/Plugin/NewItems.php @@ -0,0 +1,204 @@ +<?php +/** + * VuFind Action Helper - New Items Support Methods + * + * 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 Controller_Plugins + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://www.vufind.org Main Page + */ +namespace VuFind\Controller\Plugin; +use Zend\Mvc\Controller\Plugin\AbstractPlugin, Zend\Config\Config; + +/** + * Zend action helper to perform new items-related actions + * + * @category VuFind2 + * @package Controller_Plugins + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://www.vufind.org Main Page + */ +class NewItems extends AbstractPlugin +{ + /** + * Configuration + * + * @var Config + */ + protected $config; + + /** + * Constructor + * + * @param Config $config Configuration + */ + public function __construct(Config $config) + { + $this->config = $config; + } + + /** + * Figure out which bib IDs to load from the ILS. + * + * @param \VuFind\ILS\Connection $catalog ILS connection + * @param \VuFind\Search\Solr\Params $params Solr parameters + * @param string $range Range setting + * @param string $dept Department setting + * @param \Zend\Mvc\Controller\Plugin\FlashMessenger $flash Flash messenger + * + * @return array + */ + public function getBibIDsFromCatalog($catalog, $params, $range, $dept, $flash) + { + // The code always pulls in enough catalog results to get a fixed number + // of pages worth of Solr results. Note that if the Solr index is out of + // sync with the ILS, we may see fewer results than expected. + $resultPages = $this->getResultPages(); + $perPage = $params->getLimit(); + $newItems = $catalog->getNewItems(1, $perPage * $resultPages, $range, $dept); + + // Build a list of unique IDs + $bibIDs = array(); + for ($i=0; $i<count($newItems['results']); $i++) { + $bibIDs[] = $newItems['results'][$i]['id']; + } + + // Truncate the list if it is too long: + $limit = $params->getQueryIDLimit(); + if (count($bibIDs) > $limit) { + $bibIDs = array_slice($bibIDs, 0, $limit); + $flash->setNamespace('info')->addMessage('too_many_new_items'); + } + + return $bibIDs; + } + + /** + * Get fund list + * + * @return array + */ + public function getFundList() + { + if ($this->getMethod() == 'ils') { + $catalog = $this->getController()->getILS(); + return $catalog->checkCapability('getFunds') + ? $catalog->getFunds() : array(); + } + return array(); + } + + /** + * Get the hidden filter settings. + * + * @return array + */ + public function getHiddenFilters() + { + if (!isset($this->config->filter)) { + return array(); + } + if (is_string($this->config->filter)) { + return array($this->config->filter); + } + $hiddenFilters = array(); + foreach ($this->config->filter as $current) { + $hiddenFilters[] = $current; + } + return $hiddenFilters; + } + + /** + * Get the maximum range setting (or return 0 for no limit). + * + * @return int + */ + public function getMaxAge() + { + return max($this->getRanges()); + } + + /** + * Get method setting + * + * @return string + */ + public function getMethod() + { + return isset($this->config->method) ? $this->config->method : 'ils'; + } + + /** + * Get range settings + * + * @return array + */ + public function getRanges() + { + // Find out if there are user configured range options; if not, + // default to the standard 1/5/30 days: + $ranges = array(); + if (isset($this->config->ranges)) { + $tmp = explode(',', $this->config->ranges); + foreach ($tmp as $range) { + $range = intval($range); + if ($range > 0) { + $ranges[] = $range; + } + } + } + if (empty($ranges)) { + $ranges = array(1, 5, 30); + } + return $ranges; + } + + /** + * Get the result pages setting. + * + * @return int + */ + public function getResultPages() + { + if (isset($this->config->result_pages)) { + $resultPages = intval($this->config->result_pages); + if ($resultPages < 1) { + $resultPages = 10; + } + } else { + $resultPages = 10; + } + return $resultPages; + } + + /** + * Get a Solr filter to limit to the specified number of days. + * + * @param int $range Days to search + * + * @return string + */ + public function getSolrFilter($range) + { + return 'first_indexed:[NOW-' . $range .'DAY TO NOW]'; + } +} \ No newline at end of file diff --git a/module/VuFind/src/VuFind/Controller/SearchController.php b/module/VuFind/src/VuFind/Controller/SearchController.php index e11b50997555041ed87a10d19b135d567f95e1f8..35849a6393762077fed4f6c24bf94f2acc2b0f52 100644 --- a/module/VuFind/src/VuFind/Controller/SearchController.php +++ b/module/VuFind/src/VuFind/Controller/SearchController.php @@ -302,28 +302,11 @@ class SearchController extends AbstractSearch return $this->forwardTo('Search', 'NewItemResults'); } - // Find out if there are user configured range options; if not, - // default to the standard 1/5/30 days: - $ranges = array(); - $searchSettings = $this->getConfig('searches'); - if (isset($searchSettings->NewItem->ranges)) { - $tmp = explode(',', $searchSettings->NewItem->ranges); - foreach ($tmp as $range) { - $range = intval($range); - if ($range > 0) { - $ranges[] = $range; - } - } - } - if (empty($ranges)) { - $ranges = array(1, 5, 30); - } - - $catalog = $this->getILS(); - $fundList = $catalog->checkCapability('getFunds') - ? $catalog->getFunds() : array(); return $this->createViewModel( - array('fundList' => $fundList, 'ranges' => $ranges) + array( + 'fundList' => $this->newItems()->getFundList(), + 'ranges' => $this->newItems()->getRanges() + ) ); } @@ -340,72 +323,40 @@ class SearchController extends AbstractSearch // Validate the range parameter -- it should not exceed the greatest // configured value: - $searchSettings = $this->getConfig('searches'); - $maxAge = 0; - if (isset($searchSettings->NewItem->ranges)) { - $tmp = explode(',', $searchSettings->NewItem->ranges); - foreach ($tmp as $current) { - if (intval($current) > $maxAge) { - $maxAge = intval($current); - } - } - } + $maxAge = $this->newItems()->getMaxAge(); if ($maxAge > 0 && $range > $maxAge) { $range = $maxAge; } - // The code always pulls in enough catalog results to get a fixed number - // of pages worth of Solr results. Note that if the Solr index is out of - // sync with the ILS, we may see fewer results than expected. - if (isset($searchSettings->NewItem->result_pages)) { - $resultPages = intval($searchSettings->NewItem->result_pages); - if ($resultPages < 1) { - $resultPages = 10; - } + // Are there "new item" filter queries specified in the config file? + // If so, load them now; we may add more values. These will be applied + // later after the whole list is collected. + $hiddenFilters = $this->newItems()->getHiddenFilters(); + + // Depending on whether we're in ILS or Solr mode, we need to do some + // different processing here to retrieve the correct items: + if ($this->newItems()->getMethod() == 'ils') { + // Use standard search action with override parameter to show results: + $bibIDs = $this->newItems()->getBibIDsFromCatalog( + $this->getILS(), + $this->getResultsManager()->get('Solr')->getParams(), + $range, $dept, $this->flashMessenger() + ); + $this->getRequest()->getQuery()->set('overrideIds', $bibIDs); } else { - $resultPages = 10; - } - $catalog = $this->getILS(); - $params = $this->getResultsManager()->get('Solr')->getParams(); - $perPage = $params->getLimit(); - $newItems = $catalog->getNewItems(1, $perPage * $resultPages, $range, $dept); - - // Build a list of unique IDs - $bibIDs = array(); - for ($i=0; $i<count($newItems['results']); $i++) { - $bibIDs[] = $newItems['results'][$i]['id']; - } - - // Truncate the list if it is too long: - $limit = $params->getQueryIDLimit(); - if (count($bibIDs) > $limit) { - $bibIDs = array_slice($bibIDs, 0, $limit); - $this->flashMessenger()->setNamespace('info') - ->addMessage('too_many_new_items'); + // Use a Solr filter to show results: + $hiddenFilters[] = $this->newItems()->getSolrFilter($range); } - // Use standard search action with override parameter to show results: - $this->getRequest()->getQuery()->set('overrideIds', $bibIDs); - - // Are there "new item" filter queries specified in the config file? - // If so, we should apply them as hidden filters so they do not show - // up in the user-selected facet list. - if (isset($searchSettings->NewItem->filter)) { - if (is_string($searchSettings->NewItem->filter)) { - $hiddenFilters = array($searchSettings->NewItem->filter); - } else { - $hiddenFilters = array(); - foreach ($searchSettings->NewItem->filter as $current) { - $hiddenFilters[] = $current; - } - } + // If we found hidden filters above, apply them now: + if (!empty($hiddenFilters)) { $this->getRequest()->getQuery()->set('hiddenFilters', $hiddenFilters); } // Call rather than forward, so we can use custom template $view = $this->resultsAction(); - // Customize the URL helper to make sure it builds proper reserves URLs + // Customize the URL helper to make sure it builds proper new item URLs // (check it's set first -- RSS feed will return a response model rather // than a view model): if (isset($view->results)) { @@ -432,7 +383,7 @@ class SearchController extends AbstractSearch ) { return $this->forwardTo('Search', 'ReservesResults'); } - + // No params? Show appropriate form (varies depending on whether we're // using driver-based or Solr-based reserves searching). if ($this->reserves()->useIndex()) { diff --git a/module/VuFind/src/VuFind/Record/Loader.php b/module/VuFind/src/VuFind/Record/Loader.php index 9f03a0319d08cdd92ce0c2fb269efff2ecfade17..446003705681d3c4583bd85cc15335fb71c3ee9a 100644 --- a/module/VuFind/src/VuFind/Record/Loader.php +++ b/module/VuFind/src/VuFind/Record/Loader.php @@ -71,21 +71,29 @@ class Loader /** * Given an ID and record source, load the requested record object. * - * @param string $id Record ID - * @param string $source Record source + * @param string $id Record ID + * @param string $source Record source + * @param bool $tolerateMissing Should we load a "Missing" placeholder + * instead of throwing an exception if the record cannot be found? * * @throws \Exception * @return \VuFind\RecordDriver\AbstractBase */ - public function load($id, $source = 'VuFind') + public function load($id, $source = 'VuFind', $tolerateMissing = false) { $results = $this->searchService->retrieve($source, $id)->getRecords(); - if (count($results) < 1) { - throw new RecordMissingException( - 'Record ' . $source . ':' . $id . ' does not exist.' - ); + if (count($results) > 0) { + return $results[0]; + } + if ($tolerateMissing) { + $record = $this->recordFactory->get('Missing'); + $record->setRawData(array('id' => $id)); + $record->setSourceIdentifier($source); + return $record; } - return $results[0]; + throw new RecordMissingException( + 'Record ' . $source . ':' . $id . ' does not exist.' + ); } /** diff --git a/module/VuFind/src/VuFind/View/Helper/Root/Factory.php b/module/VuFind/src/VuFind/View/Helper/Root/Factory.php index 5177cc9cbf0177b46cfafe75985a51bddc1d5620..676edbc1d06e4df25c80ff2a4de0919b5c8bb44f 100644 --- a/module/VuFind/src/VuFind/View/Helper/Root/Factory.php +++ b/module/VuFind/src/VuFind/View/Helper/Root/Factory.php @@ -253,6 +253,21 @@ class Factory return new JsTranslations($sm->get('transesc')); } + /** + * Construct the KeepAlive helper. + * + * @param ServiceManager $sm Service manager. + * + * @return KeepAlive + */ + public static function getKeepAlive(ServiceManager $sm) + { + $config = $sm->getServiceLocator()->get('VuFind\Config')->get('config'); + return new KeepAlive( + isset($config->Session->keepAlive) ? $config->Session->keepAlive : 0 + ); + } + /** * Construct the ProxyUrl helper. * diff --git a/module/VuFind/src/VuFind/View/Helper/Root/KeepAlive.php b/module/VuFind/src/VuFind/View/Helper/Root/KeepAlive.php new file mode 100644 index 0000000000000000000000000000000000000000..8e7902b9ad92b6444cc9bdcea664c3f53ac3c4a7 --- /dev/null +++ b/module/VuFind/src/VuFind/View/Helper/Root/KeepAlive.php @@ -0,0 +1,70 @@ +<?php +/** + * KeepAlive view helper + * + * PHP version 5 + * + * Copyright (C) Villanova University 2010. + * Copyright (C) The National Library of Finland 2014. + * + * 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 View_Helpers + * @author Demian Katz <demian.katz@villanova.edu> + * @author Ere Maijala <ere.maijala@helsinki.fi> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://vufind.org Main Site + */ +namespace VuFind\View\Helper\Root; + +/** + * KeepAlive view helper + * + * @category VuFind2 + * @package View_Helpers + * @author Demian Katz <demian.katz@villanova.edu> + * @author Ere Maijala <ere.maijala@helsinki.fi> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://vufind.org Main Site + */ +class KeepAlive extends \Zend\View\Helper\AbstractHelper +{ + /** + * Keep-alive interval in seconds or 0 if disabled + * + * @var int + */ + protected $interval; + + /** + * Constructor + * + * @param int $interval Keep-alive interval in seconds or 0 if disabled + */ + public function __construct($interval) + { + $this->interval = $interval; + } + + /** + * Returns the keep-alive interval. + * + * @return int + */ + public function __invoke() + { + return $this->interval; + } +} \ No newline at end of file diff --git a/module/VuFind/tests/unit-tests/src/VuFindTest/Controller/Plugin/NewItemsTest.php b/module/VuFind/tests/unit-tests/src/VuFindTest/Controller/Plugin/NewItemsTest.php new file mode 100644 index 0000000000000000000000000000000000000000..fa528d9bf9385daf98480f29186d537cf8aaaa62 --- /dev/null +++ b/module/VuFind/tests/unit-tests/src/VuFindTest/Controller/Plugin/NewItemsTest.php @@ -0,0 +1,223 @@ +<?php + +/** + * New items controller plugin tests. + * + * 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 Tests + * @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:unit_tests Wiki + */ + +namespace VuFindTest\Controller\Plugin; + +use VuFind\Controller\Plugin\NewItems; +use VuFindTest\Unit\TestCase as TestCase; +use Zend\Config\Config; + +/** + * New items controller plugin tests. + * + * @category VuFind2 + * @package Tests + * @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:unit_tests Wiki + */ +class NewItemsTest extends TestCase +{ + /** + * Test ILS bib ID retrieval. + * + * @return void + */ + public function testGetBibIDsFromCatalog() + { + $flash = $this->getMock('Zend\Mvc\Controller\Plugin\FlashMessenger'); + $config = new Config(array('result_pages' => 10)); + $newItems = new NewItems($config); + $bibs = $newItems->getBibIDsFromCatalog( + $this->getMockCatalog(), $this->getMockParams(), 10, 'a', $flash + ); + $this->assertEquals(array(1, 2), $bibs); + } + + /** + * Test ILS bib ID retrieval with ID limit. + * + * @return void + */ + public function testGetBibIDsFromCatalogWithIDLimit() + { + $flash = $this->getMock('Zend\Mvc\Controller\Plugin\FlashMessenger'); + $flash->expects($this->once())->method('setNamespace') + ->with($this->equalTo('info'))->will($this->returnValue($flash)); + $flash->expects($this->once())->method('addMessage') + ->with($this->equalTo('too_many_new_items')); + $config = new Config(array('result_pages' => 10)); + $newItems = new NewItems($config); + $bibs = $newItems->getBibIDsFromCatalog( + $this->getMockCatalog(), $this->getMockParams(1), 10, 'a', $flash + ); + $this->assertEquals(array(1), $bibs); + } + + /** + * Test default ILS getFunds() behavior. + * + * @return void + */ + public function testGetFundList() + { + $catalog = $this->getMock( + 'VuFind\ILS\Connection', array('checkCapability', 'getFunds'), + array(), '', false + ); + $catalog->expects($this->once())->method('checkCapability') + ->with($this->equalTo('getFunds'))->will($this->returnValue(true)); + $catalog->expects($this->once())->method('getFunds') + ->will($this->returnValue(array('a', 'b', 'c'))); + $controller = $this->getMock('VuFind\Controller\SearchController'); + $controller->expects($this->once())->method('getILS') + ->will($this->returnValue($catalog)); + $newItems = new NewItems(new Config(array())); + $newItems->setController($controller); + $this->assertEquals(array('a', 'b', 'c'), $newItems->getFundList()); + } + + /** + * Test a single hidden filter. + * + * @return void + */ + public function testGetSingleHiddenFilter() + { + $config = new Config(array('filter' => 'a:b')); + $newItems = new NewItems($config); + $this->assertEquals(array('a:b'), $newItems->getHiddenFilters()); + } + + /** + * Test a single hidden filter. + * + * @return void + */ + public function testGetMultipleHiddenFilters() + { + $config = new Config(array('filter' => array('a:b', 'b:c'))); + $newItems = new NewItems($config); + $this->assertEquals(array('a:b', 'b:c'), $newItems->getHiddenFilters()); + } + + /** + * Test various default values. + * + * @return void + */ + public function testDefaults() + { + $config = new Config(array()); + $newItems = new NewItems($config); + $this->assertEquals(array(), $newItems->getHiddenFilters()); + $this->assertEquals('ils', $newItems->getMethod()); + $this->assertEquals(30, $newItems->getMaxAge()); + $this->assertEquals(array(1, 5, 30), $newItems->getRanges()); + $this->assertEquals(10, $newItems->getResultPages()); + } + + /** + * Test custom range settings. + * + * @return void + */ + public function testCustomRanges() + { + $config = new Config(array('ranges' => '10,150,300')); + $newItems = new NewItems($config); + $this->assertEquals(array(10, 150, 300), $newItems->getRanges()); + } + + /** + * Test custom result pages setting. + * + * @return void + */ + public function testCustomResultPages() + { + $config = new Config(array('result_pages' => '2')); + $newItems = new NewItems($config); + $this->assertEquals(2, $newItems->getResultPages()); + } + + /** + * Test Solr filter generator. + * + * @return void + */ + public function testGetSolrFilter() + { + $range = 30; + $expected = 'first_indexed:[NOW-' . $range .'DAY TO NOW]'; + $newItems = new NewItems(new Config(array())); + $this->assertEquals($expected, $newItems->getSolrFilter($range)); + } + + /** + * Get a mock catalog object (for use in getBibIDs tests). + * + * @return \VuFind\ILS\Connection + */ + protected function getMockCatalog() + { + $catalog = $this->getMock( + 'VuFind\ILS\Connection', array('getNewItems'), array(), '', false + ); + $catalog->expects($this->once())->method('getNewItems') + ->with( + $this->equalTo(1), $this->equalTo(200), + $this->equalTo(10), $this->equalTo('a') + ) + ->will( + $this->returnValue( + array('results' => array(array('id' => 1), array('id' => 2))) + ) + ); + return $catalog; + } + + /** + * Get a mock params object. + * + * @param int $idLimit Mock ID limit value + * + * @return \VuFind\Search\Solr\Params + */ + protected function getMockParams($idLimit = 1024) + { + $params = $this + ->getMock('VuFind\Search\Solr\Params', array(), array(), '', false); + $params->expects($this->once())->method('getLimit') + ->will($this->returnValue(20)); + $params->expects($this->once())->method('getQueryIDLimit') + ->will($this->returnValue($idLimit)); + return $params; + } +} \ No newline at end of file diff --git a/module/VuFind/tests/unit-tests/src/VuFindTest/Record/LoaderTest.php b/module/VuFind/tests/unit-tests/src/VuFindTest/Record/LoaderTest.php index 81a137fd5e9875ba78830004bc22a2a658494bcf..c034887a619e2ce0b12a788f0617167aa3159ae9 100644 --- a/module/VuFind/tests/unit-tests/src/VuFindTest/Record/LoaderTest.php +++ b/module/VuFind/tests/unit-tests/src/VuFindTest/Record/LoaderTest.php @@ -65,6 +65,28 @@ class LoaderTest extends TestCase $loader->load('test'); } + /** + * Test "tolerate missing records" feature. + * + * @return void + */ + public function testToleratedMissingRecord() + { + $collection = $this->getCollection(array()); + $service = $this->getMock('VuFindSearch\Service'); + $service->expects($this->once())->method('retrieve') + ->with($this->equalTo('VuFind'), $this->equalTo('test')) + ->will($this->returnValue($collection)); + $missing = $this->getDriver('missing', 'Missing'); + $factory = $this->getMock('VuFind\RecordDriver\PluginManager'); + $factory->expects($this->once())->method('get') + ->with($this->equalTo('Missing')) + ->will($this->returnValue($missing)); + $loader = $this->getLoader($service, $factory); + $record = $loader->load('test', 'VuFind', true); + $this->assertEquals($missing, $record); + } + /** * Test single record. * diff --git a/themes/blueprint/js/keep_alive.js b/themes/blueprint/js/keep_alive.js new file mode 100644 index 0000000000000000000000000000000000000000..5556008d6762ef7d2ebe940bba359c7196057d1d --- /dev/null +++ b/themes/blueprint/js/keep_alive.js @@ -0,0 +1,7 @@ +/*global path, keepAliveInterval */ + +$(document).ready(function() { + window.setInterval(function() { + $.getJSON(path + '/AJAX/JSON', {method: 'keepAlive'}); + }, keepAliveInterval * 1000); +}); diff --git a/themes/blueprint/templates/layout/layout.phtml b/themes/blueprint/templates/layout/layout.phtml index b280e799f557a0e88663945d4a48fc64fd63108f..d0d7f8e3948a5c2d4c468a48d5eb3cf15d0efd23 100644 --- a/themes/blueprint/templates/layout/layout.phtml +++ b/themes/blueprint/templates/layout/layout.phtml @@ -59,6 +59,13 @@ $this->headScript()->appendFile("jquery.tabSlideOut.v2.0.js"); $this->headScript()->appendFile("feedback.js"); } + + // Session keep-alive + if ($this->KeepAlive()) { + $this->headScript()->appendScript('var keepAliveInterval = ' + . $this->KeepAlive()); + $this->headScript()->appendFile("keep_alive.js"); + } ?> <?=$this->headScript()?> </head> diff --git a/themes/bootprint/js/pubdate_vis.js b/themes/bootprint/js/pubdate_vis.js index 247aa1ae52a1792ccbcd5675cad9105953b0e5bd..f72dd7dd580e42d99ca1bca27f33ed08d63dd730 100644 --- a/themes/bootprint/js/pubdate_vis.js +++ b/themes/bootprint/js/pubdate_vis.js @@ -106,12 +106,12 @@ function loadVis(facetFields, searchParams, baseURL, zooming) { }); if (hasFilter) { - var newdiv = document.createElement('div'); + var newdiv = document.createElement('span'); var text = document.getElementById("clearButtonText").innerHTML; newdiv.setAttribute('id', 'clearButton' + key); newdiv.innerHTML = '<a href="' + htmlEncode(val['removalURL']) + '">' + text + '</a>'; newdiv.className += "dateVisClear"; - placeholder.append(newdiv); + placeholder.before(newdiv); } }); } diff --git a/themes/bootstrap/js/cart.js b/themes/bootstrap/js/cart.js index 3308a86adcb0f7c72aec42d2e1c87e21fddfb361..23c61e7afc8cb244da4125627f8f4526e09ce15c 100644 --- a/themes/bootstrap/js/cart.js +++ b/themes/bootstrap/js/cart.js @@ -52,6 +52,15 @@ function addItemToCart(id,source) { $('#cartItems strong').html(parseInt($('#cartItems strong').html(), 10)+1); return true; } +function uniqueArray(op) { + var ret = []; + for(var i=0;i<op.length;i++) { + if(ret.indexOf(op[i]) < 0) { + ret.push(op[i]); + } + } + return ret; +} function removeItemFromCart(id,source) { var cartItems = getCartItems(); var cartSources = getCartSources(); @@ -77,10 +86,10 @@ function removeItemFromCart(id,source) { var oldSources = cartSources.slice(0); cartSources.splice(sourceIndex,1); // Adjust source index characters - for(var i=cartItems.length;i--;) { - var si = cartItems[i].charCodeAt(0)-65; + for(var j=cartItems.length;j--;) { + var si = cartItems[j].charCodeAt(0)-65; var ni = cartSources.indexOf(oldSources[si]); - cartItems[i] = String.fromCharCode(65+ni)+cartItems[i].substring(1); + cartItems[j] = String.fromCharCode(65+ni)+cartItems[j].substring(1); } } if(cartItems.length > 0) { @@ -95,16 +104,6 @@ function removeItemFromCart(id,source) { } return false; } -function uniqueArray(op) { - var ret = []; - for(var i=0;i<op.length;i++) { - if(ret.indexOf(op[i]) < 0) { - ret.push(op[i]); - } - } - return ret; -} - function registerUpdateCart($form) { if($form) { $("#updateCart, #bottom_updateCart").unbind('click').click(function(){ @@ -213,12 +212,12 @@ $(document).ready(function() { url: path + '/AJAX/JSON?' + $.param({method:'exportFavorites'}), type:'POST', dataType:'json', - data:getDataFromForm($(evt.target)), + data:Lightbox.getFormData($(evt.target)), success:function(data) { if(data.data.needs_redirect) { document.location.href = data.data.result_url; } else { - changeModalContent(data.data.result_additional); + Lightbox.changeContent(data.data.result_additional); } }, error:function(d,e) { diff --git a/themes/bootstrap/js/common.js b/themes/bootstrap/js/common.js index 10ff91ffdf88b9652c0adb9717a49b1aac682630..6f035b6cfbcd3c18b58811e8d56cdce8466cf9a6 100644 --- a/themes/bootstrap/js/common.js +++ b/themes/bootstrap/js/common.js @@ -1,4 +1,4 @@ -/*global path, vufindString */ +/*global Lightbox, path, vufindString */ /* --- GLOBAL FUNCTIONS --- */ function htmlEncode(value){ @@ -145,7 +145,11 @@ $(document).ready(function() { var url = window.location.href; if(url.indexOf('?' + 'print' + '=') != -1 || url.indexOf('&' + 'print' + '=') != -1) { $("link[media='print']").attr("media", "all"); - window.print(); + $(document).ajaxStop(function() { + window.print(); + }); + // Make an ajax call to ensure that ajaxStop is triggered + $.getJSON(path + '/AJAX/JSON', {method: 'keepAlive'}); } // Collapsing facets @@ -182,7 +186,7 @@ $(document).ready(function() { var parts = this.href.split('/'); return Lightbox.get(parts[parts.length-3],'Save',{id:$(this).attr('id')}); }); - Lightbox.addFormCallback('emailSearch', function(html) { + Lightbox.addFormCallback('emailSearch', function(x) { Lightbox.confirm(vufindString['bulk_email_success']); }); }); \ No newline at end of file diff --git a/themes/bootstrap/js/keep_alive.js b/themes/bootstrap/js/keep_alive.js new file mode 100644 index 0000000000000000000000000000000000000000..5556008d6762ef7d2ebe940bba359c7196057d1d --- /dev/null +++ b/themes/bootstrap/js/keep_alive.js @@ -0,0 +1,7 @@ +/*global path, keepAliveInterval */ + +$(document).ready(function() { + window.setInterval(function() { + $.getJSON(path + '/AJAX/JSON', {method: 'keepAlive'}); + }, keepAliveInterval * 1000); +}); diff --git a/themes/bootstrap/js/lightbox.js b/themes/bootstrap/js/lightbox.js index 80fc65ee094460c6b3ddd0fd1614d885a9c515b4..dc1f5fa87d0653bd18aadacd72822702f56c9b02 100644 --- a/themes/bootstrap/js/lightbox.js +++ b/themes/bootstrap/js/lightbox.js @@ -52,7 +52,7 @@ var Lightbox = { if(typeof expectsError === "undefined" || expectsError) { this.formCallbacks[formName] = function(html) { Lightbox.checkForError(html, func); - } + }; } else { this.formCallbacks[formName] = func; } @@ -383,13 +383,13 @@ var Lightbox = { } else { $(form).unbind('submit').submit(function(evt){ Lightbox.submit($(evt.target), function(html){ - Lightbox.checkForError(html, Lightbox.close) + Lightbox.checkForError(html, Lightbox.close); }); return false; }); } - }, -} + } +}; /** * This is a full handler for the login form @@ -491,7 +491,7 @@ function ajaxLogin(form) { } } }); -}; +} /** * This is where you add click events to open the lightbox. diff --git a/themes/bootstrap/js/pubdate_vis.js b/themes/bootstrap/js/pubdate_vis.js index 24c28c8d27b72043161adb2c4788cdd128455311..b166b6cbbca7ab6dbae24b4a29fdda8f6a3441bf 100644 --- a/themes/bootstrap/js/pubdate_vis.js +++ b/themes/bootstrap/js/pubdate_vis.js @@ -106,12 +106,12 @@ function loadVis(facetFields, searchParams, baseURL, zooming) { }); if (hasFilter) { - var newdiv = document.createElement('div'); + var newdiv = document.createElement('span'); var text = document.getElementById("clearButtonText").innerHTML; newdiv.setAttribute('id', 'clearButton' + key); newdiv.innerHTML = '<a href="' + htmlEncode(val['removalURL']) + '">' + text + '</a>'; newdiv.className += "dateVisClear"; - placeholder.append(newdiv); + placeholder.before(newdiv); } }); } diff --git a/themes/bootstrap/js/record.js b/themes/bootstrap/js/record.js index 3d3c66906b1720a973df8f98641023d4f7868b5a..5de7ae7f6e77356f42350d12199c398e2a81fa15 100644 --- a/themes/bootstrap/js/record.js +++ b/themes/bootstrap/js/record.js @@ -1,4 +1,4 @@ -/*global extractClassParams, Lightbox, path, vufindString */ +/*global deparam, extractClassParams, htmlEncode, Lightbox, path, vufindString */ /** * Functions and event handlers specific to record pages. @@ -164,6 +164,14 @@ $(document).ready(function(){ Lightbox.checkForError(html, Lightbox.changeContent); }); }); + // Place a Storage Hold + $('.placeStorageRetrievalRequest').click(function() { + var params = deparam($(this).attr('href')); + params.hashKey = params.hashKey.split('#')[0]; // Remove #tabnav + return Lightbox.get('Record', 'StorageRetrievalRequest', params, {}, function(html) { + Lightbox.checkForError(html, Lightbox.changeContent); + }); + }); // Save lightbox $('#save-record').click(function() { var params = extractClassParams(this); @@ -215,4 +223,7 @@ $(document).ready(function(){ Lightbox.addFormCallback('placeHold', function() { document.location.href = path+'/MyResearch/Holds'; }); + Lightbox.addFormCallback('placeStorageRetrievalRequest', function() { + document.location.href = path+'/MyResearch/StorageRetrievalRequests'; + }); }); diff --git a/themes/bootstrap/templates/layout/layout.phtml b/themes/bootstrap/templates/layout/layout.phtml index 6b8b3b94eeb82b5fff6f4c6b66367aa2979565c5..b655c1021aadd9eacea230d208bed4bcd3dfeeee 100644 --- a/themes/bootstrap/templates/layout/layout.phtml +++ b/themes/bootstrap/templates/layout/layout.phtml @@ -54,6 +54,13 @@ } $this->headScript()->appendScript($this->jsTranslations()->getScript()); } + + // Session keep-alive + if ($this->KeepAlive()) { + $this->headScript()->appendScript('var keepAliveInterval = ' + . $this->KeepAlive()); + $this->headScript()->appendFile("keep_alive.js"); + } ?> <?=$this->headScript()?> </head> diff --git a/themes/bootstrap/templates/record/storageretrievalrequest.phtml b/themes/bootstrap/templates/record/storageretrievalrequest.phtml index fcf8d7557acbdff9432bdb16fb24c3a5652e8a12..00e36e93c2d83efe2033c82727acd3be4ee06baa 100644 --- a/themes/bootstrap/templates/record/storageretrievalrequest.phtml +++ b/themes/bootstrap/templates/record/storageretrievalrequest.phtml @@ -10,7 +10,7 @@ <p class="lead"><?=$this->transEsc('storage_retrieval_request_place_text')?></p> <?=$this->flashmessages()?> <div class="storage-retrieval-request-form"> - <form action="" class="form-horizontal" method="post"> + <form name="placeStorageRetrievalRequest" action="" class="form-horizontal" method="post"> <? if (in_array("item-issue", $this->extraFields)): ?> <div class="control-group"> <div class="controls"> diff --git a/themes/jquerymobile/js/keep_alive.js b/themes/jquerymobile/js/keep_alive.js new file mode 100644 index 0000000000000000000000000000000000000000..5556008d6762ef7d2ebe940bba359c7196057d1d --- /dev/null +++ b/themes/jquerymobile/js/keep_alive.js @@ -0,0 +1,7 @@ +/*global path, keepAliveInterval */ + +$(document).ready(function() { + window.setInterval(function() { + $.getJSON(path + '/AJAX/JSON', {method: 'keepAlive'}); + }, keepAliveInterval * 1000); +}); diff --git a/themes/jquerymobile/templates/layout/layout.phtml b/themes/jquerymobile/templates/layout/layout.phtml index fd32eb7440f274f4b12e40046139356b8c2cd32e..a7aac014f016b5c62c1823b0057a85cd835f1c46 100644 --- a/themes/jquerymobile/templates/layout/layout.phtml +++ b/themes/jquerymobile/templates/layout/layout.phtml @@ -12,6 +12,13 @@ <? // Set global path for Javascript code: $this->headScript()->prependScript("path = '" . rtrim($this->url('home'), '/') . "';"); + + // Session keep-alive + if ($this->KeepAlive()) { + $this->headScript()->appendScript('var keepAliveInterval = ' + . $this->KeepAlive()); + $this->headScript()->appendFile("keep_alive.js"); + } ?> <?=$this->headScript()?> </head> diff --git a/themes/root/theme.config.php b/themes/root/theme.config.php index 5f2629921266829191a74264617f70ff7fb77582..fc46a3eac552bc4e7b3cdeb0a8715b0c4d5c3352 100644 --- a/themes/root/theme.config.php +++ b/themes/root/theme.config.php @@ -19,6 +19,7 @@ return array( 'historylabel' => array('VuFind\View\Helper\Root\Factory', 'getHistoryLabel'), 'ils' => array('VuFind\View\Helper\Root\Factory', 'getIls'), 'jstranslations' => array('VuFind\View\Helper\Root\Factory', 'getJsTranslations'), + 'keepalive' => array('VuFind\View\Helper\Root\Factory', 'getKeepAlive'), 'proxyurl' => array('VuFind\View\Helper\Root\Factory', 'getProxyUrl'), 'openurl' => array('VuFind\View\Helper\Root\Factory', 'getOpenUrl'), 'record' => array('VuFind\View\Helper\Root\Factory', 'getRecord'),