Passed
Push — release-11.2.x ( 831b53...e453b5 )
by Markus
31:21 queued 27:40
created

DataUpdateHandler::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 20
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 8
dl 0
loc 20
ccs 9
cts 9
cp 1
rs 10
c 1
b 0
f 0
cc 1
nc 1
nop 9
crap 1

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php declare(strict_types = 1);
2
namespace ApacheSolrForTypo3\Solr\Domain\Index\Queue\UpdateHandler;
3
4
/***************************************************************
5
 *  Copyright notice
6
 *
7
 *  (c) 2021 Markus Friedrich <[email protected]>
8
 *  All rights reserved
9
 *
10
 *  This script is part of the TYPO3 project. The TYPO3 project is
11
 *  free software; you can redistribute it and/or modify
12
 *  it under the terms of the GNU General Public License as published by
13
 *  the Free Software Foundation; either version 3 of the License, or
14
 *  (at your option) any later version.
15
 *
16
 *  The GNU General Public License can be found at
17
 *  http://www.gnu.org/copyleft/gpl.html.
18
 *
19
 *  This script is distributed in the hope that it will be useful,
20
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
21
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22
 *  GNU General Public License for more details.
23
 *
24
 *  This copyright notice MUST APPEAR in all copies of the script!
25
 ***************************************************************/
26
27
use TYPO3\CMS\Core\DataHandling\DataHandler;
28
use TYPO3\CMS\Backend\Utility\BackendUtility;
29
use TYPO3\CMS\Core\Utility\GeneralUtility;
30
use ApacheSolrForTypo3\Solr\FrontendEnvironment;
31
use ApacheSolrForTypo3\Solr\IndexQueue\Queue;
32
use ApacheSolrForTypo3\Solr\Domain\Index\Queue\RecordMonitor\Helper\RootPageResolver;
33
use ApacheSolrForTypo3\Solr\Domain\Index\Queue\RecordMonitor\Helper\ConfigurationAwareRecordService;
34
use ApacheSolrForTypo3\Solr\Domain\Index\Queue\RecordMonitor\Helper\MountPagesUpdater;
35
use ApacheSolrForTypo3\Solr\System\TCA\TCAService;
36
use ApacheSolrForTypo3\Solr\System\Logging\SolrLogManager;
37
use ApacheSolrForTypo3\Solr\System\Records\Pages\PagesRepository;
38
use ApacheSolrForTypo3\Solr\System\Configuration\TypoScriptConfiguration;
39
use ApacheSolrForTypo3\Solr\Util;
40
41
/**
42
 * Data update handler
43
 *
44
 * Handles update on potential relevant records e.g.
45
 * an update might require index queue updates
46
 */
