Passed
Push — release-11.5.x ( 8f2f56...f65c18 )
by Rafael
33:27
created

Indexer::getItemRecordOverlayed()   C

Complexity

Conditions 12
Paths 5

Size

Total Lines 37
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 12.8652

Importance

Changes 4
Bugs 1 Features 0
Metric Value
eloc 21
c 4
b 1
f 0
dl 0
loc 37
ccs 18
cts 22
cp 0.8182
rs 6.9666
cc 12
nc 5
nop 2
crap 12.8652

How to fix   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
declare(strict_types=1);
4
5
/*
6
 * This file is part of the TYPO3 CMS project.
7
 *
8
 * It is free software; you can redistribute it and/or modify it under
9
 * the terms of the GNU General Public License, either version 2
10
 * of the License, or any later version.
11
 *
12
 * For the full copyright and license information, please read the
13
 * LICENSE.txt file that was distributed with this source code.
14
 *
15
 * The TYPO3 project - inspiring people to share!
16
 */
17
18
namespace ApacheSolrForTypo3\Solr\IndexQueue;
19
20
use ApacheSolrForTypo3\Solr\ConnectionManager;
21
use ApacheSolrForTypo3\Solr\Domain\Search\ApacheSolrDocument\Builder;
22
use ApacheSolrForTypo3\Solr\Domain\Site\Site;
23
use ApacheSolrForTypo3\Solr\Domain\Site\SiteRepository;
24
use ApacheSolrForTypo3\Solr\FieldProcessor\Service;
25
use ApacheSolrForTypo3\Solr\FrontendEnvironment;
26
use ApacheSolrForTypo3\Solr\FrontendEnvironment\Exception\Exception as FrontendEnvironmentException;
27
use ApacheSolrForTypo3\Solr\FrontendEnvironment\Tsfe;
28
use ApacheSolrForTypo3\Solr\IndexQueue\Exception\IndexingException;
29
use ApacheSolrForTypo3\Solr\NoSolrConnectionFoundException;
30
use ApacheSolrForTypo3\Solr\System\Logging\SolrLogManager;
31
use ApacheSolrForTypo3\Solr\System\Records\Pages\PagesRepository;
32
use ApacheSolrForTypo3\Solr\System\Solr\Document\Document;
33
use ApacheSolrForTypo3\Solr\System\Solr\ResponseAdapter;
34
use ApacheSolrForTypo3\Solr\System\Solr\SolrConnection;
35
use Doctrine\DBAL\Driver\Exception as DBALDriverException;
36
use Doctrine\DBAL\Exception as DBALException;
37
use InvalidArgumentException;
38
use RuntimeException;
39
use Throwable;
40
use TYPO3\CMS\Core\Context\LanguageAspectFactory;
41
use TYPO3\CMS\Core\Exception\SiteNotFoundException;
42
use TYPO3\CMS\Core\Site\SiteFinder;
43
use TYPO3\CMS\Core\Utility\GeneralUtility;
44
use TYPO3\CMS\Core\Utility\RootlineUtility;
45
use UnexpectedValueException;
46
47
/**
48
 * A general purpose indexer to be used for indexing of any kind of regular
49
 * records like tt_news, tt_address, and so on.
50
 * Specialized indexers can extend this class to handle advanced stuff like
51
 * category resolution in tt_news or file indexing.
52
 *
53
 * @author Ingo Renner <[email protected]>
54
 * @copyright  (c) 2009-2015 Ingo Renner <[email protected]>
55
 */
