Passed
Push — task/main/2903-handle_solr_con... ( 2e79f8 )
by Markus
37:08
created

Indexer::getConnectionsForIndexableLanguages()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 18
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 3

Importance

Changes 1
Bugs 1 Features 0
Metric Value
eloc 9
c 1
b 1
f 0
dl 0
loc 18
ccs 9
cts 9
cp 1
rs 9.9666
cc 3
nc 4
nop 1
crap 3
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\NoSolrConnectionFoundException;
29
use ApacheSolrForTypo3\Solr\System\Logging\SolrLogManager;
30
use ApacheSolrForTypo3\Solr\System\Records\Pages\PagesRepository;
31
use ApacheSolrForTypo3\Solr\System\Solr\Document\Document;
32
use ApacheSolrForTypo3\Solr\System\Solr\ResponseAdapter;
33
use ApacheSolrForTypo3\Solr\System\Solr\SolrConnection;
34
use Doctrine\DBAL\Driver\Exception as DBALDriverException;
35
use Doctrine\DBAL\Exception as DBALException;
36
use InvalidArgumentException;
37
use RuntimeException;
38
use Throwable;
39
use TYPO3\CMS\Core\Context\LanguageAspectFactory;
40
use TYPO3\CMS\Core\Exception\SiteNotFoundException;
41
use TYPO3\CMS\Core\Site\SiteFinder;
42
use TYPO3\CMS\Core\Utility\GeneralUtility;
43
use TYPO3\CMS\Core\Utility\RootlineUtility;
44
use UnexpectedValueException;
45
46
/**
47
 * A general purpose indexer to be used for indexing of any kind of regular
48
 * records like tt_news, tt_address, and so on.
49
 * Specialized indexers can extend this class to handle advanced stuff like
50
 * category resolution in tt_news or file indexing.
51
 *
52
 * @author Ingo Renner <[email protected]>
53
 * @copyright  (c) 2009-2015 Ingo Renner <[email protected]>
54
 */
