Scrutinizer GitHub App not installed

We could not synchronize checks via GitHub's checks API since Scrutinizer's GitHub App is not installed for this repository.

Install GitHub App

GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Passed
Pull Request — dev-extbase-fluid (#754)
by Alexander
08:45
created

DocumentRepository::searchSolr()   C

Complexity

Conditions 16
Paths 140

Size

Total Lines 94
Code Lines 54

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 16
eloc 54
c 2
b 0
f 0
nc 140
nop 2
dl 0
loc 94
rs 5.2333

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * (c) Kitodo. Key to digital objects e.V. <[email protected]>
5
 *
6
 * This file is part of the Kitodo and TYPO3 projects.
7
 *
8
 * @license GNU General Public License version 3 or later.
9
 * For the full copyright and license information, please read the
10
 * LICENSE.txt file that was distributed with this source code.
11
 */
12
13
namespace Kitodo\Dlf\Domain\Repository;
14
15
use Kitodo\Dlf\Common\Doc;
16
use Kitodo\Dlf\Common\Helper;
17
use Kitodo\Dlf\Common\Indexer;
18
use Kitodo\Dlf\Common\Solr;
19
use Kitodo\Dlf\Domain\Model\Document;
20
use Kitodo\Dlf\Common\SolrSearchResult\ResultDocument;
21
use TYPO3\CMS\Core\Cache\CacheManager;
22
use TYPO3\CMS\Core\Database\ConnectionPool;
23
use TYPO3\CMS\Core\Database\Connection;
24
use TYPO3\CMS\Core\Utility\GeneralUtility;
25
use TYPO3\CMS\Core\Utility\MathUtility;
26
use TYPO3\CMS\Extbase\Persistence\QueryInterface;
27
28
class DocumentRepository extends \TYPO3\CMS\Extbase\Persistence\Repository
29
{
30
    /**
31
     * The controller settings passed to the repository for some special actions.
32
     *
33
     * @var array
34
     * @access protected
35
     */
36
    protected $settings;
37
38
    /**
39
     * Find one document by given parameters
40
     *
41
     * GET parameters may be:
42
     *
43
     * - 'id': the uid of the document
44
     * - 'location': the URL of the location of the XML file
45
     * - 'recordId': the record_id of the document
46
     *
47
     * @param array $parameters
48
     *
49
     * @return \Kitodo\Dlf\Domain\Model\Document|null
50
     */
51
    public function findOneByParameters($parameters)
52
    {
53
        $doc = null;
54
        $document = null;
55
56
        if (isset($parameters['id']) && MathUtility::canBeInterpretedAsInteger($parameters['id'])) {
57
58
            $document = $this->findOneByIdAndSettings($parameters['id']);
59
60
        } else if (isset($parameters['recordId'])) {
61
62
            $document = $this->findOneByRecordId($parameters['recordId']);
0 ignored issues
show
Bug introduced by
The method findOneByRecordId() does not exist on Kitodo\Dlf\Domain\Repository\DocumentRepository. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

62
            /** @scrutinizer ignore-call */ 
63
            $document = $this->findOneByRecordId($parameters['recordId']);
Loading history...
63
64
        } else if (isset($parameters['location']) && GeneralUtility::isValidUrl($parameters['location'])) {
65
66
            $doc = Doc::getInstance($parameters['location'], [], true);
67
68
            if ($doc->recordId) {
69
                $document = $this->findOneByRecordId($doc->recordId);
70
            }
71
72
            if ($document === null) {
73
                // create new (dummy) Document object
74
                $document = GeneralUtility::makeInstance(Document::class);
75
                $document->setLocation($parameters['location']);
76
            }
77
78
        }
79
80
        if ($document !== null && $doc === null) {
81
            $doc = Doc::getInstance($document->getLocation(), [], true);
0 ignored issues
show
Bug introduced by
The method getLocation() does not exist on TYPO3\CMS\Extbase\Persistence\QueryResultInterface. ( Ignorable by Annotation )

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

81
            $doc = Doc::getInstance($document->/** @scrutinizer ignore-call */ getLocation(), [], true);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
82
        }
83
84
        if ($doc !== null) {
85
            $document->setDoc($doc);
0 ignored issues
show
Bug introduced by
The method setDoc() does not exist on TYPO3\CMS\Extbase\Persistence\QueryResultInterface. ( Ignorable by Annotation )

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

85
            $document->/** @scrutinizer ignore-call */ 
86
                       setDoc($doc);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
86
        }
87
88
        return $document;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $document also could return the type TYPO3\CMS\Extbase\Persis...y<mixed,object>|integer which is incompatible with the documented return type Kitodo\Dlf\Domain\Model\Document|null.
Loading history...
89
90
    }
91
92
93
    public function findByUidAndPartOf($uid, $partOf)
94
    {
95
        $query = $this->createQuery();
96
97
        $query->matching($query->equals('uid', $uid));
98
        $query->matching($query->equals('partof', $partOf));
99
100
        return $query->execute();
101
    }
102
103
    /**
104
     * Find the oldest document
105
     *
106
     * @return \Kitodo\Dlf\Domain\Model\Document|null
107
     */
108
    public function findOldestDocument()
109
    {
110
        $query = $this->createQuery();
111
112
        $query->setOrderings(['tstamp' => QueryInterface::ORDER_ASCENDING]);
113
        $query->setLimit(1);
114
115
        return $query->execute()->getFirst();
116
    }
117
118
    /**
119
     * @param int $partOf
120
     * @param string $structure
121
     * @return array|\TYPO3\CMS\Extbase\Persistence\QueryResultInterface
122
     */
123
    public function getChildrenOfYearAnchor($partOf, $structure)
124
    {
125
        $query = $this->createQuery();
126
127
        $query->matching($query->equals('structure', Helper::getUidFromIndexName($structure, 'tx_dlf_structures')));
128
        $query->matching($query->equals('partof', $partOf));
129
130
        $query->setOrderings([
131
            'mets_orderlabel' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_ASCENDING
132
        ]);
133
134
        return $query->execute();
135
    }
136
137
    /**
138
     * Finds all documents for the given settings
139
     *
140
     * @param int $uid
141
     * @param array $settings
142
     *
143
     * @return \Kitodo\Dlf\Domain\Model\Document|null
144
     */
145
    public function findOneByIdAndSettings($uid, $settings = [])
0 ignored issues
show
Unused Code introduced by
The parameter $settings is not used and could be removed. ( Ignorable by Annotation )

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

145
    public function findOneByIdAndSettings($uid, /** @scrutinizer ignore-unused */ $settings = [])

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
146
    {
147
        $settings = ['documentSets' => $uid];
148
149
        return $this->findDocumentsBySettings($settings)->getFirst();
150
    }
151
152
    /**
153
     * Finds all documents for the given settings
154
     *
155
     * @param array $settings
156
     *
157
     * @return array|\TYPO3\CMS\Extbase\Persistence\QueryResultInterface
158
     */
159
    public function findDocumentsBySettings($settings = [])
160
    {
161
        $query = $this->createQuery();
162
163
        $constraints = [];
164
165
        if ($settings['documentSets']) {
166
            $constraints[] = $query->in('uid', GeneralUtility::intExplode(',', $settings['documentSets']));
167
        }
168
169
        if (isset($settings['excludeOther']) && (int) $settings['excludeOther'] === 0) {
170
            $query->getQuerySettings()->setRespectStoragePage(false);
171
        }
172
173
        if (count($constraints)) {
174
            $query->matching(
175
                $query->logicalAnd($constraints)
176
            );
177
        }
178
179
        return $query->execute();
180
    }
181
182
    /**
183
     * Finds all documents for the given collections
184
     *
185
     * @param array $collections
186
     * @param int $limit
187
     *
188
     * @return array|\TYPO3\CMS\Extbase\Persistence\QueryResultInterface
189
     */
190
    public function findAllByCollectionsLimited($collections, $limit = 50)
191
    {
192
        $query = $this->createQuery();
193
194
        // order by start_date -> start_time...
195
        $query->setOrderings(
196
            ['tstamp' => QueryInterface::ORDER_DESCENDING]
197
        );
198
199
        $constraints = [];
200
        if ($collections) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $collections of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
201
            $constraints[] = $query->in('collections.uid', $collections);
202
        }
203
204
        if (count($constraints)) {
205
            $query->matching(
206
                $query->logicalAnd($constraints)
207
            );
208
        }
209
210
        if ($limit > 0) {
211
            $query->setLimit((int) $limit);
212
        }
213
214
        return $query->execute();
215
    }
216
217
    /**
218
     * Find all the titles
219
     *
220
     * documents with partof == 0
221
     *
222
     * @param array $settings
223
     *
224
     * @return array|\TYPO3\CMS\Extbase\Persistence\QueryResultInterface
225
     */
226
    public function findAllTitles($settings = [])
227
    {
228
        $query = $this->createQuery();
229
230
        $constraints = [];
231
        $constraints[] = $query->equals('partof', 0);
232
233
        if ($settings['collections']) {
234
            $constraints[] = $query->in('collections.uid', GeneralUtility::intExplode(',', $settings['collections']));
235
        }
236
237
        if (count($constraints)) {
238
            $query->matching(
239
                $query->logicalAnd($constraints)
240
            );
241
        }
242
243
        return $query->execute();
244
    }
245
246
    /**
247
     * Count the titles
248
     *
249
     * documents with partof == 0
250
     *
251
     * @param array $settings
252
     *
253
     * @return int
254
     */
255
    public function countAllTitles($settings = [])
256
    {
257
        return $this->findAllTitles($settings)->count();
258
    }
259
260
    /**
261
     * Count the volumes
262
     *
263
     * documents with partof != 0
264
     *
265
     * @param array $settings
266
     *
267
     * @return int
268
     */
269
    public function countAllVolumes($settings = [])
270
    {
271
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
272
            ->getQueryBuilderForTable('tx_dlf_documents');
273
        $subQueryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
274
            ->getQueryBuilderForTable('tx_dlf_documents');
275
276
        $subQuery = $subQueryBuilder
277
            ->select('tx_dlf_documents.partof')
278
            ->from('tx_dlf_documents')
279
            ->where(
280
                $subQueryBuilder->expr()->neq('tx_dlf_documents.partof', 0)
281
            )
282
            ->groupBy('tx_dlf_documents.partof')
283
            ->getSQL();
284
285
        $countVolumes = $queryBuilder
286
            ->count('tx_dlf_documents.uid')
287
            ->from('tx_dlf_documents')
288
            ->where(
289
                $queryBuilder->expr()->eq('tx_dlf_documents.pid', intval($settings['pages'])),
290
                $queryBuilder->expr()->notIn('tx_dlf_documents.uid', $subQuery)
291
            )
292
            ->execute()
293
            ->fetchColumn(0);
294
295
        return $countVolumes;
296
    }
297
298
    public function getStatisticsForSelectedCollection($settings)
299
    {
300
        // Include only selected collections.
301
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
302
            ->getQueryBuilderForTable('tx_dlf_documents');
303
304
        $countTitles = $queryBuilder
305
            ->count('tx_dlf_documents.uid')
306
            ->from('tx_dlf_documents')
307
            ->innerJoin(
308
                'tx_dlf_documents',
309
                'tx_dlf_relations',
310
                'tx_dlf_relations_joins',
311
                $queryBuilder->expr()->eq(
312
                    'tx_dlf_relations_joins.uid_local',
313
                    'tx_dlf_documents.uid'
314
                )
315
            )
316
            ->innerJoin(
317
                'tx_dlf_relations_joins',
318
                'tx_dlf_collections',
319
                'tx_dlf_collections_join',
320
                $queryBuilder->expr()->eq(
321
                    'tx_dlf_relations_joins.uid_foreign',
322
                    'tx_dlf_collections_join.uid'
323
                )
324
            )
325
            ->where(
326
                $queryBuilder->expr()->eq('tx_dlf_documents.pid', intval($settings['pages'])),
327
                $queryBuilder->expr()->eq('tx_dlf_collections_join.pid', intval($settings['pages'])),
328
                $queryBuilder->expr()->eq('tx_dlf_documents.partof', 0),
329
                $queryBuilder->expr()->in('tx_dlf_collections_join.uid',
330
                    $queryBuilder->createNamedParameter(GeneralUtility::intExplode(',',
331
                        $settings['collections']), Connection::PARAM_INT_ARRAY)),
332
                $queryBuilder->expr()->eq('tx_dlf_relations_joins.ident',
333
                    $queryBuilder->createNamedParameter('docs_colls'))
334
            )
335
            ->execute()
336
            ->fetchColumn(0);
337
338
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
339
            ->getQueryBuilderForTable('tx_dlf_documents');
340
        $subQueryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
341
            ->getQueryBuilderForTable('tx_dlf_documents');
342
343
        $subQuery = $subQueryBuilder
344
            ->select('tx_dlf_documents.partof')
345
            ->from('tx_dlf_documents')
346
            ->where(
347
                $subQueryBuilder->expr()->neq('tx_dlf_documents.partof', 0)
348
            )
349
            ->groupBy('tx_dlf_documents.partof')
350
            ->getSQL();
351
352
        $countVolumes = $queryBuilder
353
            ->count('tx_dlf_documents.uid')
354
            ->from('tx_dlf_documents')
355
            ->innerJoin(
356
                'tx_dlf_documents',
357
                'tx_dlf_relations',
358
                'tx_dlf_relations_joins',
359
                $queryBuilder->expr()->eq(
360
                    'tx_dlf_relations_joins.uid_local',
361
                    'tx_dlf_documents.uid'
362
                )
363
            )
364
            ->innerJoin(
365
                'tx_dlf_relations_joins',
366
                'tx_dlf_collections',
367
                'tx_dlf_collections_join',
368
                $queryBuilder->expr()->eq(
369
                    'tx_dlf_relations_joins.uid_foreign',
370
                    'tx_dlf_collections_join.uid'
371
                )
372
            )
373
            ->where(
374
                $queryBuilder->expr()->eq('tx_dlf_documents.pid', intval($settings['pages'])),
375
                $queryBuilder->expr()->eq('tx_dlf_collections_join.pid', intval($settings['pages'])),
376
                $queryBuilder->expr()->notIn('tx_dlf_documents.uid', $subQuery),
377
                $queryBuilder->expr()->in('tx_dlf_collections_join.uid',
378
                    $queryBuilder->createNamedParameter(GeneralUtility::intExplode(',',
379
                        $settings['collections']), Connection::PARAM_INT_ARRAY)),
380
                $queryBuilder->expr()->eq('tx_dlf_relations_joins.ident',
381
                    $queryBuilder->createNamedParameter('docs_colls'))
382
            )
383
            ->execute()
384
            ->fetchColumn(0);
385
386
        return ['titles' => $countTitles, 'volumes' => $countVolumes];
387
    }
388
389
    public function getTableOfContentsFromDb($uid, $pid, $settings)
390
    {
391
        // Build table of contents from database.
392
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
393
            ->getQueryBuilderForTable('tx_dlf_documents');
394
395
        $excludeOtherWhere = '';
396
        if ($settings['excludeOther']) {
397
            $excludeOtherWhere = 'tx_dlf_documents.pid=' . intval($settings['pages']);
398
        }
399
        // Check if there are any metadata to suggest.
400
        $result = $queryBuilder
401
            ->select(
402
                'tx_dlf_documents.uid AS uid',
403
                'tx_dlf_documents.title AS title',
404
                'tx_dlf_documents.volume AS volume',
405
                'tx_dlf_documents.mets_label AS mets_label',
406
                'tx_dlf_documents.mets_orderlabel AS mets_orderlabel',
407
                'tx_dlf_structures_join.index_name AS type'
408
            )
409
            ->innerJoin(
410
                'tx_dlf_documents',
411
                'tx_dlf_structures',
412
                'tx_dlf_structures_join',
413
                $queryBuilder->expr()->eq(
414
                    'tx_dlf_structures_join.uid',
415
                    'tx_dlf_documents.structure'
416
                )
417
            )
418
            ->from('tx_dlf_documents')
419
            ->where(
420
                $queryBuilder->expr()->eq('tx_dlf_documents.partof', intval($uid)),
421
                $queryBuilder->expr()->eq('tx_dlf_structures_join.pid', intval($pid)),
422
                $excludeOtherWhere
423
            )
424
            ->addOrderBy('tx_dlf_documents.volume_sorting')
425
            ->addOrderBy('tx_dlf_documents.mets_orderlabel')
426
            ->execute();
427
        return $result;
428
    }
429
430
    /**
431
     * Find one document by given settings and identifier
432
     *
433
     * @param array $settings
434
     * @param array $parameters
435
     *
436
     * @return array The found document object
437
     */
438
    public function getOaiRecord($settings, $parameters)
439
    {
440
        $where = '';
441
442
        if (!$settings['show_userdefined']) {
443
            $where .= 'AND tx_dlf_collections.fe_cruser_id=0 ';
444
        }
445
446
        $connection = GeneralUtility::makeInstance(ConnectionPool::class)
447
            ->getConnectionForTable('tx_dlf_documents');
448
449
        $sql = 'SELECT `tx_dlf_documents`.*, GROUP_CONCAT(DISTINCT `tx_dlf_collections`.`oai_name` ORDER BY `tx_dlf_collections`.`oai_name` SEPARATOR " ") AS `collections` ' .
450
            'FROM `tx_dlf_documents` ' .
451
            'INNER JOIN `tx_dlf_relations` ON `tx_dlf_relations`.`uid_local` = `tx_dlf_documents`.`uid` ' .
452
            'INNER JOIN `tx_dlf_collections` ON `tx_dlf_collections`.`uid` = `tx_dlf_relations`.`uid_foreign` ' .
453
            'WHERE `tx_dlf_documents`.`record_id` = ? ' .
454
            'AND `tx_dlf_relations`.`ident`="docs_colls" ' .
455
            $where;
456
457
        $values = [
458
            $parameters['identifier']
459
        ];
460
461
        $types = [
462
            Connection::PARAM_STR
463
        ];
464
465
        // Create a prepared statement for the passed SQL query, bind the given params with their binding types and execute the query
466
        $statement = $connection->executeQuery($sql, $values, $types);
467
468
        return $statement->fetch();
469
    }
470
471
    /**
472
     * Finds all documents for the given settings
473
     *
474
     * @param array $settings
475
     * @param array $documentsToProcess
476
     *
477
     * @return array The found document objects
478
     */
479
    public function getOaiDocumentList($settings, $documentsToProcess)
0 ignored issues
show
Unused Code introduced by
The parameter $settings is not used and could be removed. ( Ignorable by Annotation )

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

479
    public function getOaiDocumentList(/** @scrutinizer ignore-unused */ $settings, $documentsToProcess)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
480
    {
481
        $connection = GeneralUtility::makeInstance(ConnectionPool::class)
482
            ->getConnectionForTable('tx_dlf_documents');
483
484
        $sql = 'SELECT `tx_dlf_documents`.*, GROUP_CONCAT(DISTINCT `tx_dlf_collections`.`oai_name` ORDER BY `tx_dlf_collections`.`oai_name` SEPARATOR " ") AS `collections` ' .
485
            'FROM `tx_dlf_documents` ' .
486
            'INNER JOIN `tx_dlf_relations` ON `tx_dlf_relations`.`uid_local` = `tx_dlf_documents`.`uid` ' .
487
            'INNER JOIN `tx_dlf_collections` ON `tx_dlf_collections`.`uid` = `tx_dlf_relations`.`uid_foreign` ' .
488
            'WHERE `tx_dlf_documents`.`uid` IN ( ? ) ' .
489
            'AND `tx_dlf_relations`.`ident`="docs_colls" ' .
490
            'AND ' . Helper::whereExpression('tx_dlf_collections') . ' ' .
491
            'GROUP BY `tx_dlf_documents`.`uid` ';
492
493
        $values = [
494
            $documentsToProcess,
495
        ];
496
497
        $types = [
498
            Connection::PARAM_INT_ARRAY,
499
        ];
500
501
        // Create a prepared statement for the passed SQL query, bind the given params with their binding types and execute the query
502
        $documents = $connection->executeQuery($sql, $values, $types);
503
504
        return $documents;
505
    }
506
507
    /**
508
     * Finds all documents with given uids
509
     *
510
     * @param array $uids
511
     *
512
     * @return array
513
     */
514
    private function findAllByUids($uids)
515
    {
516
        // get all documents from db we are talking about
517
        $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
518
        $queryBuilder = $connectionPool->getQueryBuilderForTable('tx_dlf_documents');
519
        // Fetch document info for UIDs in $documentSet from DB
520
        $kitodoDocuments = $queryBuilder
521
            ->select(
522
                'tx_dlf_documents.uid AS uid',
523
                'tx_dlf_documents.title AS title',
524
                'tx_dlf_documents.structure AS structure',
525
                'tx_dlf_documents.thumbnail AS thumbnail',
526
                'tx_dlf_documents.volume_sorting AS volumeSorting',
527
                'tx_dlf_documents.mets_orderlabel AS metsOrderlabel',
528
                'tx_dlf_documents.partof AS partOf'
529
            )
530
            ->from('tx_dlf_documents')
531
            ->where(
532
                $queryBuilder->expr()->in('tx_dlf_documents.pid', $this->settings['storagePid']),
533
                $queryBuilder->expr()->in('tx_dlf_documents.uid', $uids)
534
            )
535
            ->addOrderBy('tx_dlf_documents.volume_sorting', 'asc')
536
            ->addOrderBy('tx_dlf_documents.mets_orderlabel', 'asc')
537
            ->execute();
538
539
        $allDocuments = [];
540
        $documentStructures = Helper::getDocumentStructures($this->settings['storagePid']);
541
        // Process documents in a usable array structure
542
        while ($resArray = $kitodoDocuments->fetch()) {
543
            $resArray['structure'] = $documentStructures[$resArray['structure']];
544
            $allDocuments[$resArray['uid']] = $resArray;
545
        }
546
547
        return $allDocuments;
548
    }
549
550
    /**
551
     * Find all documents with given collection from Solr
552
     *
553
     * @param \Kitodo\Dlf\Domain\Model\Collection $collection
554
     * @param array $settings
555
     * @param array $searchParams
556
     * @param \TYPO3\CMS\Extbase\Persistence\Generic\QueryResult $listedMetadata
557
     * @return array
558
     */
559
    public function findSolrByCollection($collection, $settings, $searchParams, $listedMetadata = null)
560
    {
561
        // set settings global inside this repository
562
        $this->settings = $settings;
563
564
        // Prepare query parameters.
565
        $params = [];
566
        $matches = [];
567
        $fields = Solr::getFields();
568
569
        // Set search query.
570
        if (
571
            (!empty($searchParams['fulltext']))
572
            || preg_match('/' . $fields['fulltext'] . ':\((.*)\)/', trim($searchParams['query']), $matches)
573
        ) {
574
            // If the query already is a fulltext query e.g using the facets
575
            $searchParams['query'] = empty($matches[1]) ? $searchParams['query'] : $matches[1];
576
            // Search in fulltext field if applicable. Query must not be empty!
577
            if (!empty($searchParams['query'])) {
578
                $query = $fields['fulltext'] . ':(' . Solr::escapeQuery(trim($searchParams['query'])) . ')';
579
            }
580
            $params['fulltext'] = true;
581
        } else {
582
            // Retain given search field if valid.
583
            if (!empty($searchParams['query'])) {
584
                $query = Solr::escapeQueryKeepField(trim($searchParams['query']), $this->settings['storagePid']);
585
            }
586
        }
587
588
        // Add extended search query.
589
        if (
590
            !empty($searchParams['extQuery'])
591
            && is_array($searchParams['extQuery'])
592
        ) {
593
            $allowedOperators = ['AND', 'OR', 'NOT'];
594
            $numberOfExtQueries = count($searchParams['extQuery']);
595
            for ($i = 0; $i < $numberOfExtQueries; $i++) {
596
                if (!empty($searchParams['extQuery'][$i])) {
597
                    if (
598
                        in_array($searchParams['extOperator'][$i], $allowedOperators)
599
                    ) {
600
                        if (!empty($query)) {
601
                            $query .= ' ' . $searchParams['extOperator'][$i] . ' ';
602
                        }
603
                        $query .= Indexer::getIndexFieldName($searchParams['extField'][$i], $this->settings['storagePid']) . ':(' . Solr::escapeQuery($searchParams['extQuery'][$i]) . ')';
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $query does not seem to be defined for all execution paths leading up to this point.
Loading history...
604
                    }
605
                }
606
            }
607
        }
608
609
            // Add filter query for faceting.
610
        if (isset($searchParams['fq']) && is_array($searchParams['fq'])) {
611
            foreach ($searchParams['fq'] as $filterQuery) {
612
                $params['filterquery'][]['query'] = $filterQuery;
613
            }
614
        }
615
616
        // Add filter query for in-document searching.
617
        if (
618
            $this->settings['searchIn'] == 'document'
619
            || $this->settings['searchIn'] == 'all'
620
        ) {
621
            if (
622
                !empty($searchParams['documentId'])
623
                && \TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($searchParams['documentId'])
624
            ) {
625
                // Search in document and all subordinates (valid for up to three levels of hierarchy).
626
                $params['filterquery'][]['query'] = '_query_:"{!join from='
627
                    . $fields['uid'] . ' to=' . $fields['partof'] . '}'
628
                    . $fields['uid'] . ':{!join from=' . $fields['uid'] . ' to=' . $fields['partof'] . '}'
629
                    . $fields['uid'] . ':' . $searchParams['documentId'] . '"' . ' OR {!join from='
630
                    . $fields['uid'] . ' to=' . $fields['partof'] . '}'
631
                    . $fields['uid'] . ':' . $searchParams['documentId'] . ' OR '
632
                    . $fields['uid'] . ':' . $searchParams['documentId'];
633
//                $label .= htmlspecialchars(sprintf($this->pi_getLL('in', ''), Document::getTitle($searchParams['id'])));
634
            }
635
        }
636
637
        // if a collection is given, we prepare the collection query string
638
        if ($collection) {
0 ignored issues
show
introduced by
$collection is of type Kitodo\Dlf\Domain\Model\Collection, thus it always evaluated to true.
Loading history...
639
            $collecionsQueryString = $collection->getIndexName();
640
            $params['filterquery'][]['query'] = 'toplevel:true';
641
            $params['filterquery'][]['query'] = 'partof:0';
642
            $params['filterquery'][]['query'] = 'collection_faceting:("' . $collecionsQueryString . '")';
643
        }
644
645
        // Set some query parameters.
646
        $params['query'] = !empty($query) ? $query : '*';
647
        $params['start'] = 0;
648
        $params['rows'] = 10000;
649
650
        // order the results as given or by title as default
651
        if (!empty($searchParams['orderBy'])) {
652
            $querySort = [
653
                $searchParams['orderBy'] => $searchParams['order']
654
            ];
655
        } else {
656
            $querySort = [
657
                'year_sorting' => 'asc',
658
                'title_sorting' => 'asc'
659
            ];
660
        }
661
662
        $params['sort'] = $querySort;
663
        $params['listMetadataRecords'] = [];
664
665
        // Restrict the fields to the required ones.
666
        $params['fields'] = 'uid,id,page,title,thumbnail,partof,toplevel,type';
667
668
        if ($listedMetadata) {
669
            foreach ($listedMetadata as $metadata) {
670
                if ($metadata->getIndexStored() || $metadata->getIndexIndexed()) {
671
                    $listMetadataRecord = $metadata->getIndexName() . '_' . ($metadata->getIndexTokenized() ? 't' : 'u') . ($metadata->getIndexStored() ? 's' : 'u') . ($metadata->getIndexIndexed() ? 'i' : 'u');
672
                    $params['fields'] .= ',' . $listMetadataRecord;
673
                    $params['listMetadataRecords'][$metadata->getIndexName()] = $listMetadataRecord;
674
                }
675
            }
676
        }
677
678
        // Perform search.
679
        $result = $this->searchSolr($params, true);
680
681
        // Initialize values
682
        $numberOfToplevels = 0;
683
        $documents = [];
684
685
        if ($result['numFound'] > 0) {
686
            // flat array with uids from Solr search
687
            $documentSet = array_unique(array_column($result['documents'], 'uid'));
688
689
            if (empty($documentSet)) {
690
                // return nothing found
691
                return ['solrResults' => [], 'documents' => []];
692
            }
693
694
            // get the Extbase document objects for all uids
695
            $allDocuments = $this->findAllByUids($documentSet);
696
697
            foreach ($result['documents'] as $doc) {
698
                if (empty($documents[$doc['uid']]) && $allDocuments[$doc['uid']]) {
699
                    $documents[$doc['uid']] = $allDocuments[$doc['uid']];
700
                }
701
                if ($documents[$doc['uid']]) {
702
                    if ($doc['toplevel'] === false) {
703
                        // this maybe a chapter, article, ..., year
704
                        if ($doc['type'] === 'year') {
705
                            continue;
706
                        }
707
                        if (!empty($doc['page'])) {
708
                            // it's probably a fulltext or metadata search
709
                            $searchResult = [];
710
                            $searchResult['page'] = $doc['page'];
711
                            $searchResult['thumbnail'] = $doc['thumbnail'];
712
                            $searchResult['structure'] = $doc['type'];
713
                            $searchResult['title'] = $doc['title'];
714
                            foreach ($params['listMetadataRecords'] as $indexName => $solrField) {
715
                                if (isset($doc['metadata'][$indexName])) {
716
                                    $documents[$doc['uid']]['metadata'][$indexName] = $doc['metadata'][$indexName];
717
                                    $searchResult['metadata'][$indexName] = $doc['metadata'][$indexName];
718
                                }
719
                            }
720
                            if ($searchParams['fulltext'] == '1') {
721
                                $searchResult['snippet'] = $doc['snippet'];
722
                                $searchResult['highlight'] = $doc['highlight'];
723
                                $searchResult['highlight_word'] = $searchParams['query'];
724
                            }
725
                            $documents[$doc['uid']]['searchResults'][] = $searchResult;
726
                        }
727
                    } else if ($doc['toplevel'] === true) {
728
                        $numberOfToplevels++;
729
                        foreach ($params['listMetadataRecords'] as $indexName => $solrField) {
730
                            if (isset($doc['metadata'][$indexName])) {
731
                                $documents[$doc['uid']]['metadata'][$indexName] = $doc['metadata'][$indexName];
732
                            }
733
                        }
734
                        if ($searchParams['fulltext'] != '1') {
735
                            $documents[$doc['uid']]['page'] = 1;
736
                            $children = $this->findByPartof($doc['uid']);
0 ignored issues
show
Bug introduced by
The method findByPartof() does not exist on Kitodo\Dlf\Domain\Repository\DocumentRepository. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

736
                            /** @scrutinizer ignore-call */ 
737
                            $children = $this->findByPartof($doc['uid']);
Loading history...
737
                            foreach($children as $docChild) {
738
                                // We need only a few fields from the children, but we need them as array.
739
                                $childDocument = [
740
                                    'thumbnail' => $docChild->getThumbnail(),
741
                                    'title' => $docChild->getTitle(),
742
                                    'structure' => Helper::getIndexNameFromUid($docChild->getStructure(), 'tx_dlf_structures'),
743
                                    'metsOrderlabel' => $docChild->getMetsOrderlabel(),
744
                                    'uid' => $docChild->getUid(),
745
                                    'metadata' => $this->fetchMetadataFromSolr($docChild->getUid(), $listedMetadata)
746
                                ];
747
                                $documents[$doc['uid']]['children'][$docChild->getUid()] = $childDocument;
748
                            }
749
                        }
750
                    }
751
                    if (empty($documents[$doc['uid']]['metadata'])) {
752
                        $documents[$doc['uid']]['metadata'] = $this->fetchMetadataFromSolr($doc['uid'], $listedMetadata);
753
                    }
754
                    // get title of parent if empty
755
                    if (empty($documents[$doc['uid']]['title']) && ($documents[$doc['uid']]['partOf'] > 0)) {
756
                        $parentDocument = $this->findByUid($documents[$doc['uid']]['partOf']);
757
                        if ($parentDocument) {
758
                            $documents[$doc['uid']]['title'] = '[' . $parentDocument->getTitle() . ']';
759
                        }
760
                    }
761
                }
762
            }
763
        }
764
765
        return ['solrResults' => $result, 'numberOfToplevels' => $numberOfToplevels, 'documents' => $documents];
766
    }
767
768
    /**
769
     * Find all listed metadata for given document
770
     *
771
     * @param int $uid the uid of the document
772
     * @param \TYPO3\CMS\Extbase\Persistence\Generic\QueryResult $listedMetadata
773
     * @return array
774
     */
775
    public function fetchMetadataFromSolr($uid, $listedMetadata = [])
776
    {
777
        // Prepare query parameters.
778
        $params = [];
779
        $metadataArray = [];
780
781
        // Set some query parameters.
782
        $params['query'] = 'uid:' . $uid;
783
        $params['start'] = 0;
784
        $params['rows'] = 1;
785
        $params['sort'] = ['score' => 'desc'];
786
        $params['listMetadataRecords'] = [];
787
788
        // Restrict the fields to the required ones.
789
        $params['fields'] = 'uid,toplevel';
790
791
        if ($listedMetadata) {
792
            foreach ($listedMetadata as $metadata) {
793
                if ($metadata->getIndexIndexed()) {
794
                    $listMetadataRecord = $metadata->getIndexName() . '_' . ($metadata->getIndexTokenized() ? 't' : 'u') . ($metadata->getIndexStored() ? 's' : 'u') . 'i';
795
                    $params['fields'] .= ',' . $listMetadataRecord;
796
                    $params['listMetadataRecords'][$metadata->getIndexName()] = $listMetadataRecord;
797
                }
798
            }
799
        }
800
        // Set filter query to just get toplevel documents.
801
        $params['filterquery'][] = ['query' => 'toplevel:true'];
802
803
        // Perform search.
804
        $result = $this->searchSolr($params, true);
805
806
        if ($result['numFound'] > 0) {
807
            // There is only one result found because of toplevel:true.
808
            if (isset($result['documents'][0]['metadata'])) {
809
                $metadataArray = $result['documents'][0]['metadata'];
810
            }
811
        }
812
        return $metadataArray;
813
    }
814
815
    /**
816
     * Processes a search request
817
     *
818
     * @access public
819
     *
820
     * @param array $parameters: Additional search parameters
821
     * @param boolean $enableCache: Enable caching of Solr requests
822
     *
823
     * @return array The Apache Solr Documents that were fetched
824
     */
825
    protected function searchSolr($parameters = [], $enableCache = true)
826
    {
827
        // Set additional query parameters.
828
        $parameters['start'] = 0;
829
        // Set query.
830
        $parameters['query'] = isset($parameters['query']) ? $parameters['query'] : '*';
831
        $parameters['filterquery'] = isset($parameters['filterquery']) ? $parameters['filterquery'] : [];
832
833
        // Perform Solr query.
834
        // Instantiate search object.
835
        $solr = Solr::getInstance($this->settings['solrcore']);
836
        if (!$solr->ready) {
837
            Helper::log('Apache Solr not available', LOG_SEVERITY_ERROR);
838
            return [];
839
        }
840
841
        $cacheIdentifier = '';
842
        $cache = null;
843
        // Calculate cache identifier.
844
        if ($enableCache === true) {
845
            $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

845
            $cacheIdentifier = Helper::digest($solr->core . /** @scrutinizer ignore-type */ print_r($parameters, true));
Loading history...
846
            $cache = GeneralUtility::makeInstance(CacheManager::class)->getCache('tx_dlf_solr');
847
        }
848
        $resultSet = [
849
            'documents' => [],
850
            'numFound' => 0,
851
        ];
852
        if ($enableCache === false || ($entry = $cache->get($cacheIdentifier)) === false) {
853
            $selectQuery = $solr->service->createSelect($parameters);
854
855
            if ($parameters['fulltext'] === true) {
856
                // get highlighting component and apply settings
857
                $selectQuery->getHighlighting();
858
            }
859
860
            $solrRequest = $solr->service->createRequest($selectQuery);
861
862
            if ($parameters['fulltext'] === true) {
863
                // If it is a fulltext search, enable highlighting.
864
                // field for which highlighting is going to be performed,
865
                // is required if you want to have OCR highlighting
866
                $solrRequest->addParam('hl.ocr.fl', 'fulltext');
867
                // return the coordinates of highlighted search as absolute coordinates
868
                $solrRequest->addParam('hl.ocr.absoluteHighlights', 'on');
869
                // max amount of snippets for a single page
870
                $solrRequest->addParam('hl.snippets', 20);
871
                // we store the fulltext on page level and can disable this option
872
                $solrRequest->addParam('hl.ocr.trackPages', 'off');
873
            }
874
875
            // Perform search for all documents with the same uid that either fit to the search or marked as toplevel.
876
            $response = $solr->service->executeRequest($solrRequest);
877
            $result = $solr->service->createResult($selectQuery, $response);
878
879
            /** @scrutinizer ignore-call */
880
            $resultSet['numFound'] = $result->getNumFound();
881
            $highlighting = [];
882
            if ($parameters['fulltext'] === true) {
883
                $data = $result->getData();
884
                $highlighting = $data['ocrHighlighting'];
885
            }
886
            $fields = Solr::getFields();
887
888
            foreach ($result as $record) {
889
                $resultDocument = new ResultDocument($record, $highlighting, $fields);
890
891
                $document = [
892
                    'id' => $resultDocument->getId(),
893
                    'page' => $resultDocument->getPage(),
894
                    'snippet' => $resultDocument->getSnippets(),
895
                    'thumbnail' => $resultDocument->getThumbnail(),
896
                    'title' => $resultDocument->getTitle(),
897
                    'toplevel' => $resultDocument->getToplevel(),
898
                    'type' => $resultDocument->getType(),
899
                    'uid' => !empty($resultDocument->getUid()) ? $resultDocument->getUid() : $parameters['uid'],
900
                    'highlight' => $resultDocument->getHighlightsIds(),
901
                ];
902
                foreach ($parameters['listMetadataRecords'] as $indexName => $solrField) {
903
                    if (!empty($record->$solrField)) {
904
                        $document['metadata'][$indexName] = $record->$solrField;
905
                    }
906
                }
907
                $resultSet['documents'][] = $document;
908
            }
909
910
            // Save value in cache.
911
            if (!empty($resultSet) && $enableCache === true) {
912
                $cache->set($cacheIdentifier, $resultSet);
913
            }
914
        } else {
915
            // Return cache hit.
916
            $resultSet = $entry;
917
        }
918
        return $resultSet;
919
    }
920
921
}
922