56
class Indexer extends AbstractIndexer
57
{
58
    /**
59
     * A Solr service instance to interact with the Solr server
60
     *
61
     * @var SolrConnection|null
62
     */
63
    protected ?SolrConnection $solr;
64
65
    /**
66
     * @var ConnectionManager
67
     */
68
    protected ConnectionManager $connectionManager;
69
70
    /**
71
     * Holds options for a specific indexer
72
     *
73
     * @var array
74
     */
75
    protected array $options = [];
76
77
    /**
78
     * To log or not to log... #Shakespeare
79
     *
80
     * @var bool
81
     */
82
    protected bool $loggingEnabled = false;
83
84
    /**
85
     * @var SolrLogManager
86
     */
87
    protected SolrLogManager $logger;
88
89
    /**
90
     * @var PagesRepository
91
     */
92
    protected PagesRepository $pagesRepository;
93
94
    /**
95
     * @var Builder
96
     */
97
    protected Builder $documentBuilder;
98
99
    /**
100
     * @var FrontendEnvironment
101
     */
102
    protected FrontendEnvironment $frontendEnvironment;
103
104
    /**
105
     * Constructor
106
     *
107
     * @param array $options array of indexer options
108
     * @param PagesRepository|null $pagesRepository
109
     * @param Builder|null $documentBuilder
110
     * @param SolrLogManager|null $logger
111
     * @param ConnectionManager|null $connectionManager
112
     * @param FrontendEnvironment|null $frontendEnvironment
113
     */
114 54
    public function __construct(
115
        array $options = [],
116
        PagesRepository $pagesRepository = null,
117
        Builder $documentBuilder = null,
118
        SolrLogManager $logger = null,
119
        ConnectionManager $connectionManager = null,
120
        FrontendEnvironment $frontendEnvironment = null
121
    ) {
122 54
        $this->options = $options;
123 54
        $this->pagesRepository = $pagesRepository ?? GeneralUtility::makeInstance(PagesRepository::class);
124 54
        $this->documentBuilder = $documentBuilder ?? GeneralUtility::makeInstance(Builder::class);
125 54
        $this->logger = $logger ?? GeneralUtility::makeInstance(SolrLogManager::class, /** @scrutinizer ignore-type */ __CLASS__);
126 54
        $this->connectionManager = $connectionManager ?? GeneralUtility::makeInstance(ConnectionManager::class);
127 54
        $this->frontendEnvironment = $frontendEnvironment ?? GeneralUtility::makeInstance(FrontendEnvironment::class);
128
    }
129
130
    /**
131
     * Indexes an item from the indexing queue.
132
     *
133
     * @param Item $item An index queue item
134
     * @return bool returns true when indexed, false when not
135
     * @throws DBALDriverException
136
     * @throws DBALException
137
     * @throws FrontendEnvironmentException
138
     * @throws NoSolrConnectionFoundException
139
     * @throws SiteNotFoundException
140
     */
141 23
    public function index(Item $item): bool
142
    {
143 23
        $indexed = true;
144
145 23
        $this->type = $item->getType();
146 23
        $this->setLogging($item);
147
148 23
        $solrConnections = $this->getSolrConnectionsByItem($item);
149 23
        foreach ($solrConnections as $systemLanguageUid => $solrConnection) {
150 23
            $this->solr = $solrConnection;
151
152 23
            if (!$this->indexItem($item, (int)$systemLanguageUid)) {
153
                /*
154
                 * A single language voting for "not indexed" should make the whole
155
                 * item count as being not indexed, even if all other languages are
156
                 * indexed.
157
                 * If there is no translation for a single language, this item counts
158
                 * as TRUE since it's not an error which that should make the item
159
                 * being reindexed during another index run.
160
                 */
161
                $indexed = false;
162
            }
163
        }
164
165 23
        return $indexed;
166
    }
167
168
    /**
169
     * Creates a single Solr Document for an item in a specific language.
170
     *
171
     * @param Item $item An index queue item to index.
172
     * @param int $language The language to use.
173
     * @return bool TRUE if item was indexed successfully, FALSE on failure
174
     * @throws DBALDriverException
175
     * @throws DBALException
176
     * @throws FrontendEnvironmentException
177
     * @throws IndexingException
178
     * @throws SiteNotFoundException
179
     */
180 25
    protected function indexItem(Item $item, int $language = 0): bool
181
    {
182 25
        $documents = [];
183
184 25
        $itemDocument = $this->itemToDocument($item, $language);
185 25
        if (is_null($itemDocument)) {
186
            /*
187
             * If there is no itemDocument, this means there was no translation
188
             * for this record. This should not stop the current item to count as
189
             * being valid because not-indexing not-translated items is perfectly
190
             * fine.
191
             */
192
            return true;
193
        }
194
195 25
        $documents[] = $itemDocument;
196 25
        $documents = array_merge($documents, $this->getAdditionalDocuments($item, $language, $itemDocument));
197 25
        $documents = $this->processDocuments($item, $documents);
198 25
        $documents = self::preAddModifyDocuments($item, $language, $documents);
199
200 25
        $response = $this->solr->getWriteService()->addDocuments($documents);
0 ignored issues
show
Bug introduced by
The method getWriteService() does not exist on null. ( Ignorable by Annotation )

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

200
        $response = $this->solr->/** @scrutinizer ignore-call */ getWriteService()->addDocuments($documents);

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...
201 25
        if ($response->getHttpStatus() !== 200) {
202 1
            $responseData = json_decode($response->getRawResponse() ?? '', true);
203 1
            throw new IndexingException(
204 1
                $response->getHttpStatusMessage() . ': ' . ($responseData['error']['msg'] ?? $response->getHttpStatus()),
205 1
                1678693955
206 1
            );
207
        }
208
209 24
        $this->log($item, $documents, $response);
210
211 24
        return true;
212
    }
213
214
    /**
215
     * Gets the full item record.
216
     *
217
     * This general record indexer simply gets the record from the item. Other
218
     * more specialized indexers may provide more data for their specific item
219
     * types.
220
     *
221
     * @param Item $item The item to be indexed
222
     * @param int $language Language Id (sys_language.uid)
223
     * @return array|null The full record with fields of data to be used for indexing or NULL to prevent an item from being indexed
224
     * @throws DBALDriverException
225
     * @throws FrontendEnvironmentException
226
     * @throws SiteNotFoundException
227
     */
228 23
    protected function getFullItemRecord(Item $item, int $language = 0): ?array
229
    {
230 23
        $itemRecord = $this->getItemRecordOverlayed($item, $language);
231
232 23
        if (!is_null($itemRecord)) {
233 23
            $itemRecord['__solr_index_language'] = $language;
234
        }
235
236 23
        return $itemRecord;
237
    }
238
239
    /**
240
     * Returns the overlaid item record.
241
     *
242
     * @param Item $item
243
     * @param int $language
244
     * @return array|mixed|null
245
     * @throws DBALDriverException
246
     * @throws FrontendEnvironmentException
247
     * @throws SiteNotFoundException
248
     */
249 23
    protected function getItemRecordOverlayed(Item $item, int $language): ?array
250
    {
251 23
        $itemRecord = $item->getRecord();
252 23
        $languageField = $GLOBALS['TCA'][$item->getType()]['ctrl']['languageField'] ?? null;
253
        // skip "free content mode"-record for other languages, if item is a "free content mode"-record
254 23
        if ($this->isAFreeContentModeItemRecord($item)
255 23
            && isset($languageField)
256 23
            && (int)($itemRecord[$languageField] ?? null) !== $language
257
        ) {
258
            return null;
259
        }
260
        // skip fallback for "free content mode"-languages
261 23
        if ($this->isLanguageInAFreeContentMode($item, $language)
262 23
            && isset($languageField)
263 23
            && (int)($itemRecord[$languageField] ?? null) !== $language
264
        ) {
265
            return null;
266
        }
267
        // skip translated records for default language within "free content mode"-languages
268 23
        if ($language === 0
269 23
            && isset($languageField)
270 23
            && (int)($itemRecord[$languageField] ?? null) !== $language
271 23
            && $this->isLanguageInAFreeContentMode($item, (int)($itemRecord[$languageField] ?? null))
272
        ) {
273
            return null;
274
        }
275
276 23
        $pidToUse = $this->getPageIdOfItem($item);
277
278 23
        $globalTsfe = GeneralUtility::makeInstance(Tsfe::class);
279 23
        $specializedTsfe = $globalTsfe->getTsfeByPageIdAndLanguageId($pidToUse, $language, $item->getRootPageUid());
280
281 23
        if ($specializedTsfe === null) {
282
            return null;
283
        }
284
285 23
        return $specializedTsfe->sys_page->getLanguageOverlay($item->getType(), $itemRecord);
286
    }
287
288
    /**
289
     * @param Item $item
290
     *
291
     * @return bool
292
     */
293 23
    protected function isAFreeContentModeItemRecord(Item $item): bool
294
    {
295 23
        $languageField = $GLOBALS['TCA'][$item->getType()]['ctrl']['languageField'] ?? null;
296 23
        $itemRecord = $item->getRecord();
297
298 23
        $l10nParentField = $GLOBALS['TCA'][$item->getType()]['ctrl']['transOrigPointerField'] ?? null;
299 23
        if ($languageField === null || $l10nParentField === null) {
300
            return true;
301
        }
302 23
        $languageOfRecord = (int)($itemRecord[$languageField] ?? null);
303 23
        $l10nParentRecordUid = (int)($itemRecord[$l10nParentField] ?? null);
304
305 23
        if ($languageOfRecord > 0 && $l10nParentRecordUid === 0) {
306
            return true;
307
        }
308
309 23
        return false;
310
    }
311
312
    /**
313
     * Gets the configuration how to process an item's fields for indexing.
314
     *
315
     * @param Item $item An index queue item
316
     * @param int $language Language ID
317
     * @return array Configuration array from TypoScript
318
     * @throws DBALDriverException
319
     */
320 23
    protected function getItemTypeConfiguration(Item $item, int $language = 0): array
321
    {
322 23
        $indexConfigurationName = $item->getIndexingConfigurationName();
323 23
        $fields = $this->getFieldConfigurationFromItemRecordPage($item, $language, $indexConfigurationName);
324 23
        if (!$this->isRootPageIdPartOfRootLine($item) || count($fields) === 0) {
325 2
            $fields = $this->getFieldConfigurationFromItemRootPage($item, $language, $indexConfigurationName);
326 2
            if (count($fields) === 0) {
327
                throw new RuntimeException('The item indexing configuration "' . $item->getIndexingConfigurationName() .
328
                    '" on root page uid ' . $item->getRootPageUid() . ' could not be found!', 1455530112);
329
            }
330
        }
331
332 23
        return $fields;
333
    }
334
335
    /**
336
     * The method retrieves the field configuration of the items record page id (pid).
337
     *
338
     * @param Item $item
339
     * @param int $language
340
     * @param string $indexConfigurationName
341
     * @return array
342
     */
343 23
    protected function getFieldConfigurationFromItemRecordPage(Item $item, int $language, string $indexConfigurationName): array
344
    {
345
        try {
346 23
            $pageId = $this->getPageIdOfItem($item);
347 23
            $solrConfiguration = $this->frontendEnvironment->getSolrConfigurationFromPageId($pageId, $language, $item->getRootPageUid());
348 23
            return $solrConfiguration->getIndexQueueFieldsConfigurationByConfigurationName($indexConfigurationName, []);
349
        } catch (Throwable $e) {
350
            return [];
351
        }
352
    }
353
354
    /**
355
     * @param Item $item
356
     * @return int
357
     */
358 23
    protected function getPageIdOfItem(Item $item): int
359
    {
360 23
        if ($item->getType() === 'pages') {
361 5
            return $item->getRecordUid();
362
        }
363 18
        return $item->getRecordPageId();
364
    }
365
366
    /**
367
     * The method returns the field configuration of the items root page id (uid of the related root page).
368
     *
369
     * @param Item $item
370
     * @param int $language
371
     * @param string $indexConfigurationName
372
     * @return array
373
     * @throws DBALDriverException
374
     */
375 2
    protected function getFieldConfigurationFromItemRootPage(Item $item, int $language, string $indexConfigurationName): array
376
    {
377 2
        $solrConfiguration = $this->frontendEnvironment->getSolrConfigurationFromPageId($item->getRootPageUid(), $language);
0 ignored issues
show
Bug introduced by
It seems like $item->getRootPageUid() can also be of type null; however, parameter $pageId of ApacheSolrForTypo3\Solr\...nfigurationFromPageId() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

377
        $solrConfiguration = $this->frontendEnvironment->getSolrConfigurationFromPageId(/** @scrutinizer ignore-type */ $item->getRootPageUid(), $language);
Loading history...
378
379 2
        return $solrConfiguration->getIndexQueueFieldsConfigurationByConfigurationName($indexConfigurationName, []);
380
    }
381
382
    /**
383
     * In case of additionalStoragePid config recordPageId can be outside siteroot.
384
     * In that case we should not read TS config of foreign siteroot.
385
     *
386
     * @param Item $item
387
     * @return bool
388
     */
389 23
    protected function isRootPageIdPartOfRootLine(Item $item): bool
390
    {
391 23
        $rootPageId = (int)$item->getRootPageUid();
392 23
        $buildRootlineWithPid = $this->getPageIdOfItem($item);
393 23
        $rootlineUtility = GeneralUtility::makeInstance(RootlineUtility::class, $buildRootlineWithPid);
394 23
        $rootline = $rootlineUtility->get();
395
396 23
        $pageInRootline = array_filter($rootline, function ($page) use ($rootPageId) {
397 23
            return (int)$page['uid'] === $rootPageId;
398 23
        });
399 23
        return !empty($pageInRootline);
400
    }
401
402
    /**
403
     * Converts an item array (record) to a Solr document by mapping the
404
     * record's fields onto Solr document fields as configured in TypoScript.
405
     *
406
     * @param Item $item An index queue item
407
     * @param int $language Language Id
408
     *
409
     * @return Document|null The Solr document converted from the record
410
     *
411
     * @throws DBALDriverException
412
     * @throws FrontendEnvironmentException
413
     * @throws SiteNotFoundException
414
     */
415 23
    protected function itemToDocument(Item $item, int $language = 0): ?Document
416
    {
417 23
        $document = null;
418
419 23
        $itemRecord = $this->getFullItemRecord($item, $language);
420 23
        if (!is_null($itemRecord)) {
421 23
            $itemIndexingConfiguration = $this->getItemTypeConfiguration($item, $language);
422 23
            $document = $this->getBaseDocument($item, $itemRecord);
423 23
            $pidToUse = $this->getPageIdOfItem($item);
424 23
            $tsfe = GeneralUtility::makeInstance(Tsfe::class)->getTsfeByPageIdAndLanguageId($pidToUse, $language, $item->getRootPageUid());
425 23
            $document = $this->addDocumentFieldsFromTyposcript($document, $itemIndexingConfiguration, $itemRecord, $tsfe);
426
        }
427
428 23
        return $document;
429
    }
430
431
    /**
432
     * Creates a Solr document with the basic / core fields set already.
433
     *
434
     * @param Item $item The item to index
435
     * @param array $itemRecord The record to use to build the base document
436
     * @return Document A basic Solr document
437
     */
438 23
    protected function getBaseDocument(Item $item, array $itemRecord): Document
439
    {
440 23
        $type = $item->getType();
441 23
        $rootPageUid = $item->getRootPageUid();
442 23
        $accessRootLine = $this->getAccessRootline($item);
443 23
        return $this->documentBuilder->fromRecord($itemRecord, $type, $rootPageUid, $accessRootLine);
0 ignored issues
show
Bug introduced by
It seems like $type can also be of type null; however, parameter $type of ApacheSolrForTypo3\Solr\...t\Builder::fromRecord() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

443
        return $this->documentBuilder->fromRecord($itemRecord, /** @scrutinizer ignore-type */ $type, $rootPageUid, $accessRootLine);
Loading history...
Bug introduced by
It seems like $rootPageUid can also be of type null; however, parameter $rootPageUid of ApacheSolrForTypo3\Solr\...t\Builder::fromRecord() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

443
        return $this->documentBuilder->fromRecord($itemRecord, $type, /** @scrutinizer ignore-type */ $rootPageUid, $accessRootLine);
Loading history...
444
    }
445
446
    /**
447
     * Generates an Access Rootline for an item.
448
     *
449
     * @param Item $item Index Queue item to index.
450
     * @return mixed|string The Access Rootline for the item
451
     */
452 23
    protected function getAccessRootline(Item $item)
453
    {
454 23
        $accessRestriction = '0';
455 23
        $itemRecord = $item->getRecord();
456
457
        // TODO support access restrictions set on storage page
458
459 23
        if (isset($GLOBALS['TCA'][$item->getType()]['ctrl']['enablecolumns']['fe_group'])) {
460 5
            $accessRestriction = $itemRecord[$GLOBALS['TCA'][$item->getType()]['ctrl']['enablecolumns']['fe_group']];
461
462 5
            if (empty($accessRestriction)) {
463
                // public
464 5
                $accessRestriction = '0';
465
            }
466
        }
467
468 23
        return 'r:' . $accessRestriction;
469
    }
470
471
    /**
472
     * Sends the documents to the field processing service which takes care of
473
     * manipulating fields as defined in the field's configuration.
474
     *
475
     * @param Item $item An index queue item
476
     * @param array $documents An array of \ApacheSolrForTypo3\Solr\System\Solr\Document\Document objects to manipulate.
477
     * @return Document[] An array of manipulated Document objects.
478
     * @throws DBALDriverException
479
     * @throws DBALException
480
     */
481 23
    protected function processDocuments(Item $item, array $documents): array
482
    {
483 23
        $siteRepository = GeneralUtility::makeInstance(SiteRepository::class);
484 23
        $solrConfiguration = $siteRepository->getSiteByPageId($item->getRootPageUid())->getSolrConfiguration();
485 23
        $fieldProcessingInstructions = $solrConfiguration->getIndexFieldProcessingInstructionsConfiguration();
486
487
        // same as in the FE indexer
488 23
        if (is_array($fieldProcessingInstructions)) {
489 23
            $service = GeneralUtility::makeInstance(Service::class);
490 23
            $service->processDocuments($documents, $fieldProcessingInstructions);
491
        }
492
493 23
        return $documents;
494
    }
495
496
    /**
497
     * Allows third party extensions to provide additional documents which
498
     * should be indexed for the current item.
499
     *
500
     * @param Item $item The item currently being indexed.
501
     * @param int $language The language uid currently being indexed.
502
     * @param Document $itemDocument The document representing the item for the given language.
503
     * @return Document[] array An array of additional Document objects to index.
504
     */
505 32
    protected function getAdditionalDocuments(Item $item, int $language, Document $itemDocument): array
506
    {
507 32
        $documents = [];
508
509 32
        if (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['IndexQueueIndexer']['indexItemAddDocuments'] ?? null)) {
510 8
            foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['IndexQueueIndexer']['indexItemAddDocuments'] as $classReference) {
511 8
                if (!class_exists($classReference)) {
512 3
                    throw new InvalidArgumentException('Class does not exits' . $classReference, 1490363487);
513
                }
514 5
                $additionalIndexer = GeneralUtility::makeInstance($classReference);
515 5
                if ($additionalIndexer instanceof AdditionalIndexQueueItemIndexer) {
516 3
                    $additionalDocuments = $additionalIndexer->getAdditionalItemDocuments($item, $language, $itemDocument);
517
518 3
                    if (is_array($additionalDocuments)) {
519 3
                        $documents = array_merge(
520 3
                            $documents,
521 3
                            $additionalDocuments
522 3
                        );
523
                    }
524
                } else {
525 2
                    throw new UnexpectedValueException(
526 2
                        get_class($additionalIndexer) . ' must implement interface ' . AdditionalIndexQueueItemIndexer::class,
527 2
                        1326284551
528 2
                    );
529
                }
530
            }
531
        }
