Passed
Pull Request — release-11.5.x (#3193)
by Markus
41:15
created

AbstractUpdateHandler::isRecursiveUpdateRequired()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 10
dl 0
loc 16
rs 9.9332
c 0
b 0
f 0
cc 4
nc 5
nop 2
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 Doctrine\DBAL\Driver\Exception as DBALDriverException;
28
use TYPO3\CMS\Core\Utility\GeneralUtility;
29
use TYPO3\CMS\Core\Database\QueryGenerator;
30
use TYPO3\CMS\Backend\Utility\BackendUtility;
31
use ApacheSolrForTypo3\Solr\IndexQueue\Queue;
32
use ApacheSolrForTypo3\Solr\Domain\Index\Queue\RecordMonitor\Helper\ConfigurationAwareRecordService;
33
use ApacheSolrForTypo3\Solr\FrontendEnvironment;
34
use ApacheSolrForTypo3\Solr\System\TCA\TCAService;
35
36
/**
37
 * Abstract update handler
38
 *
39
 * Base class for Handling updates or deletions on potential
40
 * relevant records
41
 */
42
abstract class AbstractUpdateHandler
43
{
44
    /**
45
     * List of fields in the update field array that
46
     * are required for processing
47
     *
48
     * Note: For pages all fields except l10n_diffsource are
49
     *       kept, as additional fields can be configured in
50
     *       TypoScript, see AbstractDataUpdateEvent->__sleep.
51
     *
52
     * @var array
53
     */
54
    protected static $requiredUpdatedFields = [];
55
56
    /**
57
     * Configuration used to check if recursive updates are required
58
     *
59
     * Update handlers may need to determine which update combination
60
     * require a recursive change.
61
     *
62
     * The structure needs to be:
63
     *
64
     * [
65
     *      [
66
     *           'currentState' => ['fieldName1' => 'value1'],
67
     *           'changeSet' => ['fieldName1' => 'value1']
68
     *      ]
69
     * ]
70
     *
71
     * When the all values of the currentState AND all values of the changeSet match, a recursive update
72
     * will be triggered.
73
     *
74
     * @var array
75
     */
76
    protected $updateSubPagesRecursiveTriggerConfiguration = [];
77
    /**
78
     * @var ConfigurationAwareRecordService
79
     */
80
    protected $configurationAwareRecordService = null;
81
82
    /**
83
     * @var FrontendEnvironment
84
     */
85
    protected $frontendEnvironment = null;
86
87
    /**
88
     * @var TCAService
89
     */
90
    protected $tcaService = null;
91
92
    /**
93
     * @var Queue
94
     */
95
    protected $indexQueue;
96
97
    /**
98
     * @param ConfigurationAwareRecordService $recordService
99
     * @param FrontendEnvironment $frontendEnvironment
100
     * @param TCAService $tcaService
101
     */
102
    public function __construct(
103
        ConfigurationAwareRecordService $recordService,
104
        FrontendEnvironment $frontendEnvironment,
105
        TCAService $tcaService,
106
        Queue $indexQueue
107
        ) {
108
            $this->configurationAwareRecordService = $recordService;
109
            $this->frontendEnvironment = $frontendEnvironment;
110
            $this->tcaService = $tcaService;
111
            $this->indexQueue = $indexQueue;
112
    }
113
114
    /**
115
     * Returns the required fields from the updated fields array
116
     *
117
     * @return array
118
     */
119
    public static function getRequiredUpdatedFields(): array
120
    {
121
        return static::$requiredUpdatedFields;
122
    }
123
124
    /**
125
     * Add required update field
126
     *
127
     * @param string $field
128
     */
129
    public static function addRequiredUpdatedField(string $field): void
130
    {
131
        static::$requiredUpdatedFields[] = $field;
132
    }
133
134
    /**
135
     * @return array
136
     */
137
    protected function getAllRelevantFieldsForCurrentState(): array
138
    {
139
        $allCurrentStateFieldnames = [];
140
141
        foreach ($this->getUpdateSubPagesRecursiveTriggerConfiguration() as $triggerConfiguration) {
142
            if (!isset($triggerConfiguration['currentState']) || !is_array($triggerConfiguration['currentState'])) {
143
                // when no "currentState" configuration for the trigger exists we can skip it
144
                continue;
145
            }
146
147
            // we collect the currentState fields to return a unique list of all fields
148
            $allCurrentStateFieldnames = array_merge(
149
                $allCurrentStateFieldnames,
150
                array_keys($triggerConfiguration['currentState'])
151
                );
152
        }
153
154
        return array_unique($allCurrentStateFieldnames);
155
    }
156
157
    /**
158
     * When the extend to subpages flag was set, we determine the affected subpages and return them.
159
     *
160
     * @param int $pageId
161
     * @return array
162
     */
163
    protected function getSubPageIds(int $pageId): array
164
    {
165
        // here we retrieve only the subpages of this page because the permission clause is not evaluated
166
        // on the root node.
167
        $permissionClause = ' 1 ' . BackendUtility::BEenableFields('pages');
168
        $treePageIdList = (string)$this->getQueryGenerator()->getTreeList($pageId, 20, 0, $permissionClause);
169
        $treePageIds = array_map('intval', explode(',', $treePageIdList));
170
171
        // the first one can be ignored because this is the page itself
172
        array_shift($treePageIds);
173
174
        return $treePageIds;
175
    }
176
177
    /**
178
     * Checks if a page update will trigger a recursive update of pages
179
     *
180
     * This can either be the case if some $changedFields are part of the RecursiveUpdateTriggerConfiguration or
181
     * columns have explicitly been configured via plugin.tx_solr.index.queue.recursiveUpdateFields
182
     *
183
     * @param int $pageId
184
     * @param array $updatedFields
185
     * @return bool
186
     * @throws DBALDriverException
187
     */
188
    protected function isRecursivePageUpdateRequired(int $pageId, array $updatedFields): bool
189
    {
190
        // First check RecursiveUpdateTriggerConfiguration
191
        $isRecursiveUpdateRequired = $this->isRecursiveUpdateRequired($pageId, $updatedFields);
192
        // If RecursiveUpdateTriggerConfiguration is false => check if changeFields are part of recursiveUpdateFields
193
        if ($isRecursiveUpdateRequired === false) {
194
            $solrConfiguration = $this->frontendEnvironment->getSolrConfigurationFromPageId($pageId);
195
            $indexQueueConfigurationName = $this->configurationAwareRecordService->getIndexingConfigurationName(
196
                'pages',
197
                $pageId,
198
                $solrConfiguration
199
                );
200
            if ($indexQueueConfigurationName === null) {
201
                return false;
202
            }
203
            $updateFields = $solrConfiguration->getIndexQueueConfigurationRecursiveUpdateFields(
204
                $indexQueueConfigurationName
205
                );
206
207
            // Check if no additional fields have been defined and then skip recursive update
208
            if (empty($updateFields)) {
209
                return false;
210
            }
211
            // If the recursiveUpdateFields configuration is not part of the $changedFields skip recursive update
212
            if (!array_intersect_key($updatedFields, $updateFields)) {
213
                return false;
214
            }
215
        }
216
217
        return true;
218
    }
219
220
    /**
221
     * @param int $pageId
222
     * @param array $updatedFields
223
     * @return bool
224
     */
225
    protected function isRecursiveUpdateRequired(int $pageId, array $updatedFields): bool
226
    {
227
        $fieldsForCurrentState = $this->getAllRelevantFieldsForCurrentState();
228
        $fieldListToRetrieve = implode(',', $fieldsForCurrentState);
229
        $page = BackendUtility::getRecord('pages', $pageId, $fieldListToRetrieve, '', false);
230
        foreach ($this->getUpdateSubPagesRecursiveTriggerConfiguration() as $triggerConfiguration) {
231
            $allCurrentStateFieldsMatch = $this->getAllCurrentStateFieldsMatch($triggerConfiguration, $page);
0 ignored issues
show
Bug introduced by
It seems like $page can also be of type null; however, parameter $pageRecord of ApacheSolrForTypo3\Solr\...rrentStateFieldsMatch() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

231
            $allCurrentStateFieldsMatch = $this->getAllCurrentStateFieldsMatch($triggerConfiguration, /** @scrutinizer ignore-type */ $page);
Loading history...
232
            $allChangeSetValuesMatch = $this->getAllChangeSetValuesMatch($triggerConfiguration, $updatedFields);
233
234
            $aMatchingTriggerHasBeenFound = $allCurrentStateFieldsMatch && $allChangeSetValuesMatch;
235
            if ($aMatchingTriggerHasBeenFound) {
236
                return true;
237
            }
238
        }
239
240
        return false;
241
    }
242
243
    /**
244
     * @param array $triggerConfiguration
245
     * @param array $pageRecord
246
     * @return bool
247
     */
248
    protected function getAllCurrentStateFieldsMatch(array $triggerConfiguration, array $pageRecord): bool
249
    {
250
        $triggerConfigurationHasNoCurrentStateConfiguration = !array_key_exists('currentState', $triggerConfiguration);
251
        if ($triggerConfigurationHasNoCurrentStateConfiguration) {
252
            return true;
253
        }
254
        $diff = array_diff_assoc($triggerConfiguration['currentState'], $pageRecord);
255
        return empty($diff);
256
    }
257
258
    /**
259
     * @param array $triggerConfiguration
260
     * @param array $changedFields
261
     * @return bool
262
     */
263
    protected function getAllChangeSetValuesMatch(array $triggerConfiguration, array $changedFields): bool
264
    {
265
        $triggerConfigurationHasNoChangeSetStateConfiguration = !array_key_exists('changeSet', $triggerConfiguration);
266
        if ($triggerConfigurationHasNoChangeSetStateConfiguration) {
267
            return true;
268
        }
269
270
        $diff = array_diff_assoc($triggerConfiguration['changeSet'], $changedFields);
271
        return empty($diff);
272
    }
273
274
    /**
275
     * The implementation of this method need to retrieve a configuration to determine which record data
276
     * and change combination required a recursive change.
277
     *
278
     * The structure needs to be:
279
     *
280
     * [
281
     *      [
282
     *           'currentState' => ['fieldName1' => 'value1'],
283
     *           'changeSet' => ['fieldName1' => 'value1']
284
     *      ]
285
     * ]
286
     *
287
     * When the all values of the currentState AND all values of the changeSet match, a recursive update
288
     * will be triggered.
289
     *
290
     * @return array
291
     */
292
    protected function getUpdateSubPagesRecursiveTriggerConfiguration(): array
293
    {
294
        return $this->updateSubPagesRecursiveTriggerConfiguration;
295
    }
296
297
    /**
298
     * @return QueryGenerator
299
     */
300
    protected function getQueryGenerator(): QueryGenerator
301
    {
302
        return GeneralUtility::makeInstance(QueryGenerator::class);
303
    }
304
}
305