Failed Conditions
Pull Request — release-11.2.x (#3154)
by Markus
64:06 queued 19:01
created

DataUpdateHandler::processRecord()   B

Complexity

Conditions 11
Paths 16

Size

Total Lines 41
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 21
dl 0
loc 41
rs 7.3166
c 1
b 0
f 0
cc 11
nc 16
nop 3

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php 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
    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
        parent::__construct($recordService, $frontendEnvironment, $tcaService, $indexQueue);
145
146
        $this->mountPageUpdater = $mountPageUpdater;
147
        $this->rootPageResolver = $rootPageResolver;
148
        $this->pagesRepository = $pagesRepository;
149
        $this->dataHandler = $dataHandler;
150
        $this->logger = $solrLogManager ?? GeneralUtility::makeInstance(
151
            SolrLogManager::class,
152
            /** @scrutinizer ignore-type */ __CLASS__
153
        );
154
    }
155
156
    /**
157
     * Handle content element update
158
     *
159
     * @param int $uid
160
     * @param array $updatedFields
161
     */
162
    public function handleContentElementUpdate(int $uid, array $updatedFields = []): void
163
    {
164
        $pid = $updatedFields['pid'] ?? $this->getValidatedPid('tt_content', $uid);
165
        if ($pid === null) {
166
            return;
167
        }
168
169
        $this->processPageRecord($pid, (int)$pid, $updatedFields);
170
    }
171
172
    /**
173
     * Handles the deletion of a content element
174
     *
175
     * @param int $uid
176
     */
177
    public function handleContentElementDeletion(int $uid): void
178
    {
179
        // @TODO: Should be checked, is possibly unnecessary as
180
        //        also done via GarbageCollector & PageStrategy
181
182
        $pid = $this->getValidatedPid('tt_content', $uid);
183
        if ($pid === null) {
184
            return;
185
        }
186
187
        $this->indexQueue->updateItem('pages', $pid, Util::getExectionTime());
188
    }
189
190
    /**
191
     * Handles page updates
192
     *
193
     * @param int $uid
194
     * @param array $updatedFields
195
     */
196
    public function handlePageUpdate(int $uid, array $updatedFields = []): void
197
    {
198
        try {
199
            if (isset($updatedFields['l10n_parent']) && intval($updatedFields['l10n_parent']) > 0) {
200
                $pid = $updatedFields['l10n_parent'];
201
            } elseif ($this->rootPageResolver->getIsRootPageId($uid)) {
202
                $pid = $uid;
203
            } else {
204
                $pid = $updatedFields['pid'] ?? $this->getValidatedPid('pages', $uid);
205
            }
206
        } catch (\Throwable $e) {
207
            $pid = null;
208
        }
209
210
        if ($pid === null) {
211
            $this->removeFromIndexAndQueueWhenItemInQueue('pages', $uid);
212
            return;
213
        }
214
215
        $this->processPageRecord($uid, (int)$pid, $updatedFields);
216
    }
217
218
    /**
219
     * Handles record updates
220
     *
221
     * @param int $uid
222
     * @param string $table
223
     */
224
    public function handleRecordUpdate(int $uid, string $table): void
225
    {
226
        $rootPageIds = $this->getRecordRootPageIds($table, $uid);
227
        $this->processRecord($table, $uid, $rootPageIds);
228
    }
229
230
    /**
231
     * Handles a version swap
232
     *
233
     * @param int $uid
234
     * @param string $table
235
     */
236
    public function handleVersionSwap(int $uid, string $table): void
237
    {
238
        $isPageRelatedRecord = ($table === 'tt_content' || $table === 'pages');
239
        if($isPageRelatedRecord) {
240
            $uid = ($table === 'tt_content' ? $this->getValidatedPid($table, $uid) : $uid);
241
            if ($uid === null) {
242
                return;
243
            }
244
            $this->applyPageChangesToQueue($uid);
245
        } else {
246
            $recordPageId = $this->getValidatedPid($table, $uid);
247
            if ($recordPageId === null) {
248
                return;
249
            }
250
            $this->applyRecordChangesToQueue($table, $uid, $recordPageId);
251
        }
252
    }