532 27
        return $documents;
533
    }
534
535
    /**
536
     * Provides a hook to manipulate documents right before they get added to
537
     * the Solr index.
538
     *
539
     * @param Item $item The item currently being indexed.
540
     * @param int $language The language uid of the documents
541
     * @param array $documents An array of documents to be indexed
542
     * @return array An array of modified documents
543
     */
544 97
    public static function preAddModifyDocuments(Item $item, int $language, array $documents): array
545
    {
546 97
        if (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['IndexQueueIndexer']['preAddModifyDocuments'] ?? null)) {
547 3
            foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['IndexQueueIndexer']['preAddModifyDocuments'] as $classReference) {
548 3
                $documentsModifier = GeneralUtility::makeInstance($classReference);
549
550 3
                if ($documentsModifier instanceof PageIndexerDocumentsModifier) {
551 2
                    $documents = $documentsModifier->modifyDocuments($item, $language, $documents);
552
                } else {
553 1
                    throw new RuntimeException(
554 1
                        'The class "' . get_class($documentsModifier)
555 1
                        . '" registered as document modifier in hook
556
							preAddModifyDocuments must implement interface
557 1
							ApacheSolrForTypo3\Solr\IndexQueue\PageIndexerDocumentsModifier',
558 1
                        1309522677
559 1
                    );
560
                }
561
            }
