Failed Conditions
Push — release-11.2.x ( 3aa391...494b52 )
by Rafael
18:23
created

DataUpdateHandler   D

Complexity

Total Complexity 59

Size/Duplication

Total Lines 502
Duplicated Lines 0 %

Test Coverage

Coverage 93.75%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 59
eloc 151
c 2
b 0
f 0
dl 0
loc 502
ccs 150
cts 160
cp 0.9375
rs 4.08

22 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 20 1
A handleContentElementDeletion() 0 11 2
A handleContentElementUpdate() 0 8 2
A removeFromIndexAndQueue() 0 3 1
A applyRecordChangesToQueue() 0 14 4
A getSolrConfigurationFromPageId() 0 3 1
A removeFromIndexAndQueueWhenItemInQueue() 0 7 2
A handleRecordUpdate() 0 4 1
A processPageRecord() 0 18 3
A handleVersionSwap() 0 15 6
A getRecordRootPageIds() 0 9 2
A handleMovedPage() 0 3 1
A handleMovedRecord() 0 8 2
A applyPageChangesToQueue() 0 9 3
B handlePageUpdate() 0 23 7
B processRecord() 0 41 11
A getValidatedPid() 0 10 2
A getIsTranslationParentRecordEnabled() 0 4 1
A getGarbageHandler() 0 3 1
A getConfigurationPageId() 0 13 2
A updateCanonicalPages() 0 5 2
A updatePageIdItems() 0 4 2

How to fix   Complexity   

Complex Class

Complex classes like DataUpdateHandler often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use DataUpdateHandler, and based on these observations, apply Extract Interface, too.

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 43
        if ($uid === 0) {
199 1
            return;
200
        }
201
        try {
202 42
            if (isset($updatedFields['l10n_parent']) && intval($updatedFields['l10n_parent']) > 0) {
203 2
                $pid = $updatedFields['l10n_parent'];
204 40
            } elseif ($this->rootPageResolver->getIsRootPageId($uid)) {
205 18
                $pid = $uid;
206
            } else {
207 40
                $pid = $updatedFields['pid'] ?? $this->getValidatedPid('pages', $uid);
208
            }
209 2
        } catch (\Throwable $e) {
210 2
            $pid = null;
211
        }
212
213 42
        if ($pid === null) {
214 2
            $this->removeFromIndexAndQueueWhenItemInQueue('pages', $uid);
215 2
            return;
216
        }
217
218 40
        $this->processPageRecord($uid, (int)$pid, $updatedFields);
219 40
    }
220
221
    /**
222
     * Handles record updates
223
     *
224
     * @param int $uid
225
     * @param string $table
226
     */
227 13
    public function handleRecordUpdate(int $uid, string $table): void
228
    {
229 13
        $rootPageIds = $this->getRecordRootPageIds($table, $uid);
230 13
        $this->processRecord($table, $uid, $rootPageIds);
231 13
    }
232
233
    /**
234
     * Handles a version swap
235
     *
236
     * @param int $uid
237
     * @param string $table
238
     */
239 6
    public function handleVersionSwap(int $uid, string $table): void
240
    {
241 6
        $isPageRelatedRecord = ($table === 'tt_content' || $table === 'pages');
242 6
        if($isPageRelatedRecord) {
243 4
            $uid = ($table === 'tt_content' ? $this->getValidatedPid($table, $uid) : $uid);
244 4
            if ($uid === null) {
245
                return;
246
            }
247 4
            $this->applyPageChangesToQueue($uid);
248
        } else {
249 2
            $recordPageId = $this->getValidatedPid($table, $uid);
250 2
            if ($recordPageId === null) {
251
                return;
252
            }
253 2
            $this->applyRecordChangesToQueue($table, $uid, $recordPageId);
254
        }
255 6
    }
256
257
    /**
258
     * Handle page move
259
     *
260
     * @param int $uid
261
     */
262 1
    public function handleMovedPage(int $uid): void
263
    {
264 1
        $this->applyPageChangesToQueue($uid);
265 1
    }
266
267
    /**
268
     * Handle record move
269
     *
270
     * @param int $uid
271
     * @param string $table
272
     */
273 1
    public function handleMovedRecord(int $uid, string $table): void
274
    {
275 1
        $pid = $this->getValidatedPid($table, $uid);
276 1
        if ($pid === null) {
277
            return;
278
        }
279
280 1
        $this->applyRecordChangesToQueue($table, $uid, $pid);
281 1
    }
282
283
    /**
284
     * Adds a page to the queue and updates mounts, when it is enabled, otherwise ensure that the page is removed
285
     * from the queue.
286
     *
287
     * @param int $uid
288
     */
289 5
    protected function applyPageChangesToQueue(int $uid): void
290
    {
291 5
        $solrConfiguration = $this->getSolrConfigurationFromPageId($uid);
292 5
        $record = $this->configurationAwareRecordService->getRecord('pages', $uid, $solrConfiguration);
293 5
        if (!empty($record) && $this->tcaService->isEnabledRecord('pages', $record)) {
294 4
            $this->mountPageUpdater->update($uid);
295 4
            $this->indexQueue->updateItem('pages', $uid);
296
        } else {
297 1
            $this->removeFromIndexAndQueueWhenItemInQueue('pages', $uid);
298
        }
299 5
    }
