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

isRecursivePageUpdateRequired()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 30
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

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

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