562
        }
563
564 96
        return $documents;
565
    }
566
567
    // Initialization
568
569
    /**
570
     * Gets the Solr connections applicable for an item.
571
     *
572
     * The connections include the default connection and connections to be used
573
     * for translations of an item.
574
     *
575
     * @param Item $item An index queue item
576
     * @return array An array of ApacheSolrForTypo3\Solr\System\Solr\SolrConnection connections, the array's keys are the sys_language_uid of the language of the connection
577
     * @throws DBALDriverException
578
     * @throws NoSolrConnectionFoundException
579
     */
580 26
    protected function getSolrConnectionsByItem(Item $item): array
581
    {
582 26
        $solrConnections = [];
583
584 26
        $rootPageId = $item->getRootPageUid();
585 26
        if ($item->getType() === 'pages') {
586 7
            $pageId = $item->getRecordUid();
587
        } else {
588 19
            $pageId = $item->getRecordPageId();
589
        }
590
591
        // Solr configurations possible for this item
592 26
        $site = $item->getSite();
593 26
        $solrConfigurationsBySite = $site->getAllSolrConnectionConfigurations();
594 26
        $siteLanguages = [];
595 26
        foreach ($solrConfigurationsBySite as $solrConfiguration) {
596 26
            $siteLanguages[] = $solrConfiguration['language'];
597
        }
598
599 26
        $defaultLanguageUid = $this->getDefaultLanguageUid($item, $site->getRootPage(), $siteLanguages);
600 26
        $translationOverlays = $this->getTranslationOverlaysWithConfiguredSite((int)$pageId, $site, $siteLanguages);
0 ignored issues
show
Bug introduced by
It seems like $site can also be of type null; however, parameter $site of ApacheSolrForTypo3\Solr\...aysWithConfiguredSite() does only seem to accept ApacheSolrForTypo3\Solr\Domain\Site\Site, maybe add an additional type check? ( Ignorable by Annotation )

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

600
        $translationOverlays = $this->getTranslationOverlaysWithConfiguredSite((int)$pageId, /** @scrutinizer ignore-type */ $site, $siteLanguages);
Loading history...
601
602 26
        $defaultConnection = $this->connectionManager->getConnectionByPageId($rootPageId, $defaultLanguageUid, $item->getMountPointIdentifier() ?? '');
0 ignored issues
show
Bug introduced by
It seems like $rootPageId can also be of type null; however, parameter $pageId of ApacheSolrForTypo3\Solr\...getConnectionByPageId() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

602
        $defaultConnection = $this->connectionManager->getConnectionByPageId(/** @scrutinizer ignore-type */ $rootPageId, $defaultLanguageUid, $item->getMountPointIdentifier() ?? '');
Loading history...
603 26
        $translationConnections = $this->getConnectionsForIndexableLanguages($translationOverlays);
604
605 26
        if ($defaultLanguageUid == 0) {
606 24
            $solrConnections[0] = $defaultConnection;
607
        }
608
609 26
        foreach ($translationConnections as $systemLanguageUid => $solrConnection) {
610 23
            $solrConnections[$systemLanguageUid] = $solrConnection;
611
        }
612 26
        return $solrConnections;
613
    }
