Passed
Pull Request — master (#123)
by
unknown
04:02
created

SolrSearch::getNumFound()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 3
rs 10
1
<?php
2
3
namespace Kitodo\Dlf\Common\Solr;
4
5
use Kitodo\Dlf\Common\AbstractDocument;
6
use Kitodo\Dlf\Common\Helper;
7
use Kitodo\Dlf\Common\Indexer;
8
use Kitodo\Dlf\Common\Solr\SearchResult\ResultDocument;
9
use Kitodo\Dlf\Domain\Repository\DocumentRepository;
10
use Solarium\QueryType\Select\Result\Document;
11
use TYPO3\CMS\Core\Cache\CacheManager;
12
use TYPO3\CMS\Core\Utility\GeneralUtility;
13
use TYPO3\CMS\Core\Utility\MathUtility;
14
use TYPO3\CMS\Extbase\Persistence\Generic\QueryResult;
15
use TYPO3\CMS\Extbase\Persistence\QueryResultInterface;
16
17
/**
18
 * Targeted towards being used in ``PaginateController`` (``<f:widget.paginate>``).
19
 *
20
 * Notes on implementation:
21
 * - `Countable`: `count()` returns the number of toplevel documents.
22
 * - `getNumLoadedDocuments()`: Number of toplevel documents that have been fetched from Solr.
23
 * - `ArrayAccess`/`Iterator`: Access *fetched* toplevel documents indexed in order of their ranking.
24
 *
25
 * @package TYPO3
26
 * @subpackage dlf
27
 *
28
 * @access public
29
 */
30
class SolrSearch implements \Countable, \Iterator, \ArrayAccess, QueryResultInterface
31
{
32
    /**
33
     * @access private
34
     * @var DocumentRepository
35
     */
36
    private DocumentRepository $documentRepository;
37
38
    /**
39
     * @access private
40
     * @var array|QueryResultInterface
41
     */
42
    private $collections;
43
44
    /**
45
     * @access private
46
     * @var array
47
     */
48
    private array $settings;
49
50
    /**
51
     * @access private
52
     * @var array
53
     */
54
    private array $searchParams;
55
56
    /**
57
     * @access private
58
     * @var QueryResult|null
59
     */
60
    private ?QueryResult $listedMetadata;
61
62
    /**
63
     * @access private
64
     * @var QueryResult|null
65
     */
66
    private ?QueryResult $indexedMetadata;
67
68
    /**
69
     * @access private
70
     * @var array
71
     */
72
    private array $params;
73
74
    /**
75
     * @access private
76
     * @var array
77
     */
78
    private $result;
79
80
    /**
81
     * @access private
82
     * @var int
83
     */
84
    protected int $position = 0;
85
86
    /**
87
     * Constructs SolrSearch instance.
88
     *
89
     * @access public
90
     *
91
     * @param DocumentRepository $documentRepository
92
     * @param array|QueryResultInterface $collections can contain 0, 1 or many Collection objects
93
     * @param array $settings
94
     * @param array $searchParams
95
     * @param QueryResult $listedMetadata
96
     * @param QueryResult $indexedMetadata
97
     *
98
     * @return void
99
     */
100
    public function __construct(DocumentRepository $documentRepository, $collections, array $settings, array $searchParams, QueryResult $listedMetadata = null, QueryResult $indexedMetadata = null)
101
    {
102
        $this->documentRepository = $documentRepository;
103
        $this->collections = $collections;
104
        $this->settings = $settings;
105
        $this->searchParams = $searchParams;
106
        $this->listedMetadata = $listedMetadata;
107
        $this->indexedMetadata = $indexedMetadata;
108
    }
109
110
    /**
111
     * Gets amount of loaded documents.
112
     *
113
     * @access public
114
     *
115
     * @return int
116
     */
117
    public function getNumLoadedDocuments(): int
118
    {
119
        return count($this->result['documents']);
120
    }
121
122
    /**
123
     * Count results.
124
     *
125
     * @access public
126
     *
127
     * @return int
128
     */
129
    public function count(): int
130
    {
131
        if ($this->result === null) {
132
            return 0;
133
        }
134
135
        return $this->result['numberOfToplevels'];
136
    }
137
138
    /**
139
     * Current result.
140
     *
141
     * @access public
142
     *
143
     * @return array
144
     */
145
    public function current(): array
146
    {
147
        return $this[$this->position];
148
    }
149
150
    /**
151
     * Current key.
152
     *
153
     * @access public
154
     *
155
     * @return int
156
     */
157
    public function key(): int
158
    {
159
        return $this->position;
160
    }
161
162
    /**
163
     * Next key.
164
     *
165
     * @access public
166
     *
167
     * @return void
168
     */
169
    public function next(): void
170
    {
171
        $this->position++;
172
    }
173
174
    /**
175
     * First key.
176
     *
177
     * @access public
178
     *
179
     * @return void
180
     */
181
    public function rewind(): void
182
    {
183
        $this->position = 0;
184
    }
185
186
    /**
187
     * @access public
188
     *
189
     * @return bool
190
     */
191
    public function valid(): bool
192
    {
193
        return isset($this[$this->position]);
194
    }
195
196
    /**
197
     * Checks if the document with given offset exists.
198
     *
199
     * @access public
200
     *
201
     * @param int $offset
202
     *
203
     * @return bool
204
     */
205
    public function offsetExists($offset): bool
206
    {
207
        $idx = $this->result['document_keys'][$offset];
208
        return isset($this->result['documents'][$idx]);
209
    }
210
211
    /**
212
     * Gets the document with given offset.
213
     *
214
     * @access public
215
     *
216
     * @param int $offset
217
     *
218
     * @return mixed
219
     */
220
    #[\ReturnTypeWillChange]
221
    public function offsetGet($offset)
222
    {
223
        $idx = $this->result['document_keys'][$offset];
224
        $document = $this->result['documents'][$idx] ?? null;
225
226
        if ($document !== null) {
227
            // It may happen that a Solr group only includes non-toplevel results,
228
            // in which case metadata of toplevel entry isn't yet filled.
229
            if (empty($document['metadata'])) {
230
                $document['metadata'] = $this->fetchToplevelMetadataFromSolr([
231
                    'query' => 'uid:' . $document['uid'],
232
                    'start' => 0,
233
                    'rows' => 1,
234
                    'sort' => ['score' => 'desc'],
235
                ])[$document['uid']] ?? [];
236
            }
237
238
            // get title of parent/grandparent/... if empty
239
            if (empty($document['title']) && $document['partOf'] > 0) {
240
                $superiorTitle = AbstractDocument::getTitle($document['partOf'], true);
241
                if (!empty($superiorTitle)) {
242
                    $document['title'] = '[' . $superiorTitle . ']';
243
                }
244
            }
245
        }
246
247
        return $document;
248
    }
249
250
    /**
251
     * Not supported.
252
     *
253
     * @access public
254
     *
255
     * @param int $offset
256
     * @param int $value
257
     *
258
     * @return void
259
     *
260
     * @throws \Exception
261
     */
262
    public function offsetSet($offset, $value): void
263
    {
264
        throw new \Exception("SolrSearch: Modifying result list is not supported");
265
    }
266
267
    /**
268
     * Not supported.
269
     *
270
     * @access public
271
     *
272
     * @param int $offset
273
     *
274
     * @return void
275
     *
276
     * @throws \Exception
277
     */
278
    public function offsetUnset($offset): void
279
    {
280
        throw new \Exception("SolrSearch: Modifying result list is not supported");
281
    }
282
283
    /**
284
     * Gets SOLR results.
285
     *
286
     * @access public
287
     *
288
     * @return mixed
289
     */
290
    public function getSolrResults()
291
    {
292
        return $this->result['solrResults'];
293
    }
294
295
    /**
296
     * Gets by UID.
297
     *
298
     * @access public
299
     *
300
     * @param int $uid
301
     *
302
     * @return mixed
303
     */
304
    public function getByUid($uid)
305
    {
306
        return $this->result['documents'][$uid];
307
    }
308
309
    /**
310
     * Gets query.
311
     *
312
     * @access public
313
     *
314
     * @return SolrSearchQuery
315
     */
316
    public function getQuery()
317
    {
318
        return new SolrSearchQuery($this);
319
    }
320
321
    /**
322
     * Gets first.
323
     *
324
     * @access public
325
     *
326
     * @return SolrSearch
327
     */
328
    public function getFirst()
329
    {
330
        return $this[0];
331
    }
332
333
    /**
334
     * Parses results to array.
335
     *
336
     * @access public
337
     *
338
     * @return array
339
     */
340
    public function toArray()
341
    {
342
        return array_values($this->result['documents']);
343
    }
344
345
    /**
346
     * Get total number of hits.
347
     *
348
     * This can be accessed in Fluid template using `.numFound`.
349
     *
350
     * @access public
351
     *
352
     * @return int
353
     */
354
    public function getNumFound()
355
    {
356
        return $this->result['numFound'];
357
    }
358
359
    /**
360
     * Prepares SOLR search.
361
     *
362
     * @access public
363
     *
364
     * @return void
365
     */
366
    public function prepare()
367
    {
368
        // Prepare query parameters.
369
        $params = [];
370
        $matches = [];
371
        $fields = Solr::getFields();
372
        $query = '';
373
374
        // Set search query.
375
        if (
376
            !empty($this->searchParams['fulltext'])
377
            || preg_match('/' . $fields['fulltext'] . ':\((.*)\)/', trim($this->searchParams['query'] ?? ''), $matches)
378
        ) {
379
            // If the query already is a fulltext query e.g using the facets
380
            $this->searchParams['query'] = empty($matches[1]) ? $this->searchParams['query'] : $matches[1];
381
            // Search in fulltext field if applicable. Query must not be empty!
382
            if (!empty($this->searchParams['query'])) {
383
                $query = $fields['fulltext'] . ':(' . Solr::escapeQuery(trim($this->searchParams['query'])) . ')';
384
            }
385
            $params['fulltext'] = true;
386
        } else {
387
            // Retain given search field if valid.
388
            if (!empty($this->searchParams['query'])) {
389
                $query = Solr::escapeQueryKeepField(trim($this->searchParams['query']), $this->settings['storagePid']);
390
            }
391
        }
392
393
        // Add extended search query.
394
        if (
395
            !empty($this->searchParams['extQuery'])
396
            && is_array($this->searchParams['extQuery'])
397
        ) {
398
            $allowedOperators = ['AND', 'OR', 'NOT'];
399
            $numberOfExtQueries = count($this->searchParams['extQuery']);
400
            for ($i = 0; $i < $numberOfExtQueries; $i++) {
401
                if (!empty($this->searchParams['extQuery'][$i])) {
402
                    if (
403
                        in_array($this->searchParams['extOperator'][$i], $allowedOperators)
404
                    ) {
405
                        if (!empty($query)) {
406
                            $query .= ' ' . $this->searchParams['extOperator'][$i] . ' ';
407
                        }
408
                        $query .= Indexer::getIndexFieldName($this->searchParams['extField'][$i], $this->settings['storagePid']) . ':(' . Solr::escapeQuery($this->searchParams['extQuery'][$i]) . ')';
409
                    }
410
                }
411
            }
412
        }
413
414
        // Add filter query for date search
415
        if (!empty($this->searchParams['dateFrom']) && !empty($this->searchParams['dateTo'])) {
416
            // combine dateFrom and dateTo into range search
417
            $params['filterquery'][]['query'] = '{!join from=' . $fields['uid'] . ' to=' . $fields['uid'] . '}'. $fields['date'] . ':[' . $this->searchParams['dateFrom'] . ' TO ' . $this->searchParams['dateTo'] . ']';
418
        }
419
420
        // Add filter query for faceting.
421
        if (isset($this->searchParams['fq']) && is_array($this->searchParams['fq'])) {
422
            foreach ($this->searchParams['fq'] as $filterQuery) {
423
                $params['filterquery'][]['query'] = $filterQuery;
424
            }
425
        }
426
427
        // Add filter query for in-document searching.
428
        if (
429
            !empty($this->searchParams['documentId'])
430
            && MathUtility::canBeInterpretedAsInteger($this->searchParams['documentId'])
431
        ) {
432
            // Search in document and all subordinates (valid for up to three levels of hierarchy).
433
            $params['filterquery'][]['query'] = '_query_:"{!join from='
434
                . $fields['uid'] . ' to=' . $fields['partof'] . '}'
435
                . $fields['uid'] . ':{!join from=' . $fields['uid'] . ' to=' . $fields['partof'] . '}'
436
                . $fields['uid'] . ':' . $this->searchParams['documentId'] . '"' . ' OR {!join from='
437
                . $fields['uid'] . ' to=' . $fields['partof'] . '}'
438
                . $fields['uid'] . ':' . $this->searchParams['documentId'] . ' OR '
439
                . $fields['uid'] . ':' . $this->searchParams['documentId'];
440
        }
441
442
        // if collections are given, we prepare the collection query string
443
        if (!empty($this->collections)) {
444
            $params['filterquery'][]['query'] = $this->getCollectionFilterQuery($query);
445
        }
446
447
        // Set some query parameters.
448
        $params['query'] = !empty($query) ? $query : '*';
449
450
        $params['sort'] = $this->getSort();
451
        $params['listMetadataRecords'] = [];
452
453
        // Restrict the fields to the required ones.
454
        $params['fields'] = 'uid,id,page,title,thumbnail,partof,toplevel,type';
455
456
        if ($this->listedMetadata) {
457
            foreach ($this->listedMetadata as $metadata) {
458
                if ($metadata->getIndexStored() || $metadata->getIndexIndexed()) {
459
                    $listMetadataRecord = $metadata->getIndexName() . '_' . ($metadata->getIndexTokenized() ? 't' : 'u') . ($metadata->getIndexStored() ? 's' : 'u') . ($metadata->getIndexIndexed() ? 'i' : 'u');
460
                    $params['fields'] .= ',' . $listMetadataRecord;
461
                    $params['listMetadataRecords'][$metadata->getIndexName()] = $listMetadataRecord;
462
                }
463
            }
464
        }
465
466
        $this->params = $params;
467
468
        // Send off query to get total number of search results in advance
469
        $this->submit(0, 1, false);
470
    }
471
472
    /**
473
     * Submits SOLR search.
474
     *
475
     * @access public
476
     *
477
     * @param int $start
478
     * @param int $rows
479
     * @param bool $processResults default value is true
480
     *
481
     * @return void
482
     */
483
    public function submit($start, $rows, $processResults = true)
484
    {
485
        $params = $this->params;
486
        $params['start'] = $start;
487
        $params['rows'] = $rows;
488
489
        // Perform search.
490
        $result = $this->searchSolr($params, true);
491
492
        // Initialize values
493
        $documents = [];
494
495
        if ($processResults && $result['numFound'] > 0) {
496
            // flat array with uids from Solr search
497
            $documentSet = array_unique(array_column($result['documents'], 'uid'));
498
499
            if (empty($documentSet)) {
500
                // return nothing found
501
                $this->result = ['solrResults' => [], 'documents' => [], 'document_keys' => [], 'numFound' => 0];
502
                return;
503
            }
504
505
            // get the Extbase document objects for all uids
506
            $allDocuments = $this->documentRepository->findAllByUids($documentSet);
507
            $childrenOf = $this->documentRepository->findChildrenOfEach($documentSet);
508
509
            foreach ($result['documents'] as $doc) {
510
                if (empty($documents[$doc['uid']]) && isset($allDocuments[$doc['uid']])) {
511
                    $documents[$doc['uid']] = $allDocuments[$doc['uid']];
512
                }
513
                if (isset($documents[$doc['uid']])) {
514
                    $this->translateLanguageCode($doc);
515
                    if ($doc['toplevel'] === false) {
516
                        // this maybe a chapter, article, ..., year
517
                        if ($doc['type'] === 'year') {
518
                            continue;
519
                        }
520
                        if (!empty($doc['page'])) {
521
                            // it's probably a fulltext or metadata search
522
                            $searchResult = [];
523
                            $searchResult['page'] = $doc['page'];
524
                            $searchResult['thumbnail'] = $doc['thumbnail'];
525
                            $searchResult['structure'] = $doc['type'];
526
                            $searchResult['title'] = $doc['title'];
527
                            foreach ($params['listMetadataRecords'] as $indexName => $solrField) {
528
                                if (isset($doc['metadata'][$indexName])) {
529
                                    $searchResult['metadata'][$indexName] = $doc['metadata'][$indexName];
530
                                }
531
                            }
532
                            if ($this->searchParams['fulltext'] == '1') {
533
                                $searchResult['snippet'] = $doc['snippet'];
534
                                $searchResult['highlight'] = $doc['highlight'];
535
                                $searchResult['highlight_word'] = preg_replace('/^;|;$/', '',       // remove ; at beginning or end
536
                                                                  preg_replace('/;+/', ';',         // replace any multiple of ; with a single ;
537
                                                                  preg_replace('/[{~\d*}{\s+}{^=*\d+.*\d*}`~!@#$%\^&*()_|+-=?;:\'",.<>\{\}\[\]\\\]/', ';', $this->searchParams['query']))); // replace search operators and special characters with ;
538
                            }
539
                            $documents[$doc['uid']]['searchResults'][] = $searchResult;
540
                        }
541
                    } else if ($doc['toplevel'] === true) {
542
                        foreach ($params['listMetadataRecords'] as $indexName => $solrField) {
543
                            if (isset($doc['metadata'][$indexName])) {
544
                                $documents[$doc['uid']]['metadata'][$indexName] = $doc['metadata'][$indexName];
545
                            }
546
                        }
547
                        if (!array_key_exists('fulltext', $this->searchParams) || $this->searchParams['fulltext'] != '1') {
548
                            $documents[$doc['uid']]['page'] = 1;
549
                            $children = $childrenOf[$doc['uid']] ?? [];
550
                        
551
                            if (!empty($children)) {
552
                                $batchSize = 100;
553
                                $totalChildren = count($children);
554
                        
555
                                for ($start = 0; $start < $totalChildren; $start += $batchSize) {
556
                                    $batch = array_slice($children, $start, $batchSize, true);
557
                        
558
                                    // Fetch metadata for the current batch
559
                                    $metadataOf = $this->fetchToplevelMetadataFromSolr([
560
                                        'query' => 'partof:' . $doc['uid'],
561
                                        'start' => $start,
562
                                        'rows' => min($batchSize, $totalChildren - $start),
563
                                    ]);
564
                        
565
                                    foreach ($batch as $docChild) {
566
                                        // We need only a few fields from the children, but we need them as an array.
567
                                        $childDocument = [
568
                                            'thumbnail' => $docChild['thumbnail'],
569
                                            'title' => $docChild['title'],
570
                                            'structure' => $docChild['structure'],
571
                                            'metsOrderlabel' => $docChild['metsOrderlabel'],
572
                                            'uid' => $docChild['uid'],
573
                                            'metadata' => $metadataOf[$docChild['uid']],
574
                                        ];
575
                                        $documents[$doc['uid']]['children'][$docChild['uid']] = $childDocument;
576
                                    }
577
                                }
578
                            }
579
                        }
580
                    }
581
                }
582
            }
583
        }
584
585
        $this->result = ['solrResults' => $result, 'numberOfToplevels' => $result['numberOfToplevels'], 'documents' => $documents, 'document_keys' => array_keys($documents), 'numFound' => $result['numFound']];
586
    }
587
588
    /**
589
     * Find all listed metadata using specified query params.
590
     *
591
     * @access protected
592
     *
593
     * @param array $queryParams
594
     *
595
     * @return array
596
     */
597
    protected function fetchToplevelMetadataFromSolr(array $queryParams): array
598
    {
599
        // Prepare query parameters.
600
        $params = $queryParams;
601
        $metadataArray = [];
602
603
        // Set some query parameters.
604
        $params['listMetadataRecords'] = [];
605
606
        // Restrict the fields to the required ones.
607
        $params['fields'] = 'uid,toplevel';
608
609
        if ($this->listedMetadata) {
610
            foreach ($this->listedMetadata as $metadata) {
611
                if ($metadata->getIndexStored() || $metadata->getIndexIndexed()) {
612
                    $listMetadataRecord = $metadata->getIndexName() . '_' . ($metadata->getIndexTokenized() ? 't' : 'u') . ($metadata->getIndexStored() ? 's' : 'u') . ($metadata->getIndexIndexed() ? 'i' : 'u');
613
                    $params['fields'] .= ',' . $listMetadataRecord;
614
                    $params['listMetadataRecords'][$metadata->getIndexName()] = $listMetadataRecord;
615
                }
616
            }
617
        }
618
        // Set filter query to just get toplevel documents.
619
        $params['filterquery'][] = ['query' => 'toplevel:true'];
620
621
        // Perform search.
622
        $result = $this->searchSolr($params, true);
623
624
        foreach ($result['documents'] as $doc) {
625
            $this->translateLanguageCode($doc);
626
            $metadataArray[$doc['uid']] = $doc['metadata'];
627
        }
628
629
        return $metadataArray;
630
    }
631
632
    /**
633
     * Processes a search request
634
     *
635
     * @access protected
636
     *
637
     * @param array $parameters Additional search parameters
638
     * @param boolean $enableCache Enable caching of Solr requests
639
     *
640
     * @return array The Apache Solr Documents that were fetched
641
     */
642
    protected function searchSolr($parameters = [], $enableCache = true)
643
    {
644
        // Set query.
645
        $parameters['query'] = isset($parameters['query']) ? $parameters['query'] : '*';
646
        $parameters['filterquery'] = isset($parameters['filterquery']) ? $parameters['filterquery'] : [];
647
648
        // Perform Solr query.
649
        // Instantiate search object.
650
        $solr = Solr::getInstance($this->settings['solrcore']);
651
        if (!$solr->ready) {
652
            Helper::log('Apache Solr not available', LOG_SEVERITY_ERROR);
653
            return [
654
                'documents' => [],
655
                'numberOfToplevels' => 0,
656
                'numFound' => 0,
657
            ];
658
        }
659
660
        $cacheIdentifier = '';
661
        $cache = null;
662
        // Calculate cache identifier.
663
        if ($enableCache === true) {
664
            $cacheIdentifier = Helper::digest($solr->core . print_r($parameters, true));
0 ignored issues
show
Bug introduced by
Are you sure print_r($parameters, true) of type string|true can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

664
            $cacheIdentifier = Helper::digest($solr->core . /** @scrutinizer ignore-type */ print_r($parameters, true));
Loading history...
665
            $cache = GeneralUtility::makeInstance(CacheManager::class)->getCache('tx_dlf_solr');
666
        }
667
        $resultSet = [
668
            'documents' => [],
669
            'numberOfToplevels' => 0,
670
            'numFound' => 0,
671
        ];
672
        if ($enableCache === false || ($entry = $cache->get($cacheIdentifier)) === false) {
673
            $selectQuery = $solr->service->createSelect($parameters);
674
675
            $edismax = $selectQuery->getEDisMax();
676
677
            $queryFields = '';
678
679
            if ($this->indexedMetadata) {
680
                foreach ($this->indexedMetadata as $metadata) {
681
                    if ($metadata->getIndexIndexed()) {
682
                        $listMetadataRecord = $metadata->getIndexName() . '_' . ($metadata->getIndexTokenized() ? 't' : 'u') . ($metadata->getIndexStored() ? 's' : 'u') . 'i';
683
                        $queryFields .= $listMetadataRecord . '^' . $metadata->getIndexBoost() . ' ';
684
                    }
685
                }
686
            }
687
688
            $edismax->setQueryFields($queryFields);
689
690
            $grouping = $selectQuery->getGrouping();
691
            $grouping->addField('uid');
692
            $grouping->setLimit(100); // Results in group (TODO: check)
693
            $grouping->setNumberOfGroups(true);
694
695
            $fulltextExists = $parameters['fulltext'] ?? false;
696
            if ($fulltextExists === true) {
697
                // get highlighting component and apply settings
698
                $selectQuery->getHighlighting();
699
            }
700
701
            $solrRequest = $solr->service->createRequest($selectQuery);
702
703
            if ($fulltextExists === true) {
704
                // If it is a fulltext search, enable highlighting.
705
                // field for which highlighting is going to be performed,
706
                // is required if you want to have OCR highlighting
707
                $solrRequest->addParam('hl.ocr.fl', 'fulltext');
708
                // return the coordinates of highlighted search as absolute coordinates
709
                $solrRequest->addParam('hl.ocr.absoluteHighlights', 'on');
710
                // max amount of snippets for a single page
711
                $solrRequest->addParam('hl.snippets', '20');
712
                // we store the fulltext on page level and can disable this option
713
                $solrRequest->addParam('hl.ocr.trackPages', 'off');
714
            }
715
716
            // Perform search for all documents with the same uid that either fit to the search or marked as toplevel.
717
            $response = $solr->service->executeRequest($solrRequest);
718
            // return empty resultSet on error-response
719
            if ($response->getStatusCode() == 400) {
720
                return $resultSet;
721
            }
722
            $result = $solr->service->createResult($selectQuery, $response);
723
724
            // TODO: Call to an undefined method Solarium\Core\Query\Result\ResultInterface::getGrouping().
725
            // @phpstan-ignore-next-line
726
            $uidGroup = $result->getGrouping()->getGroup('uid');
0 ignored issues
show
Bug introduced by
The method getGrouping() does not exist on Solarium\Core\Query\Result\ResultInterface. It seems like you code against a sub-type of Solarium\Core\Query\Result\ResultInterface such as Solarium\QueryType\Select\Result\Result. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

726
            $uidGroup = $result->/** @scrutinizer ignore-call */ getGrouping()->getGroup('uid');
Loading history...
727
            $resultSet['numberOfToplevels'] = $uidGroup->getNumberOfGroups();
728
            $resultSet['numFound'] = $uidGroup->getMatches();
729
            $highlighting = [];
730
            if ($fulltextExists === true) {
731
                $data = $result->getData();
732
                $highlighting = $data['ocrHighlighting'];
733
            }
734
            $fields = Solr::getFields();
735
736
            foreach ($uidGroup as $group) {
737
                foreach ($group as $record) {
738
                    $resultSet['documents'][] = $this->getDocument($record, $highlighting, $fields, $parameters);
739
                }
740
            }
741
742
            // Save value in cache.
743
            if (!empty($resultSet['documents']) && $enableCache === true) {
744
                $cache->set($cacheIdentifier, $resultSet);
745
            }
746
        } else {
747
            // Return cache hit.
748
            $resultSet = $entry;
749
        }
750
        return $resultSet;
751
    }
752
753
    /**
754
     * Get collection filter query for search.
755
     *
756
     * @access private
757
     *
758
     * @param string $query
759
     *
760
     * @return string
761
     */
762
    private function getCollectionFilterQuery(string $query) : string
763
    {
764
        $collectionsQueryString = '';
765
        $virtualCollectionsQueryString = '';
766
        foreach ($this->collections as $collection) {
767
            // check for virtual collections query string
768
            if ($collection->getIndexSearch()) {
769
                $virtualCollectionsQueryString .= empty($virtualCollectionsQueryString) ? '(' . $collection->getIndexSearch() . ')' : ' OR (' . $collection->getIndexSearch() . ')';
770
            } else {
771
                $collectionsQueryString .= empty($collectionsQueryString) ? '"' . $collection->getIndexName() . '"' : ' OR "' . $collection->getIndexName() . '"';
772
            }
773
        }
774
775
        // distinguish between simple collection browsing and actual searching within the collection(s)
776
        if (!empty($collectionsQueryString)) {
777
            if (empty($query)) {
778
                $collectionsQueryString = '(collection_faceting:(' . $collectionsQueryString . ') AND toplevel:true AND partof:0)';
779
            } else {
780
                $collectionsQueryString = '(collection_faceting:(' . $collectionsQueryString . '))';
781
            }
782
        }
783
784
        // virtual collections might query documents that are neither toplevel:true nor partof:0 and need to be searched separately
785
        if (!empty($virtualCollectionsQueryString)) {
786
            $virtualCollectionsQueryString = '(' . $virtualCollectionsQueryString . ')';
787
        }
788
789
        // combine both query strings into a single filterquery via OR if both are given, otherwise pass either of those
790
        return implode(' OR ', array_filter([$collectionsQueryString, $virtualCollectionsQueryString]));
791
    }
792
793
    /**
794
     * Get sort order of the results as given or by title as default.
795
     *
796
     * @access private
797
     *
798
     * @return array
799
     */
800
    private function getSort() : array
801
    {
802
        if (!empty($this->searchParams['orderBy'])) {
803
            return [
804
                $this->searchParams['orderBy'] => $this->searchParams['order'],
805
            ];
806
        }
807
808
        return [
809
            'score' => 'desc',
810
            'year_sorting' => 'asc',
811
            'title_sorting' => 'asc',
812
            'volume' => 'asc'
813
        ];
814
    }
815
816
    /**
817
     * Gets a document
818
     *
819
     * @access private
820
     *
821
     * @param Document $record
822
     * @param array $highlighting
823
     * @param array $fields
824
     * @param array $parameters
825
     *
826
     * @return array The Apache Solr Documents that were fetched
827
     */
828
    private function getDocument(Document $record, array $highlighting, array $fields, $parameters) {
829
        $resultDocument = new ResultDocument($record, $highlighting, $fields);
830
831
        $document = [
832
            'id' => $resultDocument->getId(),
833
            'page' => $resultDocument->getPage(),
834
            'snippet' => $resultDocument->getSnippets(),
835
            'thumbnail' => $resultDocument->getThumbnail(),
836
            'title' => $resultDocument->getTitle(),
837
            'toplevel' => $resultDocument->getToplevel(),
838
            'type' => $resultDocument->getType(),
839
            'uid' => !empty($resultDocument->getUid()) ? $resultDocument->getUid() : $parameters['uid'],
840
            'highlight' => $resultDocument->getHighlightsIds(),
841
        ];
842
        
843
        foreach ($parameters['listMetadataRecords'] as $indexName => $solrField) {
844
            if (!empty($record->$solrField)) {
845
                    $document['metadata'][$indexName] = $record->$solrField;
846
            }
847
        }
848
849
        return $document;
850
    }
851
852
    /**
853
     * Translate language code if applicable.
854
     *
855
     * @access private
856
     *
857
     * @param &$doc document array
858
     *
859
     * @return void
860
     */
861
    private function translateLanguageCode(&$doc): void
862
    {
863
        if (array_key_exists('language', $doc['metadata'])) {
864
            foreach($doc['metadata']['language'] as $indexName => $language) {
865
                $doc['metadata']['language'][$indexName] = Helper::getLanguageName($language);
866
            }
867
        }
868
    }
869
}
870