diff --git a/module/VuFind/src/VuFind/Search/Base/Options.php b/module/VuFind/src/VuFind/Search/Base/Options.php new file mode 100644 index 0000000000000000000000000000000000000000..241695e06ab814c9d42de95020476d682613d3eb --- /dev/null +++ b/module/VuFind/src/VuFind/Search/Base/Options.php @@ -0,0 +1,501 @@ +<?php +/** + * Abstract options search model. + * + * 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 SearchObject + * @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\Search\Base; + +/** + * Abstract options search model. + * + * This abstract class defines the option methods for modeling a search in VuFind. + * + * @category VuFind2 + * @package SearchObject + * @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 + */ +abstract class Options +{ + // Available sort options + protected $sortOptions = array(); + protected $defaultSort = 'relevance'; + protected $defaultSortByHandler = array(); + protected $rssSort = null; + + // Search options for the user + protected $defaultHandler = null; + protected $advancedHandlers = array(); + protected $basicHandlers = array(); + protected $specialAdvancedFacets = ''; + protected $retainFiltersByDefault = true; + + // Available limit options + protected $defaultLimit = 20; + protected $limitOptions = array(); + + // Available view options + protected $defaultView = 'list'; + protected $viewOptions = array(); + + // Facet settings + protected $translatedFacets = array(); + + // Spelling + protected $spellcheck = true; + + // Shard settings + protected $shards = array(); + protected $defaultSelectedShards = array(); + protected $visibleShardCheckboxes = false; + + // Highlighting + protected $highlight = false; + + // Autocomplete setting + protected $autocompleteEnabled = false; + + // Configuration files to read search settings from + protected $searchIni = 'searches'; + protected $facetsIni = 'facets'; + + /** + * Constructor + */ + public function __construct() + { + $this->limitOptions = array($this->defaultLimit); + } + + /** + * Get string listing special advanced facet types. + * + * @return string + */ + public function getSpecialAdvancedFacets() + { + return $this->specialAdvancedFacets; + } + + /** + * Basic 'getter' for advanced search handlers. + * + * @return array + */ + public function getAdvancedHandlers() + { + return $this->advancedHandlers; + } + + /** + * Basic 'getter' for basic search handlers. + * + * @return array + */ + public function getBasicHandlers() + { + return $this->basicHandlers; + } + + /** + * Get default search handler. + * + * @return string + */ + public function getDefaultHandler() + { + return $this->defaultHandler; + } + + /** + * Get default limit setting. + * + * @return int + */ + public function getDefaultLimit() + { + return $this->defaultLimit; + } + + /** + * Get an array of limit options. + * + * @return array + */ + public function getLimitOptions() + { + return $this->limitOptions; + } + + /** + * Get the name of the ini file used for configuring facet parameters in this + * object. + * + * @return string + */ + public function getFacetsIni() + { + return $this->facetsIni; + } + + /** + * Get the name of the ini file used for configuring search parameters in this + * object. + * + * @return string + */ + public function getSearchIni() + { + return $this->searchIni; + } + + /** + * Override the limit options. + * + * @param array $options New options to set. + * + * @return void + */ + public function setLimitOptions($options) + { + if (is_array($options) && !empty($options)) { + $this->limitOptions = $options; + + // If the current default limit is no longer legal, pick the + // first option in the array as the new default: + if (!in_array($this->defaultLimit, $this->limitOptions)) { + $this->defaultLimit = $this->limitOptions[0]; + } + } + } + + /** + * Get an array of sort options. + * + * @return array + */ + public function getSortOptions() + { + return $this->sortOptions; + } + + /** + * Get the default sort option for the specified search handler. + * + * @param string $handler Search handler being used + * + * @return string + */ + public function getDefaultSortByHandler($handler = null) + { + // Use default handler if none specified: + if (empty($handler)) { + $handler = $this->getDefaultHandler(); + } + // Send back search-specific sort if available: + if (isset($this->defaultSortByHandler[$handler])) { + return $this->defaultSortByHandler[$handler]; + } + // If no search-specific sort handler was found, use the overall default: + return $this->defaultSort; + } + + /** + * Return the sorting value for RSS mode + * + * @param string $sort Sort setting to modify for RSS mode + * + * @return string + */ + public function getRssSort($sort) + { + if (empty($this->rssSort)) { + return $sort; + } + if ($sort == 'relevance') { + return $this->rssSort; + } + return $this->rssSort . ',' . $sort; + } + + /** + * Get default view setting. + * + * @return int + */ + public function getDefaultView() + { + return $this->defaultView; + } + + /** + * Get an array of view options. + * + * @return array + */ + public function getViewOptions() + { + return $this->viewOptions; + } + + /** + * Get a list of facets that are subject to translation. + * + * @return array + */ + public function getTranslatedFacets() + { + return $this->translatedFacets; + } + + /** + * Get current spellcheck setting and (optionally) change it. + * + * @param bool $bool True to enable, false to disable, null to leave alone + * + * @return bool + */ + public function spellcheckEnabled($bool = null) + { + if (!is_null($bool)) { + $this->spellcheck = $bool; + } + return $this->spellcheck; + } + + /** + * Is highlighting enabled? + * + * @return bool + */ + public function highlightEnabled() + { + return $this->highlight; + } + + /** + * Translate a field name to a displayable string for rendering a query in + * human-readable format: + * + * @param string $field Field name to display. + * + * @return string Human-readable version of field name. + */ + public function getHumanReadableFieldName($field) + { + if (isset($this->basicHandlers[$field])) { + return VF_Translator::translate($this->basicHandlers[$field]); + } else if (isset($this->advancedHandlers[$field])) { + return VF_Translator::translate($this->advancedHandlers[$field]); + } else { + return $field; + } + } + + /** + * Turn off highlighting. + * + * @return void + */ + public function disableHighlighting() + { + $this->highlight = false; + } + + /** + * Is autocomplete enabled? + * + * @return bool + */ + public function autocompleteEnabled() + { + return $this->autocompleteEnabled; + } + + /** + * Return an array describing the action used for rendering search results + * (same format as expected by the URL view helper). + * + * @return array + */ + abstract public function getSearchAction(); + + /** + * Return an array describing the action used for performing advanced searches + * (same format as expected by the URL view helper). Return false if the feature + * is not supported. + * + * @return array|bool + */ + public function getAdvancedSearchAction() + { + // Assume unsupported by default: + return false; + } + + /** + * Get a session namespace specific to the current class. + * + * @return Zend_Session_Namespace + */ + public function getSession() + { + static $session = false; + if (!$session) { + $session = new Zend_Session_Namespace(get_class($this)); + } + return $session; + } + + /** + * Remember the last sort option used. + * + * @param string $last Option to remember. + * + * @return void + */ + public function rememberLastSort($last) + { + $this->getSession()->lastSort = $last; + } + + /** + * Retrieve the last sort option used. + * + * @return string + */ + public function getLastSort() + { + $session = $this->getSession(); + return isset($session->lastSort) ? $session->lastSort : null; + } + + /** + * Remember the last limit option used. + * + * @param string $last Option to remember. + * + * @return void + */ + public function rememberLastLimit($last) + { + $this->getSession()->lastLimit = $last; + } + + /** + * Retrieve the last limit option used. + * + * @return string + */ + public function getLastLimit() + { + $session = $this->getSession(); + return isset($session->lastLimit) ? $session->lastLimit : null; + } + + /** + * Remember the last view option used. + * + * @param string $last Option to remember. + * + * @return void + */ + public function rememberLastView($last) + { + $this->getSession()->lastView = $last; + } + + /** + * Retrieve the last view option used. + * + * @return string + */ + public function getLastView() + { + $session = $this->getSession(); + return isset($session->lastView) ? $session->lastView : null; + } + + /** + * Should filter settings be retained across searches by default? + * + * @return bool + */ + public function getRetainFilterSetting() + { + return $this->retainFiltersByDefault; + } + + /** + * Get an associative array of available shards (key = internal VuFind ID for + * this shard; value = details needed to connect to shard; empty for non-sharded + * data sources). + * + * Although this mechanism was originally designed for Solr's sharding + * capabilities, it could also be useful for multi-database search situations + * (i.e. federated search, EBSCO's API, etc., etc.). + * + * @return array + */ + public function getShards() + { + return $this->shards; + } + + /** + * Get an array of default selected shards (values correspond with keys returned + * by getShards(). + * + * @return array + */ + public function getDefaultSelectedShards() + { + return $this->defaultSelectedShards; + } + + /** + * Should we display shard checkboxes for this object? + * + * @return bool + */ + public function showShardCheckboxes() + { + return $this->visibleShardCheckboxes; + } + + /** + * If there is a limit to how many search results a user can access, this + * method will return that limit. If there is no limit, this will return + * -1. + * + * @return int + */ + public function getVisibleSearchResultLimit() + { + // No limit by default: + return -1; + } +} \ No newline at end of file diff --git a/module/VuFind/src/VuFind/Search/Base/Params.php b/module/VuFind/src/VuFind/Search/Base/Params.php new file mode 100644 index 0000000000000000000000000000000000000000..1ff62b0ee8d07b4bf8eaea762769c6bc57e43528 --- /dev/null +++ b/module/VuFind/src/VuFind/Search/Base/Params.php @@ -0,0 +1,1459 @@ +<?php +/** + * Abstract parameters search model. + * + * 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 SearchObject + * @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 + */ + +/** + * Abstract parameters search model. + * + * This abstract class defines the parameters methods for modeling a search in VuFind + * + * @category VuFind2 + * @package SearchObject + * @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 VF_Search_Base_Params +{ + // Search terms + protected $searchTerms = array(); + // Page number + protected $page = 1; + // Sort settings + protected $sort = null; + protected $skipRssSort = false; + // Result limit + protected $limit = 20; + protected $searchType = 'basic'; + // Shards + protected $selectedShards = array(); + // View + protected $view = null; + // VF_Search_Base_Options subclass + protected $options; + // Recommendation settings + protected $recommend = array(); + protected $recommendationEnabled = false; + // Facet settings + protected $facetConfig = array(); + protected $checkboxFacets = array(); + protected $filterList = array(); + + /** + * Constructor + * + * @param VF_Search_Base_Options $options Options to use (null to load defaults) + */ + public function __construct($options = null) + { + if (is_null($options)) { + // Create a copy of the default configuration: + $this->options = clone( + VF_Search_Options::getInstance($this->getSearchClassId()) + ); + } else { + $this->options = $options; + } + } + + /** + * Copy constructor + * + * @return void + */ + public function __clone() + { + $this->options = clone($this->options); + } + + /** + * Get the identifier used for naming the various search classes in this family. + * + * @return string + */ + public function getSearchClassId() + { + return VF_Search_Options::extractSearchClassId(get_class($this)); + } + + /** + * External method caller (proxies unrecognized methods to options object) + * + * @param string $methodName Name of function trying to be called + * @param Array $params Parameters to be sent to the function + * + * @return mixed + */ + public function __call($methodName, $params) + { + // Proxy undefined methods to the options object: + $method = array($this->options, $methodName); + if (!is_callable($method)) { + throw new Exception($methodName . ' cannot be called.'); + } + return call_user_func_array($method, $params); + } + + + /** + * Pull the search parameters + * + * @param Zend_Controller_Request_Abstract $request the search object + * + * @return string + */ + public function initFromRequest($request) + { + // We should init view first, since RSS view may cause certain variant + // behaviors: + $this->initView($request); + $this->initLimit($request); + $this->initPage($request); + $this->initShards($request); + // We have to initialize sort after search, since the search options may + // affect the default sort option. + $this->initSearch($request); + $this->initSort($request); + $this->initFilters($request); + + // Always initialize recommendations last (since they rely on knowing + // other search settings that were set above). + $this->initRecommendations($request); + + // Remember the user's settings for future reference (we only want to do + // this in initFromRequest, since other code may call the set methods from + // other contexts!): + $this->rememberLastLimit($this->getLimit()); + $this->rememberLastSort($this->getSort()); + } + + /** + * Pull shard parameters from the request or set defaults + * + * @param Zend_Controller_Request_Abstract $request the search object + * + * @return void + */ + protected function initShards($request) + { + $legalShards = array_keys($this->options->getShards()); + $requestShards = $request->getParam('shard', array()); + if (!is_array($requestShards)) { + $requestShards = array($requestShards); + } + + // If a shard selection list is found as an incoming parameter, + // we should save valid values for future reference: + foreach ($requestShards as $current) { + if (in_array($current, $legalShards)) { + $this->selectedShards[] = $current; + } + } + + // If we got this far and still have no selections established, revert to + // defaults: + if (empty($this->selectedShards)) { + $this->selectedShards = $this->options->getDefaultSelectedShards(); + } + } + + /** + * Pull the page size parameter or set to default + * + * @param Zend_Controller_Request_Abstract $request the search object + * + * @return void + */ + protected function initLimit($request) + { + // Check for a limit parameter in the url. + $defaultLimit = $this->options->getDefaultLimit(); + if (($limit = $request->getParam('limit')) != $defaultLimit) { + // make sure the url parameter is a valid limit + if (in_array($limit, $this->options->getLimitOptions())) { + $this->limit = $limit; + return; + } + } + + // Increase default limit for RSS mode: + if ($this->getView() == 'rss' && $defaultLimit < 50) { + $defaultLimit = 50; + } + + // If we got this far, setting was missing or invalid; load the default + $this->limit = $defaultLimit; + } + + /** + * Pull the page parameter + * + * @param Zend_Controller_Request_Abstract $request the search object + * + * @return void + */ + protected function initPage($request) + { + $this->page = intval($request->getParam('page')); + if ($this->page < 1) { + $this->page = 1; + } + } + + /** + * Initialize the object's search settings from a request object. + * + * @param Zend_Controller_Request_Abstract $request A Zend request object. + * + * @return void + */ + protected function initSearch($request) + { + // Try to initialize a basic search; if that fails, try for an advanced + // search next! + if (!$this->initBasicSearch($request)) { + $this->initAdvancedSearch($request); + } + + // If no search parameters were found, default to an "all records" search: + if (count($this->searchTerms) == 0) { + // Treat it as an empty basic search + $this->setBasicSearch(''); + } + } + + /** + * Support method for initSearch() -- handle basic settings. + * + * @param Zend_Controller_Request_Abstract $request A Zend request object. + * + * @return boolean True if search settings were found, false if not. + */ + protected function initBasicSearch($request) + { + // If no lookfor parameter was found, we have no search terms to + // add to our array! + if (is_null($lookfor = $request->getParam('lookfor'))) { + return false; + } + + // If lookfor is an array, we may be dealing with a legacy Advanced + // Search URL. If there's only one parameter, we can flatten it, + // but otherwise we should treat it as an error -- no point in going + // to great lengths for compatibility. + if (is_array($lookfor)) { + if (count($lookfor) > 1) { + throw new Exception("Unsupported search URL."); + } + $lookfor = $lookfor[0]; + } + + // Flatten type arrays for backward compatibility: + $handler = $request->getParam('type'); + if (is_array($handler)) { + $handler = $handler[0]; + } + + // Set the search: + $this->setBasicSearch($lookfor, $handler); + return true; + } + + /** + * Set a basic search query: + * + * @param string $lookfor The search query + * @param string $handler The search handler (null for default) + * + * @return void + */ + public function setBasicSearch($lookfor, $handler = null) + { + $this->searchType = 'basic'; + + if (empty($handler)) { + $handler = $this->options->getDefaultHandler(); + } + + $this->searchTerms = array( + array( + 'index' => $handler, + 'lookfor' => $lookfor + ) + ); + } + + /** + * Support method for initSearch() -- handle advanced settings. Advanced + * searches have numeric subscripts on the lookfor and type parameters -- + * this is how they are distinguished from basic searches. + * + * @param Zend_Controller_Request_Abstract $request A Zend request object. + * + * @return void + */ + protected function initAdvancedSearch($request) + { + $this->searchType = 'advanced'; + + $groupCount = 0; + // Loop through each search group + while (!is_null($lookfor = $request->getParam('lookfor' . $groupCount))) { + $group = array(); + // Loop through each term inside the group + for ($i = 0; $i < count($lookfor); $i++) { + // Ignore advanced search fields with no lookup + if ($lookfor[$i] != '') { + // Use default fields if not set + $typeArr = $request->getParam('type' . $groupCount); + if (isset($typeArr[$i]) && !empty($typeArr[$i])) { + $handler = $typeArr[$i]; + } else { + $handler = $this->options->getDefaultHandler(); + } + + // Add term to this group + $boolArr = $request->getParam('bool' . $groupCount); + $group[] = array( + 'field' => $handler, + 'lookfor' => $lookfor[$i], + 'bool' => isset($boolArr[0]) ? $boolArr[0] : null + ); + } + } + + // Make sure we aren't adding groups that had no terms + if (count($group) > 0) { + // Add the completed group to the list + $this->searchTerms[] = array( + 'group' => $group, + 'join' => $request->getParam('join') + ); + } + + // Increment + $groupCount++; + } + } + + /** + * Get the value for which type of sorting to use + * + * @param Zend_Controller_Request_Abstract $request A Zend request object. + * + * @return string + */ + protected function initSort($request) + { + // Check for special parameter only relevant in RSS mode: + if ($request->getParam('skip_rss_sort', 'unset') != 'unset') { + $this->skipRssSort = true; + } + $this->setSort($request->getParam('sort')); + } + + /** + * Get the value for which results view to use + * + * @param Zend_Controller_Request_Abstract $request A Zend request object. + * + * @return string + */ + protected function initView($request) + { + // Check for a view parameter in the url. + $view = $request->getParam('view'); + $lastView = $this->getLastView(); + if (!empty($view)) { + if ($view == 'rss') { + // we don't want to store rss in the Session + $this->setView('rss'); + } else { + // store non-rss views in Session for persistence + $validViews = $this->getViewOptions(); + // make sure the url parameter is a valid view + if (in_array($view, array_keys($validViews))) { + $this->setView($view); + $this->rememberLastView($view); + } else { + $this->setView($this->getDefaultView()); + } + } + } else if (!empty($lastView)) { + // if there is nothing in the URL, check the Session + $this->setView($lastView); + } else { + // otherwise load the default + $this->setView($this->getDefaultView()); + } + } + + /** + * Return the default sorting value + * + * @return string + */ + public function getDefaultSort() + { + return $this->getDefaultSortByHandler($this->getSearchHandler()); + } + + /** + * Return the current limit value + * + * @return int + */ + public function getLimit() + { + return $this->limit; + } + + /** + * Change the value of the limit + * + * @param int $l New limit value. + * + * @return void + */ + public function setLimit($l) + { + $this->limit = $l; + } + + /** + * Change the page + * + * @param int $p New page value. + * + * @return void + */ + public function setPage($p) + { + $this->page = $p; + } + + /** + * Get the page value + * + * @return int + */ + public function getPage() + { + return $this->page; + } + + /** + * Return the sorting value + * + * @return int + */ + public function getSort() + { + return $this->sort; + } + + /** + * Set the sorting value (note: sort will be set to default if an illegal + * or empty value is passed in). + * + * @param string $sort New sort value (null for default) + * @param bool $force Set sort value without validating it? + * + * @return void + */ + public function setSort($sort, $force = false) + { + // Skip validation if requested: + if ($force) { + $this->sort = $sort; + return; + } + + // Validate and assign the sort value: + $valid = array_keys($this->getSortOptions()); + if (!empty($sort) && in_array($sort, $valid)) { + $this->sort = $sort; + } else { + $this->sort = $this->getDefaultSort(); + } + + // In RSS mode, we may want to adjust sort settings: + if (!$this->skipRssSort && $this->getView() == 'rss') { + $this->sort = $this->getRssSort($this->sort); + } + } + + /** + * Return the selected search handler (null for complex searches which have no + * single handler) + * + * @return string|null + */ + public function getSearchHandler() + { + // We can only definitively name a handler if we have a basic search: + if (count($this->searchTerms) == 1 + && isset($this->searchTerms[0]['index']) + ) { + return $this->searchTerms[0]['index']; + } + return null; + } + + /** + * Return the search type (i.e. basic or advanced) + * + * @return string + */ + public function getSearchType() + { + return $this->searchType; + } + + /** + * Return the search terms + * + * @return string + */ + public function getSearchTerms() + { + return $this->searchTerms; + } + + /** + * Return the value for which search view we use + * + * @return string + */ + public function getView() + { + return is_null($this->view) ? $this->getDefaultView() : $this->view; + } + + /** + * Set the value for which search view we use + * + * @param String $v New view setting + * + * @return void + */ + public function setView($v) + { + $this->view = $v; + } + + /** + * Get a human-readable presentation version of the advanced search query + * stored in the object. This will not work if $this->searchType is not + * 'advanced.' + * + * @return string + */ + protected function buildAdvancedDisplayQuery() + { + // Groups and exclusions. This mirrors some logic in Solr.php + $groups = array(); + $excludes = array(); + + foreach ($this->searchTerms as $search) { + $thisGroup = array(); + // Process each search group + if (isset($search['group'])) { + foreach ($search['group'] as $group) { + // Build this group individually as a basic search + $thisGroup[] = $this->getHumanReadableFieldName($group['field']) + . ":{$group['lookfor']}"; + } + } + // Is this an exclusion (NOT) group or a normal group? + if (isset($search['group'][0]['bool']) + && $search['group'][0]['bool'] == 'NOT' + ) { + $excludes[] + = join(' ' . VF_Translator::translate('OR') . ' ', $thisGroup); + } else if (isset($search['group'][0]['bool'])) { + $groups[] = join( + " " . VF_Translator::translate($search['group'][0]['bool'])." ", + $thisGroup + ); + } + } + + // Base 'advanced' query + $output = ''; + if (isset($this->searchTerms[0]['join'])) { + $output .= "(" . + join( + ") " . + VF_Translator::translate($this->searchTerms[0]['join']) . " (", + $groups + ) . + ")"; + } + + // Concatenate exclusion after that + if (count($excludes) > 0) { + $output .= ' ' . + VF_Translator::translate('NOT') . ' ((' . + join(') ' . VF_Translator::translate('OR') . ' (', $excludes) . "))"; + } + + return $output; + } + + /** + * Build a string for onscreen display showing the + * query used in the search (not the filters). + * + * @return string user friendly version of 'query' + */ + public function getDisplayQuery() + { + if ($this->searchType == 'advanced') { + return $this->buildAdvancedDisplayQuery(); + } + + // Default -- Basic search: + return isset($this->searchTerms[0]['lookfor']) + ? $this->searchTerms[0]['lookfor'] : ''; + } + + /** + * Get an array of recommendation objects for augmenting the results display. + * + * @param string $location Name of location to use as a filter (null to get + * associative array of all locations); legal non-null values: 'top', 'side' + * + * @return array + */ + public function getRecommendations($location = 'top') + { + if (!$this->recommendationsEnabled()) { + return array(); + } + if (is_null($location)) { + return $this->recommend; + } + return isset($this->recommend[$location]) + ? $this->recommend[$location] : array(); + } + + /** + * Set the enabled status of recommendation modules -- it is often useful to turn + * off recommendations when retrieving results in a context other than standard + * display of results. + * + * @param bool $bool True to enable, false to disable (null to leave unchanged) + * + * @return bool Current state of recommendations + */ + public function recommendationsEnabled($bool = null) + { + if (!is_null($bool)) { + $this->recommendationEnabled = $bool; + } + return $this->recommendationEnabled; + } + + /** + * Load all recommendation settings from the relevant ini file. Returns an + * associative array where the key is the location of the recommendations (top + * or side) and the value is the settings found in the file (which may be either + * a single string or an array of strings). + * + * @return array associative: location (top/side) => search settings + */ + protected function getRecommendationSettings() + { + // Bypass settings if recommendations are disabled. + if (!$this->recommendationsEnabled()) { + return array(); + } + + // Load the necessary settings to determine the appropriate recommendations + // module: + $searchSettings = VF_Config_Reader::getConfig($this->getSearchIni()); + + // If we have a search type set, save it so we can try to load a + // type-specific recommendations module: + $handler = $this->getSearchHandler(); + + // Load a type-specific recommendations setting if possible, or the default + // otherwise: + $recommend = array(); + if (!is_null($handler) + && isset($searchSettings->TopRecommendations->$handler) + ) { + $recommend['top'] = $searchSettings->TopRecommendations + ->$handler->toArray(); + } else { + $recommend['top'] + = isset($searchSettings->General->default_top_recommend) + ? $searchSettings->General->default_top_recommend->toArray() + : false; + } + if (!is_null($handler) + && isset($searchSettings->SideRecommendations->$handler) + ) { + $recommend['side'] = $searchSettings->SideRecommendations + ->$handler->toArray(); + } else { + $recommend['side'] + = isset($searchSettings->General->default_side_recommend) + ? $searchSettings->General->default_side_recommend->toArray() + : false; + } + if (!is_null($handler) + && isset($searchSettings->NoResultsRecommendations->$handler) + ) { + $recommend['noresults'] = $searchSettings->NoResultsRecommendations + ->$handler->toArray(); + } else { + $recommend['noresults'] + = isset($searchSettings->General->default_noresults_recommend) + ? $searchSettings->General->default_noresults_recommend->toArray() + : false; + } + + return $recommend; + } + + /** + * Initialize the recommendations modules. + * + * @param Zend_Controller_Request_Abstract $request Zend request object + * + * @return void + */ + protected function initRecommendations($request) + { + // If no settings were found, quit now: + $settings = $this->getRecommendationSettings(); + if (empty($settings)) { + return; + } + + // Process recommendations for each location: + $this->recommend = array( + 'top' => array(), 'side' => array(), 'noresults' => array() + ); + foreach ($settings as $location => $currentSet) { + // If the current location is disabled, skip processing! + if (empty($currentSet)) { + continue; + } + // Make sure the current location's set of recommendations is an array; + // if it's a single string, this normalization will simplify processing. + if (!is_array($currentSet)) { + $currentSet = array($currentSet); + } + // Now loop through all recommendation settings for the location. + foreach ($currentSet as $current) { + // Break apart the setting into module name and extra parameters: + $current = explode(':', $current); + $module = array_shift($current); + $class = 'VF_Recommend_' . $module; + $params = implode(':', $current); + + // Build a recommendation module with the provided settings. + if (@class_exists($class)) { + $obj = new $class($params); + $obj->init($this, $request); + $this->recommend[$location][] = $obj; + } else { + throw new Exception( + 'Could not load recommendation module: ' . $class + ); + } + } + } + } + + /** + * Parse apart the field and value from a URL filter string. + * + * @param string $filter A filter string from url : "field:value" + * + * @return array Array with elements 0 = field, 1 = value. + */ + public function parseFilter($filter) + { + // Split the string and assign the parts to $field and $value + $temp = explode(':', $filter, 2); + $field = array_shift($temp); + $value = count($temp) > 0 ? $temp[0] : ''; + + // Remove quotes from the value if there are any + if (substr($value, 0, 1) == '"') { + $value = substr($value, 1); + } + if (substr($value, -1, 1) == '"') { + $value = substr($value, 0, -1); + } + // One last little clean on whitespace + $value = trim($value); + + // Send back the results: + return array($field, $value); + } + + /** + * Does the object already contain the specified filter? + * + * @param string $filter A filter string from url : "field:value" + * + * @return bool + */ + public function hasFilter($filter) + { + // Extract field and value from URL string: + list($field, $value) = $this->parseFilter($filter); + + if (isset($this->filterList[$field]) + && in_array($value, $this->filterList[$field]) + ) { + return true; + } + return false; + } + + /** + * Take a filter string and add it into the protected + * array checking for duplicates. + * + * @param string $newFilter A filter string from url : "field:value" + * + * @return void + */ + public function addFilter($newFilter) + { + // Extract field and value from URL string: + list($field, $value) = $this->parseFilter($newFilter); + + // Check for duplicates -- if it's not in the array, we can add it + if (!$this->hasFilter($newFilter)) { + $this->filterList[$field][] = $value; + } + } + + /** + * Remove a filter from the list. + * + * @param string $oldFilter A filter string from url : "field:value" + * + * @return void + */ + public function removeFilter($oldFilter) + { + // Extract field and value from URL string: + list($field, $value) = $this->parseFilter($oldFilter); + + // Make sure the field exists + if (isset($this->filterList[$field])) { + // Assume by default that we will not need to rebuild the array: + $rebuildArray = false; + + // Loop through all filters on the field + for ($i = 0; $i < count($this->filterList[$field]); $i++) { + // Does it contain the value we don't want? + if ($this->filterList[$field][$i] == $value) { + // If so remove it. + unset($this->filterList[$field][$i]); + + // Flag that we now need to rebuild the array: + $rebuildArray = true; + } + } + + // If necessary, rebuild the array to remove gaps in the key sequence: + if ($rebuildArray) { + $this->filterList[$field] = array_values($this->filterList[$field]); + } + } + } + + /** + * Remove all filters from the list. + * + * @param string $field Name of field to remove filters from (null to remove + * all filters from all fields) + * + * @return void + */ + public function removeAllFilters($field = null) + { + if ($field == null) { + $this->filterList = array(); + } else { + $this->filterList[$field] = array(); + } + } + + /** + * Add a field to facet on. + * + * @param string $newField Field name + * @param string $newAlias Optional on-screen display label + * + * @return void + */ + public function addFacet($newField, $newAlias = null) + { + if ($newAlias == null) { + $newAlias = $newField; + } + $this->facetConfig[$newField] = $newAlias; + } + + /** + * Add a checkbox facet. When the checkbox is checked, the specified filter + * will be applied to the search. When the checkbox is not checked, no filter + * will be applied. + * + * @param string $filter [field]:[value] pair to associate with checkbox + * @param string $desc Description to associate with the checkbox + * + * @return void + */ + public function addCheckboxFacet($filter, $desc) + { + // Extract the facet field name from the filter, then add the + // relevant information to the array. + list($fieldName) = explode(':', $filter); + $this->checkboxFacets[$fieldName] + = array('desc' => $desc, 'filter' => $filter); + } + + /** + * Get a user-friendly string to describe the provided facet field. + * + * @param string $field Facet field name. + * + * @return string Human-readable description of field. + */ + public function getFacetLabel($field) + { + return isset($this->facetConfig[$field]) + ? $this->facetConfig[$field] : "Other"; + } + + /** + * Get the current facet configuration. + * + * @return array + */ + public function getFacetConfig() + { + return $this->facetConfig; + } + + /** + * Reset the current facet configuration. + * + * @return void + */ + public function resetFacetConfig() + { + $this->facetConfig = array(); + } + + /** + * Get the raw filter list. + * + * @return array + */ + public function getFilters() + { + return $this->filterList; + } + + /** + * Return an array structure containing all current filters + * and urls to remove them. + * + * @param bool $excludeCheckboxFilters Should we exclude checkbox filters from + * the list (to be used as a complement to getCheckboxFacets()). + * + * @return array Field, values and translation status + */ + public function getFilterList($excludeCheckboxFilters = false) + { + // Get a list of checkbox filters to skip if necessary: + $skipList = array(); + if ($excludeCheckboxFilters) { + foreach ($this->checkboxFacets as $current) { + list($field, $value) = $this->parseFilter($current['filter']); + if (!isset($skipList[$field])) { + $skipList[$field] = array(); + } + $skipList[$field][] = $value; + } + } + + $list = array(); + // Loop through all the current filter fields + foreach ($this->filterList as $field => $values) { + // and each value currently used for that field + $translate = in_array($field, $this->getTranslatedFacets()); + foreach ($values as $value) { + // Add to the list unless it's in the list of fields to skip: + if (!isset($skipList[$field]) + || !in_array($value, $skipList[$field]) + ) { + $facetLabel = $this->getFacetLabel($field); + $list[$facetLabel][] = array( + 'value' => $value, + 'displayText' => + $translate ? VF_Translator::translate($value) : $value, + 'field' => $field + ); + } + } + } + return $list; + } + + /** + * Get information on the current state of the boolean checkbox facets. + * + * @return array + */ + public function getCheckboxFacets() + { + // Build up an array of checkbox facets with status booleans and + // toggle URLs. + $facets = array(); + foreach ($this->checkboxFacets as $field => $details) { + $facets[$field] = $details; + if ($this->hasFilter($details['filter'])) { + $facets[$field]['selected'] = true; + } else { + $facets[$field]['selected'] = false; + } + // Is this checkbox always visible, even if non-selected on the + // "no results" screen? By default, no (may be overridden by + // child classes). + $facets[$field]['alwaysVisible'] = false; + } + return $facets; + } + + /** + * Support method for initDateFilters() -- normalize a year for use in a date + * range. + * + * @param string $year Value to check for valid year. + * + * @return string Formatted year. + */ + protected function formatYearForDateRange($year) + { + // Make sure parameter is set and numeric; default to wildcard otherwise: + $year = preg_match('/\d{2,4}/', $year) ? $year : '*'; + + // Pad to four digits: + if (strlen($year) == 2) { + $year = '19' . $year; + } else if (strlen($year) == 3) { + $year = '0' . $year; + } + + return $year; + } + + /** + * Support method for initDateFilters() -- build a filter query based on a range + * of dates. + * + * @param string $field field to use for filtering. + * @param string $from year for start of range. + * @param string $to year for end of range. + * + * @return string filter query. + */ + protected function buildDateRangeFilter($field, $from, $to) + { + // Make sure that $to is less than $from: + if ($to != '*' && $from!= '*' && $to < $from) { + $tmp = $to; + $to = $from; + $from = $tmp; + } + + // Assume Solr syntax -- this should be overridden in child classes where + // other indexing methodologies are used. + return "{$field}:[{$from} TO {$to}]"; + } + + /** + * Support method for initFilters() -- initialize date-related filters. Factored + * out as a separate method so that it can be more easily overridden by child + * classes. + * + * @param Zend_Controller_Request_Abstract $request A Zend request object. + * + * @return void + */ + protected function initDateFilters($request) + { + $daterange = $request->getParam('daterange'); + if (!empty($daterange)) { + $ranges = is_array($daterange) ? $daterange : array($daterange); + foreach ($ranges as $range) { + // Validate start and end of range: + $yearFrom = $this->formatYearForDateRange( + $request->getParam($range . 'from') + ); + $yearTo = $this->formatYearForDateRange( + $request->getParam($range . 'to') + ); + + // Build filter only if necessary: + if (!empty($range) && ($yearFrom != '*' || $yearTo != '*')) { + $dateFilter + = $this->buildDateRangeFilter($range, $yearFrom, $yearTo); + $this->addFilter($dateFilter); + } + } + } + } + + /** + * Add filters to the object based on values found in the request object. + * + * @param Zend_Controller_Request_Abstract $request A Zend request object. + * + * @return void + */ + protected function initFilters($request) + { + // Handle standard filters: + $filter = $request->getParam('filter'); + if (!empty($filter)) { + if (is_array($filter)) { + foreach ($filter as $current) { + $this->addFilter($current); + } + } else { + $this->addFilter($filter); + } + } + + // Handle date range filters: + $this->initDateFilters($request); + } + + /** + * Replace a search term in the query + * + * @param string $from Search term to find + * @param string $to Search term to insert + * + * @return void + */ + public function replaceSearchTerm($from, $to) + { + // Escape $from so it is regular expression safe (just in case it + // includes any weird punctuation -- unlikely but possible): + $from = addcslashes($from, '\^$.[]|()?*+{}/'); + + // If our "from" pattern contains non-word characters, we can't use word + // boundaries for matching. We want to try to use word boundaries when + // possible, however, to avoid the replacement from affecting unexpected + // parts of the search query. + if (!preg_match('/.*[^\w].*/', $from)) { + $pattern = "/\b$from\b/i"; + } else { + $pattern = "/$from/i"; + } + + // Advanced search + if (isset($this->searchTerms[0]['group'])) { + for ($i = 0; $i < count($this->searchTerms); $i++) { + for ($j = 0; $j < count($this->searchTerms[$i]['group']); $j++) { + $this->searchTerms[$i]['group'][$j]['lookfor'] + = preg_replace( + $pattern, $to, + $this->searchTerms[$i]['group'][$j]['lookfor'] + ); + } + } + } else { + // Basic search + for ($i = 0; $i < count($this->searchTerms); $i++) { + // Perform the replacement: + $this->searchTerms[$i]['lookfor'] = preg_replace( + $pattern, $to, $this->searchTerms[$i]['lookfor'] + ); + } + } + } + + /** + * Return a query string for the current search with a search term replaced. + * + * @param string $oldTerm The old term to replace + * @param string $newTerm The new term to search + * + * @return string query string + */ + public function getDisplayQueryWithReplacedTerm($oldTerm, $newTerm) + { + // Stash our old data for a minute + $oldTerms = $this->searchTerms; + // Replace the search term + $this->replaceSearchTerm($oldTerm, $newTerm); + // Get the new query string + $query = $this->getDisplayQuery(); + // Restore the old data + $this->searchTerms = $oldTerms; + // Return the query string + return $query; + } + + /** + * Find a word amongst the current search terms + * + * @param string $needle Search term to find + * + * @return bool True/False if the word was found + */ + public function findSearchTerm($needle) + { + // Escape slashes in $needle to avoid regular expression errors: + $needle = str_replace('/', '\/', $needle); + + // Advanced search + if (isset($this->searchTerms[0]['group'])) { + foreach ($this->searchTerms as $group) { + foreach ($group['group'] as $search) { + if (preg_match("/\b$needle\b/", $search['lookfor'])) { + return true; + } + } + } + } else { + // Basic search + foreach ($this->searchTerms as $haystack) { + if (preg_match("/\b$needle\b/", $haystack['lookfor'])) { + return true; + } + } + } + return false; + } + + /** + * Extract all the keywords from the advanced search as a string. + * + * @return string + */ + public function extractAdvancedTerms() + { + $terms = array(); + foreach ($this->searchTerms as $current) { + if (isset($current['lookfor'])) { + $terms[] = $current['lookfor']; + } else if (isset($current['group']) && is_array($current['group'])) { + foreach ($current['group'] as $subCurrent) { + if (isset($subCurrent['lookfor'])) { + $terms[] = $subCurrent['lookfor']; + } + } + } + } + return implode(' ', $terms); + } + + /** + * Basic 'getter' for list of available view options. + * + * @return array + */ + public function getViewList() + { + $list = array(); + foreach ($this->getViewOptions() as $key => $value) { + $list[$key] = array( + 'desc' => $value, + 'selected' => ($key == $this->getView()) + ); + } + return $list; + } + + /** + * Return a list of urls for possible limits, along with which option + * should be currently selected. + * + * @return array Limit urls, descriptions and selected flags + */ + public function getLimitList() + { + // Loop through all the current limits + $valid = $this->getLimitOptions(); + $list = array(); + foreach ($valid as $limit) { + $list[$limit] = array( + 'desc' => $limit, + 'selected' => ($limit == $this->getLimit()) + ); + } + return $list; + } + + /** + * Return a list of urls for sorting, along with which option + * should be currently selected. + * + * @return array Sort urls, descriptions and selected flags + */ + public function getSortList() + { + // Loop through all the current filter fields + $valid = $this->getSortOptions(); + $list = array(); + foreach ($valid as $sort => $desc) { + $list[$sort] = array( + 'desc' => $desc, + 'selected' => ($sort == $this->getSort()) + ); + } + return $list; + } + + /** + * Restore settings from a minified object found in the database. + * + * @param VF_MS $minified Minified Search Object + * + * @return void + */ + public function deminify($minified) + { + // Some values will transfer without changes + $this->filterList = $minified->f; + $this->searchType = $minified->ty; + + // Search terms, we need to expand keys + $tempTerms = $minified->t; + foreach ($tempTerms as $term) { + $newTerm = array(); + foreach ($term as $k => $v) { + switch ($k) { + case 'j': + $newTerm['join'] = $v; + break; + case 'i': + $newTerm['index'] = $v; + break; + case 'l': + $newTerm['lookfor'] = $v; + break; + case 'g': + $newTerm['group'] = array(); + foreach ($v as $line) { + $search = array(); + foreach ($line as $k2 => $v2) { + switch ($k2) { + case 'b': + $search['bool'] = $v2; + break; + case 'f': + $search['field'] = $v2; + break; + case 'l': + $search['lookfor'] = $v2; + break; + } + } + $newTerm['group'][] = $search; + } + break; + } + } + $this->searchTerms[] = $newTerm; + } + } + + /** + * Load all available facet settings. This is mainly useful for showing + * appropriate labels when an existing search has multiple filters associated + * with it. + * + * @param string $preferredSection Section to favor when loading settings; + * if multiple sections contain the same facet, this section's description + * will be favored. + * + * @return void + */ + public function activateAllFacets($preferredSection = false) + { + // By default, there is only set of facet settings, so this function isn't + // really necessary. However, in the Search History screen, we need to + // use this for Solr-based Search Objects, so we need this dummy method to + // allow other types of Search Objects to co-exist with Solr-based ones. + // See the Solr Search Object for details of how this works if you need to + // implement context-sensitive facet settings in another module. + } + + /** + * Override the normal search behavior with an explicit array of IDs that must + * be retrieved. + * + * @param array $ids Record IDs to load + * + * @return void + */ + public function setQueryIDs($ids) + { + // This needs to be defined in child classes: + throw new Exception(get_class($this) . ' does not support setQueryIDs().'); + } + + /** + * Get the maximum number of IDs that may be sent to setQueryIDs (-1 for no + * limit). + * + * @return int + */ + public function getQueryIDLimit() + { + return -1; + } + + /** + * Get an array of the names of all selected shards. These should correspond + * with keys in the array returned by the option class's getShards() method. + * + * @return array + */ + public function getSelectedShards() + { + return $this->selectedShards; + } +} \ No newline at end of file diff --git a/module/VuFind/src/VuFind/Search/Base/Results.php b/module/VuFind/src/VuFind/Search/Base/Results.php new file mode 100644 index 0000000000000000000000000000000000000000..dfedd72a1849958e1a275b4769d48f1f554a9e34 --- /dev/null +++ b/module/VuFind/src/VuFind/Search/Base/Results.php @@ -0,0 +1,480 @@ +<?php +/** + * Abstract results search model. + * + * 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 SearchObject + * @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 + */ + +/** + * Abstract results search model. + * + * This abstract class defines the results methods for modeling a search in VuFind. + * + * @category VuFind2 + * @package SearchObject + * @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 + */ +abstract class VF_Search_Base_Results +{ + protected $params; + // Total number of results available + protected $resultTotal = null; + // Override (only for use in very rare cases) + protected $startRecordOverride = null; + // Array of results (represented as Record Driver objects) retrieved on latest + // search: + protected $results = null; + // An ID number for saving/retrieving search + protected $searchId = null; + protected $savedSearch = null; + // STATS + protected $queryStartTime = null; + protected $queryEndTime = null; + protected $queryTime = null; + // Helper objects + protected $helpers = array(); + // Spelling + protected $suggestions = null; + + /** + * Constructor + * + * @param VF_Search_Base_Params $params Object representing user search + * parameters. + */ + public function __construct(VF_Search_Base_Params $params) + { + // Save the parameters, then perform the search: + $this->params = $params; + } + + /** + * Copy constructor + * + * @return void + */ + public function __clone() + { + $this->params = clone($this->params); + } + + /** + * Get the URL helper for this object. + * + * @return VF_Search_UrlHelper + */ + public function getUrl() + { + // Set up URL helper: + if (!isset($this->helpers['url'])) { + $this->helpers['url'] = new VF_Search_UrlHelper($this); + } + return $this->helpers['url']; + } + + /** + * Actually execute the search. + * + * @return void + */ + public function performAndProcessSearch() + { + // Initialize variables to defaults (to ensure they don't stay null + // and cause unnecessary repeat processing): + $this->resultTotal = 0; + $this->results = array(); + $this->suggestions = array(); + + // Run the search: + $this->startQueryTimer(); + $this->performSearch(); + $this->stopQueryTimer(); + + // Process recommendations: + $recommendations = $this->params->getRecommendations(null); + if (is_array($recommendations)) { + foreach ($recommendations as $currentSet) { + foreach ($currentSet as $current) { + $current->process($this); + } + } + } + } + + /** + * Returns the stored list of facets for the last search + * + * @param array $filter Array of field => on-screen description listing + * all of the desired facet fields; set to null to get all configured values. + * + * @return array Facets data arrays + */ + abstract public function getFacetList($filter = null); + + /** + * Abstract support method for performAndProcessSearch -- perform a search based + * on the parameters passed to the object. This method is responsible for + * filling in all of the key class properties: _results, _resultTotal, etc. + * + * @return void + */ + abstract protected function performSearch(); + + /** + * Static method to retrieve a record by ID. Returns a record driver object. + * + * @param string $id Unique identifier of record + * + * @return VF_RecordDriver_Base + */ + public static function getRecord($id) + { + // This needs to be defined in subclasses: + throw new Exception('getRecord needs to be defined.'); + } + + /** + * Static method to retrieve an array of records by ID. + * + * @param array $ids Array of unique record identifiers. + * + * @return array + */ + public static function getRecords($ids) + { + // This is the default, dumb behavior for retrieving multiple records -- + // just call getRecord() repeatedly. For efficiency, this method should + // be overridden in child classes when possible to reduce API calls, etc. + $retVal = array(); + foreach ($ids as $id) { + try { + $retVal[] = static::getRecord($id); + } catch (Exception $e) { + // Just omit missing records from the return array; calling code + // in the VF_Record::loadBatch() method will deal with this. + } + } + return $retVal; + } + + /** + * Allow Results object to proxy methods of Params object. + * + * @param string $methodName Method to call + * @param array $params Method parameters + * + * @return mixed + */ + public function __call($methodName, $params) + { + // Proxy undefined methods to the parameter object: + $method = array($this->params, $methodName); + if (!is_callable($method)) { + throw new Exception($methodName . ' cannot be called.'); + } + return call_user_func_array($method, $params); + } + + /** + * Get spelling suggestion information. + * + * @return array + */ + public function getSpellingSuggestions() + { + // Not supported by default: + return array(); + } + + /** + * Get total count of records in the result set (not just current page). + * + * @return int + */ + public function getResultTotal() + { + if (is_null($this->resultTotal)) { + $this->performAndProcessSearch(); + } + return $this->resultTotal; + } + + /** + * Manually override the start record number. + * + * @param int $rec Record number to use. + * + * @return void + */ + public function overrideStartRecord($rec) + { + $this->startRecordOverride = $rec; + } + + /** + * Get record number for start of range represented by current result set. + * + * @return int + */ + public function getStartRecord() + { + if (!is_null($this->startRecordOverride)) { + return $this->startRecordOverride; + } + return (($this->getPage() - 1) * $this->getLimit()) + 1; + } + + /** + * Get record number for end of range represented by current result set. + * + * @return int + */ + public function getEndRecord() + { + $total = $this->getResultTotal(); + $limit = $this->getLimit(); + $page = $this->getPage(); + if ($page * $limit > $total) { + // The end of the current page runs past the last record, use total + // results + return $total; + } else { + // Otherwise use the last record on this page + return $page * $limit; + } + } + + /** + * Basic 'getter' for search results. + * + * @return array + */ + public function getResults() + { + if (is_null($this->results)) { + $this->performAndProcessSearch(); + } + return $this->results; + } + + /** + * Basic 'getter' for ID of saved search. + * + * @return int + */ + public function getSearchId() + { + return $this->searchId; + } + + /** + * Is the current search saved in the database? + * + * @return bool + */ + public function isSavedSearch() + { + // This data is not available until VuFind_Model_Db_Search::saveSearch() + // is called... blow up if somebody tries to get data that is not yet + // available. + if (is_null($this->savedSearch)) { + throw new Exception( + 'Cannot retrieve save status before updateSaveStatus is called.' + ); + } + return $this->savedSearch; + } + + /** + * Given a database row corresponding to the current search object, + * mark whether this search is saved and what its database ID is. + * + * @param Zend_Db_Table_Row $row Relevant database row. + * + * @return void + */ + public function updateSaveStatus($row) + { + $this->searchId = $row->id; + $this->savedSearch = ($row->saved == true); + } + + /** + * Start the timer to figure out how long a query takes. Complements + * stopQueryTimer(). + * + * @return void + */ + protected function startQueryTimer() + { + // Get time before the query + $time = explode(" ", microtime()); + $this->queryStartTime = $time[1] + $time[0]; + } + + /** + * End the timer to figure out how long a query takes. Complements + * startQueryTimer(). + * + * @return void + */ + protected function stopQueryTimer() + { + $time = explode(" ", microtime()); + $this->queryEndTime = $time[1] + $time[0]; + $this->queryTime = $this->queryEndTime - $this->queryStartTime; + } + + /** + * Basic 'getter' for query speed. + * + * @return float + */ + public function getQuerySpeed() + { + if (is_null($this->queryTime)) { + $this->performAndProcessSearch(); + } + return $this->queryTime; + } + + /** + * Basic 'getter' for query start time. + * + * @return float + */ + public function getStartTime() + { + if (is_null($this->queryStartTime)) { + $this->performAndProcessSearch(); + } + return $this->queryStartTime; + } + + /** + * Get a paginator for the result set. + * + * @return Zend_Paginator + */ + public function getPaginator() + { + // If there is a limit on how many pages are accessible, + // apply that limit now: + $max = $this->getVisibleSearchResultLimit(); + $total = $this->getResultTotal(); + if ($max > 0 && $total > $max) { + $total = $max; + } + + // Build the standard paginator control: + return Zend_Paginator::factory($total) + ->setCurrentPageNumber($this->getPage()) + ->setItemCountPerPage($this->getLimit()) + ->setPageRange(11); + } + + /** + * Input Tokenizer - Specifically for spelling purposes + * + * Because of its focus on spelling, these tokens are unsuitable + * for actual searching. They are stripping important search data + * such as joins and groups, simply because they don't need to be + * spellchecked. + * + * @param string $input Query to tokenize + * + * @return array Tokenized array + */ + public function spellingTokens($input) + { + $joins = array("AND", "OR", "NOT"); + $paren = array("(" => "", ")" => ""); + + // Base of this algorithm comes straight from + // PHP doco examples & benighted at gmail dot com + // http://php.net/manual/en/function.strtok.php + $tokens = array(); + $token = strtok($input, ' '); + while ($token) { + // find bracketed tokens + if ($token{0}=='(') { + $token .= ' '.strtok(')').')'; + } + // find double quoted tokens + if ($token{0}=='"') { + $token .= ' '.strtok('"').'"'; + } + // find single quoted tokens + if ($token{0}=="'") { + $token .= ' '.strtok("'")."'"; + } + $tokens[] = $token; + $token = strtok(' '); + } + // Some cleaning of tokens that are just boolean joins + // and removal of brackets + $return = array(); + foreach ($tokens as $token) { + // Ignore join + if (!in_array($token, $joins)) { + // And strip parentheses + $final = trim(strtr($token, $paren)); + if ($final != "") { + $return[] = $final; + } + } + } + return $return; + } + + /** + * Basic 'getter' for suggestion list. + * + * @return array + */ + public function getRawSuggestions() + { + if (is_null($this->suggestions)) { + $this->performAndProcessSearch(); + } + return $this->suggestions; + } + + /** + * Restore settings from a minified object found in the database. + * + * @param VF_MS $minified Minified Search Object + * + * @return void + */ + public function deminify($minified) + { + $this->searchId = $minified->id; + $this->queryStartTime = $minified->i; + $this->queryTime = $minified->s; + $this->resultTotal = $minified->r; + } +} \ No newline at end of file diff --git a/module/VuFind/src/VuFind/Search/Empty/Results.php b/module/VuFind/src/VuFind/Search/Empty/Results.php new file mode 100644 index 0000000000000000000000000000000000000000..2dc46a2dba21a162dee6bdc74a780b079094fcb2 --- /dev/null +++ b/module/VuFind/src/VuFind/Search/Empty/Results.php @@ -0,0 +1,77 @@ +<?php +/** + * Empty Search Object + * + * 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 SearchObject + * @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 + */ + +/** + * Simple search results object to represent an empty set (used when dealing with + * exceptions that prevent a "real" search object from being constructed). + * + * @category VuFind2 + * @package SearchObject + * @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 VF_Search_Empty_Results extends VF_Search_Base_Results +{ + /** + * Support method for constructor -- perform a search based on the parameters + * passed to the object. + * + * @return void + */ + protected function performSearch() + { + // Do nothing + } + + /** + * Returns the stored list of facets for the last search + * + * @param array $filter Array of field => on-screen description listing all + * of the desired facet fields; set to null to get all configured values. + * + * @return array Facets data arrays + */ + public function getFacetList($filter = null) + { + return array(); + } + + /** + * Static method to retrieve a record by ID. Returns a record driver object. + * + * @param string $id Unique identifier of record + * + * @throws VF_Exception_RecordMissing + * @return VF_RecordDriver_Base + */ + public static function getRecord($id) + { + throw new Exception('Cannot get record from empty set.'); + } +} \ No newline at end of file diff --git a/module/VuFind/src/VuFind/Search/Favorites/Options.php b/module/VuFind/src/VuFind/Search/Favorites/Options.php new file mode 100644 index 0000000000000000000000000000000000000000..6dd725d48acf25c8606055ce1a04cb13681fe446 --- /dev/null +++ b/module/VuFind/src/VuFind/Search/Favorites/Options.php @@ -0,0 +1,66 @@ +<?php +/** + * Favorites aspect of the Search Multi-class (Options) + * + * 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 SearchObject + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://vufind.org Main Site + */ + +/** + * Search Favorites Options + * + * @category VuFind2 + * @package SearchObject + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://vufind.org Main Site + */ +class VF_Search_Favorites_Options extends VF_Search_Base_Options +{ + /** + * Constructor + * + * @return void + */ + public function __construct() + { + parent::__construct(); + + $this->defaultSort = 'title'; + $this->sortOptions = array( + 'title' => 'sort_title', 'author' => 'sort_author', + 'year DESC' => 'sort_year', 'year' => 'sort_year asc' + ); + } + + /** + * Return an array describing the action used for rendering search results + * (same format as expected by the URL view helper). + * + * @return array + */ + public function getSearchAction() + { + return array('controller' => 'MyResearch', 'action' => 'Favorites'); + } +} \ No newline at end of file diff --git a/module/VuFind/src/VuFind/Search/Favorites/Params.php b/module/VuFind/src/VuFind/Search/Favorites/Params.php new file mode 100644 index 0000000000000000000000000000000000000000..15582743fb0298f72615ab74f827b33fa5707fd4 --- /dev/null +++ b/module/VuFind/src/VuFind/Search/Favorites/Params.php @@ -0,0 +1,82 @@ +<?php +/** + * Favorites aspect of the Search Multi-class (Params) + * + * 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 SearchObject + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://vufind.org Main Site + */ + +/** + * Search Favorites Parameters + * + * @category VuFind2 + * @package SearchObject + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://vufind.org Main Site + */ +class VF_Search_Favorites_Params extends VF_Search_Base_Params +{ + /** + * Constructor + * + * @param VF_Search_Base_Options $options Options to use (null to load defaults) + */ + public function __construct($options = null) + { + parent::__construct($options); + $this->recommendationsEnabled(true); + } + + /** + * Load all recommendation settings from the relevant ini file. Returns an + * associative array where the key is the location of the recommendations (top + * or side) and the value is the settings found in the file (which may be either + * a single string or an array of strings). + * + * @return array associative: location (top/side) => search settings + */ + protected function getRecommendationSettings() + { + return array('side' => 'FavoriteFacets'); + } + + /** + * Add filters to the object based on values found in the request object. + * + * @param Zend_Controller_Request_Abstract $request A Zend request object. + * + * @return void + */ + protected function initFilters($request) + { + // Special filter -- if the "id" parameter is set, limit to a specific list: + $id = $request->getParam('id'); + if (!empty($id)) { + $this->addFilter("lists:{$id}"); + } + + // Otherwise use standard parent behavior: + return parent::initFilters($request); + } +} \ No newline at end of file diff --git a/module/VuFind/src/VuFind/Search/Favorites/Results.php b/module/VuFind/src/VuFind/Search/Favorites/Results.php new file mode 100644 index 0000000000000000000000000000000000000000..b687f924af02b9e7c53c1b9faa3cc2a434b2b14d --- /dev/null +++ b/module/VuFind/src/VuFind/Search/Favorites/Results.php @@ -0,0 +1,209 @@ +<?php +/** + * Favorites aspect of the Search Multi-class (Results) + * + * 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 SearchObject + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://vufind.org Main Site + */ + +/** + * Search Favorites Results + * + * @category VuFind2 + * @package SearchObject + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://vufind.org Main Site + */ +class VF_Search_Favorites_Results extends VF_Search_Base_Results +{ + protected $user = null; + protected $list = false; + + /** + * Returns the stored list of facets for the last search + * + * @param array $filter Array of field => on-screen description listing + * all of the desired facet fields; set to null to get all configured values. + * + * @return array Facets data arrays + */ + public function getFacetList($filter = null) + { + // Make sure we have processed the search before proceeding: + if (is_null($this->user)) { + $this->performAndProcessSearch(); + } + + // If there is no filter, we'll use all facets as the filter: + if (is_null($filter)) { + $filter = $this->params->getFacetConfig(); + } + + // Start building the facet list: + $retVal = array(); + + // Loop through every requested field: + $validFields = array_keys($filter); + foreach ($validFields as $field) { + if (!isset($this->facets[$field])) { + $this->facets[$field] = array( + 'label' => $this->params->getFacetLabel($field), + 'list' => array() + ); + switch ($field) { + case 'lists': + $lists = $this->user ? $this->user->getLists() : array(); + foreach ($lists as $list) { + $this->facets[$field]['list'][] = array( + 'value' => $list->id, + 'displayText' => $list->title, + 'count' => $list->cnt, + 'isApplied' => + $this->params->hasFilter("$field:".$list->id) + ); + } + break; + case 'tags': + if ($this->list) { + $tags = $this->list->getTags(); + } else { + $tags = $this->user ? $this->user->getTags() : array(); + } + foreach ($tags as $tag) { + $this->facets[$field]['list'][] = array( + 'value' => $tag->tag, + 'displayText' => $tag->tag, + 'count' => $tag->cnt, + 'isApplied' => + $this->params->hasFilter("$field:".$tag->tag) + ); + } + break; + } + } + if (isset($this->facets[$field])) { + $retVal[$field] = $this->facets[$field]; + } + } + return $retVal; + } + + /** + * Support method for performAndProcessSearch -- perform a search based on the + * parameters passed to the object. + * + * @return void + */ + protected function performSearch() + { + $list = $this->getListObject(); + $account = VF_Account_Manager::getInstance(); + $this->user = $account->isLoggedIn(); + + // Make sure the user and/or list objects make it possible to view + // the current result set -- we need to check logged in status and + // list permissions. + if (is_null($list) && !$this->user) { + throw new VF_Exception_ListPermission( + 'Cannot retrieve favorites without logged in user.' + ); + } + if (!is_null($list) && !$list->public + && (!$this->user || $list->user_id != $this->user->id) + ) { + throw new VF_Exception_ListPermission( + VF_Translator::translate('list_access_denied') + ); + } + + $resource = new VuFind_Model_Db_Resource(); + $rawResults = $resource->getFavorites( + is_null($list) ? $this->user->id : $list->user_id, + isset($list->id) ? $list->id : null, + $this->getTagFilters(), $this->getSort() + ); + + // How many results were there? + $this->resultTotal = count($rawResults); + + // Retrieve record drivers for the selected items. + $end = $this->getEndRecord(); + $recordsToRequest = array(); + for ($i = $this->getStartRecord() - 1; $i < $end; $i++) { + $row = $rawResults->getRow($i); + $recordsToRequest[] = array( + 'id' => $row->record_id, 'source' => $row->source, + 'extra_fields' => array( + 'title' => $row->title + ) + ); + } + $this->results = VF_Record::loadBatch($recordsToRequest); + } + + /** + * Static method to retrieve a record by ID. Returns a record driver object. + * + * @param string $id Unique identifier of record + * + * @return VF_RecordDriver_Base + */ + public static function getRecord($id) + { + throw new Exception( + 'getRecord not supported by VF_Search_Favorites_Results' + ); + } + + /** + * Get an array of tags being applied as filters. + * + * @return array + */ + protected function getTagFilters() + { + $filters = $this->params->getFilters(); + return isset($filters['tags']) ? $filters['tags'] : array(); + } + + /** + * Get the list object associated with the current search (null if no list + * selected). + * + * @return VuFind_Model_Db_UserListRow|null + */ + public function getListObject() + { + // If we haven't previously tried to load a list, do it now: + if ($this->list === false) { + // Check the filters for a list ID, and load the corresponding object + // if one is found: + $filters = $this->params->getFilters(); + $listId = isset($filters['lists'][0]) ? $filters['lists'][0] : null; + $this->list = is_null($listId) + ? null : VuFind_Model_Db_UserList::getExisting($listId); + } + return $this->list; + } +} \ No newline at end of file diff --git a/module/VuFind/src/VuFind/Search/Memory.php b/module/VuFind/src/VuFind/Search/Memory.php new file mode 100644 index 0000000000000000000000000000000000000000..bb992063162dc2b20dccf3a8f3c7581ff7b5ce61 --- /dev/null +++ b/module/VuFind/src/VuFind/Search/Memory.php @@ -0,0 +1,80 @@ +<?php +/** + * VuFind Search Memory + * + * 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 Support_Classes + * @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 + */ + +/** + * Wrapper class to handle search memory + * + * @category VuFind2 + * @package Support_Classes + * @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 VF_Search_Memory +{ + /** + * Clear the last accessed search URL in the session. + * + * @return void + */ + public static function forgetSearch() + { + $session = new Zend_Session_Namespace('Search'); + unset($session->last); + } + + /** + * Store the last accessed search URL in the session for future reference. + * + * @param string $url URL to remember + * + * @return void + */ + public static function rememberSearch($url) + { + // Only remember URL if string is non-empty... otherwise clear the memory. + if (strlen(trim($url)) > 0) { + $session = new Zend_Session_Namespace('Search'); + $session->last = $url; + } else { + self::forgetSearch(); + } + } + + /** + * Retrieve last accessed search URL, if available. Returns null if no URL + * is available. + * + * @return string|null + */ + public static function retrieve() + { + $session = new Zend_Session_Namespace('Search'); + return isset($session->last) ? $session->last : null; + } +} \ No newline at end of file diff --git a/module/VuFind/src/VuFind/Search/MixedList/Options.php b/module/VuFind/src/VuFind/Search/MixedList/Options.php new file mode 100644 index 0000000000000000000000000000000000000000..fe9291a56e612608201e8d54970a1f2595ee7ab2 --- /dev/null +++ b/module/VuFind/src/VuFind/Search/MixedList/Options.php @@ -0,0 +1,50 @@ +<?php +/** + * Mixed List aspect of the Search Multi-class (Options) + * + * 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 SearchObject + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://vufind.org Main Site + */ + +/** + * Search Mixed List Options + * + * @category VuFind2 + * @package SearchObject + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://vufind.org Main Site + */ +class VF_Search_MixedList_Options extends VF_Search_Base_Options +{ + /** + * Return an array describing the action used for rendering search results + * (same format as expected by the URL view helper). + * + * @return array + */ + public function getSearchAction() + { + return array('controller' => 'Records', 'action' => 'Home'); + } +} \ No newline at end of file diff --git a/module/VuFind/src/VuFind/Search/MixedList/Params.php b/module/VuFind/src/VuFind/Search/MixedList/Params.php new file mode 100644 index 0000000000000000000000000000000000000000..42661e7833e57b331b8d224b2bf551b070a59bb5 --- /dev/null +++ b/module/VuFind/src/VuFind/Search/MixedList/Params.php @@ -0,0 +1,80 @@ +<?php +/** + * Mixed List aspect of the Search Multi-class (Params) + * + * 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 SearchObject + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://vufind.org Main Site + */ + +/** + * Search Mixed List Parameters + * + * @category VuFind2 + * @package SearchObject + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://vufind.org Main Site + */ +class VF_Search_MixedList_Params extends VF_Search_Base_Params +{ + protected $recordsToRequest; + + /** + * Initialize the object's search settings from a request object. + * + * @param Zend_Controller_Request_Abstract $request A Zend request object. + * + * @return void + */ + protected function initSearch($request) + { + $this->recordsToRequest = $request->getParam('id', array()); + + // We always want to display the entire list as one page: + $this->setLimit(count($this->recordsToRequest)); + } + + /** + * Get list of records to display. + * + * @return array + */ + public function getRecordsToRequest() + { + return $this->recordsToRequest; + } + + /** + * Load all recommendation settings from the relevant ini file. Returns an + * associative array where the key is the location of the recommendations (top + * or side) and the value is the settings found in the file (which may be either + * a single string or an array of strings). + * + * @return array associative: location (top/side) => search settings + */ + protected function getRecommendationSettings() + { + // No recommendation modules in mixed list view currently: + return array(); + } +} \ No newline at end of file diff --git a/module/VuFind/src/VuFind/Search/MixedList/Results.php b/module/VuFind/src/VuFind/Search/MixedList/Results.php new file mode 100644 index 0000000000000000000000000000000000000000..74be45f2e2eab176f92044947fd90432e2371c98 --- /dev/null +++ b/module/VuFind/src/VuFind/Search/MixedList/Results.php @@ -0,0 +1,80 @@ +<?php +/** + * Mixed List aspect of the Search Multi-class (Results) + * + * 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 SearchObject + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://vufind.org Main Site + */ + +/** + * Search Mixed List Results + * + * @category VuFind2 + * @package SearchObject + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://vufind.org Main Site + */ +class VF_Search_MixedList_Results extends VF_Search_Base_Results +{ + /** + * Returns the stored list of facets for the last search + * + * @param array $filter Array of field => on-screen description listing + * all of the desired facet fields; set to null to get all configured values. + * + * @return array Facets data arrays + */ + public function getFacetList($filter = null) + { + // Facets not supported here: + return array(); + } + + /** + * Support method for performAndProcessSearch -- perform a search based on the + * parameters passed to the object. + * + * @return void + */ + protected function performSearch() + { + $recordsToRequest = $this->params->getRecordsToRequest(); + $this->results = VF_Record::loadBatch($recordsToRequest); + $this->resultTotal = count($this->results); + } + + /** + * Static method to retrieve a record by ID. Returns a record driver object. + * + * @param string $id Unique identifier of record + * + * @return VF_RecordDriver_Base + */ + public static function getRecord($id) + { + throw new Exception( + 'getRecord not supported by VF_Search_MixedList_Results' + ); + } +} \ No newline at end of file diff --git a/module/VuFind/src/VuFind/Search/Options.php b/module/VuFind/src/VuFind/Search/Options.php new file mode 100644 index 0000000000000000000000000000000000000000..a5e0b189372ce9e23d351cb99031262614344972 --- /dev/null +++ b/module/VuFind/src/VuFind/Search/Options.php @@ -0,0 +1,72 @@ +<?php +/** + * Instance store for obtaining default search options objects. + * + * 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 SearchObject + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://vufind.org Main Site + */ +namespace VuFind\Search; + +/** + * Instance store for obtaining default search options objects. + * + * @category VuFind2 + * @package SearchObject + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://vufind.org Main Site + */ +class Options +{ + /** + * Basic get + * + * @param string $type The search type of the object to retrieve + * + * @return VF_Search_Base_Options + */ + public static function getInstance($type) + { + static $store = array(); + + if (!isset($store[$type])) { + $class = 'VuFind\\Search\\' . $type . '\\Options'; + $store[$type] = new $class(); + } + return $store[$type]; + } + + /** + * Extract the name of the search class family from a class name. + * + * @param string $className Class name to examine. + * + * @return string + */ + public static function extractSearchClassId($className) + { + // Parse identifier out of class name of format VuFind\Search\[id]\Params: + $class = explode('\\', $className); + return $class[2]; + } +} \ No newline at end of file diff --git a/module/VuFind/src/VuFind/Search/ResultScroller.php b/module/VuFind/src/VuFind/Search/ResultScroller.php new file mode 100644 index 0000000000000000000000000000000000000000..8dd8ecac659b5d24a8251545429fae6834800f86 --- /dev/null +++ b/module/VuFind/src/VuFind/Search/ResultScroller.php @@ -0,0 +1,347 @@ +<?php +/** + * Class for managing "next" and "previous" navigation within result sets. + * + * 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 Support_Classes + * @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/system_classes Wiki + */ + +/** + * Class for managing "next" and "previous" navigation within result sets. + * + * @category VuFind2 + * @package Support_Classes + * @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/system_classes Wiki + */ +class VF_Search_ResultScroller +{ + protected $enabled; + protected $data; + + /** + * Constructor. Create a new search result scroller. + */ + public function __construct() + { + // Is this functionality enabled in config.ini? + $config = VF_Config_Reader::getConfig(); + $this->enabled = (isset($config->Record->next_prev_navigation) + && $config->Record->next_prev_navigation); + + // Set up session namespace for the class. + $this->data = new Zend_Session_Namespace('ResultScroller'); + } + + /** + * Initialize this result set scroller. This should only be called + * prior to displaying the results of a new search. + * + * @param VF_Search_Base_Results $searchObject The search object that was + * used to execute the last search. + * + * @return bool + */ + public function init($searchObject) + { + // Do nothing if disabled: + if (!$this->enabled) { + return; + } + + // Save the details of this search in the session + $this->data->searchId = $searchObject->getSearchId(); + $this->data->page = $searchObject->getPage(); + $this->data->limit = $searchObject->getLimit(); + $this->data->total = $searchObject->getResultTotal(); + + // save the IDs of records on the current page to the session + // so we can "slide" from one record to the next/previous records + // spanning 2 consecutive pages + $this->data->currIds = $this->fetchPage($searchObject); + + // clear the previous/next page + unset($this->data->prevIds); + unset($this->data->nextIds); + + return true; + } + + /** + * Get the previous/next record in the last search + * result set relative to the current one, also return + * the position of the current record in the result set. + * Return array('previousRecord'=>previd, 'nextRecord'=>nextid, + * 'currentPosition'=>number, 'resultTotal'=>number). + * + * @param string $id The ID currently being displayed + * + * @return array + */ + public function getScrollData($id) + { + $retVal = array( + 'previousRecord'=>null, + 'nextRecord'=>null, + 'currentPosition'=>null, + 'resultTotal'=>null); + + // Do nothing if disabled: + if (!$this->enabled) { + return $retVal; + } + + if (isset($this->data->currIds) && isset($this->data->searchId)) { + // we need to restore the last search object + // to fetch either the previous/next page of results + $lastSearch = $this->restoreLastSearch(); + + // give up if we can not restore the last search + if (!$lastSearch) { + return $retVal; + } + + if (!isset($this->data->prevIds)) { + $this->data->prevIds = null; + } + if (!isset($this->data->nextIds)) { + $this->data->nextIds = null; + } + $retVal['resultTotal'] + = isset($this->data->total) ? $this->data->total : 0; + + // find where this record is in the current result page + $pos = array_search($id, $this->data->currIds); + if ($pos !== false) { + // OK, found this record in the current result page + // calculate it's position relative to the result set + $retVal['currentPosition'] + = ($this->data->page - 1) * $this->data->limit + $pos + 1; + + // count how many records in the current result page + $count = count($this->data->currIds); + + // if the current record is somewhere in the middle of the current + // page, ie: not first or last, then it is easy + if ($pos > 0 && $pos < $count - 1) { + $retVal['previousRecord'] = $this->data->currIds[$pos - 1]; + $retVal['nextRecord'] = $this->data->currIds[$pos + 1]; + // and we're done + return $retVal; + } + + // if this record is first record on the current page + if ($pos == 0) { + // if the current page is NOT the first page, and + // the previous page has not been fetched before, then + // fetch the previous page + if ($this->data->page > 1 + && $this->data->prevIds == null + ) { + $this->data->prevIds = $this->fetchPage( + $lastSearch, $this->data->page - 1 + ); + } + + // if there is something on the previous page, then the previous + // record is the last record on the previous page + if (!empty($this->data->prevIds)) { + $retVal['previousRecord'] + = $this->data->prevIds[count($this->data->prevIds) - 1]; + } + + // if it is not the last record on the current page, then + // we also have a next record on the current page + if ($pos < $count - 1) { + $retVal['nextRecord'] = $this->data->currIds[$pos + 1]; + } + + // and we're done + return $retVal; + } + + // if this record is last record on the current page + if ($pos == $count - 1) { + // if the next page has not been fetched, then + // fetch the next page + if ($this->data->nextIds == null) { + $this->data->nextIds = $this->fetchPage( + $lastSearch, $this->data->page + 1 + ); + } + + // if there is something on the next page, then the next + // record is the first record on the next page + if (!empty($this->data->nextIds)) { + $retVal['nextRecord'] = $this->data->nextIds[0]; + } + + // if it is not the first record on the current page, then + // we also have a previous record on the current page + if ($pos > 0) { + $retVal['previousRecord'] = $this->data->currIds[$pos - 1]; + } + + // and we're done + return $retVal; + } + } else { + // the current record is not on the current page + + // if there is something on the previous page + if (!empty($this->data->prevIds)) { + // check if current record is on the previous page + $pos = array_search($id, $this->data->prevIds); + if ($pos !== false) { + // decrease the page in the session because + // we're now sliding into the previous page + $this->data->page--; + + // shift pages to the right + $tmp = $this->data->currIds; + $this->data->currIds = $this->data->prevIds; + $this->data->nextIds = $tmp; + $this->data->prevIds = null; + + // now we can set the previous/next record + if ($pos > 0) { + $retVal['previousRecord'] + = $this->data->currIds[$pos - 1]; + } + $retVal['nextRecord'] = $this->data->nextIds[0]; + + // recalculate the current position + $retVal['currentPosition'] + = ($this->data->page - 1) + * $this->data->limit + $pos + 1; + + // update the search URL in the session + $lastSearch->setPage($this->data->page); + $this->rememberSearch($lastSearch); + + // and we're done + return $retVal; + } + } + + // if there is something on the next page + if (!empty($this->data->nextIds)) { + // check if current record is on the next page + $pos = array_search($id, $this->data->nextIds); + if ($pos !== false) { + // increase the page in the session because + // we're now sliding into the next page + $this->data->page++; + + // shift pages to the left + $tmp = $this->data->currIds; + $this->data->currIds = $this->data->nextIds; + $this->data->prevIds = $tmp; + $this->data->nextIds = null; + + // now we can set the previous/next record + $retVal['previousRecord'] + = $this->data->prevIds[count($this->data->prevIds) - 1]; + if ($pos < count($this->data->currIds) - 1) { + $retVal['nextRecord'] = $this->data->currIds[$pos + 1]; + } + + // recalculate the current position + $retVal['currentPosition'] + = ($this->data->page - 1) + * $this->data->limit + $pos + 1; + + // update the search URL in the session + $lastSearch->setPage($this->data->page); + $this->rememberSearch($lastSearch); + + // and we're done + return $retVal; + } + } + } + } + return $retVal; + } + + /** + * Fetch the given page of results from the given search object and + * return the IDs of the records in an array. + * + * @param object $searchObject The search object to use to execute the search + * @param int $page The page number to fetch (null for current) + * + * @return array + */ + protected function fetchPage($searchObject, $page = null) + { + if (!is_null($page)) { + $searchObject->setPage($page); + $searchObject->performAndProcessSearch(); + } + + $retVal = array(); + foreach ($searchObject->getResults() as $record) { + $retVal[] = $record->getUniqueId(); + } + return $retVal; + } + + /** + * Restore the last saved search. + * + * @return VF_Search_Base_Results + */ + protected function restoreLastSearch() + { + if (isset($this->data->searchId)) { + $searchTable = new VuFind_Model_Db_Search(); + $rows = $searchTable->find($this->data->searchId); + if (count($rows) > 0) { + $search = $rows->getRow(0); + $minSO = unserialize($search->search_object); + return $minSO->deminify(); + } + } + return null; + } + + /** + * Update the remembered "last search" in the session. + * + * @param VF_Search_Base_Results $search Search object to remember. + * + * @return void + */ + protected function rememberSearch($search) + { + $details = $search->getSearchAction(); + $fc = Zend_Controller_Front::getInstance(); + $baseUrl = $fc->getBaseUrl() . '/' . + $details['controller'] . '/' . $details['action']; + VF_Search_Memory::rememberSearch( + $baseUrl . $search->getUrl()->getParams(false) + ); + } +} diff --git a/module/VuFind/src/VuFind/Search/Solr/Options.php b/module/VuFind/src/VuFind/Search/Solr/Options.php new file mode 100644 index 0000000000000000000000000000000000000000..7538ec50ec6c8b5a5f9ba78f4411f7ca30fb43db --- /dev/null +++ b/module/VuFind/src/VuFind/Search/Solr/Options.php @@ -0,0 +1,327 @@ +<?php +/** + * Solr aspect of the Search Multi-class (Options) + * + * PHP version 5 + * + * Copyright (C) Villanova University 2011. + * + * 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 SearchObject + * @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\Search\Solr; +use VuFind\Config\Reader as ConfigReader, + VuFind\Search\Base\Options as BaseOptions; + +/** + * Solr Search Options + * + * @category VuFind2 + * @package SearchObject + * @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 Options extends BaseOptions +{ + // Spelling + protected $spellingLimit = 3; + protected $dictionary = 'default'; + protected $spellSimple = false; + protected $spellSkipNumeric = true; + + // Pre-assigned filters + protected $hiddenFilters = array(); + + // Shard fields to strip + protected $solrShardsFieldsToStrip = array(); + + /** + * Constructor + * + * @return void + */ + public function __construct() + { + parent::__construct(); + + $searchSettings = ConfigReader::getConfig($this->searchIni); + if (isset($searchSettings->General->default_limit)) { + $this->defaultLimit = $searchSettings->General->default_limit; + } + if (isset($searchSettings->General->limit_options)) { + $this->limitOptions + = explode(",", $searchSettings->General->limit_options); + } + if (isset($searchSettings->General->default_sort)) { + $this->defaultSort = $searchSettings->General->default_sort; + } + if (isset($searchSettings->DefaultSortingByType) + && count($searchSettings->DefaultSortingByType) > 0 + ) { + foreach ($searchSettings->DefaultSortingByType as $key => $val) { + $this->defaultSortByHandler[$key] = $val; + } + } + if (isset($searchSettings->RSS->sort)) { + $this->rssSort = $searchSettings->RSS->sort; + } + if (isset($searchSettings->General->default_view)) { + $this->defaultView = $searchSettings->General->default_view; + } + if (isset($searchSettings->General->default_handler)) { + $this->defaultHandler = $searchSettings->General->default_handler; + } + if (isset($searchSettings->General->retain_filters_by_default)) { + $this->retainFiltersByDefault + = $searchSettings->General->retain_filters_by_default; + } + if (isset($searchSettings->Basic_Searches)) { + foreach ($searchSettings->Basic_Searches as $key => $value) { + $this->basicHandlers[$key] = $value; + } + } + if (isset($searchSettings->Advanced_Searches)) { + foreach ($searchSettings->Advanced_Searches as $key => $value) { + $this->advancedHandlers[$key] = $value; + } + } + + // Load sort preferences (or defaults if none in .ini file): + if (isset($searchSettings->Sorting)) { + foreach ($searchSettings->Sorting as $key => $value) { + $this->sortOptions[$key] = $value; + } + } else { + $this->sortOptions = array('relevance' => 'sort_relevance', + 'year' => 'sort_year', 'year asc' => 'sort_year asc', + 'callnumber' => 'sort_callnumber', 'author' => 'sort_author', + 'title' => 'sort_title'); + } + // Load view preferences (or defaults if none in .ini file): + if (isset($searchSettings->Views)) { + foreach ($searchSettings->Views as $key => $value) { + $this->viewOptions[$key] = $value; + } + } elseif (isset($searchSettings->General->default_view)) { + $this->viewOptions = array($this->defaultView => $this->defaultView); + } else { + $this->viewOptions = array('list' => 'List'); + } + + // Load facet preferences + $facetSettings = ConfigReader::getConfig($this->facetsIni); + if (isset($facetSettings->Advanced_Settings->translated_facets) + && count($facetSettings->Advanced_Settings->translated_facets) > 0 + ) { + foreach ($facetSettings->Advanced_Settings->translated_facets as $c) { + $this->translatedFacets[] = $c; + } + } + if (isset($facetSettings->Advanced_Settings->special_facets)) { + $this->specialAdvancedFacets + = $facetSettings->Advanced_Settings->special_facets; + } + + // Load Spelling preferences + $config = ConfigReader::getConfig(); + if (isset($config->Spelling->enabled)) { + $this->spellcheck = $config->Spelling->enabled; + } + if (isset($config->Spelling->limit)) { + $this->spellingLimit = $config->Spelling->limit; + } + if (isset($config->Spelling->simple)) { + $this->spellSimple = $config->Spelling->simple; + } + if (isset($config->Spelling->skip_numeric)) { + $this->spellSkipNumeric = $config->Spelling->skip_numeric; + } + + // Turn on highlighting if the user has requested highlighting or snippet + // functionality: + $highlight = !isset($searchSettings->General->highlighting) + ? false : $searchSettings->General->highlighting; + $snippet = !isset($searchSettings->General->snippets) + ? false : $searchSettings->General->snippets; + if ($highlight || $snippet) { + $this->highlight = true; + } + + // Apply hidden filters: + if (isset($searchSettings->HiddenFilters)) { + foreach ($searchSettings->HiddenFilters as $field => $subfields) { + $rawFilter = $field.':'.'"'.addcslashes($subfields, '"').'"'; + $this->addHiddenFilter($rawFilter); + } + } + if (isset($searchSettings->RawHiddenFilters)) { + foreach ($searchSettings->RawHiddenFilters as $rawFilter) { + $this->addHiddenFilter($rawFilter); + } + } + + // Load autocomplete preference: + if (isset($searchSettings->Autocomplete->enabled)) { + $this->autocompleteEnabled = $searchSettings->Autocomplete->enabled; + } + + // Load shard settings + if (isset($searchSettings->IndexShards) + && !empty($searchSettings->IndexShards) + ) { + foreach ($searchSettings->IndexShards as $k => $v) { + $this->shards[$k] = $v; + } + // If we have a default from the configuration, use that... + if (isset($searchSettings->ShardPreferences->defaultChecked) + && !empty($searchSettings->ShardPreferences->defaultChecked) + ) { + $defaultChecked + = is_object($searchSettings->ShardPreferences->defaultChecked) + ? $searchSettings->ShardPreferences->defaultChecked->toArray() + : array($searchSettings->ShardPreferences->defaultChecked); + foreach ($defaultChecked as $current) { + $this->defaultSelectedShards[] = $current; + } + } else { + // If no default is configured, use all shards... + $this->defaultSelectedShards = array_keys($this->shards); + } + // Apply checkbox visibility setting if applicable: + if (isset($searchSettings->ShardPreferences->showCheckboxes)) { + $this->visibleShardCheckboxes + = $searchSettings->ShardPreferences->showCheckboxes; + } + // Apply field stripping if applicable: + if (isset($searchSettings->StripFields)) { + foreach ($searchSettings->StripFields as $k => $v) { + $this->solrShardsFieldsToStrip[$k] = $v; + } + } + } + } + + /** + * Add a hidden (i.e. not visible in facet controls) filter query to the object. + * + * @param string $fq Filter query for Solr. + * + * @return void + */ + public function addHiddenFilter($fq) + { + $this->hiddenFilters[] = $fq; + } + + /** + * Get an array of hidden filters. + * + * @return array + */ + public function getHiddenFilters() + { + return $this->hiddenFilters; + } + + /** + * Switch the spelling setting to simple + * + * @return void + */ + public function usesSimpleSpelling() + { + return $this->spellSimple; + } + + /** + * Switch the spelling dictionary to basic + * + * @return void + */ + public function useBasicDictionary() + { + $this->dictionary = 'basicSpell'; + } + + /** + * Get the selected spelling dictionary + * + * @return string + */ + public function getSpellingDictionary() + { + return $this->dictionary; + } + + + /** + * Are we skipping numeric words? + * + * @return bool + */ + public function shouldSkipNumericSpelling() + { + return $this->spellSkipNumeric; + } + + + /** + * Get the selected spelling dictionary + * + * @return int + */ + public function getSpellingLimit() + { + return $this->spellingLimit; + } + + /** + * Return an array describing the action used for rendering search results + * (same format as expected by the URL view helper). + * + * @return array + */ + public function getSearchAction() + { + return array('controller' => 'Search', 'action' => 'Results'); + } + + /** + * Return an array describing the action used for performing advanced searches + * (same format as expected by the URL view helper). Return false if the feature + * is not supported. + * + * @return array|bool + */ + public function getAdvancedSearchAction() + { + return array('controller' => 'Search', 'action' => 'Advanced'); + } + + /** + * Get details on which Solr fields to strip when sharding is active. + * + * @return array + */ + public function getSolrShardsFieldsToStrip() + { + return $this->solrShardsFieldsToStrip; + } +} \ No newline at end of file diff --git a/module/VuFind/src/VuFind/Search/Solr/Params.php b/module/VuFind/src/VuFind/Search/Solr/Params.php new file mode 100644 index 0000000000000000000000000000000000000000..23b37b7d182357ae45ec9e1cadf9603b32d2e44c --- /dev/null +++ b/module/VuFind/src/VuFind/Search/Solr/Params.php @@ -0,0 +1,400 @@ +<?php +/** + * Solr aspect of the Search Multi-class (Params) + * + * PHP version 5 + * + * Copyright (C) Villanova University 2011. + * + * 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 SearchObject + * @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 + */ + +/** + * Solr Search Parameters + * + * @category VuFind2 + * @package SearchObject + * @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 VF_Search_Solr_Params extends VF_Search_Base_Params +{ + // Facets + protected $facetLimit = 30; + protected $facetOffset = null; + protected $facetPrefix = null; + protected $facetSort = null; + + // Override Query + protected $overrideQuery = false; + + /** + * Constructor + * + * @param VF_Search_Base_Options $options Options to use (null to load defaults) + */ + public function __construct($options = null) + { + parent::__construct($options); + + // Use basic facet limit by default, if set: + $config = VF_Config_Reader::getConfig('facets'); + if (isset($config->Results_Settings->facet_limit) + && is_numeric($config->Results_Settings->facet_limit) + ) { + $this->setFacetLimit($config->Results_Settings->facet_limit); + } + } + + /** + * Set the override query + * + * @param string $q Override query + * + * @return void + */ + public function setOverrideQuery($q) + { + $this->overrideQuery = $q; + } + + /** + * Get the override query + * + * @return string + */ + public function getOverrideQuery() + { + return $this->overrideQuery; + } + + /** + * Return the current filters as an array of strings ['field:filter'] + * + * @return array $filterQuery + */ + public function getFilterSettings() + { + // Define Filter Query + $filterQuery = $this->getHiddenFilters(); + foreach ($this->filterList as $field => $filter) { + foreach ($filter as $value) { + // Special case -- allow trailing wildcards and ranges: + if (substr($value, -1) == '*' + || preg_match('/\[[^\]]+\s+TO\s+[^\]]+\]/', $value) + ) { + $filterQuery[] = $field.':'.$value; + } else { + $filterQuery[] = $field.':"'.$value.'"'; + } + } + } + return $filterQuery; + } + + /** + * Return current facet configurations + * + * @return array $facetSet + */ + public function getFacetSettings() + { + // Build a list of facets we want from the index + $facetSet = array(); + if (!empty($this->facetConfig)) { + $facetSet['limit'] = $this->facetLimit; + foreach ($this->facetConfig as $facetField => $facetName) { + $facetSet['field'][] = $facetField; + } + if ($this->facetOffset != null) { + $facetSet['offset'] = $this->facetOffset; + } + if ($this->facetPrefix != null) { + $facetSet['prefix'] = $this->facetPrefix; + } + if ($this->facetSort != null) { + $facetSet['sort'] = $this->facetSort; + } + } + return $facetSet; + } + + /** + * Initialize the object's search settings from a request object. + * + * @param Zend_Controller_Request_Abstract $request A Zend request object. + * + * @return void + */ + protected function initSearch($request) + { + // Special case -- did we get a list of IDs instead of a standard query? + $ids = $request->getParam('overrideIds', null); + if (is_array($ids) && !empty($ids)) { + $this->setQueryIDs($ids); + } else { + // Use standard initialization: + parent::initSearch($request); + + // Another special case -- are we doing a tag search? + $tag = $request->getParam('tag', ''); + if (!empty($tag)) { + $this->setBasicSearch($tag, 'tag'); + } + if ($this->getSearchHandler() == 'tag') { + $this->initTagSearch(); + } + } + } + + /** + * Special case -- set up a tag-based search. + * + * @return void + */ + public function initTagSearch() + { + $table = new VuFind_Model_Db_Tags(); + $tag = $table->getByText($this->getDisplayQuery()); + if (!empty($tag)) { + $rawResults = $tag->getResources('VuFind'); + } else { + $rawResults = array(); + } + $ids = array(); + foreach ($rawResults as $current) { + $ids[] = $current->record_id; + } + $this->setQueryIDs($ids); + } + + /** + * Set Facet Limit + * + * @param int $l the new limit value + * + * @return void + */ + public function setFacetLimit($l) + { + $this->facetLimit = $l; + } + + /** + * Set Facet Offset + * + * @param int $o the new offset value + * + * @return void + */ + public function setFacetOffset($o) + { + $this->facetOffset = $o; + } + + /** + * Set Facet Prefix + * + * @param string $p the new prefix value + * + * @return void + */ + public function setFacetPrefix($p) + { + $this->facetPrefix = $p; + } + + /** + * Set Facet Sorting + * + * @param string $s the new sorting action value + * + * @return void + */ + public function setFacetSort($s) + { + $this->facetSort = $s; + } + + /** + * Initialize facet settings for the advanced search screen. + * + * @return void + */ + public function initAdvancedFacets() + { + $config = VF_Config_Reader::getConfig('facets'); + if (isset($config->Advanced)) { + foreach ($config->Advanced as $key => $value) { + $this->addFacet($key, $value); + } + } + if (isset($config->Advanced_Settings->facet_limit) + && is_numeric($config->Advanced_Settings->facet_limit) + ) { + $this->setFacetLimit($config->Advanced_Settings->facet_limit); + } + } + + /** + * Initialize facet settings for the standard search screen. + * + * @return void + */ + public function initBasicFacets() + { + $config = VF_Config_Reader::getConfig('facets'); + if (isset($config->ResultsTop)) { + foreach ($config->ResultsTop as $key => $value) { + $this->addFacet($key, $value); + } + } + if (isset($config->Results)) { + foreach ($config->Results as $key => $value) { + $this->addFacet($key, $value); + } + } + } + + /** + * Adapt the search query to a spelling query + * + * @return string Spelling query + */ + protected function buildSpellingQuery() + { + if ($this->searchType == 'advanced') { + return $this->extractAdvancedTerms(); + } + return $this->getDisplayQuery(); + } + + /** + * Get Spelling Query + * + * @return string + */ + public function getSpellingQuery() + { + // Build our spellcheck query + if ($this->options->spellcheckEnabled()) { + if ($this->options->usesSimpleSpelling()) { + $this->options->useBasicDictionary(); + } + $spellcheck = $this->buildSpellingQuery(); + + // If the spellcheck query is purely numeric, skip it if + // the appropriate setting is turned on. + if ($this->options->shouldSkipNumericSpelling() + && is_numeric($spellcheck) + ) { + return ''; + } + return $spellcheck; + } + return ''; + } + + /** + * Load all available facet settings. This is mainly useful for showing + * appropriate labels when an existing search has multiple filters associated + * with it. + * + * @param string $preferredSection Section to favor when loading settings; if + * multiple sections contain the same facet, this section's description will + * be favored. + * + * @return void + */ + public function activateAllFacets($preferredSection = false) + { + // Based on preference, change the order of initialization to make sure + // that preferred facet labels come in last. + if ($preferredSection == 'Advanced') { + $this->initBasicFacets(); + $this->initAdvancedFacets(); + } else { + $this->initAdvancedFacets(); + $this->initBasicFacets(); + } + } + + /** + * Add filters to the object based on values found in the request object. + * + * @param Zend_Controller_Request_Abstract $request A Zend request object. + * + * @return void + */ + protected function initFilters($request) + { + // Use the default behavior of the parent class, but add support for the + // special illustrations filter. + parent::initFilters($request); + switch ($request->getParam('illustration', -1)) { + case 1: + $this->addFilter('illustrated:Illustrated'); + break; + case 0: + $this->addFilter('illustrated:"Not Illustrated"'); + break; + } + + // Check for hidden filters: + $hidden = $request->getParam('hiddenFilters'); + if (!empty($hidden) && is_array($hidden)) { + foreach ($hidden as $current) { + $this->addHiddenFilter($current); + } + } + } + + /** + * Override the normal search behavior with an explicit array of IDs that must + * be retrieved. + * + * @param array $ids Record IDs to load + * + * @return void + */ + public function setQueryIDs($ids) + { + // No need for spell checking on an ID query! + $this->options->spellcheckEnabled(false); + for ($i = 0; $i < count($ids); $i++) { + $ids[$i] = '"' . addcslashes($ids[$i], '"') . '"'; + } + $this->setOverrideQuery('id:(' . implode(' OR ', $ids) . ')'); + } + + /** + * Get the maximum number of IDs that may be sent to setQueryIDs (-1 for no + * limit). + * + * @return int + */ + public function getQueryIDLimit() + { + $config = VF_Config_Reader::getConfig(); + return isset($config->Index->maxBooleanClauses) + ? $config->Index->maxBooleanClauses : 1024; + } +} \ No newline at end of file diff --git a/module/VuFind/src/VuFind/Search/Solr/Results.php b/module/VuFind/src/VuFind/Search/Solr/Results.php new file mode 100644 index 0000000000000000000000000000000000000000..c621177ba95a3bd752368376401b74c9e70bd21a --- /dev/null +++ b/module/VuFind/src/VuFind/Search/Solr/Results.php @@ -0,0 +1,584 @@ +<?php +/** + * Solr aspect of the Search Multi-class (Results) + * + * PHP version 5 + * + * Copyright (C) Villanova University 2011. + * + * 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 SearchObject + * @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 + */ + +/** + * Solr Search Parameters + * + * @category VuFind2 + * @package SearchObject + * @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 VF_Search_Solr_Results extends VF_Search_Base_Results +{ + // Raw Solr search response: + protected $rawResponse = null; + + /** + * Get a connection to the Solr index. + * + * @param null|array $shards Selected shards to use (null for defaults) + * @param string $index ID of index/search classes to use (this assumes + * that VF_Search_$index_Options and VF_Connection_$index are both valid classes) + * + * @return VF_Connection_Solr + */ + public static function getSolrConnection($shards = null, $index = 'Solr') + { + // Turn on all shards by default if none are specified (since we may get + // called in a static context by getRecord(), we need to be sure that any + // given ID will yield results, even if not all shards are on by default). + $options = VF_Search_Options::getInstance($index); + $allShards = $options->getShards(); + if (is_null($shards)) { + $shards = array_keys($allShards); + } + + // If we have selected shards, we need to format them: + if (!empty($shards)) { + $selectedShards = array(); + foreach ($shards as $current) { + $selectedShards[$current] = $allShards[$current]; + } + $shards = $selectedShards; + } + + // Connect to Solr and set up shards: + $solr = VF_Connection_Manager::connectToIndex($index); + $solr->setShards($shards, $options->getSolrShardsFieldsToStrip()); + return $solr; + } + + /** + * Support method for performAndProcessSearch -- perform a search based on the + * parameters passed to the object. + * + * @return void + */ + protected function performSearch() + { + $solr = static::getSolrConnection($this->getSelectedShards()); + + // Collect the search parameters: + $overrideQuery = $this->getOverrideQuery(); + $params = array( + 'query' => !empty($overrideQuery) + ? $overrideQuery : $solr->buildQuery($this->getSearchTerms()), + 'handler' => $this->getSearchHandler(), + // Account for reserved VuFind word 'relevance' (which really means + // "no sort parameter in Solr"): + 'sort' => $this->getSort() == 'relevance' ? null : $this->getSort(), + 'start' => $this->getStartRecord() - 1, + 'limit' => $this->getLimit(), + 'facet' => $this->params->getFacetSettings(), + 'filter' => $this->params->getFilterSettings(), + 'spell' => $this->params->getSpellingQuery(), + 'dictionary' => $this->getSpellingDictionary(), + 'highlight' => $this->highlightEnabled() + ); + + // Perform the search: + $this->rawResponse = $solr->search($params); + + // How many results were there? + $this->resultTotal = isset($this->rawResponse['response']['numFound']) + ? $this->rawResponse['response']['numFound'] : 0; + + // Process spelling suggestions if no index error resulted from the query + if ($this->spellcheckEnabled()) { + // Shingle dictionary + $this->processSpelling(); + // Make sure we don't endlessly loop + if ($this->getSpellingDictionary() == 'default') { + // Expand against the basic dictionary + $this->basicSpelling(); + } + } + + // Construct record drivers for all the items in the response: + $this->results = array(); + for ($x = 0; $x < count($this->rawResponse['response']['docs']); $x++) { + $this->results[] = static::initRecordDriver( + $this->rawResponse['response']['docs'][$x] + ); + } + } + + /** + * Process spelling suggestions from the results object + * + * @return void + */ + protected function processSpelling() + { + // Do nothing if there are no suggestions + $suggestions = isset($this->rawResponse['spellcheck']['suggestions']) ? + $this->rawResponse['spellcheck']['suggestions'] : array(); + if (count($suggestions) == 0) { + return; + } + + // Loop through the array of search terms we have suggestions for + $suggestionList = array(); + foreach ($suggestions as $suggestion) { + $ourTerm = $suggestion[0]; + + // Skip numeric terms if numeric suggestions are disabled + if ($this->shouldSkipNumericSpelling() && is_numeric($ourTerm)) { + continue; + } + + $ourHit = $suggestion[1]['origFreq']; + $count = $suggestion[1]['numFound']; + $newList = $suggestion[1]['suggestion']; + + $validTerm = true; + + // Make sure the suggestion is for a valid search term. + // Sometimes shingling will have bridged two search fields (in + // an advanced search) or skipped over a stopword. + if (!$this->findSearchTerm($ourTerm)) { + $validTerm = false; + } + + // Unless this term had no hits + if ($ourHit != 0) { + // Filter out suggestions we are already using + $newList = $this->filterSpellingTerms($newList); + } + + // Make sure it has suggestions and is valid + if (count($newList) > 0 && $validTerm) { + // Did we get more suggestions then our limit? + if ($count > $this->getSpellingLimit()) { + // Cut the list at the limit + array_splice($newList, $this->getSpellingLimit()); + } + $suggestionList[$ourTerm]['freq'] = $ourHit; + // Format the list nicely + foreach ($newList as $item) { + if (is_array($item)) { + $suggestionList[$ourTerm]['suggestions'][$item['word']] + = $item['freq']; + } else { + $suggestionList[$ourTerm]['suggestions'][$item] = 0; + } + } + } + } + $this->suggestions = $suggestionList; + } + + /** + * Filter a list of spelling suggestions to remove suggestions + * we are already searching for + * + * @param array $termList List of suggestions + * + * @return array Filtered list + */ + protected function filterSpellingTerms($termList) + { + $newList = array(); + if (count($termList) == 0) { + return $newList; + } + + foreach ($termList as $term) { + if (!$this->findSearchTerm($term['word'])) { + $newList[] = $term; + } + } + return $newList; + } + + /** + * Try running spelling against the basic dictionary. + * This function should ensure it doesn't return + * single word suggestions that have been accounted + * for in the shingle suggestions above. + * + * @return array Suggestions array + */ + protected function basicSpelling() + { + // TODO: There might be a way to run the + // search against both dictionaries from + // inside solr. Investigate. Currently + // submitting a second search for this. + + // Create a new search object + $myClass = get_class($this); + $newParams = clone($this->params); + $newParams->useBasicDictionary(); + + // Don't waste time loading facets or highlighting/retrieving results: + $newParams->resetFacetConfig(); + $newParams->disableHighlighting(); + $newParams->setLimit(0); + $newParams->recommendationsEnabled(false); + + $newSearch = new $myClass($newParams); + + // Get the spelling results + $newList = $newSearch->getRawSuggestions(); + + // If there were no shingle suggestions + if (count($this->suggestions) == 0) { + // Just use the basic ones as provided + $this->suggestions = $newList; + } else { + // Otherwise... + // For all the new suggestions + foreach ($newList as $word => $data) { + // Check the old suggestions + $found = false; + foreach ($this->suggestions as $k => $v) { + // Make sure it wasn't part of a shingle + // which has been suggested at a higher + // level. + $found = preg_match("/\b$word\b/", $k) ? true : $found; + } + if (!$found) { + $this->suggestions[$word] = $data; + } + } + } + } + + /** + * Turn the list of spelling suggestions into an array of urls + * for on-screen use to implement the suggestions. + * + * @return array Spelling suggestion data arrays + */ + public function getSpellingSuggestions() + { + $returnArray = array(); + $suggestions = $this->getRawSuggestions(); + $tokens = $this->spellingTokens($this->params->getSpellingQuery()); + + foreach ($suggestions as $term => $details) { + // Find out if our suggestion is part of a token + $inToken = false; + $targetTerm = ""; + foreach ($tokens as $token) { + // TODO - Do we need stricter matching here? + // Similar to that in replaceSearchTerm()? + if (stripos($token, $term) !== false) { + $inToken = true; + // We need to replace the whole token + $targetTerm = $token; + // Go and replace this token + $returnArray = $this->doSpellingReplace( + $term, $targetTerm, $inToken, $details, $returnArray + ); + } + } + // If no tokens were found, just look for the suggestion 'as is' + if ($targetTerm == "") { + $targetTerm = $term; + $returnArray = $this->doSpellingReplace( + $term, $targetTerm, $inToken, $details, $returnArray + ); + } + } + return $returnArray; + } + + /** + * Process one instance of a spelling replacement and modify the return + * data structure with the details of what was done. + * + * @param string $term The actually term we're replacing + * @param string $targetTerm The term above, or the token it is inside + * @param bool $inToken Flag for whether the token or term is used + * @param array $details The spelling suggestions + * @param array $returnArray Return data structure so far + * + * @return array $returnArray modified + */ + protected function doSpellingReplace($term, $targetTerm, $inToken, $details, + $returnArray + ) { + $config = VF_Config_Reader::getConfig(); + + $returnArray[$targetTerm]['freq'] = $details['freq']; + foreach ($details['suggestions'] as $word => $freq) { + // If the suggested word is part of a token + if ($inToken) { + // We need to make sure we replace the whole token + $replacement = str_replace($term, $word, $targetTerm); + } else { + $replacement = $word; + } + // Do we need to show the whole, modified query? + if ($config->Spelling->phrase) { + $label = $this->getDisplayQueryWithReplacedTerm( + $targetTerm, $replacement + ); + } else { + $label = $replacement; + } + // Basic spelling suggestion data + $returnArray[$targetTerm]['suggestions'][$label] = array( + 'freq' => $freq, + 'new_term' => $replacement + ); + + // Only generate expansions if enabled in config + if ($config->Spelling->expand) { + // Parentheses differ for shingles + if (strstr($targetTerm, " ") !== false) { + $replacement = "(($targetTerm) OR ($replacement))"; + } else { + $replacement = "($targetTerm OR $replacement)"; + } + $returnArray[$targetTerm]['suggestions'][$label]['expand_term'] + = $replacement; + } + } + + return $returnArray; + } + + /** + * Returns the stored list of facets for the last search + * + * @param array $filter Array of field => on-screen description listing + * all of the desired facet fields; set to null to get all configured values. + * + * @return array Facets data arrays + */ + public function getFacetList($filter = null) + { + // Make sure we have processed the search before proceeding: + if (is_null($this->rawResponse)) { + $this->performAndProcessSearch(); + } + + // If there is no filter, we'll use all facets as the filter: + if (is_null($filter)) { + $filter = $this->params->getFacetConfig(); + } + + // Start building the facet list: + $list = array(); + + // If we have no facets to process, give up now + if (!isset($this->rawResponse['facet_counts']['facet_fields']) + || !is_array($this->rawResponse['facet_counts']['facet_fields']) + ) { + return $list; + } + + // Loop through every field returned by the result set + $validFields = array_keys($filter); + foreach ($this->rawResponse['facet_counts']['facet_fields'] + as $field => $data) { + // Skip filtered fields and empty arrays: + if (!in_array($field, $validFields) || count($data) < 1) { + continue; + } + // Initialize the settings for the current field + $list[$field] = array(); + // Add the on-screen label + $list[$field]['label'] = $filter[$field]; + // Build our array of values for this field + $list[$field]['list'] = array(); + // Should we translate values for the current facet? + $translate = in_array($field, $this->params->getTranslatedFacets()); + // Loop through values: + foreach ($data as $facet) { + // Initialize the array of data about the current facet: + $currentSettings = array(); + $currentSettings['value'] = $facet[0]; + $currentSettings['displayText'] + = $translate ? VF_Translator::translate($facet[0]) : $facet[0]; + $currentSettings['count'] = $facet[1]; + $currentSettings['isApplied'] + = $this->params->hasFilter("$field:".$facet[0]); + + // Store the collected values: + $list[$field]['list'][] = $currentSettings; + } + } + return $list; + } + + /** + * Static method to retrieve a record by ID. Returns a record driver object. + * + * @param string $id Unique identifier of record + * + * @throws VF_Exception_RecordMissing + * @return VF_RecordDriver_Base + */ + public static function getRecord($id) + { + $solr = static::getSolrConnection(); + + // Check if we need to apply hidden filters: + $options = VF_Search_Options::getInstance( + VF_Search_Options::extractSearchClassId(get_called_class()) + ); + $filters = $options->getHiddenFilters(); + $extras = empty($filters) ? array() : array('fq' => $filters); + + $record = $solr->getRecord($id, $extras); + if (empty($record)) { + throw new VF_Exception_RecordMissing( + 'Record ' . $id . ' does not exist.' + ); + } + return static::initRecordDriver($record); + } + + /** + * Static method to retrieve an array of records by ID. + * + * @param array $ids Array of unique record identifiers. + * + * @return array + */ + public static function getRecords($ids) + { + // Figure out how many records to retrieve at the same time -- + // we'll use either 100 or the ID request limit, whichever is smaller. + $params = new VF_Search_Solr_Params(); + $pageSize = $params->getQueryIDLimit(); + if ($pageSize < 1 || $pageSize > 100) { + $pageSize = 100; + } + + // Retrieve records a page at a time: + $retVal = array(); + while (count($ids) > 0) { + $currentPage = array_splice($ids, 0, $pageSize, array()); + $params->setQueryIDs($currentPage); + $params->setLimit($pageSize); + $results = new VF_Search_Solr_Results($params); + $retVal = array_merge($retVal, $results->getResults()); + } + + return $retVal; + } + + /** + * Method to retrieve records similar to the provided ID. Returns an + * array of record driver objects. + * + * @param string $id Unique identifier of record + * + * @return array + */ + public function getSimilarRecords($id) + { + $solr = static::getSolrConnection($this->getSelectedShards()); + $filters = $this->getHiddenFilters(); + $extras = empty($filters) ? array() : array('fq' => $filters); + $rawResponse = $solr->getMoreLikeThis($id, $extras); + $results = array(); + for ($x = 0; $x < count($rawResponse['response']['docs']); $x++) { + $results[] = static::initRecordDriver( + $rawResponse['response']['docs'][$x] + ); + } + return $results; + } + + /** + * Support method for _performSearch(): given an array of Solr response data, + * construct an appropriate record driver object. + * + * @param array $data Solr data + * + * @return VF_RecordDriver_Base + */ + protected static function initRecordDriver($data) + { + // Remember bad classes to prevent unnecessary file accesses. + static $badClasses = array(); + + // Determine driver path based on record type: + $driver = 'VF_RecordDriver_Solr' . ucwords($data['recordtype']); + + // If we can't load the driver, fall back to the default, index-based one: + if (isset($badClasses[$driver]) || !@class_exists($driver)) { + $badClasses[$driver] = 1; + $driver = 'VF_RecordDriver_SolrDefault'; + } + + // Build the object: + if (class_exists($driver)) { + return new $driver($data); + } + + throw new Exception('Cannot find record driver -- ' . $driver); + } + + /** + * Get complete facet counts for several index fields + * + * @param array $facetfields name of the Solr fields to return facets for + * @param bool $removeFilter Clear existing filters from selected fields (true) + * or retain them (false)? + * + * @return array an array with the facet values for each index field + */ + public function getFullFieldFacets($facetfields, $removeFilter = true) + { + $clone = clone($this); + + // Manipulate facet settings temporarily: + $clone->resetFacetConfig(); + $clone->setFacetLimit(-1); + foreach ($facetfields as $facetName) { + $clone->addFacet($facetName); + + // Clear existing filters for the selected field if necessary: + if ($removeFilter) { + $clone->removeAllFilters($facetName); + } + } + + // Do search + $result = $clone->getFacetList(); + + // Reformat into a hash: + //$returnFacets = $result['facet_counts']['facet_fields']; + foreach ($result as $key => $value) { + unset($result[$key]); + $result[$key]['data'] = $value; + } + + // Send back data: + return $result; + } +} \ No newline at end of file diff --git a/module/VuFind/src/VuFind/Search/SolrAuth/Options.php b/module/VuFind/src/VuFind/Search/SolrAuth/Options.php new file mode 100644 index 0000000000000000000000000000000000000000..12656659a1ab528940e4ed190aab01b9a4b54340 --- /dev/null +++ b/module/VuFind/src/VuFind/Search/SolrAuth/Options.php @@ -0,0 +1,75 @@ +<?php +/** + * Solr Authority aspect of the Search Multi-class (Options) + * + * PHP version 5 + * + * Copyright (C) Villanova University 2011. + * + * 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 SearchObject + * @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 + */ + +/** + * Solr Authority Search Options + * + * @category VuFind2 + * @package SearchObject + * @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 VF_Search_SolrAuth_Options extends VF_Search_Solr_Options +{ + /** + * Constructor + * + * @return void + */ + public function __construct() + { + $this->facetsIni = $this->searchIni = 'authority'; + parent::__construct(); + $this->spellcheck = false; + } + + /** + * Return an array describing the action used for rendering search results + * (same format as expected by the URL view helper). + * + * @return array + */ + public function getSearchAction() + { + return array('controller' => 'Authority', 'action' => 'Search'); + } + + /** + * Return an array describing the action used for performing advanced searches + * (same format as expected by the URL view helper). Return false if the feature + * is not supported. + * + * @return array|bool + */ + public function getAdvancedSearchAction() + { + // Not currently supported: + return false; + } +} \ No newline at end of file diff --git a/module/VuFind/src/VuFind/Search/SolrAuth/Params.php b/module/VuFind/src/VuFind/Search/SolrAuth/Params.php new file mode 100644 index 0000000000000000000000000000000000000000..aea7563f3cd44332d44341559c872bf5ebc1ef4a --- /dev/null +++ b/module/VuFind/src/VuFind/Search/SolrAuth/Params.php @@ -0,0 +1,40 @@ +<?php +/** + * Solr Authority aspect of the Search Multi-class (Params) + * + * PHP version 5 + * + * Copyright (C) Villanova University 2011. + * + * 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 SearchObject + * @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 + */ + +/** + * Solr Authority Search Parameters + * + * @category VuFind2 + * @package SearchObject + * @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 VF_Search_SolrAuth_Params extends VF_Search_Solr_Params +{ +} \ No newline at end of file diff --git a/module/VuFind/src/VuFind/Search/SolrAuth/Results.php b/module/VuFind/src/VuFind/Search/SolrAuth/Results.php new file mode 100644 index 0000000000000000000000000000000000000000..88ade212815fa5a1a281caccbdca93145031b359 --- /dev/null +++ b/module/VuFind/src/VuFind/Search/SolrAuth/Results.php @@ -0,0 +1,77 @@ +<?php +/** + * Solr Authority aspect of the Search Multi-class (Results) + * + * PHP version 5 + * + * Copyright (C) Villanova University 2011. + * + * 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 SearchObject + * @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 + */ + +/** + * Solr Authority Search Parameters + * + * @category VuFind2 + * @package SearchObject + * @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 VF_Search_SolrAuth_Results extends VF_Search_Solr_Results +{ + /** + * Constructor + * + * @param VF_Search_Base_Params $params Object representing user search + * parameters. + */ + public function __construct(VF_Search_Base_Params $params) + { + parent::__construct($params); + } + + /** + * Get a connection to the Solr index. + * + * @param null|array $shards Selected shards to use (null for defaults) + * @param string $index ID of index/search classes to use (this assumes + * that VF_Search_$index_Options and VF_Connection_$index are both valid classes) + * + * @return VF_Connection_Solr + */ + public static function getSolrConnection($shards = null, $index = 'SolrAuth') + { + return parent::getSolrConnection($shards, $index); + } + + /** + * Support method for _performSearch(): given an array of Solr response data, + * construct an appropriate record driver object. + * + * @param array $data Solr data + * + * @return VF_RecordDriver_Base + */ + protected static function initRecordDriver($data) + { + return new VF_RecordDriver_SolrAuth($data); + } +} \ No newline at end of file diff --git a/module/VuFind/src/VuFind/Search/SolrAuthor/Options.php b/module/VuFind/src/VuFind/Search/SolrAuthor/Options.php new file mode 100644 index 0000000000000000000000000000000000000000..3813a822f8778f05164807fe2c5e05c7cf6ad060 --- /dev/null +++ b/module/VuFind/src/VuFind/Search/SolrAuthor/Options.php @@ -0,0 +1,61 @@ +<?php +/** + * Author aspect of the Search Multi-class (Options) + * + * 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 SearchObject + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://vufind.org Main Site + */ + +/** + * Author Search Options + * + * @category VuFind2 + * @package SearchObject + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://vufind.org Main Site + */ +class VF_Search_SolrAuthor_Options extends VF_Search_Solr_Options +{ + /** + * Constructor + */ + public function __construct() + { + parent::__construct(); + + // No spell check needed in author module: + $this->spellcheck = false; + } + + /** + * Return an array describing the action used for rendering search results + * (same format as expected by the URL view helper). + * + * @return array + */ + public function getSearchAction() + { + return array('controller' => 'Author', 'action' => 'Home'); + } +} \ No newline at end of file diff --git a/module/VuFind/src/VuFind/Search/SolrAuthor/Params.php b/module/VuFind/src/VuFind/Search/SolrAuthor/Params.php new file mode 100644 index 0000000000000000000000000000000000000000..ee30a716d432c54ca13b7c62e44ba60cbcc86a08 --- /dev/null +++ b/module/VuFind/src/VuFind/Search/SolrAuthor/Params.php @@ -0,0 +1,107 @@ +<?php +/** + * Author aspect of the Search Multi-class (Params) + * + * 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 SearchObject + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://vufind.org Main Site + */ + +/** + * Author Search Options + * + * @category VuFind2 + * @package SearchObject + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://vufind.org Main Site + */ +class VF_Search_SolrAuthor_Params extends VF_Search_Solr_Params +{ + /** + * Support method for _initSearch() -- handle basic settings. + * + * @param Zend_Controller_Request_Abstract $request A Zend request object. + * + * @return boolean True if search settings were found, false if not. + */ + protected function initBasicSearch($request) + { + // If no lookfor parameter was found, we have no search terms to + // add to our array! + if (is_null($lookfor = $request->getParam('author'))) { + return false; + } + + // Force the search to be a phrase: + $lookfor = '"' . str_replace('"', '\"', $lookfor) . '"'; + + // Set the search (handler is always Author for this module): + $this->setBasicSearch($lookfor, 'Author'); + return true; + } + + /** + * Build a string for onscreen display showing the + * query used in the search (not the filters). + * + * @return string user friendly version of 'query' + */ + public function getDisplayQuery() + { + // For display purposes, undo the query manipulation performed above + // in initBasicSearch(): + $q = parent::getDisplayQuery(); + return str_replace('\"', '"', substr($q, 1, strlen($q) - 2)); + } + + /** + * Load all recommendation settings from the relevant ini file. Returns an + * associative array where the key is the location of the recommendations (top + * or side) and the value is the settings found in the file (which may be either + * a single string or an array of strings). + * + * @return array associative: location (top/side) => search settings + */ + protected function getRecommendationSettings() + { + // Load the necessary settings to determine the appropriate recommendations + // module: + $ss = VF_Config_Reader::getConfig($this->getSearchIni()); + + // Load the AuthorModuleRecommendations configuration if available, use + // standard defaults otherwise: + if (isset($ss->AuthorModuleRecommendations)) { + $recommend = array(); + foreach ($ss->AuthorModuleRecommendations as $section => $content) { + $recommend[$section] = array(); + foreach ($content as $current) { + $recommend[$section][] = $current; + } + } + } else { + $recommend = array('side' => array('ExpandFacets:Author')); + } + + return $recommend; + } +} \ No newline at end of file diff --git a/module/VuFind/src/VuFind/Search/SolrAuthor/Results.php b/module/VuFind/src/VuFind/Search/SolrAuthor/Results.php new file mode 100644 index 0000000000000000000000000000000000000000..7c919db68e1c89c00ef99fc5832f685ddb5f140a --- /dev/null +++ b/module/VuFind/src/VuFind/Search/SolrAuthor/Results.php @@ -0,0 +1,66 @@ +<?php +/** + * Author aspect of the Search Multi-class (Results) + * + * 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 SearchObject + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://vufind.org Main Site + */ + +/** + * Author Search Options + * + * @category VuFind2 + * @package SearchObject + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://vufind.org Main Site + */ + +class VF_Search_SolrAuthor_Results extends VF_Search_Solr_Results +{ + /** + * Constructor + * + * @param VF_Search_Base_Params $params Object representing user search + * parameters. + */ + public function __construct($params) + { + // Call parent constructor: + parent::__construct($params); + + // Set up URL helper to use appropriate search parameter: + $this->getUrl()->setBasicSearchParam('author'); + } + + /** + * Is the current search saved in the database? + * + * @return bool + */ + public function isSavedSearch() + { + // Author searches are never saved: + return false; + } +} \ No newline at end of file diff --git a/module/VuFind/src/VuFind/Search/SolrAuthorFacets/Options.php b/module/VuFind/src/VuFind/Search/SolrAuthorFacets/Options.php new file mode 100644 index 0000000000000000000000000000000000000000..b1eb87883e593662f1f0e682152085e3883f4a65 --- /dev/null +++ b/module/VuFind/src/VuFind/Search/SolrAuthorFacets/Options.php @@ -0,0 +1,70 @@ +<?php +/** + * AuthorFacets aspect of the Search Multi-class (Options) + * + * 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 SearchObject + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://vufind.org Main Site + */ + +/** + * AuthorFacets Search Options + * + * @category VuFind2 + * @package SearchObject + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://vufind.org Main Site + */ +class VF_Search_SolrAuthorFacets_Options extends VF_Search_Solr_Options +{ + /** + * Constructor + */ + public function __construct() + { + parent::__construct(); + + // Special sort options... + // It's important to remember here we are talking about on-screen + // sort values, not what is sent to Solr, since this screen + // is really using facet sorting. + $this->sortOptions = array( + 'relevance' => 'sort_author_relevance', + 'author' => 'sort_author_author' + ); + + // No spell check needed in author module: + $this->spellcheck = false; + } + + /** + * Return an array describing the action used for rendering search results + * (same format as expected by the URL view helper). + * + * @return array + */ + public function getSearchAction() + { + return array('controller' => 'Author', 'action' => 'Search'); + } +} \ No newline at end of file diff --git a/module/VuFind/src/VuFind/Search/SolrAuthorFacets/Params.php b/module/VuFind/src/VuFind/Search/SolrAuthorFacets/Params.php new file mode 100644 index 0000000000000000000000000000000000000000..a8b710b092e7b489417e1314ebe0458a872628f0 --- /dev/null +++ b/module/VuFind/src/VuFind/Search/SolrAuthorFacets/Params.php @@ -0,0 +1,110 @@ +<?php +/** + * AuthorFacets aspect of the Search Multi-class (Params) + * + * 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 SearchObject + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://vufind.org Main Site + */ + +/** + * AuthorFacets Search Parameters + * + * @category VuFind2 + * @package SearchObject + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://vufind.org Main Site + */ +class VF_Search_SolrAuthorFacets_Params extends VF_Search_Solr_Params +{ + /** + * Set parameters based on a search object + * + * @param Zend_Controller_Request_Abstract $request Zend request object + * + * @return void + */ + public function initFromRequest($request) + { + parent::initFromRequest($request); + + // Force custom facet settings: + $this->facetConfig = array(); + $this->addFacet('authorStr'); + $this->setFacetOffset(($this->getPage() - 1) * $this->getLimit()); + $this->setFacetLimit($this->getLimit() * 10); + // Sorting - defaults to off with unlimited facets, so let's + // be explicit here for simplicity. + if ($this->getSort() == 'author') { + $this->setFacetSort('index'); + } else { + $this->setFacetSort('count'); + } + } + + /** + * Support method for _initSearch() -- handle basic settings. + * + * @param Zend_Controller_Request_Abstract $request A Zend request object. + * + * @return boolean True if search settings were found, false if not. + */ + protected function initBasicSearch($request) + { + // If no lookfor parameter was found, we have no search terms to + // add to our array! + if (is_null($lookfor = $request->getParam('lookfor'))) { + return false; + } + + // Set the search (handler is always Author for this module): + $this->setBasicSearch($lookfor, 'Author'); + return true; + } + + /** + * Load all recommendation settings from the relevant ini file. Returns an + * associative array where the key is the location of the recommendations (top + * or side) and the value is the settings found in the file (which may be either + * a single string or an array of strings). + * + * @return array associative: location (top/side) => search settings + */ + protected function getRecommendationSettings() + { + // No recommendations here: + return array(); + } + + /** + * Initialize view + * + * @param Zend_Controller_Request_Abstract $request A Zend request object. + * + * @return void + */ + protected function initView($request) + { + $this->view = 'authorfacets'; + } +} \ No newline at end of file diff --git a/module/VuFind/src/VuFind/Search/SolrAuthorFacets/Results.php b/module/VuFind/src/VuFind/Search/SolrAuthorFacets/Results.php new file mode 100644 index 0000000000000000000000000000000000000000..44520312a7546ac093c1be22b51babedb6a12051 --- /dev/null +++ b/module/VuFind/src/VuFind/Search/SolrAuthorFacets/Results.php @@ -0,0 +1,83 @@ +<?php +/** + * AuthorFacets aspect of the Search Multi-class (Results) + * + * 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 SearchObject + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://vufind.org Main Site + */ + +/** + * AuthorFacets Search Results + * + * @category VuFind2 + * @package SearchObject + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://vufind.org Main Site + */ +class VF_Search_SolrAuthorFacets_Results extends VF_Search_Solr_Results +{ + /** + * Support method for performAndProcessSearch -- perform a search based on the + * parameters passed to the object. + * + * @return void + */ + protected function performSearch() + { + $solr = VF_Connection_Manager::connectToIndex(); + + // Collect the search parameters: + $params = array( + 'query' => $solr->buildQuery($this->getSearchTerms()), + 'handler' => $this->getSearchHandler(), + 'limit' => 0, + 'facet' => $this->params->getFacetSettings(), + ); + + // Perform the search: + $this->rawResponse = $solr->search($params); + + // Get the facets from which we will build our results: + $facets = $this->getFacetList(array('authorStr' => null)); + if (isset($facets['authorStr'])) { + $this->resultTotal + = (($this->getPage() - 1) * $this->getLimit()) + + count($facets['authorStr']['list']); + $this->results = array_slice( + $facets['authorStr']['list'], 0, $this->getLimit() + ); + } + } + + /** + * Is the current search saved in the database? + * + * @return bool + */ + public function isSavedSearch() + { + // Author searches are never saved: + return false; + } +} \ No newline at end of file diff --git a/module/VuFind/src/VuFind/Search/SolrReserves/Options.php b/module/VuFind/src/VuFind/Search/SolrReserves/Options.php new file mode 100644 index 0000000000000000000000000000000000000000..b13bc66edd28143381a400a4957df4982333a1b1 --- /dev/null +++ b/module/VuFind/src/VuFind/Search/SolrReserves/Options.php @@ -0,0 +1,77 @@ +<?php +/** + * Solr Reserves aspect of the Search Multi-class (Options) + * + * PHP version 5 + * + * Copyright (C) Villanova University 2011. + * + * 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 SearchObject + * @author Demian Katz <demian.katz@villanova.edu> + * @author Tuan Nguyen <tuan@yorku.ca> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://www.vufind.org Main Page + */ + +/** + * Solr Reserves Search Options + * + * @category VuFind2 + * @package SearchObject + * @author Demian Katz <demian.katz@villanova.edu> + * @author Tuan Nguyen <tuan@yorku.ca> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://www.vufind.org Main Page + */ +class VF_Search_SolrReserves_Options extends VF_Search_Solr_Options +{ + /** + * Constructor + * + * @return void + */ + public function __construct() + { + $this->facetsIni = $this->searchIni = 'reserves'; + parent::__construct(); + $this->spellcheck = false; + } + + /** + * Return an array describing the action used for rendering search results + * (same format as expected by the URL view helper). + * + * @return array + */ + public function getSearchAction() + { + return array('controller' => 'Search', 'action' => 'Reserves'); + } + + /** + * Return an array describing the action used for performing advanced searches + * (same format as expected by the URL view helper). Return false if the feature + * is not supported. + * + * @return array|bool + */ + public function getAdvancedSearchAction() + { + // Not currently supported: + return false; + } +} \ No newline at end of file diff --git a/module/VuFind/src/VuFind/Search/SolrReserves/Params.php b/module/VuFind/src/VuFind/Search/SolrReserves/Params.php new file mode 100644 index 0000000000000000000000000000000000000000..10eb95a963366008218bdf15713caafafbcc9048 --- /dev/null +++ b/module/VuFind/src/VuFind/Search/SolrReserves/Params.php @@ -0,0 +1,42 @@ +<?php +/** + * Solr Reserves aspect of the Search Multi-class (Params) + * + * PHP version 5 + * + * Copyright (C) Villanova University 2011. + * + * 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 SearchObject + * @author Demian Katz <demian.katz@villanova.edu> + * @author Tuan Nguyen <tuan@yorku.ca> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://www.vufind.org Main Page + */ + +/** + * Solr Reserves Search Parameters + * + * @category VuFind2 + * @package SearchObject + * @author Demian Katz <demian.katz@villanova.edu> + * @author Tuan Nguyen <tuan@yorku.ca> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://www.vufind.org Main Page + */ +class VF_Search_SolrReserves_Params extends VF_Search_Solr_Params +{ +} \ No newline at end of file diff --git a/module/VuFind/src/VuFind/Search/SolrReserves/Results.php b/module/VuFind/src/VuFind/Search/SolrReserves/Results.php new file mode 100644 index 0000000000000000000000000000000000000000..b5033d7286b3b6ed24a4f7d2278a2290e895caad --- /dev/null +++ b/module/VuFind/src/VuFind/Search/SolrReserves/Results.php @@ -0,0 +1,80 @@ +<?php +/** + * Solr Reserves aspect of the Search Multi-class (Results) + * + * PHP version 5 + * + * Copyright (C) Villanova University 2011. + * + * 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 SearchObject + * @author Demian Katz <demian.katz@villanova.edu> + * @author Tuan Nguyen <tuan@yorku.ca> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://www.vufind.org Main Page + */ + +/** + * Solr Reserves Search Parameters + * + * @category VuFind2 + * @package SearchObject + * @author Demian Katz <demian.katz@villanova.edu> + * @author Tuan Nguyen <tuan@yorku.ca> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://www.vufind.org Main Page + */ +class VF_Search_SolrReserves_Results extends VF_Search_Solr_Results +{ + /** + * Constructor + * + * @param VF_Search_Base_Params $params Object representing user search + * parameters. + */ + public function __construct(VF_Search_Base_Params $params) + { + parent::__construct($params); + } + + /** + * Get a connection to the Solr index. + * + * @param null|array $shards Selected shards to use (null for defaults) + * @param string $index ID of index/search classes to use (this assumes + * that VF_Search_$index_Options and VF_Connection_$index are both valid classes) + * + * @return VF_Connection_Solr + */ + public static function getSolrConnection($shards = null, + $index = 'SolrReserves' + ) { + return parent::getSolrConnection($shards, $index); + } + + /** + * Support method for _performSearch(): given an array of Solr response data, + * construct an appropriate record driver object. + * + * @param array $data Solr data + * + * @return VF_RecordDriver_Base + */ + protected static function initRecordDriver($data) + { + return new VF_RecordDriver_SolrReserves($data); + } +} \ No newline at end of file diff --git a/module/VuFind/src/VuFind/Search/Summon/Options.php b/module/VuFind/src/VuFind/Search/Summon/Options.php new file mode 100644 index 0000000000000000000000000000000000000000..b2bc186c46ef62915f00f20f916c623d40358400 --- /dev/null +++ b/module/VuFind/src/VuFind/Search/Summon/Options.php @@ -0,0 +1,153 @@ +<?php +/** + * Summon Search Options + * + * PHP version 5 + * + * Copyright (C) Villanova University 2011. + * + * 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 SearchObject + * @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 + */ + +/** + * Summon Search Options + * + * @category VuFind2 + * @package SearchObject + * @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 VF_Search_Summon_Options extends VF_Search_Base_Options +{ + protected $resultLimit = 400; + + /** + * Constructor + * + * @return void + */ + public function __construct() + { + $this->searchIni = $this->facetsIni = 'Summon'; + parent::__construct(); + + // Load facet preferences: + $facetSettings = VF_Config_Reader::getConfig($this->facetsIni); + if (isset($facetSettings->Advanced_Settings->translated_facets) + && count($facetSettings->Advanced_Facet_Settings->translated_facets) > 0 + ) { + $list = $facetSettings->Advanced_Facet_Settings->translated_facets; + foreach ($list as $c) { + $this->translatedFacets[] = $c; + } + } + if (isset($facetSettings->Advanced_Facet_Settings->special_facets)) { + $this->specialAdvancedFacets + = $facetSettings->Advanced_Facet_Settings->special_facets; + } + + // Load the search configuration file: + $searchSettings = VF_Config_Reader::getConfig($this->searchIni); + + // Set up highlighting preference + if (isset($searchSettings->General->highlighting)) { + $this->highlight = $searchSettings->General->highlighting; + } + + // Set up spelling preference + if (isset($searchSettings->Spelling->enabled)) { + $this->spellcheck = $searchSettings->Spelling->enabled; + } + + // Load search preferences: + if (isset($searchSettings->General->retain_filters_by_default)) { + $this->retainFiltersByDefault + = $searchSettings->General->retain_filters_by_default; + } + if (isset($searchSettings->General->result_limit)) { + $this->resultLimit = $searchSettings->General->result_limit; + } + + // Search handler setup: + if (isset($searchSettings->Basic_Searches)) { + foreach ($searchSettings->Basic_Searches as $key => $value) { + $this->basicHandlers[$key] = $value; + } + } + if (isset($searchSettings->Advanced_Searches)) { + foreach ($searchSettings->Advanced_Searches as $key => $value) { + $this->advancedHandlers[$key] = $value; + } + } + + // Load sort preferences: + if (isset($searchSettings->Sorting)) { + foreach ($searchSettings->Sorting as $key => $value) { + $this->sortOptions[$key] = $value; + } + } + if (isset($searchSettings->General->default_sort)) { + $this->defaultSort = $searchSettings->General->default_sort; + } + if (isset($searchSettings->DefaultSortingByType) + && count($searchSettings->DefaultSortingByType) > 0 + ) { + foreach ($searchSettings->DefaultSortingByType as $key => $val) { + $this->defaultSortByHandler[$key] = $val; + } + } + } + + /** + * Return an array describing the action used for rendering search results + * (same format as expected by the URL view helper). + * + * @return array + */ + public function getSearchAction() + { + return array('controller' => 'Summon', 'action' => 'Search'); + } + + /** + * Return an array describing the action used for performing advanced searches + * (same format as expected by the URL view helper). Return false if the feature + * is not supported. + * + * @return array|bool + */ + public function getAdvancedSearchAction() + { + return array('controller' => 'Summon', 'action' => 'Advanced'); + } + + /** + * If there is a limit to how many search results a user can access, this + * method will return that limit. If there is no limit, this will return + * -1. + * + * @return int + */ + public function getVisibleSearchResultLimit() + { + return intval($this->resultLimit); + } +} \ No newline at end of file diff --git a/module/VuFind/src/VuFind/Search/Summon/Params.php b/module/VuFind/src/VuFind/Search/Summon/Params.php new file mode 100644 index 0000000000000000000000000000000000000000..de56e03135a67683a2e0cbb519e0e5e614c5c85e --- /dev/null +++ b/module/VuFind/src/VuFind/Search/Summon/Params.php @@ -0,0 +1,126 @@ +<?php +/** + * Summon Search Parameters + * + * PHP version 5 + * + * Copyright (C) Villanova University 2011. + * + * 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 SearchObject + * @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 + */ + +/** + * Summon Search Parameters + * + * @category VuFind2 + * @package SearchObject + * @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 VF_Search_Summon_Params extends VF_Search_Base_Params +{ + protected $fullFacetSettings = array(); + protected $dateFacetSettings = array(); + + /** + * Add a field to facet on. + * + * @param string $newField Field name + * @param string $newAlias Optional on-screen display label + * + * @return void + */ + public function addFacet($newField, $newAlias = null) + { + // Save the full field name (which may include extra parameters); + // we'll need these to do the proper search using the Summon class: + if (strstr($newField, 'PublicationDate')) { + // Special case -- we don't need to send this to the Summon API, + // but we do need to set a flag so VuFind knows to display the + // date facet control. + $this->dateFacetSettings[] = 'PublicationDate'; + } else { + $this->fullFacetSettings[] = $newField; + } + + // Field name may have parameters attached -- remove them: + $parts = explode(',', $newField); + return parent::addFacet($parts[0], $newAlias); + } + + /** + * Get the full facet settings stored by addFacet -- these may include extra + * parameters needed by the search results class. + * + * @return array + */ + public function getFullFacetSettings() + { + return $this->fullFacetSettings; + } + + /** + * Get the date facet settings stored by addFacet. + * + * @return array + */ + public function getDateFacetSettings() + { + return $this->dateFacetSettings; + } + + /** + * Get a user-friendly string to describe the provided facet field. + * + * @param string $field Facet field name. + * + * @return string Human-readable description of field. + */ + public function getFacetLabel($field) + { + // The default use of "Other" for undefined facets doesn't work well with + // checkbox facets -- we'll use field names as the default within the Summon + // search object. + return isset($this->facetConfig[$field]) + ? $this->facetConfig[$field] : $field; + } + + /** + * Get information on the current state of the boolean checkbox facets. + * + * @return array + */ + public function getCheckboxFacets() + { + // Grab checkbox facet details using the standard method: + $facets = parent::getCheckboxFacets(); + + // Special case -- if we have a "holdings only" facet, we want this to + // always appear, even on the "no results" screen, since setting this + // facet actually EXPANDS the result set, rather than reducing it: + if (isset($facets['holdingsOnly'])) { + $facets['holdingsOnly']['alwaysVisible'] = true; + } + + // Return modified list: + return $facets; + } +} \ No newline at end of file diff --git a/module/VuFind/src/VuFind/Search/Summon/Results.php b/module/VuFind/src/VuFind/Search/Summon/Results.php new file mode 100644 index 0000000000000000000000000000000000000000..fbd19868b81e4ca04d1845ed96f8352e8a5d1dba --- /dev/null +++ b/module/VuFind/src/VuFind/Search/Summon/Results.php @@ -0,0 +1,316 @@ +<?php +/** + * Summon Search Results + * + * PHP version 5 + * + * Copyright (C) Villanova University 2011. + * + * 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 SearchObject + * @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 + */ + +/** + * Summon Search Parameters + * + * @category VuFind2 + * @package SearchObject + * @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 VF_Search_Summon_Results extends VF_Search_Base_Results +{ + // Raw search response: + protected $rawResponse = null; + + /** + * Get a connection to the Summon API. + * + * @return VF_Connection_Summon + */ + public static function getSummonConnection() + { + static $conn = false; + if (!$conn) { + $config = VF_Config_Reader::getConfig(); + $id = isset($config->Summon->apiId) ? $config->Summon->apiId : null; + $key = isset($config->Summon->apiKey) ? $config->Summon->apiKey : null; + $conn = new VF_Connection_Summon($id, $key); + } + return $conn; + } + + /** + * Support method for performAndProcessSearch -- perform a search based on the + * parameters passed to the object. + * + * @return void + */ + protected function performSearch() + { + // The "relevance" sort option is a VuFind reserved word; we need to make + // this null in order to achieve the desired effect with Summon: + $sort = $this->params->getSort(); + $finalSort = ($sort == 'relevance') ? null : $sort; + + // Perform the actual search + $summon = self::getSummonConnection(); + $query = new VF_Connection_Summon_Query( + $summon->buildQuery($this->getSearchTerms()), + array( + 'sort' => $finalSort, + 'pageNumber' => $this->params->getPage(), + 'pageSize' => $this->params->getLimit(), + 'didYouMean' => $this->spellcheckEnabled() + ) + ); + if ($this->highlightEnabled()) { + $query->setHighlight(true); + $query->setHighlightStart('{{{{START_HILITE}}}}'); + $query->setHighlightEnd('{{{{END_HILITE}}}}'); + } + $query->initFacets($this->params->getFullFacetSettings()); + $query->initFilters($this->params->getFilterList()); + $this->rawResponse = $summon->query($query); + + // Add fake date facets if flagged earlier; this is necessary in order + // to display the date range facet control in the interface. + $dateFacets = $this->params->getDateFacetSettings(); + if (!empty($dateFacets)) { + if (!isset($this->rawResponse['facetFields'])) { + $this->rawResponse['facetFields'] = array(); + } + foreach ($dateFacets as $dateFacet) { + $this->rawResponse['facetFields'][] = array( + 'fieldName' => 'PublicationDate', + 'displayName' => 'PublicationDate', + 'counts' => array() + ); + } + } + + // Save spelling details if they exist. + if ($this->spellcheckEnabled()) { + $this->processSpelling(); + } + + // Store relevant details from the search results: + $this->resultTotal = $this->rawResponse['recordCount']; + + // Construct record drivers for all the items in the response: + $this->results = array(); + foreach ($this->rawResponse['documents'] as $current) { + $this->results[] = self::initRecordDriver($current); + } + } + + /** + * Static method to retrieve a record by ID. Returns a record driver object. + * + * @param string $id Unique identifier of record + * + * @throws VF_Exception_RecordMissing + * @return VF_RecordDriver_Base + */ + public static function getRecord($id) + { + $summon = static::getSummonConnection(); + $record = $summon->getRecord($id); + if (empty($record) || !isset($record['documents'][0])) { + throw new VF_Exception_RecordMissing( + 'Record ' . $id . ' does not exist.' + ); + } + return static::initRecordDriver($record['documents'][0]); + } + + /** + * Support method for _performSearch(): given an array of Solr response data, + * construct an appropriate record driver object. + * + * @param array $data Raw record data + * + * @return VF_RecordDriver_Base + */ + protected static function initRecordDriver($data) + { + return new VF_RecordDriver_Summon($data); + } + + /** + * Returns the stored list of facets for the last search + * + * @param array $filter Array of field => on-screen description listing + * all of the desired facet fields; set to null to get all configured values. + * + * @return array Facets data arrays + */ + public function getFacetList($filter = null) + { + // If there is no filter, we'll use all facets as the filter: + if (is_null($filter)) { + $filter = $this->params->getFacetConfig(); + } else { + // If there is a filter, make sure the field names are properly + // stripped of extra parameters: + $oldFilter = $filter; + $filter = array(); + foreach ($oldFilter as $key => $value) { + $key = explode(',', $key); + $key = trim($key[0]); + $filter[$key] = $value; + } + } + + // We want to sort the facets to match the order in the .ini file. Let's + // create a lookup array to determine order: + $i = 0; + $order = array(); + foreach ($filter as $key => $value) { + $order[$key] = $i++; + } + + // Loop through the facets returned by Summon. + $facetResult = array(); + if (isset($this->rawResponse['facetFields']) + && is_array($this->rawResponse['facetFields']) + ) { + // Get the filter list -- we'll need to check it below: + $filterList = $this->params->getFilters(); + + foreach ($this->rawResponse['facetFields'] as $current) { + // The "displayName" value is actually the name of the field on + // Summon's side -- we'll probably need to translate this to a + // different value for actual display! + $field = $current['displayName']; + + // Is this one of the fields we want to display? If so, do work... + if (isset($filter[$field])) { + // Should we translate values for the current facet? + $translate + = in_array($field, $this->params->getTranslatedFacets()); + + // Loop through all the facet values to see if any are applied. + foreach ($current['counts'] as $facetIndex => $facetDetails) { + // Is the current field negated? If so, we don't want to + // show it -- this is currently used only for the special + // "exclude newspapers" facet: + if ($facetDetails['isNegated']) { + unset($current['counts'][$facetIndex]); + continue; + } + + // We need to check two things to determine if the current + // value is an applied filter. First, is the current field + // present in the filter list? Second, is the current value + // an active filter for the current field? + $isApplied = in_array($field, array_keys($filterList)) + && in_array( + $facetDetails['value'], $filterList[$field] + ); + + // Inject "applied" value into Summon results: + $current['counts'][$facetIndex]['isApplied'] = $isApplied; + + // Create display value: + $current['counts'][$facetIndex]['displayText'] = $translate + ? VF_Translator::translate($facetDetails['value']) + : $facetDetails['value']; + } + + // Put the current facet cluster in order based on the .ini + // settings, then override the display name again using .ini + // settings. + $i = $order[$field]; + $current['label'] = $filter[$field]; + + // Create a reference to counts called list for consistency with + // Solr output format -- this allows the facet recommendations + // modules to be shared between the Search and Summon modules. + $current['list'] = & $current['counts']; + $facetResult[$i] = $current; + } + } + } + ksort($facetResult); + + // Rewrite the sorted array with appropriate keys: + $finalResult = array(); + foreach ($facetResult as $current) { + $finalResult[$current['displayName']] = $current; + } + + return $finalResult; + } + + /** + * Process spelling suggestions from the results object + * + * @return void + */ + protected function processSpelling() + { + if (isset($this->rawResponse['didYouMeanSuggestions']) + && is_array($this->rawResponse['didYouMeanSuggestions']) + ) { + $this->suggestions = array(); + foreach ($this->rawResponse['didYouMeanSuggestions'] as $current) { + if (!isset($this->suggestions[$current['originalQuery']])) { + $this->suggestions[$current['originalQuery']] = array( + 'suggestions' => array() + ); + } + $this->suggestions[$current['originalQuery']]['suggestions'][] + = $current['suggestedQuery']; + } + } + } + + /** + * Turn the list of spelling suggestions into an array of urls + * for on-screen use to implement the suggestions. + * + * @return array Spelling suggestion data arrays + */ + public function getSpellingSuggestions() + { + $retVal = array(); + foreach ($this->getRawSuggestions() as $term => $details) { + foreach ($details['suggestions'] as $word) { + // Strip escaped characters in the search term (for example, "\:") + $term = stripcslashes($term); + $word = stripcslashes($word); + $retVal[$term]['suggestions'][$word] = array('new_term' => $word); + } + } + return $retVal; + } + + /** + * Get database recommendations from Summon, if any. + * + * @return array|bool false if no recommendations, detailed array otherwise. + */ + public function getDatabaseRecommendations() + { + return isset($this->rawResponse['recommendationLists']['database']) ? + $this->rawResponse['recommendationLists']['database'] : false; + } +} \ No newline at end of file diff --git a/module/VuFind/src/VuFind/Search/Tags/Options.php b/module/VuFind/src/VuFind/Search/Tags/Options.php new file mode 100644 index 0000000000000000000000000000000000000000..3c61609075671b38559ac043f3811c5795371a20 --- /dev/null +++ b/module/VuFind/src/VuFind/Search/Tags/Options.php @@ -0,0 +1,66 @@ +<?php +/** + * Tags aspect of the Search Multi-class (Options) + * + * 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 SearchObject + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://vufind.org Main Site + */ + +/** + * Search Tags Options + * + * @category VuFind2 + * @package SearchObject + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://vufind.org Main Site + */ +class VF_Search_Tags_Options extends VF_Search_Base_Options +{ + /** + * Constructor + * + * @return void + */ + public function __construct() + { + parent::__construct(); + $this->basicHandlers = array('tags' => 'Tag'); + $this->defaultSort = 'title'; + $this->sortOptions = array( + 'title' => 'sort_title', 'author' => 'sort_author', + 'year DESC' => 'sort_year', 'year' => 'sort_year asc' + ); + } + + /** + * Return an array describing the action used for rendering search results + * (same format as expected by the URL view helper). + * + * @return array + */ + public function getSearchAction() + { + return array('controller' => 'Tag', 'action' => 'Home'); + } +} \ No newline at end of file diff --git a/module/VuFind/src/VuFind/Search/Tags/Params.php b/module/VuFind/src/VuFind/Search/Tags/Params.php new file mode 100644 index 0000000000000000000000000000000000000000..10008e0534c5f261e5c34bc1d41baa65a1599057 --- /dev/null +++ b/module/VuFind/src/VuFind/Search/Tags/Params.php @@ -0,0 +1,53 @@ +<?php +/** + * Tags aspect of the Search Multi-class (Params) + * + * 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 SearchObject + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://vufind.org Main Site + */ + +/** + * Search Tags Parameters + * + * @category VuFind2 + * @package SearchObject + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://vufind.org Main Site + */ +class VF_Search_Tags_Params extends VF_Search_Base_Params +{ + /** + * Load all recommendation settings from the relevant ini file. Returns an + * associative array where the key is the location of the recommendations (top + * or side) and the value is the settings found in the file (which may be either + * a single string or an array of strings). + * + * @return array associative: location (top/side) => search settings + */ + protected function getRecommendationSettings() + { + // No recommendation modules in tag view currently: + return array(); + } +} \ No newline at end of file diff --git a/module/VuFind/src/VuFind/Search/Tags/Results.php b/module/VuFind/src/VuFind/Search/Tags/Results.php new file mode 100644 index 0000000000000000000000000000000000000000..2ccb7dac6618b7afed3a2449658197d826ba7b25 --- /dev/null +++ b/module/VuFind/src/VuFind/Search/Tags/Results.php @@ -0,0 +1,97 @@ +<?php +/** + * Tags aspect of the Search Multi-class (Results) + * + * 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 SearchObject + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://vufind.org Main Site + */ + +/** + * Search Tags Results + * + * @category VuFind2 + * @package SearchObject + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://vufind.org Main Site + */ +class VF_Search_Tags_Results extends VF_Search_Base_Results +{ + /** + * Support method for performAndProcessSearch -- perform a search based on the + * parameters passed to the object. + * + * @return void + */ + protected function performSearch() + { + $table = new VuFind_Model_Db_Tags(); + $tag = $table->getByText($this->getDisplayQuery()); + if (!empty($tag)) { + $rawResults = $tag->getResources(null, $this->getSort()); + } else { + $rawResults = array(); + } + + // How many results were there? + $this->resultTotal = count($rawResults); + + // Retrieve record drivers for the selected items. + $end = $this->getEndRecord(); + $recordsToRequest = array(); + for ($i = $this->getStartRecord() - 1; $i < $end; $i++) { + $row = $rawResults->getRow($i); + $recordsToRequest[] + = array('id' => $row->record_id, 'source' => $row->source); + } + $this->results = VF_Record::loadBatch($recordsToRequest); + } + + /** + * Static method to retrieve a record by ID. Returns a record driver object. + * + * @param string $id Unique identifier of record + * + * @return VF_RecordDriver_Base + */ + public static function getRecord($id) + { + throw new Exception( + 'getRecord not supported by VF_Tags_Favorites_Results' + ); + } + + /** + * Returns the stored list of facets for the last search + * + * @param array $filter Array of field => on-screen description listing + * all of the desired facet fields; set to null to get all configured values. + * + * @return array Facets data arrays + */ + public function getFacetList($filter = null) + { + // Facets not supported: + return array(); + } +} \ No newline at end of file diff --git a/module/VuFind/src/VuFind/Search/UrlHelper.php b/module/VuFind/src/VuFind/Search/UrlHelper.php new file mode 100644 index 0000000000000000000000000000000000000000..0d43f52c42be95388f335f6e7b509c905b0a8a17 --- /dev/null +++ b/module/VuFind/src/VuFind/Search/UrlHelper.php @@ -0,0 +1,450 @@ +<?php +/** + * Class to help build URLs and forms in the view based on search settings. + * + * 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 View_Helpers + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://vufind.org Main Site + */ + +/** + * Class to help build URLs and forms in the view based on search settings. + * + * @category VuFind2 + * @package View_Helpers + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://vufind.org Main Site + */ +class VF_Search_UrlHelper extends Zend_View_Helper_Abstract +{ + protected $results; + protected $basicSearchParam = 'lookfor'; + protected $defaultParams = array(); + + /** + * Constructor + * + * @param VF_Search_Base_Results $results VuFind search results object. + */ + public function __construct($results) + { + $this->results = $results; + } + + /** + * Set the name of the parameter used for basic search terms. + * + * @param string $param Parameter name to set. + * + * @return void + */ + public function setBasicSearchParam($param) + { + $this->basicSearchParam = $param; + } + + /** + * Add a parameter to the object. + * + * @param string $name Name of parameter + * @param string $value Value of parameter + * + * @return void + */ + public function setDefaultParameter($name, $value) + { + $this->defaultParams[$name] = $value; + } + + /** + * Get an array of URL parameters. + * + * @return array + */ + protected function getParamArray() + { + $params = $this->defaultParams; + + // Build all the URL parameters based on search object settings: + if ($this->results->getSearchType() == 'advanced') { + $terms = $this->results->getSearchTerms(); + if (isset($terms[0]['join'])) { + $params['join'] = $terms[0]['join']; + } + for ($i = 0; $i < count($terms); $i++) { + if (isset($terms[$i]['group'])) { + $params['bool' . $i] = array($terms[$i]['group'][0]['bool']); + for ($j = 0; $j < count($terms[$i]['group']); $j++) { + if (!isset($params['lookfor' . $i])) { + $params['lookfor' . $i] = array(); + } + if (!isset($params['type' . $i])) { + $params['type' . $i] = array(); + } + $params['lookfor'.$i][] = $terms[$i]['group'][$j]['lookfor']; + $params['type' . $i][] = $terms[$i]['group'][$j]['field']; + } + } + } + } else { + $search = $this->results->getDisplayQuery(); + if (!empty($search)) { + $params[$this->basicSearchParam] = $search; + } + $type = $this->results->getSearchHandler(); + if (!empty($type)) { + $params['type'] = $type; + } + } + $sort = $this->results->getSort(); + if (!is_null($sort) && $sort != $this->results->getDefaultSort()) { + $params['sort'] = $sort; + } + $limit = $this->results->getLimit(); + if (!is_null($limit) && $limit != $this->results->getDefaultLimit()) { + $params['limit'] = $limit; + } + $view = $this->results->getView(); + if (!is_null($view) && $view != $this->results->getDefaultView()) { + $params['view'] = $view; + } + if ($this->results->getPage() != 1) { + $params['page'] = $this->results->getPage(); + } + $filters = $this->results->getFilters(); + if (!empty($filters)) { + $params['filter'] = array(); + foreach ($filters as $field => $values) { + foreach ($values as $current) { + $params['filter'][] = $field . ':"' . $current . '"'; + } + } + } + $shards = $this->results->getSelectedShards(); + if (!empty($shards)) { + sort($shards); + $key = implode(':::', $shards); + $defaultShards = $this->results->getDefaultSelectedShards(); + sort($defaultShards); + if (implode(':::', $shards) != implode(':::', $defaultShards)) { + $params['shard'] = $shards; + } + } + + return $params; + } + + /** + * Replace a term in the search query (used for spelling replacement) + * + * @param string $from Search term to find + * @param string $to Search term to insert + * + * @return string + */ + public function replaceTerm($from, $to) + { + $newResults = clone($this->results); + $newResults->replaceSearchTerm($from, $to); + $myClass = get_class($this); + $helper = new $myClass($newResults); + return $helper->getParams(); + } + + /** + * Add a facet to the parameters. + * + * @param string $field Facet field + * @param string $value Facet value + * + * @return string + */ + public function addFacet($field, $value) + { + // Facets are just a special case of filters: + return $this->addFilter($field . ':"' . $value . '"'); + } + + /** + * Add a filter to the parameters. + * + * @param string $filter Filter to add + * + * @return string + */ + public function addFilter($filter) + { + $params = $this->getParamArray(); + + // Add the filter: + if (!isset($params['filter'])) { + $params['filter'] = array(); + } + $params['filter'][] = $filter; + + // Clear page: + unset($params['page']); + + return '?' . $this->buildQueryString($params); + } + + /** + * Get the current search parameters as a GET query. + * + * @param bool $escape Should we escape the string for use in the view? + * + * @return string + */ + public function getParams($escape = true) + { + return '?' . $this->buildQueryString($this->getParamArray(), $escape); + } + + /** + * Remove a facet from the parameters. + * + * @param string $field Facet field + * @param string $value Facet value + * @param bool $escape Should we escape the string for use in the view? + * + * @return string + */ + public function removeFacet($field, $value, $escape = true) + { + $params = $this->getParamArray(); + + // Remove the filter: + $newFilter = array(); + if (isset($params['filter']) && is_array($params['filter'])) { + foreach ($params['filter'] as $current) { + list($currentField, $currentValue) + = $this->results->parseFilter($current); + if ($currentField != $field || $currentValue != $value) { + $newFilter[] = $current; + } + } + } + if (empty($newFilter)) { + unset($params['filter']); + } else { + $params['filter'] = $newFilter; + } + + // Clear page: + unset($params['page']); + + return '?' . $this->buildQueryString($params, $escape); + } + + /** + * Remove a filter from the parameters. + * + * @param string $filter Filter to add + * @param bool $escape Should we escape the string for use in the view? + * + * @return string + */ + public function removeFilter($filter, $escape = true) + { + // Treat this as a special case of removeFacet: + list($field, $value) = $this->results->parseFilter($filter); + return $this->removeFacet($field, $value, $escape); + } + + /** + * Return HTTP parameters to render a different page of results. + * + * @param string $p New page parameter (null for NO page parameter) + * @param bool $escape Should we escape the string for use in the view? + * + * @return string + */ + public function setPage($p, $escape = true) + { + return $this->updateQueryString('page', $p, 1, $escape); + } + + /** + * Return HTTP parameters to render the current page with a different sort + * parameter. + * + * @param string $s New sort parameter (null for NO sort parameter) + * @param bool $escape Should we escape the string for use in the view? + * + * @return string + */ + public function setSort($s, $escape = true) + { + return $this->updateQueryString( + 'sort', $s, $this->results->getDefaultSort(), $escape + ); + } + + /** + * Return HTTP parameters to render the current page with a different search + * handler. + * + * @param string $handler new Handler. + * @param bool $escape Should we escape the string for use in the view? + * + * @return string + */ + public function setHandler($handler, $escape = true) + { + return $this->updateQueryString( + 'type', $handler, $this->results->getDefaultHandler(), $escape + ); + } + + /** + * Return HTTP parameters to render the current page with a different view + * parameter. + * + * Note: This is called setViewParam rather than setView to avoid a conflict + * with the Zend_View_Helper_Abstract interface. + * + * @param string $v New sort parameter (null for NO view parameter) + * @param bool $escape Should we escape the string for use in the view? + * + * @return string + */ + public function setViewParam($v, $escape = true) + { + // Because of the way view settings are stored in the session, we always + // want an explicit value here (hence null rather than default view in + // third parameter below): + return $this->updateQueryString('view', $v, null, $escape); + } + + /** + * Return HTTP parameters to render the current page with a different limit + * parameter. + * + * @param string $l New limit parameter (null for NO limit parameter) + * @param bool $escape Should we escape the string for use in the view? + * + * @return string + */ + public function setLimit($l, $escape = true) + { + return $this->updateQueryString( + 'limit', $l, $this->results->getDefaultLimit(), $escape + ); + } + + /** + * Turn the current GET parameters into a set of hidden form fields. + * + * @param array $filter Array of parameters to exclude -- key = field name, + * value = regular expression to exclude. + * + * @return string + */ + public function asHiddenFields($filter = array()) + { + $retVal = ''; + foreach ($this->getParamArray() as $paramName => $paramValue) { + if (is_array($paramValue)) { + foreach ($paramValue as $paramValue2) { + if (!$this->filtered($paramName, $paramValue2, $filter)) { + $retVal .= '<input type="hidden" name="' . + htmlspecialchars($paramName) . '[]" value="' . + htmlspecialchars($paramValue2) . '" />'; + } + } + } else { + if (!$this->filtered($paramName, $paramValue, $filter)) { + $retVal .= '<input type="hidden" name="' . + htmlspecialchars($paramName) . '" value="' . + htmlspecialchars($paramValue) . '" />'; + } + } + } + return $retVal; + } + + /** + * Support method for asHiddenFields -- are the provided field and value + * excluded by the provided filter? + * + * @param string $field Field to check + * @param string $value Regular expression to check + * @param array $filter Filter provided to asHiddenFields() above + * + * @return bool + */ + protected function filtered($field, $value, $filter) + { + return (isset($filter[$field]) && preg_match($filter[$field], $value)); + } + + /** + * Generic case of parameter rebuilding. + * + * @param string $field Field to update + * @param string $value Value to use (null to skip field entirely) + * @param string $default Default value (skip field if $value matches; null + * for no default). + * @param bool $escape Should we escape the string for use in the view? + * + * @return string + */ + protected function updateQueryString($field, $value, $default = null, + $escape = true + ) { + $params = $this->getParamArray(); + if (is_null($value) || $value == $default) { + unset($params[$field]); + } else { + $params[$field] = $value; + } + return '?' . $this->buildQueryString($params, $escape); + } + + /** + * Turn an array into a properly URL-encoded query string. This is + * equivalent to the built-in PHP http_build_query function, but it handles + * arrays in a more compact way and ensures that ampersands don't get + * messed up based on server-specific settings. + * + * @param array $a Array of parameters to turn into a GET string + * @param bool $escape Should we escape the string for use in the view? + * + * @return string + */ + protected function buildQueryString($a, $escape = true) + { + $parts = array(); + foreach ($a as $key => $value) { + if (is_array($value)) { + foreach ($value as $current) { + $parts[] = urlencode($key . '[]') . '=' . urlencode($current); + } + } else { + $parts[] = urlencode($key) . '=' . urlencode($value); + } + } + $retVal = implode('&', $parts); + return $escape ? htmlspecialchars($retVal) : $retVal; + } +} \ No newline at end of file diff --git a/module/VuFind/src/VuFind/Search/WorldCat/Options.php b/module/VuFind/src/VuFind/Search/WorldCat/Options.php new file mode 100644 index 0000000000000000000000000000000000000000..508b8bdb4009a3cea972ef9cbd82bcc04ede25ab --- /dev/null +++ b/module/VuFind/src/VuFind/Search/WorldCat/Options.php @@ -0,0 +1,106 @@ +<?php +/** + * WorldCat Search Options + * + * PHP version 5 + * + * Copyright (C) Villanova University 2011. + * + * 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 SearchObject + * @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 + */ + +/** + * WorldCat Search Options + * + * @category VuFind2 + * @package SearchObject + * @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 VF_Search_WorldCat_Options extends VF_Search_Base_Options +{ + /** + * Constructor + * + * @return void + */ + public function __construct() + { + $this->searchIni = $this->facetsIni = 'WorldCat'; + parent::__construct(); + + // Load the configuration file: + $searchSettings = VF_Config_Reader::getConfig($this->searchIni); + + // Search handler setup: + $this->defaultHandler = 'srw.kw'; + if (isset($searchSettings->Basic_Searches)) { + foreach ($searchSettings->Basic_Searches as $key => $value) { + $this->basicHandlers[$key] = $value; + } + } + if (isset($searchSettings->Advanced_Searches)) { + foreach ($searchSettings->Advanced_Searches as $key => $value) { + $this->advancedHandlers[$key] = $value; + } + } + + // Load sort preferences: + if (isset($searchSettings->Sorting)) { + foreach ($searchSettings->Sorting as $key => $value) { + $this->sortOptions[$key] = $value; + } + } + if (isset($searchSettings->General->default_sort)) { + $this->defaultSort = $searchSettings->General->default_sort; + } + if (isset($searchSettings->DefaultSortingByType) + && count($searchSettings->DefaultSortingByType) > 0 + ) { + foreach ($searchSettings->DefaultSortingByType as $key => $val) { + $this->defaultSortByHandler[$key] = $val; + } + } + } + + /** + * Return an array describing the action used for rendering search results + * (same format as expected by the URL view helper). + * + * @return array + */ + public function getSearchAction() + { + return array('controller' => 'WorldCat', 'action' => 'Search'); + } + + /** + * Return an array describing the action used for performing advanced searches + * (same format as expected by the URL view helper). Return false if the feature + * is not supported. + * + * @return array|bool + */ + public function getAdvancedSearchAction() + { + return array('controller' => 'WorldCat', 'action' => 'Advanced'); + } +} \ No newline at end of file diff --git a/module/VuFind/src/VuFind/Search/WorldCat/Params.php b/module/VuFind/src/VuFind/Search/WorldCat/Params.php new file mode 100644 index 0000000000000000000000000000000000000000..c11e2712d19639ae66517741ef9f46b76487122b --- /dev/null +++ b/module/VuFind/src/VuFind/Search/WorldCat/Params.php @@ -0,0 +1,64 @@ +<?php +/** + * WorldCat Search Parameters + * + * PHP version 5 + * + * Copyright (C) Villanova University 2011. + * + * 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 SearchObject + * @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 + */ + +/** + * WorldCat Search Parameters + * + * @category VuFind2 + * @package SearchObject + * @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 VF_Search_WorldCat_Params extends VF_Search_Base_Params +{ + // Override Query + protected $overrideQuery = false; + + /** + * Set the override query + * + * @param string $q Override query + * + * @return void + */ + public function setOverrideQuery($q) + { + $this->overrideQuery = $q; + } + + /** + * Get the override query + * + * @return string + */ + public function getOverrideQuery() + { + return $this->overrideQuery; + } +} \ No newline at end of file diff --git a/module/VuFind/src/VuFind/Search/WorldCat/Results.php b/module/VuFind/src/VuFind/Search/WorldCat/Results.php new file mode 100644 index 0000000000000000000000000000000000000000..728b5d87224eebd6b1c51b4f81d4600fe38c3557 --- /dev/null +++ b/module/VuFind/src/VuFind/Search/WorldCat/Results.php @@ -0,0 +1,141 @@ +<?php +/** + * WorldCat Search Results + * + * PHP version 5 + * + * Copyright (C) Villanova University 2011. + * + * 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 SearchObject + * @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 + */ + +/** + * WorldCat Search Parameters + * + * @category VuFind2 + * @package SearchObject + * @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 VF_Search_WorldCat_Results extends VF_Search_Base_Results +{ + // Raw search response: + protected $rawResponse = null; + + /** + * Get a connection to the WorldCat API. + * + * @return VF_Connection_WorldCat + */ + public static function getWorldCatConnection() + { + static $wc = false; + if (!$wc) { + $wc = new VF_Connection_WorldCat(); + } + return $wc; + } + + /** + * Support method for performAndProcessSearch -- perform a search based on the + * parameters passed to the object. + * + * @return void + */ + protected function performSearch() + { + // Collect the search parameters: + $config = VF_Config_Reader::getConfig(); + $wc = static::getWorldCatConnection(); + $overrideQuery = $this->getOverrideQuery(); + $query = empty($overrideQuery) + ? $wc->buildQuery($this->getSearchTerms()) : $overrideQuery; + + // Perform the search: + $this->rawResponse = $wc->search( + $query, $config->WorldCat->OCLCCode, $this->getPage(), $this->getLimit(), + $this->getSort() + ); + + // How many results were there? + $this->resultTotal = isset($this->rawResponse->numberOfRecords) + ? intval($this->rawResponse->numberOfRecords) : 0; + + // Construct record drivers for all the items in the response: + $this->results = array(); + if (isset($this->rawResponse->records->record) + && count($this->rawResponse->records->record) > 0 + ) { + foreach ($this->rawResponse->records->record as $current) { + $this->results[] = static::initRecordDriver( + $current->recordData->record->asXML() + ); + } + } + } + + /** + * Static method to retrieve a record by ID. Returns a record driver object. + * + * @param string $id Unique identifier of record + * + * @throws VF_Exception_RecordMissing + * @return VF_RecordDriver_Base + */ + public static function getRecord($id) + { + $wc = static::getWorldCatConnection(); + $record = $wc->getRecord($id); + if (empty($record)) { + throw new VF_Exception_RecordMissing( + 'Record ' . $id . ' does not exist.' + ); + } + return static::initRecordDriver($record); + } + + /** + * Support method for _performSearch(): given an array of Solr response data, + * construct an appropriate record driver object. + * + * @param array $data Raw record data + * + * @return VF_RecordDriver_Base + */ + protected static function initRecordDriver($data) + { + return new VF_RecordDriver_WorldCat($data); + } + + /** + * Returns the stored list of facets for the last search + * + * @param array $filter Array of field => on-screen description listing + * all of the desired facet fields; set to null to get all configured values. + * + * @return array Facets data arrays + */ + public function getFacetList($filter = null) + { + // No facets in WorldCat: + return array(); + } +} \ No newline at end of file diff --git a/module/VuFind/src/VuFind/Theme/Root/Helper/SearchOptions.php b/module/VuFind/src/VuFind/Theme/Root/Helper/SearchOptions.php index cc3f11effeb18e4d731e446e556aaeb4b0e41c85..e71341e525f75daf893c37c504800b63d462fe60 100644 --- a/module/VuFind/src/VuFind/Theme/Root/Helper/SearchOptions.php +++ b/module/VuFind/src/VuFind/Theme/Root/Helper/SearchOptions.php @@ -25,6 +25,8 @@ * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License * @link http://vufind.org/wiki/building_a_recommendations_module Wiki */ +namespace VuFind\Theme\Root\Helper; +use VuFind\Search\Options, Zend\View\Helper\AbstractHelper; /** * "Retrieve search options" view helper @@ -35,17 +37,17 @@ * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License * @link http://vufind.org/wiki/building_a_recommendations_module Wiki */ -class VuFind_Theme_Root_Helper_SearchOptions extends Zend_View_Helper_Abstract +class SearchOptions extends AbstractHelper { /** * Wrapper function to the VF_Search_Options getInstance function * * @param string $type The search type of the object to retrieve * - * @return VF_Search_Base_Options + * @return SearchOptions */ - public function searchOptions($type = 'Solr') + public function __invoke($type = 'Solr') { - return VF_Search_Options::getInstance($type); + return Options::getInstance($type); } } \ No newline at end of file diff --git a/themes/vufind/root/theme.ini b/themes/vufind/root/theme.ini index e681ac4e3bf472d74ff5b3a3fc6f9b1bbaf5cd06..308b7e07c08bd79f48ab6d9ebfc5d92767b9edbb 100644 --- a/themes/vufind/root/theme.ini +++ b/themes/vufind/root/theme.ini @@ -7,5 +7,6 @@ helpers_to_register[] = "HeadScript" helpers_to_register[] = "HeadThemeResources" helpers_to_register[] = "ImageLink" helpers_to_register[] = "MobileUrl" +helpers_to_register[] = "SearchOptions" helpers_to_register[] = "TransEsc" helpers_to_register[] = "Translate"