614
615
    /**
616
     * @param int $pageId
617
     * @param Site $site
618
     * @param array $siteLanguages
619
     * @return array
620
     */
621 26
    protected function getTranslationOverlaysWithConfiguredSite(int $pageId, Site $site, array $siteLanguages): array
622
    {
623 26
        $translationOverlays = $this->pagesRepository->findTranslationOverlaysByPageId($pageId);
624 26
        $translatedLanguages = [];
625 26
        foreach ($translationOverlays as $key => $translationOverlay) {
626 6
            if (!in_array($translationOverlay['sys_language_uid'], $siteLanguages)) {
627
                unset($translationOverlays[$key]);
628
            } else {
629 6
                $translatedLanguages[] = (int)$translationOverlay['sys_language_uid'];
630
            }
631
        }
632
633 26
        if (count($translationOverlays) + 1 !== count($siteLanguages)) {
634
            // not all Languages are translated
635
            // add Language Fallback
636 25
            foreach ($siteLanguages as $languageId) {
637 25
                if ($languageId !== 0 && !in_array((int)$languageId, $translatedLanguages, true)) {
638 25
                    $fallbackLanguageIds = $this->getFallbackOrder($site, (int)$languageId);
639 25
                    foreach ($fallbackLanguageIds as $fallbackLanguageId) {
640 24
                        if ($fallbackLanguageId === 0 || in_array((int)$fallbackLanguageId, $translatedLanguages, true)) {
641 18
                            $translationOverlay = [
642 18
                                'pid' => $pageId,
643 18
                                'sys_language_uid' => $languageId,
644 18
                                'l10n_parent' => $pageId,
645 18
                            ];
646 18
                            $translationOverlays[] = $translationOverlay;
647 18
                            continue 2;
648
                        }
649
                    }
650
                }
651
            }
652
        }
653 26
        return $translationOverlays;
654
    }
