Passed
Branch master (5f6eff)
by Rafael
04:03
created

Indexer::isRootPageIdPartOfRootLine()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 1.0013

Importance

Changes 2
Bugs 1 Features 0
Metric Value
eloc 7
c 2
b 1
f 0
dl 0
loc 11
ccs 8
cts 9
cp 0.8889
rs 10
cc 1
nc 1
nop 1
crap 1.0013
1
<?php
2
namespace ApacheSolrForTypo3\Solr\IndexQueue;
3
4
/*
5
 * This file is part of the TYPO3 CMS project.
6
 *
7
 * It is free software; you can redistribute it and/or modify it under
8
 * the terms of the GNU General Public License, either version 2
9
 * of the License, or any later version.
10
 *
11
 * For the full copyright and license information, please read the
12
 * LICENSE.txt file that was distributed with this source code.
13
 *
14
 * The TYPO3 project - inspiring people to share!
15
 */
16
17
use ApacheSolrForTypo3\Solr\ConnectionManager;
18
use ApacheSolrForTypo3\Solr\Domain\Search\ApacheSolrDocument\Builder;
19
use ApacheSolrForTypo3\Solr\FieldProcessor\Service;
20
use ApacheSolrForTypo3\Solr\FrontendEnvironment;
21
use ApacheSolrForTypo3\Solr\NoSolrConnectionFoundException;
22
use ApacheSolrForTypo3\Solr\Domain\Site\Site;
23
use ApacheSolrForTypo3\Solr\System\Logging\SolrLogManager;
24
use ApacheSolrForTypo3\Solr\System\Records\Pages\PagesRepository;
25
use ApacheSolrForTypo3\Solr\System\Solr\Document\Document;
26
use ApacheSolrForTypo3\Solr\System\Solr\ResponseAdapter;
27
use ApacheSolrForTypo3\Solr\System\Solr\SolrConnection;
28
use Exception;
29
use RuntimeException;
30
use Solarium\Exception\HttpException;
31
use TYPO3\CMS\Core\Context\Context;
32
use TYPO3\CMS\Core\Context\LanguageAspect;
33
use TYPO3\CMS\Core\Context\LanguageAspectFactory;
34
use TYPO3\CMS\Core\Error\Http\ServiceUnavailableException;
35
use TYPO3\CMS\Core\Exception\SiteNotFoundException;
36
use TYPO3\CMS\Core\Http\ImmediateResponseException;
37
use TYPO3\CMS\Core\Site\SiteFinder;
38
use TYPO3\CMS\Core\Utility\GeneralUtility;
39
use TYPO3\CMS\Core\Utility\RootlineUtility;
40
use TYPO3\CMS\Frontend\Page\PageRepository;
41
42
/**
43
 * A general purpose indexer to be used for indexing of any kind of regular
44
 * records like tt_news, tt_address, and so on.
45
 * Specialized indexers can extend this class to handle advanced stuff like
46
 * category resolution in tt_news or file indexing.
47
 *
48
 * @author Ingo Renner <[email protected]>
49
 * @copyright  (c) 2009-2015 Ingo Renner <[email protected]>
50
 */