300
301
    /**
302
     * Adds a record to the queue if it is monitored and enabled, otherwise it removes the record from the queue.
303
     *
304
     * @param string $table
305
     * @param int $uid
306
     * @param int $pid
307
     */
308 3
    protected function applyRecordChangesToQueue(string $table, int $uid, int $pid): void
309
    {
310 3
        $solrConfiguration = $this->getSolrConfigurationFromPageId($pid);
311 3
        $isMonitoredTable = $solrConfiguration->getIndexQueueIsMonitoredTable($table);
312
313 3
        if ($isMonitoredTable) {
314 3
            $record = $this->configurationAwareRecordService->getRecord($table, $uid, $solrConfiguration);
315
316 3
            if (!empty($record) && $this->tcaService->isEnabledRecord($table, $record)) {
317 2
                $uid = $this->tcaService->getTranslationOriginalUidIfTranslated($table, $record, $uid);
318 2
                $this->indexQueue->updateItem($table, $uid);
319
            } else {
320
                // TODO should be moved to garbage collector
321 1
                $this->removeFromIndexAndQueueWhenItemInQueue($table, $uid);
322
            }
323
        }
324 3
    }
325
326
    /**
327
     * Removes record from the index queue and from the solr index
328
     *
329
     * @param string $recordTable Name of table where the record lives
330
     * @param int $recordUid Id of record
331
     */
332 6
    protected function removeFromIndexAndQueue(string $recordTable, int $recordUid): void
333
    {
334 6
        $this->getGarbageHandler()->collectGarbage($recordTable, $recordUid);
335 6
    }
336
337
    /**
338
     * Removes record from the index queue and from the solr index when the item is in the queue.
339
     *
340
     * @param string $recordTable Name of table where the record lives
341
     * @param int $recordUid Id of record
342
     */
343 8
    protected function removeFromIndexAndQueueWhenItemInQueue(string $recordTable, int $recordUid): void
344
    {
345 8
        if (!$this->indexQueue->containsItem($recordTable, $recordUid)) {
346 2
            return;
347
        }
348
349 6
        $this->removeFromIndexAndQueue($recordTable, $recordUid);
350 6
    }
351
352
    /**
353
     * @param $pageId
354
     * @return TypoScriptConfiguration
355
     */
356 74
    protected function getSolrConfigurationFromPageId(int $pageId): TypoScriptConfiguration
357
    {
358 74
        return $this->frontendEnvironment->getSolrConfigurationFromPageId($pageId);
359
    }
360
361
    /**
362
     * Fetch record root page ids
363
     *
364
     * @param string $recordTable The table the record belongs to
365
     * @param int $recordUid
366
     * @return int[]
367
     */
368 13
    protected function getRecordRootPageIds(string $recordTable, int $recordUid): array
369
    {
370
        try {
371 13
            $rootPageIds = $this->rootPageResolver->getResponsibleRootPageIds($recordTable, $recordUid);
372
        } catch (\InvalidArgumentException $e) {
373
            $rootPageIds = [];
374
        }
375
376 13
        return $rootPageIds;
377
    }
378
379
    /**
380
     * Processes a page record
381
     *
382
     * Note: Also used if content element is updated, the page
383
     * of the content element is processed here
384
     *
385
     * @param int $uid
386
     * @param int $pid
387
     * @param array $updatedFields
388
     */
389 54
    protected function processPageRecord(int $uid, int $pid, array $updatedFields = []): void
390
    {
391 54
        $configurationPageId = $this->getConfigurationPageId('pages', (int)$pid, $uid);
392 54
        if ($configurationPageId === 0) {
393 1
            $this->mountPageUpdater->update($uid);
394 1
            return;
395
        }
396 53
        $rootPageIds = [$configurationPageId];
397
398 53
        $this->processRecord('pages', $uid, $rootPageIds);
399
400 53
        $this->updateCanonicalPages($uid);
401 53
        $this->mountPageUpdater->update($uid);
402
403 53
        $recursiveUpdateRequired = $this->isRecursivePageUpdateRequired($uid, $updatedFields);
404 53
        if ($recursiveUpdateRequired) {
405 12
            $treePageIds = $this->getSubPageIds($uid);
406 12
            $this->updatePageIdItems($treePageIds);
407
        }
408 53
    }
409
410
    /**
411
     * Process a record
412
     *
413
     * @param string $recordTable
414
     * @param int $recordUid
415
     * @param array $rootPageIds
416
     */
417 66
    protected function processRecord(string $recordTable, int $recordUid, array $rootPageIds): void