655
656
    /**
657
     * @param Site $site
658
     * @param int $languageId
659
     * @return array
660
     */
661 25
    protected function getFallbackOrder(Site $site, int $languageId): array
662
    {
663 25
        $fallbackChain = [];
664 25
        $siteFinder = GeneralUtility::makeInstance(SiteFinder::class);
665
        try {
666 25
            $site = $siteFinder->getSiteByRootPageId($site->getRootPageId());
667 24
            $languageAspect = LanguageAspectFactory::createFromSiteLanguage($site->getLanguageById($languageId));
668 24
            $fallbackChain = $languageAspect->getFallbackChain();
669 1
        } catch (SiteNotFoundException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
670
        }
671 25
        return $fallbackChain;
672
    }
673
674
    /**
675
     * @param Item $item An index queue item
676
     * @param array $rootPage
677
     * @param array $siteLanguages
678
     *
679
     * @return int
680
     * @throws RuntimeException
681
     */
682 26
    protected function getDefaultLanguageUid(Item $item, array $rootPage, array $siteLanguages): int
683
    {
684 26
        $defaultLanguageUid = 0;
685 26
        if (($rootPage['l18n_cfg'] & 1) == 1 && count($siteLanguages) == 1 && $siteLanguages[min(array_keys($siteLanguages))] > 0) {
686
            $defaultLanguageUid = $siteLanguages[min(array_keys($siteLanguages))];
687 26
        } elseif (($rootPage['l18n_cfg'] & 1) == 1 && count($siteLanguages) > 1) {
688 2
            unset($siteLanguages[array_search('0', $siteLanguages)]);
689 2
            $defaultLanguageUid = $siteLanguages[min(array_keys($siteLanguages))];
690 24
        } elseif (($rootPage['l18n_cfg'] & 1) == 1 && count($siteLanguages) == 1) {
691
            $message = 'Root page ' . (int)$item->getRootPageUid() . ' is set to hide default translation, but no other language is configured!';
692
            throw new RuntimeException($message);
693
        }
694
695 26
        return $defaultLanguageUid;
696
    }