51
class Indexer extends AbstractIndexer
52
{
53
54
    # TODO change to singular $document instead of plural $documents
55
56
    /**
57
     * A Solr service instance to interact with the Solr server
58
     *
59
     * @var SolrConnection
60
     */
61
    protected $solr;
62
63
    /**
64
     * @var ConnectionManager
65
     */
66
    protected $connectionManager;
67
68
    /**
69
     * Holds options for a specific indexer
70
     *
71
     * @var array
72
     */
73
    protected $options = [];
74
75
    /**
76
     * To log or not to log... #Shakespeare
77
     *
78
     * @var bool
79
     */
80
    protected $loggingEnabled = false;
81
82
    /**
83
     * @var SolrLogManager
84
     */
85
    protected $logger = null;
86
87
    /**
88
     * @var PagesRepository
89
     */
90
    protected $pagesRepository;
91
92
    /**
93
     * @var Builder
94
     */
95
    protected $documentBuilder;
96
97
    /**
98
     * @var FrontendEnvironment
99
     */
100
    protected $frontendEnvironment = null;
101
102
    /**
103
     * Constructor
104
     *
105
     * @param array $options array of indexer options
106
     * @param PagesRepository|null $pagesRepository
107
     * @param Builder|null $documentBuilder
108
     * @param SolrLogManager|null $logger
109
     * @param ConnectionManager|null $connectionManager
110
     * @param FrontendEnvironment|null $frontendEnvironment
111
     */
112 36
    public function __construct(
113
        array $options = [],
114
        PagesRepository $pagesRepository = null,
115
        Builder $documentBuilder = null,
116
        SolrLogManager $logger = null,
117
        ConnectionManager $connectionManager = null,
118
        FrontendEnvironment $frontendEnvironment = null
119
    )
120
    {
121 36
        $this->options = $options;
122 36
        $this->pagesRepository = $pagesRepository ?? GeneralUtility::makeInstance(PagesRepository::class);
123 36
        $this->documentBuilder = $documentBuilder ?? GeneralUtility::makeInstance(Builder::class);
124 36
        $this->logger = $logger ?? GeneralUtility::makeInstance(SolrLogManager::class, /** @scrutinizer ignore-type */ __CLASS__);
125 36
        $this->connectionManager = $connectionManager ?? GeneralUtility::makeInstance(ConnectionManager::class);
126 36
        $this->frontendEnvironment = $frontendEnvironment ?? GeneralUtility::makeInstance(FrontendEnvironment::class);
127 36
    }
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
     */
135 19
    public function index(Item $item)
136
    {
137 19
        $indexed = true;
138
139 19
        $this->type = $item->getType();
140 19
        $this->setLogging($item);
141
142 19
        $solrConnections = $this->getSolrConnectionsByItem($item);
143 19
        foreach ($solrConnections as $systemLanguageUid => $solrConnection) {
144 19
            $this->solr = $solrConnection;
145
146 19
            if (!$this->indexItem($item, (int)$systemLanguageUid)) {
147
                /*
148
                 * A single language voting for "not indexed" should make the whole
149
                 * item count as being not indexed, even if all other languages are
150
                 * indexed.
151
                 * If there is no translation for a single language, this item counts
152
                 * as TRUE since it's not an error which that should make the item
153
                 * being reindexed during another index run.
154
                 */
155
                $indexed = false;
156
            }
157
        }
158
159 19
        return $indexed;
160
    }
161
162
    /**
163
     * Creates a single Solr Document for an item in a specific language.
164
     *
165
     * @param Item $item An index queue item to index.
166
     * @param int $language The language to use.
167
     * @return bool TRUE if item was indexed successfully, FALSE on failure
168
     */
169 19
    protected function indexItem(Item $item, int $language = 0)
170
    {
171 19
        $itemIndexed = false;
172 19
        $documents = [];
173
174 19
        $itemDocument = $this->itemToDocument($item, $language);
175 19
        if (is_null($itemDocument)) {
176
            /*
177
             * If there is no itemDocument, this means there was no translation
178
             * for this record. This should not stop the current item to count as
179
             * being valid because not-indexing not-translated items is perfectly
180
             * fine.
181
             */
182
            return true;
183
        }
184
185 19
        $documents[] = $itemDocument;
186 19
        $documents = array_merge($documents, $this->getAdditionalDocuments($item, $language, $itemDocument));
187 19
        $documents = $this->processDocuments($item, $documents);
188 19
        $documents = $this->preAddModifyDocuments($item, $language, $documents);
189
190
        try {
191 19
            $response = $this->solr->getWriteService()->addDocuments($documents);
192 19
            if ($response->getHttpStatus() == 200) {
193 19
                $itemIndexed = true;
194
            }
195
        } catch (HttpException $e) {
196
            $response = new ResponseAdapter($e->getBody(), $httpStatus = 500, $e->getStatusMessage());
197
        }
198
199 19
        $this->log($item, $documents, $response);
200
201 19
        return $itemIndexed;
202
    }
203
204
    /**
205
     * Gets the full item record.
206
     *
207
     * This general record indexer simply gets the record from the item. Other
208
     * more specialized indexers may provide more data for their specific item
209
     * types.
210
     *
211
     * @param Item $item The item to be indexed
212
     * @param int $language Language Id (sys_language.uid)
213
     * @return array|NULL The full record with fields of data to be used for indexing or NULL to prevent an item from being indexed
214
     */
215 19
    protected function getFullItemRecord(Item $item, int $language = 0)
216
    {
217 19
        $itemRecord = $this->getItemRecordOverlayed($item, $language);
218
219 19
        if (!is_null($itemRecord)) {
220 19
            $itemRecord['__solr_index_language'] = $language;
221
        }
222
223 19
        return $itemRecord;
224
    }
225
226
    /**
227
     * Returns the overlayed item record.
228
     *
229
     * @param Item $item
230
     * @param int $language
231
     * @return array|mixed|null
232
     * @throws \TYPO3\CMS\Core\Context\Exception\AspectNotFoundException
233
     */
234 19
    protected function getItemRecordOverlayed(Item $item, int $language): ?array
235
    {
236 19
        $itemRecord = $item->getRecord();
237
238
        // Bugfix: This issue fixes a problem with the free mode temporary.
239 19
        $languageField = $this->getLanguageFieldFromTable($item->getType());
240 19
        if ($languageField !== null &&
241 19
            (int)$itemRecord[$languageField] > 0 &&
242 19
            (int)$itemRecord[$languageField] !== $language) {
243
            return null;
244
        }
245
246 19
        if ($language > 0) {
247
            // @TODO Information from the site configuration are required!
248
            /* @var Context $context */
249 17
            $context = GeneralUtility::makeInstance(Context::class);
250
            /* @var LanguageAspect $languageAspect */
251 17
            if ($context->hasAspect('language')) {
252 17
                $languageAspect = $context->getAspect('language');
253 17
                $languageAspect = new LanguageAspect(
254 17
                    $languageAspect->getId(),
0 ignored issues
show
Bug introduced by
The method getId() does not exist on TYPO3\CMS\Core\Context\AspectInterface. Did you maybe mean get()? ( Ignorable by Annotation )

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

254
                    $languageAspect->/** @scrutinizer ignore-call */ 
255
                                     getId(),

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...
255 17
                    (int)$language,
256 17
                    $languageAspect->getOverlayType(),
0 ignored issues
show
Bug introduced by
The method getOverlayType() does not exist on TYPO3\CMS\Core\Context\AspectInterface. It seems like you code against a sub-type of TYPO3\CMS\Core\Context\AspectInterface such as TYPO3\CMS\Core\Context\LanguageAspect. ( Ignorable by Annotation )

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

256
                    $languageAspect->/** @scrutinizer ignore-call */ 
257
                                     getOverlayType(),
Loading history...
257 17
                    $languageAspect->getFallbackChain()
0 ignored issues
show
Bug introduced by
The method getFallbackChain() does not exist on TYPO3\CMS\Core\Context\AspectInterface. It seems like you code against a sub-type of TYPO3\CMS\Core\Context\AspectInterface such as TYPO3\CMS\Core\Context\LanguageAspect. ( Ignorable by Annotation )

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

257
                    $languageAspect->/** @scrutinizer ignore-call */ 
258
                                     getFallbackChain()
Loading history...
258
                );
259
            } else {
260
                $languageAspect = new LanguageAspect(
261
                    0,
262
                    (int)$language
263
                );
264
            }
265
266 17
            $context->setAspect('language', $languageAspect);
267 17
            $page = GeneralUtility::makeInstance(PageRepository::class, $context);
268 17
            $itemRecord = $page->getLanguageOverlay($item->getType(), $itemRecord);
269
        }
270
271 19
        if (!$itemRecord) {
272
            $itemRecord = null;
273
        }
274
275 19
        return $itemRecord;
276
    }
277
278
    /**
279
     * Gets the configuration how to process an item's fields for indexing.
280
     *
281
     * @param Item $item An index queue item
282
     * @param int $language Language ID
283
     * @throws RuntimeException
284
     * @return array Configuration array from TypoScript
285
     */
286 19
    protected function getItemTypeConfiguration(Item $item, int $language = 0): array
287
    {
288 19
        $indexConfigurationName = $item->getIndexingConfigurationName();
289 19
        $fields = $this->getFieldConfigurationFromItemRecordPage($item, $language, $indexConfigurationName);
290 19
        if (!$this->isRootPageIdPartOfRootLine($item) || count($fields) === 0) {
291 2
            $fields = $this->getFieldConfigurationFromItemRootPage($item, $language, $indexConfigurationName);
292 2
            if (count($fields) === 0) {
293
                throw new RuntimeException('The item indexing configuration "' . $item->getIndexingConfigurationName() .
294
                    '" on root page uid ' . $item->getRootPageUid() . ' could not be found!', 1455530112);
295
            }
296
        }
297
298 19
        return $fields;
299
    }
300
301
    /**
302
     * The method retrieves the field configuration of the items record page id (pid).
303
     *
304
     * @param Item $item
305
     * @param integer $language
306
     * @param string $indexConfigurationName
307
     * @return array
308
     */
309 19
    protected function getFieldConfigurationFromItemRecordPage(Item $item, int $language, $indexConfigurationName): array
310
    {
311
        try {
312 19
            $pageId = $this->getPageIdOfItem($item);
313 19
            $solrConfiguration = $this->frontendEnvironment->getSolrConfigurationFromPageId($pageId, $language);
314 19
            return $solrConfiguration->getIndexQueueFieldsConfigurationByConfigurationName($indexConfigurationName, []);
315
        } catch (Exception $e) {
316
            return [];
317
        }
318
    }
319
320
    /**
321
     * @param Item $item
322
     * @return int
323
     */
324 19
    protected function getPageIdOfItem(Item $item): int
325
    {
326 19
        if ($item->getType() === 'pages') {
327 2
            return (int)$item->getRecordUid();
328
        }
329 17
        return (int)$item->getRecordPageId();
330
    }
331
332
    /**
333
     * The method returns the field configuration of the items root page id (uid of the related root page).
334
     *
335
     * @param Item $item
336
     * @param integer $language
337
     * @param string $indexConfigurationName
338
     * @return array
339
     */
340 2
    protected function getFieldConfigurationFromItemRootPage(Item $item, int $language, $indexConfigurationName)
341
    {
342 2
        $solrConfiguration = $this->frontendEnvironment->getSolrConfigurationFromPageId($item->getRootPageUid(), $language);
343
344 2
        return $solrConfiguration->getIndexQueueFieldsConfigurationByConfigurationName($indexConfigurationName, []);
345
    }
346
347
    /**
348
     * In case of additionalStoragePid config recordPageId can be outside of siteroot.
349
     * In that case we should not read TS config of foreign siteroot.
350
     *
351
     * @param Item $item
352
     * @return bool
353
     */
354 19
    protected function isRootPageIdPartOfRootLine(Item $item): bool
355
    {
356 19
        $rootPageId = (int)$item->getRootPageUid();
357 19
        $buildRootlineWithPid = $this->getPageIdOfItem($item);
358 19
        $rootlineUtility = GeneralUtility::makeInstance(RootlineUtility::class, $buildRootlineWithPid);
359 19
        $rootline = $rootlineUtility->get();
360
361
        $pageInRootline = array_filter($rootline, function($page) use ($rootPageId) {
362 19
            return (int)$page['uid'] === $rootPageId;
363 19
        });
364 19
        return !empty($pageInRootline);
365
    }
366
367
    /**
368
     * Converts an item array (record) to a Solr document by mapping the
369
     * record's fields onto Solr document fields as configured in TypoScript.
370
     *
371
     * @param Item $item An index queue item
372
     * @param int $language Language Id
373
     * @return Document|null The Solr document converted from the record
374
     * @throws SiteNotFoundException
375
     * @throws ServiceUnavailableException
376
     * @throws ImmediateResponseException
377
     */
378 19
    protected function itemToDocument(Item $item, int $language = 0): ?Document
379
    {
380 19
        $document = null;
381 19
        if ($item->getType() === 'pages') {
382 2
            $this->frontendEnvironment->initializeTsfe($item->getRecordUid(), $language);
383
        } else {
384 17
            $this->frontendEnvironment->initializeTsfe($item->getRootPageUid(), $language);
385
        }
386
387 19
        $itemRecord = $this->getFullItemRecord($item, $language);
388 19
        if (!is_null($itemRecord)) {
389 19
            $itemIndexingConfiguration = $this->getItemTypeConfiguration($item, $language);
390 19
            $document = $this->getBaseDocument($item, $itemRecord);
391 19
            $document = $this->addDocumentFieldsFromTyposcript($document, $itemIndexingConfiguration, $itemRecord);
392
        }
393
394 19
        return $document;
395
    }
396
397
    /**
398
     * Creates a Solr document with the basic / core fields set already.
399
     *
400
     * @param Item $item The item to index
401
     * @param array $itemRecord The record to use to build the base document
402
     * @return Document A basic Solr document
403
     */
404 19
    protected function getBaseDocument(Item $item, array $itemRecord)
405
    {
406 19
        $type = $item->getType();
407 19
        $rootPageUid = $item->getRootPageUid();
408 19
        $accessRootLine = $this->getAccessRootline($item);
409 19
        return $this->documentBuilder->fromRecord($itemRecord, $type, $rootPageUid, $accessRootLine);
410
    }
411
412
    /**
413
     * Generates an Access Rootline for an item.
414
     *
415
     * @param Item $item Index Queue item to index.
416
     * @return string The Access Rootline for the item
417
     */
418 19
    protected function getAccessRootline(Item $item)
419
    {
420 19
        $accessRestriction = '0';
421 19
        $itemRecord = $item->getRecord();
422
423
        // TODO support access restrictions set on storage page
424
425 19
        if (isset($GLOBALS['TCA'][$item->getType()]['ctrl']['enablecolumns']['fe_group'])) {
426 2
            $accessRestriction = $itemRecord[$GLOBALS['TCA'][$item->getType()]['ctrl']['enablecolumns']['fe_group']];
427
428 2
            if (empty($accessRestriction)) {
429
                // public
430 2
                $accessRestriction = '0';
431
            }
432
        }
433
434 19
        return 'r:' . $accessRestriction;
435
    }
436
437
    /**
438
     * Sends the documents to the field processing service which takes care of
439
     * manipulating fields as defined in the field's configuration.
440
     *
441
     * @param Item $item An index queue item
442
     * @param array $documents An array of \ApacheSolrForTypo3\Solr\System\Solr\Document\Document objects to manipulate.
443
     * @return Document[] array Array of manipulated Document objects.
444
     */
445 19
    protected function processDocuments(Item $item, array $documents)
446
    {
447
        // needs to respect the TS settings for the page the item is on, conditions may apply
448 19
        $solrConfiguration = $this->frontendEnvironment->getSolrConfigurationFromPageId($item->getRootPageUid());
449 19
        $fieldProcessingInstructions = $solrConfiguration->getIndexFieldProcessingInstructionsConfiguration();
450
451
        // same as in the FE indexer
452 19
        if (is_array($fieldProcessingInstructions)) {
0 ignored issues
show
introduced by
The condition is_array($fieldProcessingInstructions) is always true.
Loading history...
453 19
            $service = GeneralUtility::makeInstance(Service::class);
454 19
            $service->processDocuments($documents, $fieldProcessingInstructions);
455
        }
456
457 19
        return $documents;
458
    }
459
460
    /**
461
     * Allows third party extensions to provide additional documents which
462
     * should be indexed for the current item.
463
     *
464
     * @param Item $item The item currently being indexed.
465
     * @param int $language The language uid currently being indexed.
466
     * @param Document $itemDocument The document representing the item for the given language.
467
     * @return Document[] array An array of additional Document objects to index.
468
     */
469 23
    protected function getAdditionalDocuments(Item $item, int $language, Document $itemDocument)
470
    {
471 23
        $documents = [];
472
473 23
        if (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['IndexQueueIndexer']['indexItemAddDocuments'])) {
474 4
            foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['IndexQueueIndexer']['indexItemAddDocuments'] as $classReference) {
475 4
                if (!class_exists($classReference)) {
476 2
                    throw new \InvalidArgumentException('Class does not exits' . $classReference, 1490363487);
477
                }
478 2
                $additionalIndexer = GeneralUtility::makeInstance($classReference);
479 2
                if ($additionalIndexer instanceof AdditionalIndexQueueItemIndexer) {
480 1
                    $additionalDocuments = $additionalIndexer->getAdditionalItemDocuments($item, $language, $itemDocument);
481
482 1
                    if (is_array($additionalDocuments)) {
483 1
                        $documents = array_merge($documents,
484
                            $additionalDocuments);
485
                    }
486
                } else {
487 1
                    throw new \UnexpectedValueException(
488 1
                        get_class($additionalIndexer) . ' must implement interface ' . AdditionalIndexQueueItemIndexer::class,
489 1
                        1326284551
490
                    );
491
                }
492
            }
493
        }