253
254
    /**
255
     * Handle page move
256
     *
257
     * @param int $uid
258
     */
259
    public function handleMovedPage(int $uid): void
260
    {
261
        $this->applyPageChangesToQueue($uid);
262
    }
263
264
    /**
265
     * Handle record move
266
     *
267
     * @param int $uid
268
     * @param string $table
269
     */
270
    public function handleMovedRecord(int $uid, string $table): void
271
    {
272
        $pid = $this->getValidatedPid($table, $uid);
273
        if ($pid === null) {
274
            return;
275
        }
276
277
        $this->applyRecordChangesToQueue($table, $uid, $pid);
278
    }
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
    protected function applyPageChangesToQueue(int $uid): void
287
    {
288
        $solrConfiguration = $this->getSolrConfigurationFromPageId($uid);
289
        $record = $this->configurationAwareRecordService->getRecord('pages', $uid, $solrConfiguration);
290
        if (!empty($record) && $this->tcaService->isEnabledRecord('pages', $record)) {
291
            $this->mountPageUpdater->update($uid);
292
            $this->indexQueue->updateItem('pages', $uid);
293
        } else {
294
            $this->removeFromIndexAndQueueWhenItemInQueue('pages', $uid);
295
        }
296
    }
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
    protected function applyRecordChangesToQueue(string $table, int $uid, int $pid): void
306
    {
307
        $solrConfiguration = $this->getSolrConfigurationFromPageId($pid);
308
        $isMonitoredTable = $solrConfiguration->getIndexQueueIsMonitoredTable($table);
309
310
        if ($isMonitoredTable) {
311
            $record = $this->configurationAwareRecordService->getRecord($table, $uid, $solrConfiguration);
312
313
            if (!empty($record) && $this->tcaService->isEnabledRecord($table, $record)) {
314
                $uid = $this->tcaService->getTranslationOriginalUidIfTranslated($table, $record, $uid);
315
                $this->indexQueue->updateItem($table, $uid);
316
            } else {
317
                // TODO should be moved to garbage collector
318
                $this->removeFromIndexAndQueueWhenItemInQueue($table, $uid);
319
            }
320
        }
321
    }
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
    protected function removeFromIndexAndQueue(string $recordTable, int $recordUid): void
330
    {
331
        $this->getGarbageHandler()->collectGarbage($recordTable, $recordUid);
332
    }
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
    protected function removeFromIndexAndQueueWhenItemInQueue(string $recordTable, int $recordUid): void
341
    {
342
        if (!$this->indexQueue->containsItem($recordTable, $recordUid)) {
343
            return;
344
        }
345
346
        $this->removeFromIndexAndQueue($recordTable, $recordUid);
347
    }
348
349
    /**
350
     * @param $pageId
351
     * @return TypoScriptConfiguration
352
     */
353
    protected function getSolrConfigurationFromPageId(int $pageId): TypoScriptConfiguration
