Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
57.77% |
119 / 206 |
|
39.53% |
17 / 43 |
CRAP | |
0.00% |
0 / 1 |
Results | |
57.77% |
119 / 206 |
|
39.53% |
17 / 43 |
588.51 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
__clone | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
getParams | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setParams | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getOptions | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getUrlQueryHelperOptions | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getUrlQuery | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
2 | |||
setHelper | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
performAndProcessSearch | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 | |||
getFacetList | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
performSearch | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
getSpellingSuggestions | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getResultTotal | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
overrideStartRecord | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getStartRecord | |
77.78% |
7 / 9 |
|
0.00% |
0 / 1 |
4.18 | |||
getEndRecord | |
88.89% |
8 / 9 |
|
0.00% |
0 / 1 |
4.02 | |||
getResults | |
66.67% |
2 / 3 |
|
0.00% |
0 / 1 |
2.15 | |||
getErrors | |
66.67% |
2 / 3 |
|
0.00% |
0 / 1 |
2.15 | |||
getBackendId | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getSearchId | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
isSavedSearch | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
getNotificationFrequency | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
updateSaveStatus | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
startQueryTimer | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
stopQueryTimer | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
getQuerySpeed | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
getStartTime | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
getPaginator | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
12 | |||
getRawSuggestions | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
getScores | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getMaxScore | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getExtraData | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setExtraData | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
minify | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
1 | |||
deminify | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
1 | |||
getRecommendations | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
setRecommendations | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getSearchService | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
translate | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
getUrlQueryHelperFactory | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
setUrlQueryHelperFactory | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setHierarchicalFacetHelper | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getFullFieldFacets | |
0.00% |
0 / 27 |
|
0.00% |
0 / 1 |
56 | |||
getExtraSearchBackendDetails | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
buildFacetList | |
94.55% |
52 / 55 |
|
0.00% |
0 / 1 |
12.02 |
1 | <?php |
2 | |
3 | /** |
4 | * Abstract results search model. |
5 | * |
6 | * PHP version 8 |
7 | * |
8 | * Copyright (C) Villanova University 2010. |
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 Search_Base |
25 | * @author Demian Katz <demian.katz@villanova.edu> |
26 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
27 | * @link https://vufind.org Main Page |
28 | */ |
29 | |
30 | namespace VuFind\Search\Base; |
31 | |
32 | use Laminas\Paginator\Paginator; |
33 | use VuFind\Record\Loader; |
34 | use VuFind\Search\Factory\UrlQueryHelperFactory; |
35 | use VuFindSearch\Service as SearchService; |
36 | |
37 | use function call_user_func_array; |
38 | use function count; |
39 | use function func_get_args; |
40 | use function get_class; |
41 | use function in_array; |
42 | use function is_callable; |
43 | use function is_object; |
44 | |
45 | /** |
46 | * Abstract results search model. |
47 | * |
48 | * This abstract class defines the results methods for modeling a search in VuFind. |
49 | * |
50 | * @category VuFind |
51 | * @package Search_Base |
52 | * @author Demian Katz <demian.katz@villanova.edu> |
53 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
54 | * @link https://vufind.org Main Page |
55 | */ |
56 | abstract class Results |
57 | { |
58 | /** |
59 | * Search parameters |
60 | * |
61 | * @var Params |
62 | */ |
63 | protected $params; |
64 | |
65 | /** |
66 | * Total number of results available |
67 | * |
68 | * @var int |
69 | */ |
70 | protected $resultTotal = null; |
71 | |
72 | /** |
73 | * Search backend identifier. |
74 | * |
75 | * @var string |
76 | */ |
77 | protected $backendId; |
78 | |
79 | /** |
80 | * Override (only for use in very rare cases) |
81 | * |
82 | * @var int |
83 | */ |
84 | protected $startRecordOverride = null; |
85 | |
86 | /** |
87 | * Array of results (represented as Record Driver objects) retrieved on latest |
88 | * search |
89 | * |
90 | * @var array |
91 | */ |
92 | protected $results = null; |
93 | |
94 | /** |
95 | * Any errors reported by the search backend |
96 | * |
97 | * @var array |
98 | */ |
99 | protected $errors = null; |
100 | |
101 | /** |
102 | * An ID number for saving/retrieving search |
103 | * |
104 | * @var int |
105 | */ |
106 | protected $searchId = null; |
107 | |
108 | /** |
109 | * Is this a user-saved search? |
110 | * |
111 | * @var bool |
112 | */ |
113 | protected $savedSearch = null; |
114 | |
115 | /** |
116 | * How frequently will a user be notified about this search (0 = never)? |
117 | * |
118 | * @var int |
119 | */ |
120 | protected $notificationFrequency = null; |
121 | |
122 | /** |
123 | * Query start time |
124 | * |
125 | * @var float |
126 | */ |
127 | protected $queryStartTime = null; |
128 | |
129 | /** |
130 | * Query end time |
131 | * |
132 | * @var float |
133 | */ |
134 | protected $queryEndTime = null; |
135 | |
136 | /** |
137 | * Query time (total) |
138 | * |
139 | * @var float |
140 | */ |
141 | protected $queryTime = null; |
142 | |
143 | /** |
144 | * Helper objects |
145 | * |
146 | * @var array |
147 | */ |
148 | protected $helpers = []; |
149 | |
150 | /** |
151 | * Spelling suggestions |
152 | * |
153 | * @var array |
154 | */ |
155 | protected $suggestions = null; |
156 | |
157 | /** |
158 | * Recommendations |
159 | * |
160 | * @var array |
161 | */ |
162 | protected $recommend = []; |
163 | |
164 | /** |
165 | * Search service. |
166 | * |
167 | * @var SearchService |
168 | */ |
169 | protected $searchService; |
170 | |
171 | /** |
172 | * Record loader |
173 | * |
174 | * @var Loader |
175 | */ |
176 | protected $recordLoader; |
177 | |
178 | /** |
179 | * URL query helper factory |
180 | * |
181 | * @var UrlQueryHelperFactory |
182 | */ |
183 | protected $urlQueryHelperFactory = null; |
184 | |
185 | /** |
186 | * Hierarchical facet helper |
187 | * |
188 | * @var HierarchicalFacetHelperInterface |
189 | */ |
190 | protected $hierarchicalFacetHelper = null; |
191 | |
192 | /** |
193 | * Extra search details. |
194 | * |
195 | * @var ?array |
196 | */ |
197 | protected $extraSearchBackendDetails = null; |
198 | |
199 | /** |
200 | * Constructor |
201 | * |
202 | * @param \VuFind\Search\Base\Params $params Object representing user |
203 | * search parameters. |
204 | * @param SearchService $searchService Search service |
205 | * @param Loader $recordLoader Record loader |
206 | */ |
207 | public function __construct( |
208 | Params $params, |
209 | SearchService $searchService, |
210 | Loader $recordLoader |
211 | ) { |
212 | $this->setParams($params); |
213 | $this->searchService = $searchService; |
214 | $this->recordLoader = $recordLoader; |
215 | } |
216 | |
217 | /** |
218 | * Copy constructor |
219 | * |
220 | * @return void |
221 | */ |
222 | public function __clone() |
223 | { |
224 | if (is_object($this->params)) { |
225 | $this->params = clone $this->params; |
226 | } |
227 | $this->helpers = []; |
228 | } |
229 | |
230 | /** |
231 | * Get the search parameters object. |
232 | * |
233 | * @return \VuFind\Search\Base\Params |
234 | */ |
235 | public function getParams() |
236 | { |
237 | return $this->params; |
238 | } |
239 | |
240 | /** |
241 | * Set the search parameters object. |
242 | * |
243 | * @param \VuFind\Search\Base\Params $params Parameters to set |
244 | * |
245 | * @return void |
246 | */ |
247 | public function setParams($params) |
248 | { |
249 | $this->params = $params; |
250 | } |
251 | |
252 | /** |
253 | * Get the search options object. |
254 | * |
255 | * @return \VuFind\Search\Base\Options |
256 | */ |
257 | public function getOptions() |
258 | { |
259 | return $this->getParams()->getOptions(); |
260 | } |
261 | |
262 | /** |
263 | * Options for UrlQueryHelper |
264 | * |
265 | * @return array |
266 | */ |
267 | protected function getUrlQueryHelperOptions() |
268 | { |
269 | return []; |
270 | } |
271 | |
272 | /** |
273 | * Get the URL helper for this object. |
274 | * |
275 | * @return \VuFind\Search\UrlQueryHelper |
276 | */ |
277 | public function getUrlQuery() |
278 | { |
279 | // Set up URL helper: |
280 | if (!isset($this->helpers['urlQuery'])) { |
281 | $factory = $this->getUrlQueryHelperFactory(); |
282 | $this->helpers['urlQuery'] = $factory->fromParams( |
283 | $this->getParams(), |
284 | $this->getUrlQueryHelperOptions() |
285 | ); |
286 | } |
287 | return $this->helpers['urlQuery']; |
288 | } |
289 | |
290 | /** |
291 | * Override a helper object. |
292 | * |
293 | * @param string $key Name of helper to set |
294 | * @param object $value Helper object |
295 | * |
296 | * @return void |
297 | */ |
298 | public function setHelper($key, $value) |
299 | { |
300 | $this->helpers[$key] = $value; |
301 | } |
302 | |
303 | /** |
304 | * Actually execute the search. |
305 | * |
306 | * @return void |
307 | */ |
308 | public function performAndProcessSearch() |
309 | { |
310 | // Initialize variables to defaults (to ensure they don't stay null |
311 | // and cause unnecessary repeat processing): |
312 | // The value of -1 indicates that resultTotal is not available. |
313 | $this->resultTotal = -1; |
314 | $this->results = []; |
315 | $this->suggestions = []; |
316 | $this->errors = []; |
317 | |
318 | // Run the search: |
319 | $this->startQueryTimer(); |
320 | $this->performSearch(); |
321 | $this->stopQueryTimer(); |
322 | } |
323 | |
324 | /** |
325 | * Returns the stored list of facets for the last search |
326 | * |
327 | * @param array $filter Array of field => on-screen description listing |
328 | * all of the desired facet fields; set to null to get all configured values. |
329 | * |
330 | * @return array Facets data arrays |
331 | */ |
332 | abstract public function getFacetList($filter = null); |
333 | |
334 | /** |
335 | * Abstract support method for performAndProcessSearch -- perform a search based |
336 | * on the parameters passed to the object. This method is responsible for |
337 | * filling in all of the key class properties: results, resultTotal, etc. |
338 | * |
339 | * @return void |
340 | */ |
341 | abstract protected function performSearch(); |
342 | |
343 | /** |
344 | * Get spelling suggestion information. |
345 | * |
346 | * @return array |
347 | */ |
348 | public function getSpellingSuggestions() |
349 | { |
350 | // Not supported by default: |
351 | return []; |
352 | } |
353 | |
354 | /** |
355 | * Get total count of records in the result set (not just current page). |
356 | * |
357 | * @return int |
358 | */ |
359 | public function getResultTotal() |
360 | { |
361 | if (null === $this->resultTotal) { |
362 | $this->performAndProcessSearch(); |
363 | } |
364 | return $this->resultTotal; |
365 | } |
366 | |
367 | /** |
368 | * Manually override the start record number. |
369 | * |
370 | * @param int $rec Record number to use. |
371 | * |
372 | * @return void |
373 | */ |
374 | public function overrideStartRecord($rec) |
375 | { |
376 | $this->startRecordOverride = $rec; |
377 | } |
378 | |
379 | /** |
380 | * Get record number for start of range represented by current result set. |
381 | * |
382 | * @return int |
383 | */ |
384 | public function getStartRecord() |
385 | { |
386 | if (null !== $this->startRecordOverride) { |
387 | return $this->startRecordOverride; |
388 | } |
389 | $params = $this->getParams(); |
390 | $page = $params->getPage(); |
391 | $pageLimit = $params->getLimit(); |
392 | $resultLimit = $this->getOptions()->getVisibleSearchResultLimit(); |
393 | if ($resultLimit > -1 && $resultLimit < $page * $pageLimit) { |
394 | $page = ceil($resultLimit / $pageLimit); |
395 | } |
396 | return (($page - 1) * $pageLimit) + 1; |
397 | } |
398 | |
399 | /** |
400 | * Get record number for end of range represented by current result set. |
401 | * |
402 | * @return int |
403 | */ |
404 | public function getEndRecord() |
405 | { |
406 | $total = $this->getResultTotal(); |
407 | $params = $this->getParams(); |
408 | $page = $params->getPage(); |
409 | $pageLimit = $params->getLimit(); |
410 | $resultLimit = $this->getOptions()->getVisibleSearchResultLimit(); |
411 | |
412 | if ($resultLimit > -1 && $resultLimit < ($page * $pageLimit)) { |
413 | $record = $resultLimit; |
414 | } else { |
415 | $record = $page * $pageLimit; |
416 | } |
417 | // If the end of the current page runs past the last record, use total |
418 | // results; otherwise use the last record on this page: |
419 | return ($record > $total) ? $total : $record; |
420 | } |
421 | |
422 | /** |
423 | * Basic 'getter' for search results. |
424 | * |
425 | * @return array |
426 | */ |
427 | public function getResults() |
428 | { |
429 | if (null === $this->results) { |
430 | $this->performAndProcessSearch(); |
431 | } |
432 | return $this->results; |
433 | } |
434 | |
435 | /** |
436 | * Basic 'getter' for errors. |
437 | * |
438 | * @return array |
439 | */ |
440 | public function getErrors() |
441 | { |
442 | if (null === $this->errors) { |
443 | $this->performAndProcessSearch(); |
444 | } |
445 | return $this->errors; |
446 | } |
447 | |
448 | /** |
449 | * Basic 'getter' of search backend identifier. |
450 | * |
451 | * @return string |
452 | */ |
453 | public function getBackendId() |
454 | { |
455 | return $this->backendId; |
456 | } |
457 | |
458 | /** |
459 | * Basic 'getter' for ID of saved search. |
460 | * |
461 | * @return int |
462 | */ |
463 | public function getSearchId() |
464 | { |
465 | return $this->searchId; |
466 | } |
467 | |
468 | /** |
469 | * Is the current search saved in the database? |
470 | * |
471 | * @return bool |
472 | */ |
473 | public function isSavedSearch() |
474 | { |
475 | // This data is not available until the search has been saved; blow up if somebody |
476 | // tries to get data that is not yet available. |
477 | if (null === $this->savedSearch) { |
478 | throw new \Exception( |
479 | 'Cannot retrieve save status before updateSaveStatus is called.' |
480 | ); |
481 | } |
482 | return $this->savedSearch; |
483 | } |
484 | |
485 | /** |
486 | * How frequently (in days) will the current user be notified about updates to |
487 | * these search results (0 = never)? |
488 | * |
489 | * @return int |
490 | * @throws \Exception |
491 | */ |
492 | public function getNotificationFrequency(): int |
493 | { |
494 | // This data is not available until the search has been saved; blow up if somebody |
495 | // tries to get data that is not yet available. |
496 | if (null === $this->notificationFrequency) { |
497 | throw new \Exception( |
498 | 'Cannot retrieve notification frequency before updateSaveStatus is called.' |
499 | ); |
500 | } |
501 | return $this->notificationFrequency; |
502 | } |
503 | |
504 | /** |
505 | * Given a database row corresponding to the current search object, |
506 | * mark whether this search is saved and what its database ID is. |
507 | * |
508 | * @param SearchEntityInterface $row Relevant database row. |
509 | * |
510 | * @return void |
511 | */ |
512 | public function updateSaveStatus($row) |
513 | { |
514 | $this->searchId = $row->getId(); |
515 | $this->savedSearch = $row->getSaved(); |
516 | $this->notificationFrequency = $this->savedSearch ? $row->getNotificationFrequency() : 0; |
517 | } |
518 | |
519 | /** |
520 | * Start the timer to figure out how long a query takes. Complements |
521 | * stopQueryTimer(). |
522 | * |
523 | * @return void |
524 | */ |
525 | protected function startQueryTimer() |
526 | { |
527 | // Get time before the query |
528 | $time = explode(' ', microtime()); |
529 | $this->queryStartTime = $time[1] + $time[0]; |
530 | } |
531 | |
532 | /** |
533 | * End the timer to figure out how long a query takes. Complements |
534 | * startQueryTimer(). |
535 | * |
536 | * @return void |
537 | */ |
538 | protected function stopQueryTimer() |
539 | { |
540 | $time = explode(' ', microtime()); |
541 | $this->queryEndTime = $time[1] + $time[0]; |
542 | $this->queryTime = $this->queryEndTime - $this->queryStartTime; |
543 | } |
544 | |
545 | /** |
546 | * Basic 'getter' for query speed. |
547 | * |
548 | * @return float |
549 | */ |
550 | public function getQuerySpeed() |
551 | { |
552 | if (null === $this->queryTime) { |
553 | $this->performAndProcessSearch(); |
554 | } |
555 | return $this->queryTime; |
556 | } |
557 | |
558 | /** |
559 | * Basic 'getter' for query start time. |
560 | * |
561 | * @return float |
562 | */ |
563 | public function getStartTime() |
564 | { |
565 | if (null === $this->queryStartTime) { |
566 | $this->performAndProcessSearch(); |
567 | } |
568 | return $this->queryStartTime; |
569 | } |
570 | |
571 | /** |
572 | * Get a paginator for the result set. |
573 | * |
574 | * @return Paginator |
575 | */ |
576 | public function getPaginator() |
577 | { |
578 | // If there is a limit on how many pages are accessible, |
579 | // apply that limit now: |
580 | $max = $this->getOptions()->getVisibleSearchResultLimit(); |
581 | $total = $this->getResultTotal(); |
582 | if ($max > 0 && $total > $max) { |
583 | $total = $max; |
584 | } |
585 | |
586 | // Build the standard paginator control: |
587 | $nullAdapter = "Laminas\Paginator\Adapter\NullFill"; |
588 | $paginator = new Paginator(new $nullAdapter($total)); |
589 | $paginator->setCurrentPageNumber($this->getParams()->getPage()) |
590 | ->setItemCountPerPage($this->getParams()->getLimit()) |
591 | ->setPageRange(11); |
592 | return $paginator; |
593 | } |
594 | |
595 | /** |
596 | * Basic 'getter' for suggestion list. |
597 | * |
598 | * @return array |
599 | */ |
600 | public function getRawSuggestions() |
601 | { |
602 | if (null === $this->suggestions) { |
603 | $this->performAndProcessSearch(); |
604 | } |
605 | return $this->suggestions; |
606 | } |
607 | |
608 | /** |
609 | * Get the scores of the results |
610 | * |
611 | * @return array |
612 | */ |
613 | public function getScores() |
614 | { |
615 | // Not implemented in the base class |
616 | return []; |
617 | } |
618 | |
619 | /** |
620 | * Getting the highest relevance of all the results |
621 | * |
622 | * @return ?float |
623 | */ |
624 | public function getMaxScore() |
625 | { |
626 | // Not implemented in the base class |
627 | return null; |
628 | } |
629 | |
630 | /** |
631 | * Get extra data for the search. |
632 | * |
633 | * Extra data can be used to store local implementation-specific information. |
634 | * Contents must be serializable. It is recommended to make the array as small |
635 | * as possible. |
636 | * |
637 | * @return array |
638 | */ |
639 | public function getExtraData(): array |
640 | { |
641 | // Not implemented in the base class |
642 | return []; |
643 | } |
644 | |
645 | /** |
646 | * Set extra data for the search. |
647 | * |
648 | * @param array $data Extra data |
649 | * |
650 | * @return void |
651 | */ |
652 | public function setExtraData(array $data): void |
653 | { |
654 | // Not implemented in the base class |
655 | if (!empty($data)) { |
656 | error_log(get_class($this) . ': Extra data passed but not handled'); |
657 | } |
658 | } |
659 | |
660 | /** |
661 | * Add settings to a minified object. |
662 | * |
663 | * @param \VuFind\Search\Minified $minified Minified Search Object |
664 | * |
665 | * @return void |
666 | */ |
667 | public function minify(&$minified): void |
668 | { |
669 | $minified->id = $this->getSearchId(); |
670 | $minified->i = $this->getStartTime(); |
671 | $minified->s = $this->getQuerySpeed(); |
672 | $minified->r = $this->getResultTotal(); |
673 | $minified->ex = $this->getExtraData(); |
674 | |
675 | $this->getParams()->minify($minified); |
676 | } |
677 | |
678 | /** |
679 | * Restore settings from a minified object found in the database. |
680 | * |
681 | * @param \VuFind\Search\Minified $minified Minified Search Object |
682 | * |
683 | * @return void |
684 | */ |
685 | public function deminify($minified) |
686 | { |
687 | $this->searchId = $minified->id; |
688 | $this->queryStartTime = $minified->i; |
689 | $this->queryTime = $minified->s; |
690 | $this->resultTotal = $minified->r; |
691 | $this->setExtraData($minified->ex); |
692 | |
693 | $this->getParams()->deminify($minified); |
694 | } |
695 | |
696 | /** |
697 | * Get an array of recommendation objects for augmenting the results display. |
698 | * |
699 | * @param string $location Name of location to use as a filter (null to get |
700 | * associative array of all locations); legal non-null values: 'top', 'side' |
701 | * |
702 | * @return array |
703 | */ |
704 | public function getRecommendations($location = 'top') |
705 | { |
706 | if (null === $location) { |
707 | return $this->recommend; |
708 | } |
709 | return $this->recommend[$location] ?? []; |
710 | } |
711 | |
712 | /** |
713 | * Set the recommendation objects (see \VuFind\Search\RecommendListener). |
714 | * |
715 | * @param array $recommend Recommendations |
716 | * |
717 | * @return void |
718 | */ |
719 | public function setRecommendations($recommend) |
720 | { |
721 | $this->recommend = $recommend; |
722 | } |
723 | |
724 | /** |
725 | * Return search service. |
726 | * |
727 | * @return SearchService |
728 | * |
729 | * @todo May better error handling, throw a custom exception if search service |
730 | * not present |
731 | */ |
732 | protected function getSearchService() |
733 | { |
734 | return $this->searchService; |
735 | } |
736 | |
737 | /** |
738 | * Translate a string if a translator is available (proxies method in Options). |
739 | * |
740 | * @return string |
741 | */ |
742 | public function translate() |
743 | { |
744 | return call_user_func_array( |
745 | [$this->getOptions(), 'translate'], |
746 | func_get_args() |
747 | ); |
748 | } |
749 | |
750 | /** |
751 | * Get URL query helper factory |
752 | * |
753 | * @return UrlQueryHelperFactory |
754 | */ |
755 | protected function getUrlQueryHelperFactory() |
756 | { |
757 | if (null === $this->urlQueryHelperFactory) { |
758 | $this->urlQueryHelperFactory = new UrlQueryHelperFactory(); |
759 | } |
760 | return $this->urlQueryHelperFactory; |
761 | } |
762 | |
763 | /** |
764 | * Set URL query helper factory |
765 | * |
766 | * @param UrlQueryHelperFactory $factory UrlQueryHelperFactory object |
767 | * |
768 | * @return void |
769 | */ |
770 | public function setUrlQueryHelperFactory(UrlQueryHelperFactory $factory) |
771 | { |
772 | $this->urlQueryHelperFactory = $factory; |
773 | } |
774 | |
775 | /** |
776 | * Set hierarchical facet helper |
777 | * |
778 | * @param HierarchicalFacetHelperInterface $helper Hierarchical facet helper |
779 | * |
780 | * @return void |
781 | */ |
782 | public function setHierarchicalFacetHelper( |
783 | HierarchicalFacetHelperInterface $helper |
784 | ) { |
785 | $this->hierarchicalFacetHelper = $helper; |
786 | } |
787 | |
788 | /** |
789 | * Get complete facet counts for several index fields |
790 | * |
791 | * @param array $facetfields name of the Solr fields to return facets for |
792 | * @param bool $removeFilter Clear existing filters from selected fields (true) |
793 | * or retain them (false)? |
794 | * @param int $limit A limit for the number of facets returned, this |
795 | * may be useful for very large amounts of facets that can break the JSON parse |
796 | * method because of PHP out of memory exceptions (default = -1, no limit). |
797 | * @param string $facetSort A facet sort value to use (null to retain current) |
798 | * |
799 | * @return array an array with the facet values for each index field |
800 | */ |
801 | public function getFullFieldFacets( |
802 | $facetfields, |
803 | $removeFilter = true, |
804 | $limit = -1, |
805 | $facetSort = null |
806 | ) { |
807 | if (!method_exists($this, 'getPartialFieldFacets')) { |
808 | throw new \Exception('getPartialFieldFacets not implemented'); |
809 | } |
810 | $page = 1; |
811 | $facets = []; |
812 | do { |
813 | $facetpage = $this->getPartialFieldFacets( |
814 | $facetfields, |
815 | $removeFilter, |
816 | $limit, |
817 | $facetSort, |
818 | $page |
819 | ); |
820 | $nextfields = []; |
821 | foreach ($facetfields as $field) { |
822 | if (!empty($facetpage[$field]['data']['list'])) { |
823 | if (!isset($facets[$field])) { |
824 | $facets[$field] = $facetpage[$field]; |
825 | $facets[$field]['more'] = false; |
826 | } else { |
827 | $facets[$field]['data']['list'] = array_merge( |
828 | $facets[$field]['data']['list'], |
829 | $facetpage[$field]['data']['list'] |
830 | ); |
831 | } |
832 | if ($facetpage[$field]['more'] !== false) { |
833 | $nextfields[] = $field; |
834 | } |
835 | } |
836 | } |
837 | $facetfields = $nextfields; |
838 | $page++; |
839 | } while ($limit == -1 && !empty($facetfields)); |
840 | return $facets; |
841 | } |
842 | |
843 | /** |
844 | * Get the extra search details |
845 | * |
846 | * @return ?array |
847 | */ |
848 | public function getExtraSearchBackendDetails() |
849 | { |
850 | return $this->extraSearchBackendDetails; |
851 | } |
852 | |
853 | /** |
854 | * A helper method that converts the list of facets for the last search from |
855 | * RecordCollection's facet list. |
856 | * |
857 | * @param array $facetList Facet list |
858 | * @param array $filter Array of field => on-screen description listing |
859 | * all of the desired facet fields; set to null to get all configured values. |
860 | * |
861 | * @return array Facets data arrays |
862 | */ |
863 | protected function buildFacetList(array $facetList, array $filter = null): array |
864 | { |
865 | // If there is no filter, we'll use all facets as the filter: |
866 | if (null === $filter) { |
867 | $filter = $this->getParams()->getFacetConfig(); |
868 | } |
869 | |
870 | // Start building the facet list: |
871 | $result = []; |
872 | |
873 | // Loop through every field returned by the result set |
874 | $translatedFacets = $this->getOptions()->getTranslatedFacets(); |
875 | $hierarchicalFacets |
876 | = is_callable([$this->getOptions(), 'getHierarchicalFacets']) |
877 | ? $this->getOptions()->getHierarchicalFacets() |
878 | : []; |
879 | $hierarchicalFacetSortSettings |
880 | = is_callable([$this->getOptions(), 'getHierarchicalFacetSortSettings']) |
881 | ? $this->getOptions()->getHierarchicalFacetSortSettings() |
882 | : []; |
883 | |
884 | foreach (array_keys($filter) as $field) { |
885 | $data = $facetList[$field] ?? []; |
886 | // Skip empty arrays: |
887 | if (count($data) < 1) { |
888 | continue; |
889 | } |
890 | // Initialize the settings for the current field |
891 | $result[$field] = [ |
892 | 'label' => $filter[$field], |
893 | 'list' => [], |
894 | ]; |
895 | // Should we translate values for the current facet? |
896 | $translate = in_array($field, $translatedFacets); |
897 | $hierarchical = in_array($field, $hierarchicalFacets); |
898 | $operator = $this->getParams()->getFacetOperator($field); |
899 | $resultList = []; |
900 | // Loop through values: |
901 | foreach ($data as $value => $count) { |
902 | $displayText = $this->getParams() |
903 | ->getFacetValueRawDisplayText($field, $value); |
904 | if ($hierarchical) { |
905 | if (!$this->hierarchicalFacetHelper) { |
906 | throw new \Exception( |
907 | get_class($this) |
908 | . ': hierarchical facet helper unavailable' |
909 | ); |
910 | } |
911 | $displayText = $this->hierarchicalFacetHelper |
912 | ->formatDisplayText($displayText); |
913 | } |
914 | $displayText = $translate |
915 | ? $this->getParams()->translateFacetValue($field, $displayText) |
916 | : $displayText; |
917 | $isApplied = $this->getParams()->hasFilter("$field:" . $value) |
918 | || $this->getParams()->hasFilter("~$field:" . $value); |
919 | |
920 | // Store the collected values: |
921 | $resultList[] = compact( |
922 | 'value', |
923 | 'displayText', |
924 | 'count', |
925 | 'operator', |
926 | 'isApplied' |
927 | ); |
928 | } |
929 | |
930 | if ($hierarchical) { |
931 | $sort = $hierarchicalFacetSortSettings[$field] |
932 | ?? $hierarchicalFacetSortSettings['*'] ?? 'count'; |
933 | $this->hierarchicalFacetHelper->sortFacetList($resultList, $sort); |
934 | |
935 | $resultList |
936 | = $this->hierarchicalFacetHelper->buildFacetArray($field, $resultList); |
937 | } |
938 | |
939 | $result[$field]['list'] = $resultList; |
940 | } |
941 | return $result; |
942 | } |
943 | } |