494 20
        return $documents;
495
    }
496
497
    /**
498
     * Provides a hook to manipulate documents right before they get added to
499
     * the Solr index.
500
     *
501
     * @param Item $item The item currently being indexed.
502
     * @param int $language The language uid of the documents
503
     * @param array $documents An array of documents to be indexed
504
     * @return array An array of modified documents
505
     */
506 19
    protected function preAddModifyDocuments(Item $item, int $language, array $documents)
507
    {
508 19
        if (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['IndexQueueIndexer']['preAddModifyDocuments'])) {
509
            foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['IndexQueueIndexer']['preAddModifyDocuments'] as $classReference) {
510
                $documentsModifier = GeneralUtility::makeInstance($classReference);
511
512
                if ($documentsModifier instanceof PageIndexerDocumentsModifier) {
513
                    $documents = $documentsModifier->modifyDocuments($item, $language, $documents);
514
                } else {
515
                    throw new RuntimeException(
516
                        'The class "' . get_class($documentsModifier)
517
                        . '" registered as document modifier in hook
518
							preAddModifyDocuments must implement interface
519
							ApacheSolrForTypo3\Solr\IndexQueue\PageIndexerDocumentsModifier',
520
                        1309522677
521
                    );
522
                }
523
            }
524
        }
525
526 19
        return $documents;
