Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
87.43% |
160 / 183 |
|
92.00% |
23 / 25 |
CRAP | |
0.00% |
0 / 1 |
Backend | |
87.43% |
160 / 183 |
|
92.00% |
23 / 25 |
73.39 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
setPageSize | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
search | |
83.33% |
5 / 6 |
|
0.00% |
0 / 1 |
2.02 | |||
rawJsonSearch | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
2 | |||
getExtraRequestDetails | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
resetExtraRequestDetails | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getIds | |
100.00% |
13 / 13 |
|
100.00% |
1 / 1 |
3 | |||
random | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
2 | |||
retrieve | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
2 | |||
retrieveBatch | |
100.00% |
20 / 20 |
|
100.00% |
1 / 1 |
5 | |||
similar | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
2 | |||
terms | |
100.00% |
19 / 19 |
|
100.00% |
1 / 1 |
9 | |||
alphabeticBrowse | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
3 | |||
writeDocument | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
2 | |||
setQueryBuilder | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getQueryBuilder | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
setSimilarBuilder | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getSimilarBuilder | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
getRecordCollectionFactory | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
getConnector | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
createRecordCollection | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
deserialize | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
2 | |||
refineBrowseException | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
4 | |||
injectResponseWriter | |
100.00% |
16 / 16 |
|
100.00% |
1 / 1 |
5 | |||
workKeysSearch | |
0.00% |
0 / 22 |
|
0.00% |
0 / 1 |
72 |
1 | <?php |
2 | |
3 | /** |
4 | * SOLR backend. |
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 |
25 | * @author David Maus <maus@hab.de> |
26 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
27 | * @link https://vufind.org |
28 | */ |
29 | |
30 | namespace VuFindSearch\Backend\Solr; |
31 | |
32 | use VuFindSearch\Backend\AbstractBackend; |
33 | use VuFindSearch\Backend\Exception\BackendException; |
34 | use VuFindSearch\Backend\Exception\RemoteErrorException; |
35 | use VuFindSearch\Backend\Solr\Document\DocumentInterface; |
36 | use VuFindSearch\Backend\Solr\Response\Json\Terms; |
37 | use VuFindSearch\Exception\InvalidArgumentException; |
38 | use VuFindSearch\Feature\ExtraRequestDetailsInterface; |
39 | use VuFindSearch\Feature\GetIdsInterface; |
40 | use VuFindSearch\Feature\RandomInterface; |
41 | use VuFindSearch\Feature\RetrieveBatchInterface; |
42 | use VuFindSearch\Feature\SimilarInterface; |
43 | use VuFindSearch\ParamBag; |
44 | use VuFindSearch\Query\AbstractQuery; |
45 | use VuFindSearch\Query\WorkKeysQuery; |
46 | use VuFindSearch\Response\RecordCollectionFactoryInterface; |
47 | use VuFindSearch\Response\RecordCollectionInterface; |
48 | |
49 | use function count; |
50 | use function is_int; |
51 | use function sprintf; |
52 | |
53 | /** |
54 | * SOLR backend. |
55 | * |
56 | * @category VuFind |
57 | * @package Search |
58 | * @author David Maus <maus@hab.de> |
59 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
60 | * @link https://vufind.org |
61 | */ |
62 | class Backend extends AbstractBackend implements |
63 | SimilarInterface, |
64 | RetrieveBatchInterface, |
65 | RandomInterface, |
66 | ExtraRequestDetailsInterface, |
67 | GetIdsInterface |
68 | { |
69 | /** |
70 | * Limit for records per query in a batch retrieval. |
71 | * |
72 | * @var int |
73 | */ |
74 | protected $pageSize = 100; |
75 | |
76 | /** |
77 | * Connector. |
78 | * |
79 | * @var Connector |
80 | */ |
81 | protected $connector; |
82 | |
83 | /** |
84 | * Query builder. |
85 | * |
86 | * @var QueryBuilder |
87 | */ |
88 | protected $queryBuilder = null; |
89 | |
90 | /** |
91 | * Similar records query builder. |
92 | * |
93 | * @var SimilarBuilder |
94 | */ |
95 | protected $similarBuilder = null; |
96 | |
97 | /** |
98 | * Constructor. |
99 | * |
100 | * @param Connector $connector SOLR connector |
101 | * |
102 | * @return void |
103 | */ |
104 | public function __construct(Connector $connector) |
105 | { |
106 | $this->connector = $connector; |
107 | $this->identifier = null; |
108 | } |
109 | |
110 | /** |
111 | * Set the limit for batch queries |
112 | * |
113 | * @param int $pageSize Records per Query |
114 | * |
115 | * @return void |
116 | */ |
117 | public function setPageSize($pageSize) |
118 | { |
119 | $this->pageSize = $pageSize; |
120 | } |
121 | |
122 | /** |
123 | * Perform a search and return record collection. |
124 | * |
125 | * @param AbstractQuery $query Search query |
126 | * @param int $offset Search offset |
127 | * @param int $limit Search limit |
128 | * @param ParamBag $params Search backend parameters |
129 | * |
130 | * @return RecordCollectionInterface |
131 | */ |
132 | public function search( |
133 | AbstractQuery $query, |
134 | $offset, |
135 | $limit, |
136 | ParamBag $params = null |
137 | ) { |
138 | if ($query instanceof WorkKeysQuery) { |
139 | return $this->workKeysSearch($query, $offset, $limit, $params); |
140 | } |
141 | $json = $this->rawJsonSearch($query, $offset, $limit, $params); |
142 | $collection = $this->createRecordCollection($json); |
143 | $this->injectSourceIdentifier($collection); |
144 | |
145 | return $collection; |
146 | } |
147 | |
148 | /** |
149 | * Perform a search and return a raw response. |
150 | * |
151 | * @param AbstractQuery $query Search query |
152 | * @param int $offset Search offset |
153 | * @param int $limit Search limit |
154 | * @param ParamBag $params Search backend parameters |
155 | * |
156 | * @return string |
157 | */ |
158 | public function rawJsonSearch( |
159 | AbstractQuery $query, |
160 | $offset, |
161 | $limit, |
162 | ParamBag $params = null |
163 | ) { |
164 | $params = $params ?: new ParamBag(); |
165 | $this->injectResponseWriter($params); |
166 | |
167 | $params->set('rows', $limit); |
168 | $params->set('start', $offset); |
169 | $params->mergeWith($this->getQueryBuilder()->build($query, $params)); |
170 | return $this->connector->search($params); |
171 | } |
172 | |
173 | /** |
174 | * Returns some extra details about the search. |
175 | * |
176 | * @return array |
177 | */ |
178 | public function getExtraRequestDetails() |
179 | { |
180 | return [ |
181 | 'solrRequestUrl' => $this->connector->getLastUrl(), |
182 | ]; |
183 | } |
184 | |
185 | /** |
186 | * Clears all accumulated extra request details |
187 | * |
188 | * @return void |
189 | */ |
190 | public function resetExtraRequestDetails() |
191 | { |
192 | $this->connector->resetLastUrl(); |
193 | } |
194 | |
195 | /** |
196 | * Perform a search and return record collection of only record identifiers. |
197 | * |
198 | * @param AbstractQuery $query Search query |
199 | * @param int $offset Search offset |
200 | * @param int $limit Search limit |
201 | * @param ParamBag $params Search backend parameters |
202 | * |
203 | * @return RecordCollectionInterface |
204 | */ |
205 | public function getIds( |
206 | AbstractQuery $query, |
207 | $offset, |
208 | $limit, |
209 | ParamBag $params = null |
210 | ) { |
211 | $params = $params ?: new ParamBag(); |
212 | $this->injectResponseWriter($params); |
213 | |
214 | $params->set('rows', $limit); |
215 | $params->set('start', $offset); |
216 | $flParts = [$this->getConnector()->getUniqueKey()]; |
217 | if ($fl = $params->get('fl')) { |
218 | // Merge multiple values if necessary, then split on delimiter: |
219 | $flParts = array_unique(array_merge($flParts, explode(',', implode(',', $fl)))); |
220 | } |
221 | $params->set('fl', implode(',', $flParts)); |
222 | $params->mergeWith($this->getQueryBuilder()->build($query)); |
223 | $response = $this->connector->search($params); |
224 | $collection = $this->createRecordCollection($response); |
225 | $this->injectSourceIdentifier($collection); |
226 | |
227 | return $collection; |
228 | } |
229 | |
230 | /** |
231 | * Get Random records |
232 | * |
233 | * @param AbstractQuery $query Search query |
234 | * @param int $limit Search limit |
235 | * @param ParamBag $params Search backend parameters |
236 | * |
237 | * @return RecordCollectionInterface |
238 | */ |
239 | public function random( |
240 | AbstractQuery $query, |
241 | $limit, |
242 | ParamBag $params = null |
243 | ) { |
244 | $params = $params ?: new ParamBag(); |
245 | $this->injectResponseWriter($params); |
246 | |
247 | $random = rand(0, 1000000); |
248 | $sort = "{$random}_random asc"; |
249 | $params->set('sort', $sort); |
250 | |
251 | return $this->search($query, 0, $limit, $params); |
252 | } |
253 | |
254 | /** |
255 | * Retrieve a single document. |
256 | * |
257 | * @param string $id Document identifier |
258 | * @param ParamBag $params Search backend parameters |
259 | * |
260 | * @return RecordCollectionInterface |
261 | */ |
262 | public function retrieve($id, ParamBag $params = null) |
263 | { |
264 | $params = $params ?: new ParamBag(); |
265 | $this->injectResponseWriter($params); |
266 | |
267 | $response = $this->connector->retrieve($id, $params); |
268 | $collection = $this->createRecordCollection($response); |
269 | $this->injectSourceIdentifier($collection); |
270 | return $collection; |
271 | } |
272 | |
273 | /** |
274 | * Retrieve a batch of documents. |
275 | * |
276 | * @param array $ids Array of document identifiers |
277 | * @param ParamBag $params Search backend parameters |
278 | * |
279 | * @return RecordCollectionInterface |
280 | */ |
281 | public function retrieveBatch($ids, ParamBag $params = null) |
282 | { |
283 | $params = $params ?: new ParamBag(); |
284 | |
285 | // Callback function for formatting IDs: |
286 | $formatIds = function ($i) { |
287 | return '"' . addcslashes($i, '"') . '"'; |
288 | }; |
289 | |
290 | // Retrieve records a page at a time: |
291 | $results = false; |
292 | while (count($ids) > 0) { |
293 | $currentPage = array_splice($ids, 0, $this->pageSize, []); |
294 | $currentPage = array_map($formatIds, $currentPage); |
295 | $params->set('q', 'id:(' . implode(' OR ', $currentPage) . ')'); |
296 | $params->set('start', 0); |
297 | $params->set('rows', $this->pageSize); |
298 | $this->injectResponseWriter($params); |
299 | $next = $this->createRecordCollection( |
300 | $this->connector->search($params) |
301 | ); |
302 | if (!$results) { |
303 | $results = $next; |
304 | } else { |
305 | foreach ($next->getRecords() as $record) { |
306 | $results->add($record); |
307 | } |
308 | } |
309 | } |
310 | $this->injectSourceIdentifier($results); |
311 | return $results; |
312 | } |
313 | |
314 | /** |
315 | * Return similar records. |
316 | * |
317 | * @param string $id Id of record to compare with |
318 | * @param ParamBag $params Search backend parameters |
319 | * |
320 | * @return RecordCollectionInterface |
321 | */ |
322 | public function similar($id, ParamBag $params = null) |
323 | { |
324 | $params = $params ?: new ParamBag(); |
325 | $this->injectResponseWriter($params); |
326 | |
327 | $params->mergeWith($this->getSimilarBuilder()->build($id)); |
328 | $response = $this->connector->similar($id, $params); |
329 | $collection = $this->createRecordCollection($response); |
330 | $this->injectSourceIdentifier($collection); |
331 | return $collection; |
332 | } |
333 | |
334 | /** |
335 | * Return terms from SOLR index. |
336 | * |
337 | * @param string $field Index field |
338 | * @param string $start Starting term (blank for beginning of list) |
339 | * @param int $limit Maximum number of terms |
340 | * @param ParamBag $params Additional parameters |
341 | * |
342 | * @return Terms |
343 | */ |
344 | public function terms( |
345 | $field = null, |
346 | $start = null, |
347 | $limit = null, |
348 | ParamBag $params = null |
349 | ) { |
350 | // Support alternate syntax with ParamBag as first parameter: |
351 | if ($field instanceof ParamBag && $params === null) { |
352 | $params = $field; |
353 | $field = null; |
354 | } |
355 | |
356 | // Create empty ParamBag if none provided: |
357 | $params = $params ?: new ParamBag(); |
358 | $this->injectResponseWriter($params); |
359 | |
360 | // Always enable terms: |
361 | $params->set('terms', 'true'); |
362 | |
363 | // Use parameters if provided: |
364 | if (null !== $field) { |
365 | $params->set('terms.fl', $field); |
366 | } |
367 | if (null !== $start) { |
368 | $params->set('terms.lower', $start); |
369 | } |
370 | if (null !== $limit) { |
371 | $params->set('terms.limit', $limit); |
372 | } |
373 | |
374 | // Set defaults unless overridden: |
375 | if (!$params->hasParam('terms.lower.incl')) { |
376 | $params->set('terms.lower.incl', 'false'); |
377 | } |
378 | if (!$params->hasParam('terms.sort')) { |
379 | $params->set('terms.sort', 'index'); |
380 | } |
381 | |
382 | $response = $this->connector->terms($params); |
383 | $terms = new Terms($this->deserialize($response)); |
384 | return $terms; |
385 | } |
386 | |
387 | /** |
388 | * Obtain information from an alphabetic browse index. |
389 | * |
390 | * @param string $source Name of index to search |
391 | * @param string $from Starting point for browse results |
392 | * @param int $page Result page to return (starts at 0) |
393 | * @param int $limit Number of results to return on each page |
394 | * @param ParamBag $params Additional parameters |
395 | * @param int $offsetDelta Delta to use when calculating page |
396 | * offset (useful for showing a few results above the highlighted row) |
397 | * |
398 | * @return array |
399 | */ |
400 | public function alphabeticBrowse( |
401 | $source, |
402 | $from, |
403 | $page, |
404 | $limit = 20, |
405 | $params = null, |
406 | $offsetDelta = 0 |
407 | ) { |
408 | $params = $params ?: new ParamBag(); |
409 | $this->injectResponseWriter($params); |
410 | |
411 | $params->set('from', $from); |
412 | $params->set('offset', ($page * $limit) + $offsetDelta); |
413 | $params->set('rows', $limit); |
414 | $params->set('source', $source); |
415 | |
416 | $response = null; |
417 | try { |
418 | $response = $this->connector->query('browse', $params); |
419 | } catch (RemoteErrorException $e) { |
420 | $this->refineBrowseException($e); |
421 | } |
422 | return $this->deserialize($response); |
423 | } |
424 | |
425 | /** |
426 | * Write a document to Solr. Return an array of details about the updated index. |
427 | * |
428 | * @param DocumentInterface $doc Document to write |
429 | * @param ?int $timeout Timeout value (null for default) |
430 | * @param string $handler Handler to use |
431 | * @param ?ParamBag $params Search backend parameters |
432 | * |
433 | * @return array |
434 | */ |
435 | public function writeDocument( |
436 | DocumentInterface $doc, |
437 | int $timeout = null, |
438 | string $handler = 'update', |
439 | ?ParamBag $params = null |
440 | ) { |
441 | $connector = $this->getConnector(); |
442 | |
443 | // Write! |
444 | $connector->callWithHttpOptions( |
445 | is_int($timeout ?? null) ? compact('timeout') : [], |
446 | 'write', |
447 | $doc, |
448 | $handler, |
449 | $params |
450 | ); |
451 | |
452 | // Save the core name in the results in case the caller needs it. |
453 | return ['core' => $connector->getCore()]; |
454 | } |
455 | |
456 | /** |
457 | * Set the query builder. |
458 | * |
459 | * @param QueryBuilder $queryBuilder Query builder |
460 | * |
461 | * @return void |
462 | */ |
463 | public function setQueryBuilder(QueryBuilder $queryBuilder) |
464 | { |
465 | $this->queryBuilder = $queryBuilder; |
466 | } |
467 | |
468 | /** |
469 | * Return query builder. |
470 | * |
471 | * Lazy loads an empty default QueryBuilder if none was set. |
472 | * |
473 | * @return QueryBuilder |
474 | */ |
475 | public function getQueryBuilder() |
476 | { |
477 | if (!$this->queryBuilder) { |
478 | $this->queryBuilder = new QueryBuilder(); |
479 | } |
480 | return $this->queryBuilder; |
481 | } |
482 | |
483 | /** |
484 | * Set the similar records query builder. |
485 | * |
486 | * @param SimilarBuilder $similarBuilder Similar builder |
487 | * |
488 | * @return void |
489 | */ |
490 | public function setSimilarBuilder(SimilarBuilder $similarBuilder) |
491 | { |
492 | $this->similarBuilder = $similarBuilder; |
493 | } |
494 | |
495 | /** |
496 | * Return similar records query builder. |
497 | * |
498 | * Lazy loads an empty default SimilarBuilder if none was set. |
499 | * |
500 | * @return SimilarBuilder |
501 | */ |
502 | public function getSimilarBuilder() |
503 | { |
504 | if (!$this->similarBuilder) { |
505 | $this->similarBuilder = new SimilarBuilder(); |
506 | } |
507 | return $this->similarBuilder; |
508 | } |
509 | |
510 | /** |
511 | * Return the record collection factory. |
512 | * |
513 | * Lazy loads a generic collection factory. |
514 | * |
515 | * @return RecordCollectionFactoryInterface |
516 | */ |
517 | public function getRecordCollectionFactory() |
518 | { |
519 | if (!$this->collectionFactory) { |
520 | $this->collectionFactory = new Response\Json\RecordCollectionFactory(); |
521 | } |
522 | return $this->collectionFactory; |
523 | } |
524 | |
525 | /** |
526 | * Return the SOLR connector. |
527 | * |
528 | * @return Connector |
529 | */ |
530 | public function getConnector() |
531 | { |
532 | return $this->connector; |
533 | } |
534 | |
535 | /// Internal API |
536 | |
537 | /** |
538 | * Create record collection. |
539 | * |
540 | * @param string $json Serialized JSON response |
541 | * |
542 | * @return RecordCollectionInterface |
543 | */ |
544 | protected function createRecordCollection($json) |
545 | { |
546 | return $this->getRecordCollectionFactory() |
547 | ->factory($this->deserialize($json)); |
548 | } |
549 | |
550 | /** |
551 | * Deserialize JSON response. |
552 | * |
553 | * @param string $json Serialized JSON response |
554 | * |
555 | * @return array |
556 | * |
557 | * @throws BackendException Deserialization error |
558 | */ |
559 | protected function deserialize($json) |
560 | { |
561 | $response = json_decode($json, true); |
562 | $error = json_last_error(); |
563 | if ($error != \JSON_ERROR_NONE) { |
564 | throw new BackendException( |
565 | sprintf('JSON decoding error: %s -- %s', $error, $json) |
566 | ); |
567 | } |
568 | $qtime = $response['responseHeader']['QTime'] ?? 'n/a'; |
569 | $this->log('debug', 'Deserialized SOLR response', ['qtime' => $qtime]); |
570 | return $response; |
571 | } |
572 | |
573 | /** |
574 | * Improve the exception message for alphaBrowse errors when appropriate. |
575 | * |
576 | * @param RemoteErrorException $e Exception to clean up |
577 | * |
578 | * @return void |
579 | * @throws RemoteErrorException |
580 | */ |
581 | protected function refineBrowseException(RemoteErrorException $e) |
582 | { |
583 | $error = $e->getMessage() . $e->getResponse(); |
584 | if ( |
585 | strstr($error, 'does not exist') || strstr($error, 'no such table') |
586 | || strstr($error, 'couldn\'t find a browse index') |
587 | ) { |
588 | throw new RemoteErrorException( |
589 | 'Alphabetic Browse index missing. See ' . |
590 | 'https://vufind.org/wiki/indexing:alphabetical_heading_browse for ' . |
591 | 'details on generating the index.', |
592 | $e->getCode(), |
593 | $e->getResponse(), |
594 | $e->getPrevious() |
595 | ); |
596 | } |
597 | throw $e; |
598 | } |
599 | |
600 | /** |
601 | * Inject response writer and named list implementation into parameters. |
602 | * |
603 | * @param ParamBag $params Parameters |
604 | * |
605 | * @return void |
606 | * |
607 | * @throws InvalidArgumentException Response writer and named list |
608 | * implementation already set to an incompatible type. |
609 | */ |
610 | protected function injectResponseWriter(ParamBag $params) |
611 | { |
612 | if (array_diff($params->get('wt') ?: [], ['json'])) { |
613 | throw new InvalidArgumentException( |
614 | sprintf( |
615 | 'Invalid response writer type: %s', |
616 | implode(', ', $params->get('wt')) |
617 | ) |
618 | ); |
619 | } |
620 | if (array_diff($params->get('json.nl') ?: [], ['arrarr'])) { |
621 | throw new InvalidArgumentException( |
622 | sprintf( |
623 | 'Invalid named list implementation type: %s', |
624 | implode(', ', $params->get('json.nl')) |
625 | ) |
626 | ); |
627 | } |
628 | $params->set('wt', ['json']); |
629 | $params->set('json.nl', ['arrarr']); |
630 | } |
631 | |
632 | /** |
633 | * Return work expressions. |
634 | * |
635 | * @param WorkKeysQuery $query Search query |
636 | * @param int $offset Search offset |
637 | * @param int $limit Search limit |
638 | * @param ParamBag $defaultParams Search backend parameters |
639 | * |
640 | * @return RecordCollectionInterface |
641 | */ |
642 | protected function workKeysSearch( |
643 | WorkKeysQuery $query, |
644 | int $offset, |
645 | int $limit, |
646 | ParamBag $defaultParams = null |
647 | ): RecordCollectionInterface { |
648 | $id = $query->getId(); |
649 | if ('' === $id) { |
650 | throw new BackendException('Record ID empty in work keys query'); |
651 | } |
652 | if (!($workKeys = $query->getWorkKeys())) { |
653 | $recordResponse = $this->connector->retrieve($id); |
654 | $recordCollection = $this->createRecordCollection($recordResponse); |
655 | $record = $recordCollection->first(); |
656 | if (!$record || !($workKeys = $record->tryMethod('getWorkKeys'))) { |
657 | return $this->createRecordCollection('{}'); |
658 | } |
659 | } |
660 | |
661 | $params = $defaultParams ? clone $defaultParams : new \VuFindSearch\ParamBag(); |
662 | $this->injectResponseWriter($params); |
663 | $params->set('q', "{!terms f=work_keys_str_mv separator=\"\u{001f}\"}" . implode("\u{001f}", $workKeys)); |
664 | if (!$query->getIncludeSelf()) { |
665 | $params->add('fq', sprintf('-id:"%s"', addcslashes($id, '"'))); |
666 | } |
667 | $params->set('rows', $limit); |
668 | $params->set('start', $offset); |
669 | if (!$params->hasParam('sort')) { |
670 | $params->add('sort', 'publishDateSort desc, title_sort asc'); |
671 | } |
672 | $response = $this->connector->search($params); |
673 | $collection = $this->createRecordCollection($response); |
674 | $this->injectSourceIdentifier($collection); |
675 | return $collection; |
676 | } |
677 | } |