55
class Indexer extends AbstractIndexer
56
{
57
    /**
58
     * A Solr service instance to interact with the Solr server
59
     *
60
     * @var SolrConnection|null
61
     */
62
    protected ?SolrConnection $solr;
63
64
    /**
65
     * @var ConnectionManager
66
     */
67
    protected ConnectionManager $connectionManager;
68
69
    /**
70
     * Holds options for a specific indexer
71
     *
72
     * @var array
73
     */
74
    protected array $options = [];
75
76
    /**
77
     * To log or not to log... #Shakespeare
78
     *
79
     * @var bool
80
     */
81
    protected bool $loggingEnabled = false;
82
83
    /**
84
     * @var SolrLogManager
85
     */
86
    protected SolrLogManager $logger;
87
88
    /**
89
     * @var PagesRepository
90
     */
91
    protected PagesRepository $pagesRepository;
92
93
    /**
94
     * @var Builder
95
     */
96
    protected Builder $documentBuilder;
97
98
    /**
99
     * @var FrontendEnvironment
100
     */
101
    protected FrontendEnvironment $frontendEnvironment;
102
103
    /**
104
     * Constructor
105
     *
106
     * @param array $options array of indexer options
107
     * @param PagesRepository|null $pagesRepository
108
     * @param Builder|null $documentBuilder
109
     * @param SolrLogManager|null $logger
110
     * @param ConnectionManager|null $connectionManager
111
     * @param FrontendEnvironment|null $frontendEnvironment
112
     */
113 51
    public function __construct(
114
        array $options = [],
115
        PagesRepository $pagesRepository = null,
116
        Builder $documentBuilder = null,
117
        SolrLogManager $logger = null,
118
        ConnectionManager $connectionManager = null,
119
        FrontendEnvironment $frontendEnvironment = null
120
    ) {
121 51
        $this->options = $options;
122 51
        $this->pagesRepository = $pagesRepository ?? GeneralUtility::makeInstance(PagesRepository::class);
123 51
        $this->documentBuilder = $documentBuilder ?? GeneralUtility::makeInstance(Builder::class);
124 51
        $this->logger = $logger ?? GeneralUtility::makeInstance(SolrLogManager::class, /** @scrutinizer ignore-type */ __CLASS__);
125 51
        $this->connectionManager = $connectionManager ?? GeneralUtility::makeInstance(ConnectionManager::class);
126 51
        $this->frontendEnvironment = $frontendEnvironment ?? GeneralUtility::makeInstance(FrontendEnvironment::class);
127
    }
128
129
    /**
130
     * Indexes an item from the indexing queue.
131
     *
132
     * @param Item $item An index queue item
133
     * @return bool returns true when indexed, false when not
134
     * @throws DBALDriverException
135
     * @throws DBALException
136
     * @throws FrontendEnvironmentException
137
     * @throws NoSolrConnectionFoundException
138
     * @throws SiteNotFoundException
139
     */
140 20
    public function index(Item $item): bool
141
    {
142 20
        $indexed = true;
143
144 20
        $this->type = $item->getType();
145 20
        $this->setLogging($item);
146
147 20
        $solrConnections = $this->getSolrConnectionsByItem($item);
148 20
        foreach ($solrConnections as $systemLanguageUid => $solrConnection) {
149 20
            $this->solr = $solrConnection;
150
151 20
            if (!$this->indexItem($item, (int)$systemLanguageUid)) {
152
                /*
153
                 * A single language voting for "not indexed" should make the whole
154
                 * item count as being not indexed, even if all other languages are
155
                 * indexed.
156
                 * If there is no translation for a single language, this item counts
157
                 * as TRUE since it's not an error which that should make the item
158
                 * being reindexed during another index run.
159
                 */
160
                $indexed = false;
161
            }
162
        }
163
164 20
        return $indexed;
165
    }
166
167
    /**
168
     * Creates a single Solr Document for an item in a specific language.
169
     *
170
     * @param Item $item An index queue item to index.
171
     * @param int $language The language to use.
172
     * @return bool TRUE if item was indexed successfully, FALSE on failure
173
     * @throws DBALDriverException
174
     * @throws DBALException
175
     * @throws FrontendEnvironmentException
176
     * @throws SiteNotFoundException
177
     */
178 20
    protected function indexItem(Item $item, int $language = 0): bool
179
    {
180 20
        $itemIndexed = false;
181 20
        $documents = [];
182
183 20
        $itemDocument = $this->itemToDocument($item, $language);
184 20
        if (is_null($itemDocument)) {
185
            /*
186
             * If there is no itemDocument, this means there was no translation
187
             * for this record. This should not stop the current item to count as
188
             * being valid because not-indexing not-translated items is perfectly
189
             * fine.
190
             */
191
            return true;
192
        }
193
194 20
        $documents[] = $itemDocument;
195 20
        $documents = array_merge($documents, $this->getAdditionalDocuments($item, $language, $itemDocument));
196 20
        $documents = $this->processDocuments($item, $documents);
197 20
        $documents = self::preAddModifyDocuments($item, $language, $documents);
198
199 20
        $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

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

359
        $solrConfiguration = $this->frontendEnvironment->getSolrConfigurationFromPageId(/** @scrutinizer ignore-type */ $item->getRootPageUid(), $language);
Loading history...
360
361 2
        return $solrConfiguration->getIndexQueueFieldsConfigurationByConfigurationName($indexConfigurationName, []);
362
    }
363
364
    /**
365
     * In case of additionalStoragePid config recordPageId can be outside siteroot.
366
     * In that case we should not read TS config of foreign siteroot.
367
     *
368
     * @param Item $item
369
     * @return bool
370
     */
371 20
    protected function isRootPageIdPartOfRootLine(Item $item): bool
372
    {
373 20
        $rootPageId = (int)$item->getRootPageUid();
374 20
        $buildRootlineWithPid = $this->getPageIdOfItem($item);
375 20
        $rootlineUtility = GeneralUtility::makeInstance(RootlineUtility::class, $buildRootlineWithPid);
376 20
        $rootline = $rootlineUtility->get();
377
378 20
        $pageInRootline = array_filter($rootline, function ($page) use ($rootPageId) {
379 20
            return (int)$page['uid'] === $rootPageId;
380 20
        });
381 20
        return !empty($pageInRootline);
382
    }
383
384
    /**
385
     * Converts an item array (record) to a Solr document by mapping the
386
     * record's fields onto Solr document fields as configured in TypoScript.
387
     *
388
     * @param Item $item An index queue item
389
     * @param int $language Language Id
390
     *
391
     * @return Document|null The Solr document converted from the record
392
     *
393
     * @throws DBALDriverException
394
     * @throws FrontendEnvironmentException
395
     * @throws SiteNotFoundException
396
     */
397 20
    protected function itemToDocument(Item $item, int $language = 0): ?Document
398
    {
399 20
        $document = null;
400
401 20
        $itemRecord = $this->getFullItemRecord($item, $language);
402 20
        if (!is_null($itemRecord)) {
403 20
            $itemIndexingConfiguration = $this->getItemTypeConfiguration($item, $language);
404 20
            $document = $this->getBaseDocument($item, $itemRecord);
405 20
            $pidToUse = $this->getPageIdOfItem($item);
406 20
            $tsfe = GeneralUtility::makeInstance(Tsfe::class)->getTsfeByPageIdAndLanguageId($pidToUse, $language, $item->getRootPageUid());
407 20
            $document = $this->addDocumentFieldsFromTyposcript($document, $itemIndexingConfiguration, $itemRecord, $tsfe);
408
        }
409
410 20
        return $document;
411
    }
412
413
    /**
414
     * Creates a Solr document with the basic / core fields set already.
415
     *
416
     * @param Item $item The item to index
417
     * @param array $itemRecord The record to use to build the base document
418
     * @return Document A basic Solr document
419
     */
420 20
    protected function getBaseDocument(Item $item, array $itemRecord): Document
421
    {
422 20
        $type = $item->getType();
423 20
        $rootPageUid = $item->getRootPageUid();
424 20
        $accessRootLine = $this->getAccessRootline($item);
425 20
        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

425
        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

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

585
        $translationOverlays = $this->getTranslationOverlaysWithConfiguredSite((int)$pageId, /** @scrutinizer ignore-type */ $site, $siteLanguages);
Loading history...
586
587 23
        $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

587
        $defaultConnection = $this->connectionManager->getConnectionByPageId(/** @scrutinizer ignore-type */ $rootPageId, $defaultLanguageUid, $item->getMountPointIdentifier() ?? '');
Loading history...
588 23
        $translationConnections = $this->getConnectionsForIndexableLanguages($translationOverlays);
589
590 23
        if ($defaultLanguageUid == 0) {
591 21
            $solrConnections[0] = $defaultConnection;
592
        }
593
594 23
        foreach ($translationConnections as $systemLanguageUid => $solrConnection) {
595 20
            $solrConnections[$systemLanguageUid] = $solrConnection;
596
        }
597 23
        return $solrConnections;
598
    }
599
600
    /**
601
     * @param int $pageId
602
     * @param Site $site
603
     * @param array $siteLanguages
604
     * @return array
605
     */
606 23
    protected function getTranslationOverlaysWithConfiguredSite(int $pageId, Site $site, array $siteLanguages): array
607
    {
608 23
        $translationOverlays = $this->pagesRepository->findTranslationOverlaysByPageId($pageId);
609 23
        $translatedLanguages = [];
610 23
        foreach ($translationOverlays as $key => $translationOverlay) {
611 6
            if (!in_array($translationOverlay['sys_language_uid'], $siteLanguages)) {
612
                unset($translationOverlays[$key]);
613
            } else {
614 6
                $translatedLanguages[] = (int)$translationOverlay['sys_language_uid'];
615
            }
616
        }
617
618 23
        if (count($translationOverlays) + 1 !== count($siteLanguages)) {
619
            // not all Languages are translated
620
            // add Language Fallback
621 22
            foreach ($siteLanguages as $languageId) {
622 22
                if ($languageId !== 0 && !in_array((int)$languageId, $translatedLanguages, true)) {
623 22
                    $fallbackLanguageIds = $this->getFallbackOrder($site, (int)$languageId);
624 22
                    foreach ($fallbackLanguageIds as $fallbackLanguageId) {
625 21
                        if ($fallbackLanguageId === 0 || in_array((int)$fallbackLanguageId, $translatedLanguages, true)) {
626 15
                            $translationOverlay = [
627 15
                                'pid' => $pageId,
628 15
                                'sys_language_uid' => $languageId,
629 15
                                'l10n_parent' => $pageId,
630 15
                            ];
631 15
                            $translationOverlays[] = $translationOverlay;
632 15
                            continue 2;
633
                        }
634
                    }
635
                }
636
            }
637
        }
638 23
        return $translationOverlays;
639
    }
640
641
    /**
642
     * @param Site $site
643
     * @param int $languageId
644
     * @return array
645
     */
646 22
    protected function getFallbackOrder(Site $site, int $languageId): array
647
    {
648 22
        $fallbackChain = [];
649 22
        $siteFinder = GeneralUtility::makeInstance(SiteFinder::class);
650
        try {
651 22
            $site = $siteFinder->getSiteByRootPageId($site->getRootPageId());
652 21
            $languageAspect = LanguageAspectFactory::createFromSiteLanguage($site->getLanguageById($languageId));
653 21
            $fallbackChain = $languageAspect->getFallbackChain();
654 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...
655
        }
656 22
        return $fallbackChain;
657
    }
658
659
    /**
660
     * @param Item $item An index queue item
661
     * @param array $rootPage
662
     * @param array $siteLanguages
663
     *
664
     * @return int
665
     * @throws RuntimeException
666
     */
667 23
    protected function getDefaultLanguageUid(Item $item, array $rootPage, array $siteLanguages): int
668
    {
669 23
        $defaultLanguageUid = 0;
670 23
        if (($rootPage['l18n_cfg'] & 1) == 1 && count($siteLanguages) == 1 && $siteLanguages[min(array_keys($siteLanguages))] > 0) {
671
            $defaultLanguageUid = $siteLanguages[min(array_keys($siteLanguages))];
672 23
        } elseif (($rootPage['l18n_cfg'] & 1) == 1 && count($siteLanguages) > 1) {
673 2
            unset($siteLanguages[array_search('0', $siteLanguages)]);
674 2
            $defaultLanguageUid = $siteLanguages[min(array_keys($siteLanguages))];
675 21
        } elseif (($rootPage['l18n_cfg'] & 1) == 1 && count($siteLanguages) == 1) {
676
            $message = 'Root page ' . (int)$item->getRootPageUid() . ' is set to hide default translation, but no other language is configured!';
677
            throw new RuntimeException($message);
678
        }
679
680 23
        return $defaultLanguageUid;
681
    }
682
683
    /**
684
     * Checks for which languages connections have been configured and returns
685
     * these connections.
686
     *
687
     * @param array $translationOverlays An array of translation overlays to check for configured connections.
688
     * @return array An array of ApacheSolrForTypo3\Solr\System\Solr\SolrConnection connections.
689
     * @throws DBALDriverException
690
     */
691 23
    protected function getConnectionsForIndexableLanguages(array $translationOverlays): array
692
    {
693 23
        $connections = [];
694
695 23
        foreach ($translationOverlays as $translationOverlay) {
696 21
            $pageId = $translationOverlay['l10n_parent'];
697 21
            $languageId = $translationOverlay['sys_language_uid'];
698
699
            try {
700 21
                $connection = $this->connectionManager->getConnectionByPageId($pageId, $languageId);
701 20
                $connections[$languageId] = $connection;
702 1
            } catch (NoSolrConnectionFoundException $e) {
703
                // ignore the exception as we seek only those connections
704
                // actually available
705
            }
706
        }
707
708 23
        return $connections;
709
    }
710
711
    // Utility methods
712
713
    // FIXME extract log() and setLogging() to ApacheSolrForTypo3\Solr\IndexQueue\AbstractIndexer
714
    // FIXME extract an interface Tx_Solr_IndexQueue_ItemInterface
715
716
    /**
717
     * Enables logging dependent on the configuration of the item's site
718
     *
719
     * @param Item $item An item being indexed
720
     * @throws DBALDriverException
721
     */
722 21
    protected function setLogging(Item $item)
723
    {
724 21
        $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

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