527
    }
528
529
    // Initialization
530
531
    /**
532
     * Gets the Solr connections applicable for an item.
533
     *
534
     * The connections include the default connection and connections to be used
535
     * for translations of an item.
536
     *
537
     * @param Item $item An index queue item
538
     * @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
539
     */
540 21
    protected function getSolrConnectionsByItem(Item $item)
541
    {
542 21
        $solrConnections = [];
543
544 21
        $rootPageId = $item->getRootPageUid();
545 21
        if ($item->getType() === 'pages') {
546 4
            $pageId = $item->getRecordUid();
547
        } else {
548 17
            $pageId = $item->getRecordPageId();
549
        }
550
551
        // Solr configurations possible for this item
552 21
        $site = $item->getSite();
553 21
        $solrConfigurationsBySite = $site->getAllSolrConnectionConfigurations();
554 21
        $siteLanguages = [];
555 21
        foreach ($solrConfigurationsBySite as $solrConfiguration) {
556 21
            $siteLanguages[] = $solrConfiguration['language'];
557
        }
558
559 21
        $defaultLanguageUid = $this->getDefaultLanguageUid($item, $site->getRootPage(), $siteLanguages);
560 21
        $translationOverlays = $this->getTranslationOverlaysWithConfiguredSite((int)$pageId, $site, (array)$siteLanguages);
561
562 21
        $defaultConnection = $this->connectionManager->getConnectionByPageId($rootPageId, $defaultLanguageUid, $item->getMountPointIdentifier());
563 21
        $translationConnections = $this->getConnectionsForIndexableLanguages($translationOverlays);
564
565 21
        if ($defaultLanguageUid == 0) {
566 19
            $solrConnections[0] = $defaultConnection;
567
        }
568
569 21
        foreach ($translationConnections as $systemLanguageUid => $solrConnection) {
570 19
            $solrConnections[$systemLanguageUid] = $solrConnection;
571
        }
572 21
        return $solrConnections;
573
    }