697
698
    /**
699
     * Checks for which languages connections have been configured and returns
700
     * these connections.
701
     *
702
     * @param array $translationOverlays An array of translation overlays to check for configured connections.
703
     * @return array An array of ApacheSolrForTypo3\Solr\System\Solr\SolrConnection connections.
704
     * @throws DBALDriverException
705
     */
706 26
    protected function getConnectionsForIndexableLanguages(array $translationOverlays): array
707
    {
708 26
        $connections = [];
709
710 26
        foreach ($translationOverlays as $translationOverlay) {
711 24
            $pageId = $translationOverlay['l10n_parent'];
712 24
            $languageId = $translationOverlay['sys_language_uid'];
713
714
            try {
715 24
                $connection = $this->connectionManager->getConnectionByPageId($pageId, $languageId);
716 23
                $connections[$languageId] = $connection;
717 1
            } catch (NoSolrConnectionFoundException $e) {
718
                // ignore the exception as we seek only those connections
719
                // actually available
720
            }
721
        }
722
723 26
        return $connections;
724
    }
725
726
    // Utility methods
727
728
    // FIXME extract log() and setLogging() to ApacheSolrForTypo3\Solr\IndexQueue\AbstractIndexer
729
    // FIXME extract an interface Tx_Solr_IndexQueue_ItemInterface
