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

DataUpdateHandler::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 20
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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

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
    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