574
575
    /**
576
     * @param int $pageId
577
     * @param Site $site
578
     * @param array $siteLanguages
579
     * @return array
580
     */
581 21
    protected function getTranslationOverlaysWithConfiguredSite(int $pageId, Site $site, array $siteLanguages): array
582
    {
583 21
        $translationOverlays = $this->pagesRepository->findTranslationOverlaysByPageId($pageId);
584 21
        $translatedLanguages = [];
585 21
        foreach ($translationOverlays as $key => $translationOverlay) {
586 6
            if (!in_array($translationOverlay['sys_language_uid'], $siteLanguages)) {
587
                unset($translationOverlays[$key]);
588
            } else {
589 6
                $translatedLanguages[] = (int)$translationOverlay['sys_language_uid'];
590
            }
591
        }
592
593 21
        if (count($translationOverlays) + 1 !== count($siteLanguages)) {
594
            // not all Languages are translated
595
            // add Language Fallback
596 21
            foreach ($siteLanguages as $languageId) {
597 21
                if ($languageId !== 0 && !in_array((int)$languageId, $translatedLanguages, true)) {
598 21
                    $fallbackLanguageIds = $this->getFallbackOrder($site, (int)$languageId, (int)$pageId);
599 21
                    foreach ($fallbackLanguageIds as $fallbackLanguageId) {
600 20
                        if ($fallbackLanguageId === 0 || in_array((int)$fallbackLanguageId, $translatedLanguages, true)) {
601
                            $translationOverlay = [
602 14
                                'pid' => $pageId,
603 14
                                'sys_language_uid' => $languageId,
604 14
                                'l10n_parent' => $pageId
605
                            ];
606 14
                            $translationOverlays[] = $translationOverlay;
607 14
                            continue 2;
608
                        }
609
                    }
610
                }
611
            }
612
        }
613 21
        return $translationOverlays;
614
    }
