diff --git a/config/vufind/searches.ini b/config/vufind/searches.ini index 3dc20f1133a559432909a7cf4ad3e522d3a9d29d..447696c215c76b37612d55384f54bc7525b00e90 100644 --- a/config/vufind/searches.ini +++ b/config/vufind/searches.ini @@ -319,12 +319,20 @@ CallNumber = callnumber-sort ; set of authority records is used for recommendations; for example: ; AuthorityRecommend:record_type:Heading* OR Topical*:source:FAST ; limits record_type to strings starting with "Heading" or "Topical" and -; limits source to FAST. A special field name of "__resultlimit__" may be +; limits source to FAST. A special field name of "__resultlimit__" may be ; used to suppress authority results when the result set contains more items ; than the number specified as the corresponding value (e.g. if you configure ; AuthorityRecommend:__resultlimit__:50 then authority recommendations will ; only display on result screens displaying fewer than 50 hits; by default, -; recommendations will always display). Filtering is optional. +; recommendations will always display). A special field name of "__mode__" +; may be used to control what type of suggestions are presented (default is +; both seealso and usefor results, but you can turn on just one of these +; options if desired; for example "__mode__:seealso" would only show "see +; also" results from records whose main headings match the current search +; terms while "__mode__:usefor" would show only main headings from records +; whose "use for" headings match the current search. You can use "__mode__:*" +; to turn on all options (but this is default behavior if no __mode__ is +; set). Filtering is optional. ; Channels ; Display a link to the Channeled Browse functionality leading to channels of ; records related to the current search. diff --git a/module/VuFind/src/VuFind/Recommend/AuthorityRecommend.php b/module/VuFind/src/VuFind/Recommend/AuthorityRecommend.php index 2505ad82193361bdca50281f2e4100288284b9e7..53e7935a0b72090a1b6f3cdefb2bbe98a96d6c94 100644 --- a/module/VuFind/src/VuFind/Recommend/AuthorityRecommend.php +++ b/module/VuFind/src/VuFind/Recommend/AuthorityRecommend.php @@ -94,6 +94,13 @@ class AuthorityRecommend implements RecommendInterface */ protected $resultsManager; + /** + * Which lookup mode(s) to use. + * + * @var string + */ + protected $mode = '*'; + /** * Constructor * @@ -118,6 +125,8 @@ class AuthorityRecommend implements RecommendInterface if (isset($params[$i + 1])) { if ($params[$i] == '__resultlimit__') { $this->resultLimit = intval($params[$i + 1]); + } elseif ($params[$i] == '__mode__') { + $this->mode = strtolower($params[$i + 1]); } else { $this->filters[] = $params[$i] . ':' . $params[$i + 1]; } @@ -144,64 +153,70 @@ class AuthorityRecommend implements RecommendInterface } /** - * Called after the Search Results object has performed its main search. This - * may be used to extract necessary information from the Search Results object - * or to perform completely unrelated processing. + * Perform a search of the authority index. * - * @param \VuFind\Search\Base\Results $results Search results object + * @param array $params Array of request parameters. * - * @return void + * @return array */ - public function process($results) + protected function performSearch($params) { - $this->results = $results; - - // function will return blank on Advanced Search - if ($results->getParams()->getSearchType() == 'advanced') { - return; - } - - // check result limit before proceeding... - if ($this->resultLimit > 0 - && $this->resultLimit < $results->getResultTotal() - ) { - return; - } - - // Build an advanced search request that prevents Solr from retrieving - // records that would already have been retrieved by a search of the biblio - // core, i.e. it only returns results where $lookfor IS found in in the - // "Heading" search and IS NOT found in the "MainHeading" search defined - // in authsearchspecs.yaml. - $request = new Parameters( - [ - 'join' => 'AND', - 'bool0' => ['AND'], - 'lookfor0' => [$this->lookfor], - 'type0' => ['Heading'], - 'bool1' => ['NOT'], - 'lookfor1' => [$this->lookfor], - 'type1' => ['MainHeading'] - ] - ); - // Initialise and process search (ignore Solr errors -- no reason to fail // just because search syntax is not compatible with Authority core): try { $authResults = $this->resultsManager->get('SolrAuth'); $authParams = $authResults->getParams(); - $authParams->initFromRequest($request); + $authParams->initFromRequest(new Parameters($params)); foreach ($this->filters as $filter) { $authParams->addHiddenFilter($filter); } - $results = $authResults->getResults(); + return $authResults->getResults(); } catch (RequestErrorException $e) { - return; + return []; } + } + + /** + * Return true if $a and $b are similar enough to represent the same heading. + * + * @param string $a First string to compare + * @param string $b Second string to compare + * + * @return bool + */ + protected function fuzzyCompare($a, $b) + { + $normalize = function ($str) { + return trim(strtolower(preg_replace('/\W/', '', $str))); + }; + return $normalize($a) == $normalize($b); + } + + /** + * Add main headings from records that match search terms on use_for/see_also. + * + * @return void + */ + protected function addUseForHeadings() + { + // Build an advanced search request that prevents Solr from retrieving + // records that would already have been retrieved by a search of the biblio + // core, i.e. it only returns results where $lookfor IS found in in the + // "Heading" search and IS NOT found in the "MainHeading" search defined + // in authsearchspecs.yaml. + $params = [ + 'join' => 'AND', + 'bool0' => ['AND'], + 'lookfor0' => [$this->lookfor], + 'type0' => ['Heading'], + 'bool1' => ['NOT'], + 'lookfor1' => [$this->lookfor], + 'type1' => ['MainHeading'] + ]; // loop through records and assign id and headings to separate arrays defined // above - foreach ($results as $result) { + foreach ($this->performSearch($params) as $result) { // Extract relevant details: $recordArray = [ 'id' => $result->getUniqueID(), @@ -211,12 +226,85 @@ class AuthorityRecommend implements RecommendInterface // check for duplicates before adding record to recordSet if (!$this->inArrayR($recordArray['heading'], $this->recommendations)) { array_push($this->recommendations, $recordArray); - } else { - continue; } } } + /** + * Add "see also" headings from records that match search terms on main heading. + * + * @return void + */ + protected function addSeeAlsoReferences() + { + // Build a simple "MainHeading" search. + $params = [ + 'lookfor' => [$this->lookfor], + 'type' => ['MainHeading'] + ]; + + // loop through records and assign id and headings to separate arrays defined + // above + foreach ($this->performSearch($params) as $result) { + foreach ($result->getSeeAlso() as $seeAlso) { + // check for duplicates before adding record to recordSet + if (!$this->fuzzyCompare($seeAlso, $this->lookfor) + && !$this->inArrayR($seeAlso, $this->recommendations) + ) { + array_push($this->recommendations, ['heading' => $seeAlso]); + } + } + } + } + + /** + * Is the specified mode configured to be active? + * + * @param string $mode Mode to check + * + * @return bool + */ + protected function isModeActive($mode) + { + return $this->mode === '*' || strpos($this->mode, $mode) !== false; + } + + /** + * Called after the Search Results object has performed its main search. This + * may be used to extract necessary information from the Search Results object + * or to perform completely unrelated processing. + * + * @param \VuFind\Search\Base\Results $results Search results object + * + * @return void + */ + public function process($results) + { + $this->results = $results; + + // function will return blank on Advanced Search + if ($results->getParams()->getSearchType() == 'advanced') { + return; + } + + // check result limit before proceeding... + if ($this->resultLimit > 0 + && $this->resultLimit < $results->getResultTotal() + ) { + return; + } + + // see if we can add main headings matching use_for/see_also fields... + if ($this->isModeActive('usefor')) { + $this->addUseForHeadings(); + } + + // see if we can add see-also references associated with main headings... + if ($this->isModeActive('seealso')) { + $this->addSeeAlsoReferences(); + } + } + /** * Get recommendations (for use in the view). * @@ -248,12 +336,8 @@ class AuthorityRecommend implements RecommendInterface protected function inArrayR($needle, $haystack) { foreach ($haystack as $v) { - if ($needle == $v) { + if ($needle == $v || (is_array($v) && $this->inArrayR($needle, $v))) { return true; - } elseif (is_array($v)) { - if ($this->inArrayR($needle, $v)) { - return true; - } } } return false;