354
    {
355
        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
    protected function getRecordRootPageIds(string $recordTable, int $recordUid): array
366
    {
367
        try {
368
            $rootPageIds = $this->rootPageResolver->getResponsibleRootPageIds($recordTable, $recordUid);
369
        } catch (\InvalidArgumentException $e) {
370
            $rootPageIds = [];
371
        }
372
373
        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
    protected function processPageRecord(int $uid, int $pid, array $updatedFields = []): void
387
    {
388
        $configurationPageId = $this->getConfigurationPageId('pages', (int)$pid, $uid);
389
        if ($configurationPageId === 0) {
390
            $this->mountPageUpdater->update($uid);
391
            return;
392
        }
393
        $rootPageIds = [$configurationPageId];
394
395
        $this->processRecord('pages', $uid, $rootPageIds);
396
397
        $this->updateCanonicalPages($uid);
398
        $this->mountPageUpdater->update($uid);
399
400
        $recursiveUpdateRequired = $this->isRecursivePageUpdateRequired($uid, $updatedFields);
401
        if ($recursiveUpdateRequired) {
402
            $treePageIds = $this->getSubPageIds($uid);
403
            $this->updatePageIdItems($treePageIds);
404
        }
405
    }
406
407
    /**
408
     * Process a record
409
     *
410
     * @param string $recordTable
411
     * @param int $recordUid
412
     * @param array $rootPageIds
413
     */
414
    protected function processRecord(string $recordTable, int $recordUid, array $rootPageIds): void
415
    {
416
        if (empty($rootPageIds)) {
417
            $this->removeFromIndexAndQueueWhenItemInQueue($recordTable, $recordUid);
418
            return;
419
        }
420
421
        foreach ($rootPageIds as $configurationPageId) {
422
            $solrConfiguration = $this->getSolrConfigurationFromPageId($configurationPageId);
423
            $isMonitoredRecord = $solrConfiguration->getIndexQueueIsMonitoredTable($recordTable);
424
            if (!$isMonitoredRecord) {
425
                // when it is a non monitored record, we can skip it.
426
                continue;
427
            }
428
429
            $record = $this->configurationAwareRecordService->getRecord($recordTable, $recordUid, $solrConfiguration);
430
            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
                $this->removeFromIndexAndQueueWhenItemInQueue($recordTable, $recordUid);
434
                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
            $isTranslation = !empty($record['sys_language_uid']) && $record['sys_language_uid'] !== 0;
440
            if ($recordTable === 'pages' && !$isTranslation) {
441
                $this->indexQueue->deleteItem('pages', $recordUid);
442
            }
443
444
            // only update/insert the item if we actually found a record
445
            $isLocalizedRecord = $this->tcaService->isLocalizedRecord($recordTable, $record);
446
            $recordUid = $this->tcaService->getTranslationOriginalUidIfTranslated($recordTable, $record, $recordUid);
447
448
            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
            if ($this->tcaService->isEnabledRecord($recordTable, $record)) {
454
                $this->indexQueue->updateItem($recordTable, $recordUid);
455
            }
456
        }
457
    }
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
    protected function getConfigurationPageId(string $recordTable, int $recordPageId, int $recordUid): int
468
    {
469
        $rootPageId = $this->rootPageResolver->getRootPageId($recordPageId);
470
        if ($this->rootPageResolver->getIsRootPageId($rootPageId)) {
471
            return $recordPageId;
472
        }
473
474
        $alternativeSiteRoots = $this->rootPageResolver->getAlternativeSiteRootPagesIds(
475
            $recordTable,
476
            $recordUid,
477
            $recordPageId
478
        );
479
        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
    protected function getIsTranslationParentRecordEnabled(string $recordTable, int $recordUid): bool
490
    {
491
        $l10nParentRecord = (array)BackendUtility::getRecord($recordTable, $recordUid, '*', '', false);
492
        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
    protected function updatePageIdItems(array $treePageIds): void
501
    {
502
        foreach ($treePageIds as $treePageId) {
503
            $this->indexQueue->updateItem('pages', $treePageId);
504
        }
505
    }
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
    protected function updateCanonicalPages(int $pageId): void
514
    {
515
        $canonicalPages = $this->pagesRepository->findPageUidsWithContentsFromPid((int)$pageId);
516
        foreach ($canonicalPages as $page) {
517
            $this->indexQueue->updateItem('pages', $page['uid']);
518
        }
519
    }
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
    protected function getValidatedPid(string $table, int $uid): ?int
529
    {
530
        $pid = $this->dataHandler->getPID($table, $uid);
531
        if ($pid === false) {
532
            $message = 'Record without valid pid was processed ' . $table . ':' . $uid;
533
            $this->logger->log(SolrLogManager::WARNING, $message);
534
            return null;
535
        }
536
537
        return (int)$pid;
538
    }
539
540
    /**
541
     * @return GarbageHandler
542
     */
543
    protected function getGarbageHandler(): GarbageHandler
544
    {
545
        return GeneralUtility::makeInstance(GarbageHandler::class);
546
    }
547
}
548