Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 59 |
|
0.00% |
0 / 12 |
CRAP | |
0.00% |
0 / 1 |
AuthorityRecommend | |
0.00% |
0 / 59 |
|
0.00% |
0 / 12 |
930 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setConfig | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
42 | |||
init | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
performSearch | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
12 | |||
fuzzyCompare | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
addUseForHeadings | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
6 | |||
addSeeAlsoReferences | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
20 | |||
isModeActive | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
6 | |||
process | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
56 | |||
getHeader | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getRecommendations | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getResults | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | /** |
4 | * AuthorityRecommend Recommendations Module |
5 | * |
6 | * PHP version 8 |
7 | * |
8 | * Copyright (C) Villanova University 2012. |
9 | * |
10 | * This program is free software; you can redistribute it and/or modify |
11 | * it under the terms of the GNU General Public License version 2, |
12 | * as published by the Free Software Foundation. |
13 | * |
14 | * This program is distributed in the hope that it will be useful, |
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
17 | * GNU General Public License for more details. |
18 | * |
19 | * You should have received a copy of the GNU General Public License |
20 | * along with this program; if not, write to the Free Software |
21 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
22 | * |
23 | * @category VuFind |
24 | * @package Recommendations |
25 | * @author Lutz Biedinger <vufind-tech@lists.sourceforge.net> |
26 | * @author Ronan McHugh <vufind-tech@lists.sourceforge.net> |
27 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
28 | * @link https://vufind.org Main Page |
29 | */ |
30 | |
31 | namespace VuFind\Recommend; |
32 | |
33 | use Laminas\Stdlib\Parameters; |
34 | use VuFindSearch\Backend\Exception\RequestErrorException; |
35 | |
36 | use function count; |
37 | use function intval; |
38 | |
39 | /** |
40 | * AuthorityRecommend Module |
41 | * |
42 | * This class provides recommendations based on Authority records. |
43 | * i.e. searches for a pseudonym will provide the user with a link |
44 | * to the official name (according to the Authority index) |
45 | * |
46 | * Originally developed at the National Library of Ireland by Lutz |
47 | * Biedinger and Ronan McHugh. |
48 | * |
49 | * @category VuFind |
50 | * @package Recommendations |
51 | * @author Lutz Biedinger <vufind-tech@lists.sourceforge.net> |
52 | * @author Ronan McHugh <vufind-tech@lists.sourceforge.net> |
53 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
54 | * @link https://vufind.org Main Page |
55 | */ |
56 | class AuthorityRecommend implements RecommendInterface |
57 | { |
58 | /** |
59 | * User search query |
60 | * |
61 | * @var string |
62 | */ |
63 | protected $lookfor; |
64 | |
65 | /** |
66 | * Configured filters for authority searches |
67 | * |
68 | * @var array |
69 | */ |
70 | protected $filters = []; |
71 | |
72 | /** |
73 | * Maximum number of results that will be accompanied by recommendations (set |
74 | * to 0 for no limit). |
75 | * |
76 | * @var int |
77 | */ |
78 | protected $resultLimit = 0; |
79 | |
80 | /** |
81 | * Current user search |
82 | * |
83 | * @var \VuFind\Search\Base\Results |
84 | */ |
85 | protected $results; |
86 | |
87 | /** |
88 | * Generated recommendations |
89 | * |
90 | * @var array |
91 | */ |
92 | protected $recommendations = []; |
93 | |
94 | /** |
95 | * Results plugin manager |
96 | * |
97 | * @var \VuFind\Search\Results\PluginManager |
98 | */ |
99 | protected $resultsManager; |
100 | |
101 | /** |
102 | * Which lookup mode(s) to use. |
103 | * |
104 | * @var string |
105 | */ |
106 | protected $mode = '*'; |
107 | |
108 | /** |
109 | * Header to use in the user interface. |
110 | * |
111 | * @var string |
112 | */ |
113 | protected $header = 'See also'; |
114 | |
115 | /** |
116 | * Constructor |
117 | * |
118 | * @param \VuFind\Search\Results\PluginManager $results Results plugin manager |
119 | */ |
120 | public function __construct(\VuFind\Search\Results\PluginManager $results) |
121 | { |
122 | $this->resultsManager = $results; |
123 | } |
124 | |
125 | /** |
126 | * Store the configuration of the recommendation module. |
127 | * |
128 | * @param string $settings Settings from searches.ini. |
129 | * |
130 | * @return void |
131 | */ |
132 | public function setConfig($settings) |
133 | { |
134 | $params = explode(':', $settings); |
135 | for ($i = 0; $i < count($params); $i += 2) { |
136 | if (isset($params[$i + 1])) { |
137 | if ($params[$i] == '__resultlimit__') { |
138 | $this->resultLimit = intval($params[$i + 1]); |
139 | } elseif ($params[$i] == '__mode__') { |
140 | $this->mode = strtolower($params[$i + 1]); |
141 | } elseif ($params[$i] == '__header__') { |
142 | $this->header = $params[$i + 1]; |
143 | } else { |
144 | $this->filters[] = $params[$i] . ':' . $params[$i + 1]; |
145 | } |
146 | } |
147 | } |
148 | } |
149 | |
150 | /** |
151 | * Called before the Search Results object performs its main search |
152 | * (specifically, in response to \VuFind\Search\SearchRunner::EVENT_CONFIGURED). |
153 | * This method is responsible for setting search parameters needed by the |
154 | * recommendation module and for reading any existing search parameters that may |
155 | * be needed. |
156 | * |
157 | * @param \VuFind\Search\Base\Params $params Search parameter object |
158 | * @param Parameters $request Parameter object representing user |
159 | * request. |
160 | * |
161 | * @return void |
162 | * |
163 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
164 | */ |
165 | public function init($params, $request) |
166 | { |
167 | // Save user search query: |
168 | $this->lookfor = $request->get('lookfor'); |
169 | } |
170 | |
171 | /** |
172 | * Perform a search of the authority index. |
173 | * |
174 | * @param array $params Array of request parameters. |
175 | * |
176 | * @return array |
177 | */ |
178 | protected function performSearch($params) |
179 | { |
180 | // Initialise and process search (ignore Solr errors -- no reason to fail |
181 | // just because search syntax is not compatible with Authority core): |
182 | try { |
183 | $authResults = $this->resultsManager->get('SolrAuth'); |
184 | $authParams = $authResults->getParams(); |
185 | $authParams->initFromRequest(new Parameters($params)); |
186 | foreach ($this->filters as $filter) { |
187 | $authParams->addHiddenFilter($filter); |
188 | } |
189 | return $authResults->getResults(); |
190 | } catch (RequestErrorException $e) { |
191 | return []; |
192 | } |
193 | } |
194 | |
195 | /** |
196 | * Return true if $a and $b are similar enough to represent the same heading. |
197 | * |
198 | * @param string $a First string to compare |
199 | * @param string $b Second string to compare |
200 | * |
201 | * @return bool |
202 | */ |
203 | protected function fuzzyCompare($a, $b) |
204 | { |
205 | $normalize = function ($str) { |
206 | return trim(strtolower(preg_replace('/\W/', '', $str))); |
207 | }; |
208 | return $normalize($a) == $normalize($b); |
209 | } |
210 | |
211 | /** |
212 | * Add main headings from records that match search terms on use_for/see_also. |
213 | * |
214 | * @return void |
215 | */ |
216 | protected function addUseForHeadings() |
217 | { |
218 | // Build an advanced search request that prevents Solr from retrieving |
219 | // records that would already have been retrieved by a search of the biblio |
220 | // core, i.e. it only returns results where $lookfor IS found in in the |
221 | // "Heading" search and IS NOT found in the "MainHeading" search defined |
222 | // in authsearchspecs.yaml. |
223 | $params = [ |
224 | 'join' => 'AND', |
225 | 'bool0' => ['AND'], |
226 | 'lookfor0' => [$this->lookfor], |
227 | 'type0' => ['Heading'], |
228 | 'bool1' => ['NOT'], |
229 | 'lookfor1' => [$this->lookfor], |
230 | 'type1' => ['MainHeading'], |
231 | ]; |
232 | |
233 | // loop through records and assign id and headings to separate arrays defined |
234 | // above |
235 | foreach ($this->performSearch($params) as $result) { |
236 | $this->recommendations[] = $result->getBreadcrumb(); |
237 | } |
238 | } |
239 | |
240 | /** |
241 | * Add "see also" headings from records that match search terms on main heading. |
242 | * |
243 | * @return void |
244 | */ |
245 | protected function addSeeAlsoReferences() |
246 | { |
247 | // Build a simple "MainHeading" search. |
248 | $params = [ |
249 | 'lookfor' => [$this->lookfor], |
250 | 'type' => ['MainHeading'], |
251 | ]; |
252 | |
253 | // loop through records and assign id and headings to separate arrays defined |
254 | // above |
255 | foreach ($this->performSearch($params) as $result) { |
256 | foreach ($result->getSeeAlso() as $seeAlso) { |
257 | // check for duplicates before adding record to recordSet |
258 | if (!$this->fuzzyCompare($seeAlso, $this->lookfor)) { |
259 | $this->recommendations[] = $seeAlso; |
260 | } |
261 | } |
262 | } |
263 | } |
264 | |
265 | /** |
266 | * Is the specified mode configured to be active? |
267 | * |
268 | * @param string $mode Mode to check |
269 | * |
270 | * @return bool |
271 | */ |
272 | protected function isModeActive($mode) |
273 | { |
274 | return $this->mode === '*' || str_contains($this->mode, $mode); |
275 | } |
276 | |
277 | /** |
278 | * Called after the Search Results object has performed its main search. This |
279 | * may be used to extract necessary information from the Search Results object |
280 | * or to perform completely unrelated processing. |
281 | * |
282 | * @param \VuFind\Search\Base\Results $results Search results object |
283 | * |
284 | * @return void |
285 | */ |
286 | public function process($results) |
287 | { |
288 | $this->results = $results; |
289 | |
290 | // empty searches such as New Items will return blank |
291 | if ($this->lookfor == null) { |
292 | return; |
293 | } |
294 | |
295 | // function will return blank on Advanced Search |
296 | if ($results->getParams()->getSearchType() == 'advanced') { |
297 | return; |
298 | } |
299 | |
300 | // check result limit before proceeding... |
301 | if ( |
302 | $this->resultLimit > 0 |
303 | && $this->resultLimit < $results->getResultTotal() |
304 | ) { |
305 | return; |
306 | } |
307 | |
308 | // see if we can add main headings matching use_for/see_also fields... |
309 | if ($this->isModeActive('usefor')) { |
310 | $this->addUseForHeadings(); |
311 | } |
312 | |
313 | // see if we can add see-also references associated with main headings... |
314 | if ($this->isModeActive('seealso')) { |
315 | $this->addSeeAlsoReferences(); |
316 | } |
317 | } |
318 | |
319 | /** |
320 | * Get the header to display in the user interface. |
321 | * |
322 | * @return string |
323 | */ |
324 | public function getHeader() |
325 | { |
326 | return $this->header; |
327 | } |
328 | |
329 | /** |
330 | * Get recommendations (for use in the view). |
331 | * |
332 | * @return array |
333 | */ |
334 | public function getRecommendations() |
335 | { |
336 | return array_unique($this->recommendations); |
337 | } |
338 | |
339 | /** |
340 | * Get results stored in the object. |
341 | * |
342 | * @return \VuFind\Search\Base\Results |
343 | */ |
344 | public function getResults() |
345 | { |
346 | return $this->results; |
347 | } |
348 | } |