Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
63.55% |
129 / 203 |
|
63.16% |
12 / 19 |
CRAP | |
0.00% |
0 / 1 |
ResultScroller | |
63.55% |
129 / 203 |
|
63.16% |
12 / 19 |
307.36 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
init | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
3 | |||
addData | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
2 | |||
ensureRoomInSessionStorage | |
12.50% |
1 / 8 |
|
0.00% |
0 / 1 |
21.75 | |||
scrollOnCurrentPage | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
fetchPreviousPage | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
5 | |||
fetchNextPage | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
6 | |||
scrollToPreviousPage | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
6 | |||
scrollToNextPage | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
6 | |||
scrollToFirstRecord | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
3 | |||
scrollToLastRecord | |
100.00% |
15 / 15 |
|
100.00% |
1 / 1 |
3 | |||
getFirstRecordId | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
getLastPageNumber | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getLastRecordId | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
getScrollData | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
5 | |||
buildScrollDataArray | |
72.50% |
29 / 40 |
|
0.00% |
0 / 1 |
26.51 | |||
fetchPage | |
90.00% |
9 / 10 |
|
0.00% |
0 / 1 |
4.02 | |||
restoreSearch | |
0.00% |
0 / 18 |
|
0.00% |
0 / 1 |
12 | |||
rememberSearch | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | /** |
4 | * Class for managing "next" and "previous" navigation within result sets. |
5 | * |
6 | * PHP version 8 |
7 | * |
8 | * Copyright (C) Villanova University 2010 |
9 | * Copyright (C) The National Library of Finland 2023 |
10 | * |
11 | * This program is free software; you can redistribute it and/or modify |
12 | * it under the terms of the GNU General Public License version 2, |
13 | * as published by the Free Software Foundation. |
14 | * |
15 | * This program is distributed in the hope that it will be useful, |
16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
18 | * GNU General Public License for more details. |
19 | * |
20 | * You should have received a copy of the GNU General Public License |
21 | * along with this program; if not, write to the Free Software |
22 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
23 | * |
24 | * @category VuFind |
25 | * @package Controller_Plugins |
26 | * @author Demian Katz <demian.katz@villanova.edu> |
27 | * @author Ere Maijala <ere.maijala@helsinki.fi> |
28 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
29 | * @link https://vufind.org/wiki/development Wiki |
30 | */ |
31 | |
32 | namespace VuFind\Controller\Plugin; |
33 | |
34 | use Exception; |
35 | use Laminas\Mvc\Controller\Plugin\AbstractPlugin; |
36 | use Laminas\Session\Container as SessionContainer; |
37 | use VuFind\Db\Service\SearchServiceInterface; |
38 | use VuFind\RecordDriver\AbstractBase as BaseRecord; |
39 | use VuFind\Search\Base\Results; |
40 | use VuFind\Search\Memory as SearchMemory; |
41 | use VuFind\Search\Results\PluginManager as ResultsManager; |
42 | |
43 | use function count; |
44 | use function is_array; |
45 | |
46 | /** |
47 | * Class for managing "next" and "previous" navigation within result sets. |
48 | * |
49 | * @category VuFind |
50 | * @package Controller_Plugins |
51 | * @author Demian Katz <demian.katz@villanova.edu> |
52 | * @author Ere Maijala <ere.maijala@helsinki.fi> |
53 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
54 | * @link https://vufind.org/wiki/development Wiki |
55 | */ |
56 | class ResultScroller extends AbstractPlugin |
57 | { |
58 | /** |
59 | * Maximum number of last searches to track |
60 | * |
61 | * @var int |
62 | */ |
63 | public const LAST_SEARCH_LIMIT = 10; |
64 | |
65 | /** |
66 | * Is scroller enabled? |
67 | * |
68 | * @var bool |
69 | */ |
70 | protected $enabled; |
71 | |
72 | /** |
73 | * Session data used by scroller |
74 | * |
75 | * @var SessionContainer |
76 | */ |
77 | protected $session; |
78 | |
79 | /** |
80 | * Results manager |
81 | * |
82 | * @var ResultsManager |
83 | */ |
84 | protected $resultsManager; |
85 | |
86 | /** |
87 | * Search memory |
88 | * |
89 | * @var SearchMemory |
90 | */ |
91 | protected $searchMemory; |
92 | |
93 | /** |
94 | * Currently active scroll data |
95 | * |
96 | * @var \stdClass |
97 | */ |
98 | protected $data = null; |
99 | |
100 | /** |
101 | * Constructor. Create a new search result scroller. |
102 | * |
103 | * @param SessionContainer $session Session container |
104 | * @param ResultsManager $rm Results manager |
105 | * @param SearchMemory $sm Search memory |
106 | * @param bool $enabled Is the scroller enabled? |
107 | */ |
108 | public function __construct( |
109 | SessionContainer $session, |
110 | ResultsManager $rm, |
111 | SearchMemory $sm, |
112 | $enabled = true |
113 | ) { |
114 | $this->enabled = $enabled; |
115 | $this->session = $session; |
116 | $this->resultsManager = $rm; |
117 | $this->searchMemory = $sm; |
118 | } |
119 | |
120 | /** |
121 | * Initialize this result set scroller. This should only be called |
122 | * prior to displaying the results of a new search. |
123 | * |
124 | * @param Results $searchObject The search object that was used to execute the |
125 | * last search. |
126 | * |
127 | * @return bool True if enabled and initialized with results, false otherwise. |
128 | */ |
129 | public function init($searchObject) |
130 | { |
131 | // Do nothing if disabled or search is empty: |
132 | if (!$this->enabled || $searchObject->getResultTotal() <= 0) { |
133 | return false; |
134 | } |
135 | |
136 | // Save the details of this search in the session: |
137 | $this->addData($searchObject); |
138 | return (bool)$this->session->s[$searchObject->getSearchId()]->currIds; |
139 | } |
140 | |
141 | /** |
142 | * Add data to session for a search |
143 | * |
144 | * @param Results $searchObject Search object |
145 | * |
146 | * @return void |
147 | */ |
148 | protected function addData(Results $searchObject): void |
149 | { |
150 | $data = new \stdClass(); |
151 | $data->page = $searchObject->getParams()->getPage(); |
152 | $data->limit = $searchObject->getParams()->getLimit(); |
153 | $data->sort = $searchObject->getParams()->getSort(); |
154 | $data->total = $searchObject->getResultTotal(); |
155 | $data->firstlast = $searchObject->getOptions()->recordFirstLastNavigationEnabled(); |
156 | |
157 | // save the IDs of records on the current page to the session |
158 | // so we can "slide" from one record to the next/previous records |
159 | // spanning 2 consecutive pages |
160 | $data->currIds = $this->fetchPage($searchObject); |
161 | |
162 | // Store last access time for eviction |
163 | $data->lastAccessTime = time(); |
164 | |
165 | if (!isset($this->session->s)) { |
166 | $this->session->s = []; |
167 | } |
168 | |
169 | $this->ensureRoomInSessionStorage(); |
170 | $this->session->s[$searchObject->getSearchId()] = $data; |
171 | } |
172 | |
173 | /** |
174 | * Make room for a new entry in the session storage as necessary |
175 | * |
176 | * @return void |
177 | */ |
178 | protected function ensureRoomInSessionStorage(): void |
179 | { |
180 | // Evict oldest entry if storage is full: |
181 | while (count($this->session->s) >= static::LAST_SEARCH_LIMIT) { |
182 | $oldest = null; |
183 | $oldestTime = null; |
184 | foreach ($this->session->s as $id => $search) { |
185 | if (null === $oldest || $search->lastAccessTime < $oldestTime) { |
186 | $oldest = $id; |
187 | $oldestTime = $search->lastAccessTime; |
188 | } |
189 | } |
190 | unset($this->session->s[$oldest]); |
191 | } |
192 | } |
193 | |
194 | /** |
195 | * Return a modified results array to help scroll the user through the current |
196 | * page of results |
197 | * |
198 | * @param array $retVal Return values (in progress) |
199 | * @param int $pos Current position within current page |
200 | * |
201 | * @return array |
202 | */ |
203 | protected function scrollOnCurrentPage($retVal, $pos) |
204 | { |
205 | $retVal['previousRecord'] = $this->data->currIds[$pos - 1]; |
206 | $retVal['nextRecord'] = $this->data->currIds[$pos + 1]; |
207 | // and we're done |
208 | return $retVal; |
209 | } |
210 | |
211 | /** |
212 | * Return a modified results array for the case where the user is on the cusp of |
213 | * the previous page of results |
214 | * |
215 | * @param array $retVal Return values (in progress) |
216 | * @param Results $lastSearch Representation of last search |
217 | * @param int $pos Current position within current |
218 | * page |
219 | * @param int $count Size of current page of results |
220 | * |
221 | * @return array |
222 | */ |
223 | protected function fetchPreviousPage($retVal, $lastSearch, $pos, $count) |
224 | { |
225 | // if the current page is NOT the first page, and |
226 | // the previous page has not been fetched before, then |
227 | // fetch the previous page |
228 | if ($this->data->page > 1 && $this->data->prevIds == null) { |
229 | $this->data->prevIds = $this->fetchPage( |
230 | $lastSearch, |
231 | $this->data->page - 1 |
232 | ); |
233 | } |
234 | |
235 | // if there is something on the previous page, then the previous |
236 | // record is the last record on the previous page |
237 | if (!empty($this->data->prevIds)) { |
238 | $retVal['previousRecord'] |
239 | = $this->data->prevIds[count($this->data->prevIds) - 1]; |
240 | } |
241 | |
242 | // if it is not the last record on the current page, then |
243 | // we also have a next record on the current page |
244 | if ($pos < $count - 1) { |
245 | $retVal['nextRecord'] = $this->data->currIds[$pos + 1]; |
246 | } |
247 | |
248 | // and we're done |
249 | return $retVal; |
250 | } |
251 | |
252 | /** |
253 | * Return a modified results array for the case where the user is on the cusp of |
254 | * the next page of results |
255 | * |
256 | * @param array $retVal Return values (in progress) |
257 | * @param Results $lastSearch Representation of last search |
258 | * @param int $pos Current position within current |
259 | * page |
260 | * |
261 | * @return array |
262 | */ |
263 | protected function fetchNextPage($retVal, $lastSearch, $pos) |
264 | { |
265 | // if the current page is NOT the last page, and the next page has not been |
266 | // fetched, then fetch the next page |
267 | if ( |
268 | $this->data->page < ceil($this->data->total / $this->data->limit) |
269 | && $this->data->nextIds == null |
270 | ) { |
271 | $this->data->nextIds = $this->fetchPage( |
272 | $lastSearch, |
273 | $this->data->page + 1 |
274 | ); |
275 | } |
276 | |
277 | // if there is something on the next page, then the next |
278 | // record is the first record on the next page |
279 | if (is_array($this->data->nextIds) && count($this->data->nextIds) > 0) { |
280 | $retVal['nextRecord'] = $this->data->nextIds[0]; |
281 | } |
282 | |
283 | // if it is not the first record on the current page, then |
284 | // we also have a previous record on the current page |
285 | if ($pos > 0) { |
286 | $retVal['previousRecord'] = $this->data->currIds[$pos - 1]; |
287 | } |
288 | |
289 | // and we're done |
290 | return $retVal; |
291 | } |
292 | |
293 | /** |
294 | * Return a modified results array for the case where we need to retrieve data |
295 | * from the previous page of results |
296 | * |
297 | * @param array $retVal Return values (in progress) |
298 | * @param Results $lastSearch Representation of last search |
299 | * @param int $pos Current position within |
300 | * previous page |
301 | * |
302 | * @return array |
303 | */ |
304 | protected function scrollToPreviousPage($retVal, $lastSearch, $pos) |
305 | { |
306 | // decrease the page in the session because |
307 | // we're now sliding into the previous page |
308 | // (-- doesn't work on ArrayObjects) |
309 | $this->data->page = $this->data->page - 1; |
310 | |
311 | // shift pages to the right |
312 | $tmp = $this->data->currIds; |
313 | $this->data->currIds = $this->data->prevIds; |
314 | $this->data->nextIds = $tmp; |
315 | $this->data->prevIds = null; |
316 | |
317 | // now we can set the previous/next record |
318 | if ($pos > 0) { |
319 | $retVal['previousRecord'] |
320 | = $this->data->currIds[$pos - 1]; |
321 | } |
322 | $retVal['nextRecord'] = $this->data->nextIds[0]; |
323 | |
324 | // recalculate the current position |
325 | $retVal['currentPosition'] |
326 | = ($this->data->page - 1) |
327 | * $this->data->limit + $pos + 1; |
328 | |
329 | // update the search URL in the session |
330 | $lastSearch->getParams()->setPage($this->data->page); |
331 | $this->rememberSearch($lastSearch); |
332 | |
333 | // and we're done |
334 | return $retVal; |
335 | } |
336 | |
337 | /** |
338 | * Return a modified results array for the case where we need to retrieve data |
339 | * from the next page of results |
340 | * |
341 | * @param array $retVal Return values (in progress) |
342 | * @param Results $lastSearch Representation of last search |
343 | * @param int $pos Current position within next |
344 | * page |
345 | * |
346 | * @return array |
347 | */ |
348 | protected function scrollToNextPage($retVal, $lastSearch, $pos) |
349 | { |
350 | // increase the page in the session because |
351 | // we're now sliding into the next page |
352 | // (++ doesn't work on ArrayObjects) |
353 | $this->data->page = $this->data->page + 1; |
354 | |
355 | // shift pages to the left |
356 | $tmp = $this->data->currIds; |
357 | $this->data->currIds = $this->data->nextIds; |
358 | $this->data->prevIds = $tmp; |
359 | $this->data->nextIds = null; |
360 | |
361 | // now we can set the previous/next record |
362 | $retVal['previousRecord'] |
363 | = $this->data->prevIds[count($this->data->prevIds) - 1]; |
364 | if ($pos < count($this->data->currIds) - 1) { |
365 | $retVal['nextRecord'] = $this->data->currIds[$pos + 1]; |
366 | } |
367 | |
368 | // recalculate the current position |
369 | $retVal['currentPosition'] |
370 | = ($this->data->page - 1) |
371 | * $this->data->limit + $pos + 1; |
372 | |
373 | // update the search URL in the session |
374 | $lastSearch->getParams()->setPage($this->data->page); |
375 | $this->rememberSearch($lastSearch); |
376 | |
377 | // and we're done |
378 | return $retVal; |
379 | } |
380 | |
381 | /** |
382 | * Return a modified results array for the case where we need to retrieve data |
383 | * from the the first page of results |
384 | * |
385 | * @param array $retVal Return values (in progress) |
386 | * @param Results $lastSearch Representation of last search |
387 | * |
388 | * @return array |
389 | */ |
390 | protected function scrollToFirstRecord($retVal, $lastSearch) |
391 | { |
392 | // Set page in session to First Page |
393 | $this->data->page = 1; |
394 | // update the search URL in the session |
395 | $lastSearch->getParams()->setPage($this->data->page); |
396 | $this->rememberSearch($lastSearch); |
397 | |
398 | // update current, next and prev Ids |
399 | $this->data->currIds = $this->fetchPage($lastSearch, $this->data->page); |
400 | $this->data->nextIds = $this->fetchPage($lastSearch, $this->data->page + 1); |
401 | $this->data->prevIds = null; |
402 | |
403 | // now we can set the previous/next record |
404 | $retVal['previousRecord'] = null; |
405 | $retVal['nextRecord'] = $this->data->currIds[1] ?? null; |
406 | // cover extremely unlikely edge case -- page size of 1: |
407 | if (null === $retVal['nextRecord'] && isset($this->data->nextIds[0])) { |
408 | $retVal['nextRecord'] = $this->data->nextIds[0]; |
409 | } |
410 | |
411 | // recalculate the current position |
412 | $retVal['currentPosition'] = 1; |
413 | |
414 | // and we're done |
415 | return $retVal; |
416 | } |
417 | |
418 | /** |
419 | * Return a modified results array for the case where we need to retrieve data |
420 | * from the the last page of results |
421 | * |
422 | * @param array $retVal Return values (in progress) |
423 | * @param Results $lastSearch Representation of last search |
424 | * |
425 | * @return array |
426 | */ |
427 | protected function scrollToLastRecord($retVal, $lastSearch) |
428 | { |
429 | // Set page in session to Last Page |
430 | $this->data->page = $this->getLastPageNumber(); |
431 | // update the search URL in the session |
432 | $lastSearch->getParams()->setPage($this->data->page); |
433 | $this->rememberSearch($lastSearch); |
434 | |
435 | // update current, next and prev Ids |
436 | $this->data->currIds = $this->fetchPage($lastSearch, $this->data->page); |
437 | $this->data->prevIds = $this->fetchPage($lastSearch, $this->data->page - 1); |
438 | $this->data->nextIds = null; |
439 | |
440 | // recalculate the current position |
441 | $retVal['currentPosition'] = $this->data->total; |
442 | |
443 | // now we can set the previous/next record |
444 | $retVal['nextRecord'] = null; |
445 | if (count($this->data->currIds) > 1) { |
446 | $pos = count($this->data->currIds) - 2; |
447 | $retVal['previousRecord'] = $this->data->currIds[$pos]; |
448 | } elseif (count($this->data->prevIds) > 0) { |
449 | $prevPos = count($this->data->prevIds) - 1; |
450 | $retVal['previousRecord'] = $this->data->prevIds[$prevPos]; |
451 | } |
452 | |
453 | // and we're done |
454 | return $retVal; |
455 | } |
456 | |
457 | /** |
458 | * Get the ID of the first record in the result set. |
459 | * |
460 | * @param Results $lastSearch Representation of last search |
461 | * |
462 | * @return string |
463 | */ |
464 | protected function getFirstRecordId($lastSearch) |
465 | { |
466 | if (!isset($this->data->firstId)) { |
467 | $firstPage = $this->fetchPage($lastSearch, 1); |
468 | $this->data->firstId = $firstPage[0]; |
469 | } |
470 | return $this->data->firstId; |
471 | } |
472 | |
473 | /** |
474 | * Calculate the last page number in the result set. |
475 | * |
476 | * @return int |
477 | */ |
478 | protected function getLastPageNumber() |
479 | { |
480 | return ceil($this->data->total / $this->data->limit); |
481 | } |
482 | |
483 | /** |
484 | * Get the ID of the last record in the result set. |
485 | * |
486 | * @param Results $lastSearch Representation of last search |
487 | * |
488 | * @return string |
489 | */ |
490 | protected function getLastRecordId($lastSearch) |
491 | { |
492 | if (!isset($this->data->lastId)) { |
493 | $results = $this->fetchPage($lastSearch, $this->getLastPageNumber()); |
494 | $this->data->lastId = array_pop($results); |
495 | } |
496 | return $this->data->lastId; |
497 | } |
498 | |
499 | /** |
500 | * Get the previous/next record in the last search |
501 | * result set relative to the current one, also return |
502 | * the position of the current record in the result set. |
503 | * Return array('previousRecord'=>previd, 'nextRecord'=>nextid, |
504 | * 'currentPosition'=>number, 'resultTotal'=>number). |
505 | * |
506 | * @param BaseRecord $driver Driver for the record currently being displayed |
507 | * |
508 | * @return array |
509 | */ |
510 | public function getScrollData($driver) |
511 | { |
512 | $retVal = [ |
513 | 'firstRecord' => null, 'lastRecord' => null, |
514 | 'previousRecord' => null, 'nextRecord' => null, |
515 | 'currentPosition' => null, 'resultTotal' => null, |
516 | ]; |
517 | |
518 | $searchId = $this->searchMemory->getLastSearchId(); |
519 | // Process scroll data only if enabled and data exists: |
520 | if ( |
521 | !$this->enabled || !$searchId || !isset($this->session->s[$searchId]) |
522 | || !($lastSearch = $this->restoreSearch($searchId)) |
523 | ) { |
524 | return $retVal; |
525 | } |
526 | $this->data = $this->session->s[$searchId]; |
527 | // Get results: |
528 | $result = $this->buildScrollDataArray($retVal, $driver, $lastSearch); |
529 | // Touch and update session with any changes: |
530 | $this->data->lastAccessTime = time(); |
531 | $this->session->s[$searchId] = $this->data; |
532 | |
533 | return $result; |
534 | } |
535 | |
536 | /** |
537 | * Build and return the scroll data array |
538 | * |
539 | * @param array $retVal Return values (in progress) |
540 | * @param BaseRecord $driver Driver for the record currently being displayed |
541 | * @param Results $lastSearch Representation of last search |
542 | * |
543 | * @return array |
544 | */ |
545 | protected function buildScrollDataArray( |
546 | array $retVal, |
547 | BaseRecord $driver, |
548 | Results $lastSearch |
549 | ): array { |
550 | // Make sure expected data elements are populated: |
551 | if (!isset($this->data->prevIds)) { |
552 | $this->data->prevIds = null; |
553 | } |
554 | if (!isset($this->data->nextIds)) { |
555 | $this->data->nextIds = null; |
556 | } |
557 | |
558 | // Store total result set size: |
559 | $retVal['resultTotal'] = $this->data->total ?? 0; |
560 | |
561 | // Set first and last record IDs |
562 | if ($this->data->firstlast) { |
563 | $retVal['firstRecord'] = $this->getFirstRecordId($lastSearch); |
564 | $retVal['lastRecord'] = $this->getLastRecordId($lastSearch); |
565 | } |
566 | |
567 | // build a full ID string using the driver: |
568 | $id = $driver->getSourceIdentifier() . '|' . $driver->getUniqueId(); |
569 | |
570 | // find where this record is in the current result page |
571 | $pos = is_array($this->data->currIds) |
572 | ? array_search($id, $this->data->currIds) |
573 | : false; |
574 | if ($pos !== false) { |
575 | // OK, found this record in the current result page |
576 | // calculate its position relative to the result set |
577 | $retVal['currentPosition'] |
578 | = ($this->data->page - 1) * $this->data->limit + $pos + 1; |
579 | |
580 | // count how many records in the current result page |
581 | $count = count($this->data->currIds); |
582 | if ($pos > 0 && $pos < $count - 1) { |
583 | // the current record is somewhere in the middle of the current |
584 | // page, ie: not first or last |
585 | return $this->scrollOnCurrentPage($retVal, $pos); |
586 | } elseif ($pos == 0) { |
587 | // this record is first record on the current page |
588 | return $this |
589 | ->fetchPreviousPage($retVal, $lastSearch, $pos, $count); |
590 | } elseif ($pos == $count - 1) { |
591 | // this record is last record on the current page |
592 | return $this->fetchNextPage($retVal, $lastSearch, $pos); |
593 | } |
594 | } else { |
595 | // the current record is not on the current page |
596 | // if there is something on the previous page |
597 | if (!empty($this->data->prevIds)) { |
598 | // check if current record is on the previous page |
599 | $pos = is_array($this->data->prevIds) |
600 | ? array_search($id, $this->data->prevIds) : false; |
601 | if ($pos !== false) { |
602 | return $this |
603 | ->scrollToPreviousPage($retVal, $lastSearch, $pos); |
604 | } |
605 | } |
606 | // if there is something on the next page |
607 | if (!empty($this->data->nextIds)) { |
608 | // check if current record is on the next page |
609 | $pos = is_array($this->data->nextIds) |
610 | ? array_search($id, $this->data->nextIds) : false; |
611 | if ($pos !== false) { |
612 | return $this->scrollToNextPage($retVal, $lastSearch, $pos); |
613 | } |
614 | } |
615 | if ($this->data->firstlast) { |
616 | if ($id == $retVal['firstRecord']) { |
617 | return $this->scrollToFirstRecord($retVal, $lastSearch); |
618 | } |
619 | if ($id == $retVal['lastRecord']) { |
620 | return $this->scrollToLastRecord($retVal, $lastSearch); |
621 | } |
622 | } |
623 | } |
624 | |
625 | return $retVal; |
626 | } |
627 | |
628 | /** |
629 | * Fetch the given page of results from the given search object and |
630 | * return the IDs of the records in an array. |
631 | * |
632 | * @param object $searchObject The search object to use to execute the search |
633 | * @param int $page The page number to fetch (null for current) |
634 | * |
635 | * @return array |
636 | */ |
637 | protected function fetchPage($searchObject, $page = null) |
638 | { |
639 | if (null !== $page) { |
640 | $searchObject->getParams()->setPage($page); |
641 | $searchObject->performAndProcessSearch(); |
642 | } |
643 | |
644 | $retVal = []; |
645 | foreach ($searchObject->getResults() as $record) { |
646 | if (!($record instanceof BaseRecord)) { |
647 | return false; |
648 | } |
649 | $retVal[] |
650 | = $record->getSourceIdentifier() . '|' . $record->getUniqueId(); |
651 | } |
652 | return $retVal; |
653 | } |
654 | |
655 | /** |
656 | * Restore a saved search. |
657 | * |
658 | * @param int $searchId Search ID |
659 | * |
660 | * @return ?Results |
661 | */ |
662 | protected function restoreSearch(int $searchId): ?Results |
663 | { |
664 | $searchService = $this->getController()->getDbService(SearchServiceInterface::class); |
665 | $row = $searchService->getSearchByIdAndOwner( |
666 | $searchId, |
667 | $this->session->getManager()->getId(), |
668 | null |
669 | ); |
670 | if (!empty($row)) { |
671 | $search = $row->getSearchObject()?->deminify($this->resultsManager); |
672 | if (!$search) { |
673 | throw new Exception("Problem getting search object from search {$row->getId()}."); |
674 | } |
675 | // The saved search does not remember its original limit or sort; |
676 | // we should reapply them from the session data: |
677 | $search->getParams()->setLimit( |
678 | $this->session->s[$searchId]->limit ?? null |
679 | ); |
680 | $search->getParams()->setSort( |
681 | $this->session->s[$searchId]->sort ?? null |
682 | ); |
683 | return $search; |
684 | } |
685 | return null; |
686 | } |
687 | |
688 | /** |
689 | * Update the remembered "last search" in the session. |
690 | * |
691 | * @param Results $search Search object to remember. |
692 | * |
693 | * @return void |
694 | */ |
695 | protected function rememberSearch($search) |
696 | { |
697 | $baseUrl = $this->getController()->url()->fromRoute( |
698 | $search->getOptions()->getSearchAction() |
699 | ); |
700 | $this->searchMemory->rememberSearch( |
701 | $baseUrl . $search->getUrlQuery()->getParams(false), |
702 | $search->getSearchId() |
703 | ); |
704 | } |
705 | } |