615
616
    /**
617
     * @param Site $site
618
     * @param int $languageId
619
     * @param int $pageId
620
     * @return array
621
     */
622 21
    protected function getFallbackOrder(Site $site,  int $languageId, int $pageId): array
0 ignored issues
show
Unused Code introduced by
The parameter $pageId 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

622
    protected function getFallbackOrder(Site $site,  int $languageId, /** @scrutinizer ignore-unused */ int $pageId): array

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...
623
    {
624 21
        $fallbackChain = [];
625 21
        $siteFinder = GeneralUtility::makeInstance(SiteFinder::class);
626
        try {
627 21
            $site = $siteFinder->getSiteByRootPageId($site->getRootPageId());
628 20
            $languageAspect = LanguageAspectFactory::createFromSiteLanguage($site->getLanguageById($languageId));
629 20
            $fallbackChain = $languageAspect->getFallbackChain();
630 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...
631
632
        }
633 21
        return $fallbackChain;
634
    }
635
636
    /**
637
     * @param Item $item An index queue item
638
     * @param array $rootPage
639
     * @param array $siteLanguages
640
     *
641
     * @return int
642
     * @throws RuntimeException
643
     */
644 21
    protected function getDefaultLanguageUid(Item $item, array $rootPage, array $siteLanguages)