47
class DataUpdateHandler extends AbstractUpdateHandler
48
{
49
    /**
50
     * List of fields in the update field array that
51
     * are required for processing
52
     *
53
     * Note: For pages all fields except l10n_diffsource are
54
     *       kept, as additional fields can be configured in
55
     *       TypoScript, see AbstractDataUpdateEvent->_sleep.
56
     *
57
     * @var array
58
     */
59
    protected static $requiredUpdatedFields = [
60
        'pid',
61
    ];
62
63
    /**
64
     * Configuration used to check if recursive updates are required
65
     *
66
     * Holds the configuration when a recursive page queuing should be triggered, while processing record
67
     * updates
68
     *
69
     * Note: The SQL transaction is already committed, so the current state covers only "non"-changed fields.
70
     *
71
     * @var array
72
     */
73
    protected $updateSubPagesRecursiveTriggerConfiguration = [
74
        // the current page has the both fields "extendToSubpages" and "hidden" set from 1 to 0 => requeue subpages
75
        'HiddenAndExtendToSubpageWereDisabled' => [
76
            'changeSet' => [
77
                'hidden' => '0',
78
                'extendToSubpages' => '0'
79
            ]
80
        ],
81
        // the current page has the field "extendToSubpages" enabled and the field "hidden" was set to 0 => requeue subpages
82
        'extendToSubpageEnabledAndHiddenFlagWasRemoved' => [
83
            'currentState' =>  ['extendToSubpages' => '1'],
84
            'changeSet' => ['hidden' => '0']
85
        ],
86
        // the current page has the field "hidden" enabled and the field "extendToSubpages" was set to 0 => requeue subpages
87
        'hiddenIsEnabledAndExtendToSubPagesWasRemoved' => [
88
            'currentState' =>  ['hidden' => '1'],
89
            'changeSet' => ['extendToSubpages' => '0']
90
        ],
91
        // the field "no_search_sub_entries" of current page was set to 0
92
        'no_search_sub_entriesFlagWasAdded' => [
93
            'changeSet' => ['no_search_sub_entries' => '0']
94
        ],
95
    ];
96
97
    /**
98
     * @var MountPagesUpdater
99
     */
100
    protected $mountPageUpdater;
101
102
    /**
103
     * @var RootPageResolver
104
     */
105
    protected $rootPageResolver = null;
106
107
    /**
108
     * @var PagesRepository
109
     */
110
    protected $pagesRepository;
111
112
    /**
113
     * @var SolrLogManager
114
     */
115
    protected $logger = null;
116
117
    /**
118
     * @var DataHandler
119
     */
120
    protected $dataHandler;
121
122
    /**
123
     * @param ConfigurationAwareRecordService $recordService
124
     * @param FrontendEnvironment $frontendEnvironment
125
     * @param TCAService $tcaService
126
     * @param Queue $indexQueue
127
     * @param MountPagesUpdater $mountPageUpdater
128
     * @param RootPageResolver $rootPageResolver
129
     * @param PagesRepository $pagesRepository
130
     * @param SolrLogManager $solrLogManager
131
     * @param DataHandler $dataHandler
132
     */
133 81
    public function __construct(
134
        ConfigurationAwareRecordService $recordService ,
135
        FrontendEnvironment $frontendEnvironment,
136
        TCAService $tcaService,
137
        Queue $indexQueue,
138
        MountPagesUpdater $mountPageUpdater,
139
        RootPageResolver $rootPageResolver,
140
        PagesRepository $pagesRepository,
141
        DataHandler $dataHandler,
142
        SolrLogManager $solrLogManager = null
143
    ) {
144 81
        parent::__construct($recordService, $frontendEnvironment, $tcaService, $indexQueue);
145
146 81
        $this->mountPageUpdater = $mountPageUpdater;
147 81
        $this->rootPageResolver = $rootPageResolver;
148 81
        $this->pagesRepository = $pagesRepository;
149 81
        $this->dataHandler = $dataHandler;
150 81
        $this->logger = $solrLogManager ?? GeneralUtility::makeInstance(
151 64
            SolrLogManager::class,
152 64
            /** @scrutinizer ignore-type */ __CLASS__
153
        );
154 81
    }
155
156
    /**
157
     * Handle content element update
158
     *
159
     * @param int $uid
160
     * @param array $updatedFields
161
     */
162 15
    public function handleContentElementUpdate(int $uid, array $updatedFields = []): void
163
    {
164 15
        $pid = $updatedFields['pid'] ?? $this->getValidatedPid('tt_content', $uid);
165 15
        if ($pid === null) {
166 1
            return;
167
        }
168
169 14
        $this->processPageRecord($pid, (int)$pid, $updatedFields);
170 14
    }
171
172
    /**
173
     * Handles the deletion of a content element
174
     *
175
     * @param int $uid
176
     */
177 2
    public function handleContentElementDeletion(int $uid): void
178
    {
179
        // @TODO: Should be checked, is possibly unnecessary as
180
        //        also done via GarbageCollector & PageStrategy
181
182 2
        $pid = $this->getValidatedPid('tt_content', $uid);
183 2
        if ($pid === null) {
184
            return;
185
        }
186
187 2
        $this->indexQueue->updateItem('pages', $pid, Util::getExectionTime());
188 2
    }
189
190
    /**
191
     * Handles page updates
192
     *
193
     * @param int $uid
194
     * @param array $updatedFields
195
     */
196 43
    public function handlePageUpdate(int $uid, array $updatedFields = []): void
197
    {
198
        try {
199 43
            if (isset($updatedFields['l10n_parent']) && intval($updatedFields['l10n_parent']) > 0) {
200 2
                $pid = $updatedFields['l10n_parent'];
201 41
            } elseif ($this->rootPageResolver->getIsRootPageId($uid)) {
202 18
                $pid = $uid;
203
            } else {
204 41
                $pid = $updatedFields['pid'] ?? $this->getValidatedPid('pages', $uid);
205
            }
206 2
        } catch (\Throwable $e) {
207 2
            $pid = null;
208
        }
209
210 43
        if ($pid === null) {
211 3
            $this->removeFromIndexAndQueueWhenItemInQueue('pages', $uid);
212 3
            return;
213
        }
214
215 40
        $this->processPageRecord($uid, (int)$pid, $updatedFields);
216 40
    }
217
218
    /**
219
     * Handles record updates
220
     *
221
     * @param int $uid
222
     * @param string $table
223
     */
224 13
    public function handleRecordUpdate(int $uid, string $table): void
225
    {
226 13
        $rootPageIds = $this->getRecordRootPageIds($table, $uid);
227 13
        $this->processRecord($table, $uid, $rootPageIds);
228 13
    }
229
230
    /**
231
     * Handles a version swap
232
     *
233
     * @param int $uid
234
     * @param string $table
235
     */
236 6
    public function handleVersionSwap(int $uid, string $table): void
237
    {
238 6
        $isPageRelatedRecord = ($table === 'tt_content' || $table === 'pages');
239 6
        if($isPageRelatedRecord) {
240 4
            $uid = ($table === 'tt_content' ? $this->getValidatedPid($table, $uid) : $uid);
241 4
            if ($uid === null) {
242
                return;
243
            }
244 4
            $this->applyPageChangesToQueue($uid);
245
        } else {
246 2
            $recordPageId = $this->getValidatedPid($table, $uid);
247 2
            if ($recordPageId === null) {
248
                return;
249
            }
250 2
            $this->applyRecordChangesToQueue($table, $uid, $recordPageId);
251
        }
252 6
    }
253
254
    /**
255
     * Handle page move
256
     *
257
     * @param int $uid
258
     */
259 1
    public function handleMovedPage(int $uid): void
260
    {
261 1
        $this->applyPageChangesToQueue($uid);
262 1
    }
263
264
    /**
265
     * Handle record move
266
     *
267
     * @param int $uid
268
     * @param string $table
269
     */
270 1
    public function handleMovedRecord(int $uid, string $table): void
271
    {
272 1
        $pid = $this->getValidatedPid($table, $uid);
273 1
        if ($pid === null) {
274
            return;
275
        }
276
277 1
        $this->applyRecordChangesToQueue($table, $uid, $pid);
278 1
    }
279
280
    /**
281
     * Adds a page to the queue and updates mounts, when it is enabled, otherwise ensure that the page is removed
282
     * from the queue.
283
     *
284
     * @param int $uid
285
     */
286 5
    protected function applyPageChangesToQueue(int $uid): void
287
    {
288 5
        $solrConfiguration = $this->getSolrConfigurationFromPageId($uid);
289 5
        $record = $this->configurationAwareRecordService->getRecord('pages', $uid, $solrConfiguration);
290 5
        if (!empty($record) && $this->tcaService->isEnabledRecord('pages', $record)) {
291 4
            $this->mountPageUpdater->update($uid);
292 4
            $this->indexQueue->updateItem('pages', $uid);
293
        } else {
294 1
            $this->removeFromIndexAndQueueWhenItemInQueue('pages', $uid);
295
        }
296 5
    }
297
298
    /**
299
     * Adds a record to the queue if it is monitored and enabled, otherwise it removes the record from the queue.
300
     *
301
     * @param string $table
302
     * @param int $uid
303
     * @param int $pid
304
     */
305 3
    protected function applyRecordChangesToQueue(string $table, int $uid, int $pid): void
306
    {
307 3
        $solrConfiguration = $this->getSolrConfigurationFromPageId($pid);
308 3
        $isMonitoredTable = $solrConfiguration->getIndexQueueIsMonitoredTable($table);
309
310 3
        if ($isMonitoredTable) {
311 3
            $record = $this->configurationAwareRecordService->getRecord($table, $uid, $solrConfiguration);
312
313 3
            if (!empty($record) && $this->tcaService->isEnabledRecord($table, $record)) {
314 2
                $uid = $this->tcaService->getTranslationOriginalUidIfTranslated($table, $record, $uid);
315 2
                $this->indexQueue->updateItem($table, $uid);
316
            } else {
317
                // TODO should be moved to garbage collector
318 1
                $this->removeFromIndexAndQueueWhenItemInQueue($table, $uid);
319
            }
320
        }
321 3
    }
322
323
    /**
324
     * Removes record from the index queue and from the solr index
325
     *
326
     * @param string $recordTable Name of table where the record lives
327
     * @param int $recordUid Id of record
328
     */
329 6
    protected function removeFromIndexAndQueue(string $recordTable, int $recordUid): void
330
    {
331 6
        $this->getGarbageHandler()->collectGarbage($recordTable, $recordUid);
332 6
    }
333
334
    /**
335
     * Removes record from the index queue and from the solr index when the item is in the queue.
336
     *
337
     * @param string $recordTable Name of table where the record lives
338
     * @param int $recordUid Id of record
339
     */
340 9
    protected function removeFromIndexAndQueueWhenItemInQueue(string $recordTable, int $recordUid): void
341
    {
342 9
        if (!$this->indexQueue->containsItem($recordTable, $recordUid)) {
343 3
            return;
344
        }
345
346 6
        $this->removeFromIndexAndQueue($recordTable, $recordUid);
347 6
    }
348
349
    /**
350
     * @param $pageId
351
     * @return TypoScriptConfiguration
352
     */
353 74
    protected function getSolrConfigurationFromPageId(int $pageId): TypoScriptConfiguration
354
    {
355 74
        return $this->frontendEnvironment->getSolrConfigurationFromPageId($pageId);
356
    }
357
358
    /**
359
     * Fetch record root page ids
360
     *
361
     * @param string $recordTable The table the record belongs to
362
     * @param int $recordUid
363
     * @return int[]
364
     */
365 13
    protected function getRecordRootPageIds(string $recordTable, int $recordUid): array
366
    {
367
        try {
368 13
            $rootPageIds = $this->rootPageResolver->getResponsibleRootPageIds($recordTable, $recordUid);
369
        } catch (\InvalidArgumentException $e) {
370
            $rootPageIds = [];
371
        }
372
373 13
        return $rootPageIds;
374
    }
375
376
    /**
377
     * Processes a page record
378
     *
379
     * Note: Also used if content element is updated, the page
380
     * of the content element is processed here
381
     *
382
     * @param int $uid
383
     * @param int $pid
384
     * @param array $updatedFields
385
     */
386 54
    protected function processPageRecord(int $uid, int $pid, array $updatedFields = []): void
387
    {
388 54
        $configurationPageId = $this->getConfigurationPageId('pages', (int)$pid, $uid);
389 54
        if ($configurationPageId === 0) {
390 1
            $this->mountPageUpdater->update($uid);
391 1
            return;
392
        }
393 53
        $rootPageIds = [$configurationPageId];
394
395 53
        $this->processRecord('pages', $uid, $rootPageIds);
396
397 53
        $this->updateCanonicalPages($uid);
398 53
        $this->mountPageUpdater->update($uid);
399
400 53
        $recursiveUpdateRequired = $this->isRecursivePageUpdateRequired($uid, $updatedFields);
401 53
        if ($recursiveUpdateRequired) {
402 12
            $treePageIds = $this->getSubPageIds($uid);
403 12
            $this->updatePageIdItems($treePageIds);
404
        }
405 53
    }
406
407
    /**
408
     * Process a record
409
     *
410
     * @param string $recordTable
411
     * @param int $recordUid
412
     * @param array $rootPageIds
413
     */
414 66
    protected function processRecord(string $recordTable, int $recordUid, array $rootPageIds): void
415
    {
416 66
        if (empty($rootPageIds)) {
417
            $this->removeFromIndexAndQueueWhenItemInQueue($recordTable, $recordUid);
418
            return;
419
        }
420
421 66
        foreach ($rootPageIds as $configurationPageId) {
422 66
            $solrConfiguration = $this->getSolrConfigurationFromPageId($configurationPageId);
423 66
            $isMonitoredRecord = $solrConfiguration->getIndexQueueIsMonitoredTable($recordTable);
424 66
            if (!$isMonitoredRecord) {
425
                // when it is a non monitored record, we can skip it.
426 2
                continue;
427
            }
428
429 64
            $record = $this->configurationAwareRecordService->getRecord($recordTable, $recordUid, $solrConfiguration);
430 64
            if (empty($record)) {
431
                // TODO move this part to the garbage collector
432
                // check if the item should be removed from the index because it no longer matches the conditions
433 4
                $this->removeFromIndexAndQueueWhenItemInQueue($recordTable, $recordUid);
434 4
                continue;
435
            }
436
            // Clear existing index queue items to prevent mount point duplicates.
437
            // This needs to be done before the overlay handling, because handling an overlay record should
438
            // not trigger a deletion.
439 62
            $isTranslation = !empty($record['sys_language_uid']) && $record['sys_language_uid'] !== 0;
440 62
            if ($recordTable === 'pages' && !$isTranslation) {
441 48
                $this->indexQueue->deleteItem('pages', $recordUid);
442
            }
443
444
            // only update/insert the item if we actually found a record
445 62
            $isLocalizedRecord = $this->tcaService->isLocalizedRecord($recordTable, $record);
446 62
            $recordUid = $this->tcaService->getTranslationOriginalUidIfTranslated($recordTable, $record, $recordUid);
447
448 62
            if ($isLocalizedRecord && !$this->getIsTranslationParentRecordEnabled($recordTable, $recordUid)) {
449
                // we have a localized record without a visible parent record. Nothing to do.
450
                continue;
451
            }
452
453 62
            if ($this->tcaService->isEnabledRecord($recordTable, $record)) {
454 56
                $this->indexQueue->updateItem($recordTable, $recordUid);
455
            }
456
        }
457 66
    }
458
459
    /**
460
     * This method is used to determine the pageId that should be used to retrieve the index queue configuration.
461
     *
462
     * @param string $recordTable
463
     * @param int $recordPageId
464
     * @param int $recordUid
465
     * @return int
466
     */
467 54
    protected function getConfigurationPageId(string $recordTable, int $recordPageId, int $recordUid): int
468
    {
469 54
        $rootPageId = $this->rootPageResolver->getRootPageId($recordPageId);
470 54
        if ($this->rootPageResolver->getIsRootPageId($rootPageId)) {
471 53
            return $recordPageId;
472
        }
473
474 1
        $alternativeSiteRoots = $this->rootPageResolver->getAlternativeSiteRootPagesIds(
475 1
            $recordTable,
476 1
            $recordUid,
477 1
            $recordPageId
478
        );
479 1
        return (int)array_pop($alternativeSiteRoots);
480
    }
481
482
    /**
483
     * Checks if the parent record of the translated record is enabled.
484
     *
485
     * @param string $recordTable
486
     * @param int $recordUid
487
     * @return bool
488
     */
489 2
    protected function getIsTranslationParentRecordEnabled(string $recordTable, int $recordUid): bool
490
    {
491 2
        $l10nParentRecord = (array)BackendUtility::getRecord($recordTable, $recordUid, '*', '', false);
492 2
        return $this->tcaService->isEnabledRecord($recordTable, $l10nParentRecord);
493
    }
494
495
    /**
496
     * Applies the updateItem instruction on a collection of pageIds.
497
     *
498
     * @param array $treePageIds
499
     */
500 12
    protected function updatePageIdItems(array $treePageIds): void
501
    {
502 12
        foreach ($treePageIds as $treePageId) {
503 10
            $this->indexQueue->updateItem('pages', $treePageId);
504
        }
505 12
    }
506
507
    /**
508
     * Triggers Index Queue updates for other pages showing content from the
509
     * page currently being updated.
510
     *
511
     * @param int $pageId UID of the page currently being updated
512
     */
513 53
    protected function updateCanonicalPages(int $pageId): void
514
    {
515 53
        $canonicalPages = $this->pagesRepository->findPageUidsWithContentsFromPid((int)$pageId);
516 53
        foreach ($canonicalPages as $page) {
517
            $this->indexQueue->updateItem('pages', $page['uid']);
518
        }
519 53
    }
520
521
    /**
522
     * Retrieves the pid of a record, returns null if no pid could be found
523
     *
524
     * @param string $table
525
     * @param int $uid
526
     * @return int|null
527
     */
528 35
    protected function getValidatedPid(string $table, int $uid): ?int
529
    {
530 35
        $pid = $this->dataHandler->getPID($table, $uid);
531 35
        if ($pid === false) {
532 2
            $message = 'Record without valid pid was processed ' . $table . ':' . $uid;
533 2
            $this->logger->log(SolrLogManager::WARNING, $message);
534 2
            return null;
535
        }
536
537 33
        return (int)$pid;
538
    }
539
540
    /**
541
     * @return GarbageHandler
542
     */
543 6
    protected function getGarbageHandler(): GarbageHandler
544
    {
545 6
        return GeneralUtility::makeInstance(GarbageHandler::class);
546
    }
547
}
548