730
731
    /**
732
     * Enables logging dependent on the configuration of the item's site
733
     *
734
     * @param Item $item An item being indexed
735
     * @throws DBALDriverException
736
     */
737 24
    protected function setLogging(Item $item)
738
    {
739 24
        $solrConfiguration = $this->frontendEnvironment->getSolrConfigurationFromPageId($item->getRootPageUid());
0 ignored issues
show
Bug introduced by
It seems like $item->getRootPageUid() can also be of type null; however, parameter $pageId of ApacheSolrForTypo3\Solr\...nfigurationFromPageId() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

739
        $solrConfiguration = $this->frontendEnvironment->getSolrConfigurationFromPageId(/** @scrutinizer ignore-type */ $item->getRootPageUid());
Loading history...
740 24
        $this->loggingEnabled = $solrConfiguration->getLoggingIndexingQueueOperationsByConfigurationNameWithFallBack(
741 24
            $item->getIndexingConfigurationName()
742 24
        );
743
    }
744
745
    /**
746
     * Logs the item and what document was created from it
747
     *
748
     * @param Item $item The item that is being indexed.
749
     * @param array $itemDocuments An array of Solr documents created from the item's data
750
     * @param ResponseAdapter $response The Solr response for the particular index document
751
     */
752 24
    protected function log(Item $item, array $itemDocuments, ResponseAdapter $response)
753
    {
754 24
        if (!$this->loggingEnabled) {
755 24
            return;
756
        }
757
758
        $message = 'Index Queue indexing ' . $item->getType() . ':' . $item->getRecordUid() . ' - ';
759
760
        // preparing data
761
        $documents = [];
762
        foreach ($itemDocuments as $document) {
763
            $documents[] = (array)$document;
764
        }
765
766
        $logData = ['item' => (array)$item, 'documents' => $documents, 'response' => (array)$response];
767
768
        if ($response->getHttpStatus() == 200) {
769
            $severity = SolrLogManager::NOTICE;
770
            $message .= 'Success';
771
        } else {
772
            $severity = SolrLogManager::ERROR;
773
            $message .= 'Failure';
774
775
            $logData['status'] = $response->getHttpStatus();
776
            $logData['status message'] = $response->getHttpStatusMessage();
777
        }
778
779
        $this->logger->log($severity, $message, $logData);
780
    }
781
782
    /**
783
     * Returns the language field from given table or null
784
     *
785
     * @param string $tableName
786
     * @return string|null
787
     */
788
    protected function getLanguageFieldFromTable(string $tableName): ?string
789
    {
790
        $tableControl = $GLOBALS['TCA'][$tableName]['ctrl'] ?? [];
791
792
        if (!empty($tableControl['languageField'])) {
793
            return $tableControl['languageField'];
794
        }
795
796
        return null;
797
    }
798
799
    /**
800
     * Checks the given language, if it is in "free" mode.
801
     *
802
     * @param Item $item
803
     * @param int $language
804
     * @return bool
805
     */
806 23
    protected function isLanguageInAFreeContentMode(Item $item, int $language): bool
807
    {
808 23
        if ($language === 0) {
809 23
            return false;
810
        }
811 21
        $typo3site = $item->getSite()->getTypo3SiteObject();
812 21
        $typo3siteLanguage = $typo3site->getLanguageById($language);
813 21
        $typo3siteLanguageFallbackType = $typo3siteLanguage->getFallbackType();
814 21
        if ($typo3siteLanguageFallbackType === 'free') {
815
            return true;
816
        }
817 21
        return false;
818
    }
819
}
820