645
    {
646 21
        $defaultLanguageUid = 0;
647 21
        if (($rootPage['l18n_cfg'] & 1) == 1 && count($siteLanguages) == 1 && $siteLanguages[min(array_keys($siteLanguages))] > 0) {
648
            $defaultLanguageUid = $siteLanguages[min(array_keys($siteLanguages))];
649 21
        } elseif (($rootPage['l18n_cfg'] & 1) == 1 && count($siteLanguages) > 1) {
650 2
            unset($siteLanguages[array_search('0', $siteLanguages)]);
651 2
            $defaultLanguageUid = $siteLanguages[min(array_keys($siteLanguages))];
652 19
        } elseif (($rootPage['l18n_cfg'] & 1) == 1 && count($siteLanguages) == 1) {
653
            $message = 'Root page ' . (int)$item->getRootPageUid() . ' is set to hide default translation, but no other language is configured!';
654
            throw new RuntimeException($message);
655
        }
656
657 21
        return $defaultLanguageUid;
658
    }
659
660
    /**
661
     * Checks for which languages connections have been configured and returns
662
     * these connections.
663
     *
664
     * @param array $translationOverlays An array of translation overlays to check for configured connections.
665
     * @return array An array of ApacheSolrForTypo3\Solr\System\Solr\SolrConnection connections.
666
     */
