Passed
Pull Request — master (#123)
by
unknown
07:53 queued 02:59
created

SolrSearch   F

Complexity

Total Complexity 119

Size/Duplication

Total Lines 835
Duplicated Lines 0 %

Importance

Changes 5
Bugs 0 Features 0
Metric Value
eloc 286
c 5
b 0
f 0
dl 0
loc 835
rs 2
wmc 119

26 Methods

Rating   Name   Duplication   Size   Complexity  
F searchSolr() 0 109 20
A getSolrResults() 0 3 1
A valid() 0 3 1
A current() 0 3 1
A getFirst() 0 3 1
A getQuery() 0 3 1
A getDocument() 0 22 4
A translateLanguageCode() 0 5 3
A key() 0 3 1
A toArray() 0 3 1
A __construct() 0 8 1
A getNumFound() 0 3 1
D submit() 0 103 21
A offsetGet() 0 28 6
A rewind() 0 3 1
A next() 0 3 1
F prepare() 0 104 28
B fetchToplevelMetadataFromSolr() 0 33 9
A count() 0 7 2
A offsetExists() 0 4 1
B getCollectionFilterQuery() 0 29 8
A getSort() 0 13 2
A offsetUnset() 0 3 1
A offsetSet() 0 3 1
A getByUid() 0 3 1
A getNumLoadedDocuments() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like SolrSearch often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SolrSearch, and based on these observations, apply Extract Interface, too.

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

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

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