418
    {
419 66
        if (empty($rootPageIds)) {
420
            $this->removeFromIndexAndQueueWhenItemInQueue($recordTable, $recordUid);
421
            return;
422
        }
423
424 66
        foreach ($rootPageIds as $configurationPageId) {
425 66
            $solrConfiguration = $this->getSolrConfigurationFromPageId($configurationPageId);
426 66
            $isMonitoredRecord = $solrConfiguration->getIndexQueueIsMonitoredTable($recordTable);
427 66
            if (!$isMonitoredRecord) {
428
                // when it is a non monitored record, we can skip it.
429 2
                continue;
430
            }
431
432 64
            $record = $this->configurationAwareRecordService->getRecord($recordTable, $recordUid, $solrConfiguration);
433 64
            if (empty($record)) {
434
                // TODO move this part to the garbage collector
435
                // check if the item should be removed from the index because it no longer matches the conditions
436 4
                $this->removeFromIndexAndQueueWhenItemInQueue($recordTable, $recordUid);
437 4
                continue;
438
            }
439
            // Clear existing index queue items to prevent mount point duplicates.
440
            // This needs to be done before the overlay handling, because handling an overlay record should
441
            // not trigger a deletion.
442 62
            $isTranslation = !empty($record['sys_language_uid']) && $record['sys_language_uid'] !== 0;
443 62
            if ($recordTable === 'pages' && !$isTranslation) {
444 48
                $this->indexQueue->deleteItem('pages', $recordUid);
445
            }
446
447
            // only update/insert the item if we actually found a record
448 62
            $isLocalizedRecord = $this->tcaService->isLocalizedRecord($recordTable, $record);
449 62
            $recordUid = $this->tcaService->getTranslationOriginalUidIfTranslated($recordTable, $record, $recordUid);
450
451 62
            if ($isLocalizedRecord && !$this->getIsTranslationParentRecordEnabled($recordTable, $recordUid)) {
452
                // we have a localized record without a visible parent record. Nothing to do.
453
                continue;
454
            }
455
456 62
            if ($this->tcaService->isEnabledRecord($recordTable, $record)) {
457 56
                $this->indexQueue->updateItem($recordTable, $recordUid);
458
            }
459
        }
460 66
    }
461
462
    /**
463
     * This method is used to determine the pageId that should be used to retrieve the index queue configuration.
464
     *
465
     * @param string $recordTable
466
     * @param int $recordPageId
467
     * @param int $recordUid
468
     * @return int
469
     */
470 54
    protected function getConfigurationPageId(string $recordTable, int $recordPageId, int $recordUid): int
471
    {
472 54
        $rootPageId = $this->rootPageResolver->getRootPageId($recordPageId);
473 54
        if ($this->rootPageResolver->getIsRootPageId($rootPageId)) {
474 53
            return $recordPageId;
475
        }
476
477 1
        $alternativeSiteRoots = $this->rootPageResolver->getAlternativeSiteRootPagesIds(
478 1
            $recordTable,
479
            $recordUid,
480
            $recordPageId
481
        );
482 1
        return (int)array_pop($alternativeSiteRoots);
483
    }
484
485
    /**
486
     * Checks if the parent record of the translated record is enabled.
487
     *
488
     * @param string $recordTable
489
     * @param int $recordUid
490
     * @return bool
491
     */
492 2
    protected function getIsTranslationParentRecordEnabled(string $recordTable, int $recordUid): bool
493
    {
494 2
        $l10nParentRecord = (array)BackendUtility::getRecord($recordTable, $recordUid, '*', '', false);
495 2
        return $this->tcaService->isEnabledRecord($recordTable, $l10nParentRecord);
496
    }
497
498
    /**
499
     * Applies the updateItem instruction on a collection of pageIds.
500
     *
501
     * @param array $treePageIds
502
     */
503 12
    protected function updatePageIdItems(array $treePageIds): void
504
    {
505 12
        foreach ($treePageIds as $treePageId) {
506 10
            $this->indexQueue->updateItem('pages', $treePageId);
507
        }
508 12
    }
509
510
    /**
511
     * Triggers Index Queue updates for other pages showing content from the
512
     * page currently being updated.
513
     *
514
     * @param int $pageId UID of the page currently being updated
515
     */
516 53
    protected function updateCanonicalPages(int $pageId): void
517
    {
518 53
        $canonicalPages = $this->pagesRepository->findPageUidsWithContentsFromPid((int)$pageId);
519 53
        foreach ($canonicalPages as $page) {
520
            $this->indexQueue->updateItem('pages', $page['uid']);
521
        }
522 53
    }
523
524
    /**
525
     * Retrieves the pid of a record, returns null if no pid could be found
526
     *
527
     * @param string $table
528
     * @param int $uid
529
     * @return int|null
530
     */
531 34
    protected function getValidatedPid(string $table, int $uid): ?int
532
    {
533 34
        $pid = $this->dataHandler->getPID($table, $uid);
534 34
        if ($pid === false) {
535 1
            $message = 'Record without valid pid was processed ' . $table . ':' . $uid;
536 1
            $this->logger->log(SolrLogManager::WARNING, $message);
537 1
            return null;
538
        }
539
540 33
        return (int)$pid;
541
    }
542
543
    /**
544
     * @return GarbageHandler
545
     */
546 6
    protected function getGarbageHandler(): GarbageHandler
547
    {
548 6
        return GeneralUtility::makeInstance(GarbageHandler::class);
549
    }
550
}
551