667 21
    protected function getConnectionsForIndexableLanguages(array $translationOverlays)
668
    {
669 21
        $connections = [];
670
671 21
        foreach ($translationOverlays as $translationOverlay) {
672 20
            $pageId = $translationOverlay['l10n_parent'];
673 20
            $languageId = $translationOverlay['sys_language_uid'];
674
675
            try {
676 20
                $connection = $this->connectionManager->getConnectionByPageId($pageId, $languageId);
677 19
                $connections[$languageId] = $connection;
678 1
            } catch (NoSolrConnectionFoundException $e) {
679
                // ignore the exception as we seek only those connections
680
                // actually available
681
            }
682
        }
683
684 21
        return $connections;
685
    }
686
687
    // Utility methods
688
689
    // FIXME extract log() and setLogging() to ApacheSolrForTypo3\Solr\IndexQueue\AbstractIndexer
690
    // FIXME extract an interface Tx_Solr_IndexQueue_ItemInterface
691
692
    /**
693
     * Enables logging dependent on the configuration of the item's site
694
     *
695
     * @param Item $item An item being indexed
696
     * @return    void
697
     */
698 19
    protected function setLogging(Item $item)
699
    {
700 19
        $solrConfiguration = $this->frontendEnvironment->getSolrConfigurationFromPageId($item->getRootPageUid());
701 19
        $this->loggingEnabled = $solrConfiguration->getLoggingIndexingQueueOperationsByConfigurationNameWithFallBack(
702 19
            $item->getIndexingConfigurationName()
703
        );
704 19
    }
705
706
    /**
707
     * Logs the item and what document was created from it
708
     *
709
     * @param Item $item The item that is being indexed.
710
     * @param array $itemDocuments An array of Solr documents created from the item's data
711
     * @param ResponseAdapter $response The Solr response for the particular index document
712
     */
713 19
    protected function log(Item $item, array $itemDocuments, ResponseAdapter $response)
714
    {
715 19
        if (!$this->loggingEnabled) {
716 19
            return;
717
        }
718
719
        $message = 'Index Queue indexing ' . $item->getType() . ':' . $item->getRecordUid() . ' - ';
720
721
        // preparing data
722
        $documents = [];
723
        foreach ($itemDocuments as $document) {
724
            $documents[] = (array)$document;
725
        }
726
727
        $logData = ['item' => (array)$item, 'documents' => $documents, 'response' => (array)$response];
728
729
        if ($response->getHttpStatus() == 200) {
730
            $severity = SolrLogManager::NOTICE;
731
            $message .= 'Success';
732
        } else {
733
            $severity = SolrLogManager::ERROR;
734
            $message .= 'Failure';
735
736
            $logData['status'] = $response->getHttpStatus();
737
            $logData['status message'] = $response->getHttpStatusMessage();
738
        }
739
740
        $this->logger->log($severity, $message, $logData);
741
    }
742
743
    /**
744
     * Returns the language field from given table or null
745
     *
746
     * @param string $tableName
747
     * @return string|null
748
     */
749 19
    protected function getLanguageFieldFromTable(string $tableName): ?string
750
    {
751 19
        $tableControl = $GLOBALS['TCA'][$tableName]['ctrl'] ?? [];
752
753 19
        if (!empty($tableControl['languageField'])) {
754 19
            return $tableControl['languageField'];
755
        }
756
757
        return null;
758
    }
759
}
760