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

GarbageHandler::handlePageMovement()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 2
dl 0
loc 11
rs 10
c 1
b 0
f 0
cc 1
nc 1
nop 1
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\Backend\Utility\BackendUtility;
28
use ApacheSolrForTypo3\Solr\Domain\Index\Queue\GarbageRemover\StrategyFactory;
29
30
/**
31
 * Garbage handler
32
 *
33
 * Handles updates on potential relevant records and
34
 * collects the garbage, e.g. a deletion might require
35
 * index and index queue updates.
36
 */
37
class GarbageHandler extends AbstractUpdateHandler
38
{
39
    /**
40
     * Configuration used to check if recursive updates are required
41
     *
42
     * Holds the configuration when a recursive page queuing should be triggered, while processing record
43
     * updates
44
     *
45
     * Note: The SQL transaction is already committed, so the current state covers only "non"-changed fields.
46
     *
47
     * @var array
48
     */
49
    protected $updateSubPagesRecursiveTriggerConfiguration = [
50
        // the current page has the field "extendToSubpages" enabled and the field "hidden" was set to 1
51
        // covers following scenarios:
52
        //   'currentState' =>  ['hidden' => '0', 'extendToSubpages' => '0|1'], 'changeSet' => ['hidden' => '1', (optional)'extendToSubpages' => '1']
53
        'extendToSubpageEnabledAndHiddenFlagWasAdded' => [
54
            'currentState' =>  ['extendToSubpages' => '1'],
55
            'changeSet' => ['hidden' => '1']
56
        ],
57
        // the current page has the field "hidden" enabled and the field "extendToSubpages" was set to 1
58
        // covers following scenarios:
59
        //   'currentState' =>  ['hidden' => '0|1', 'extendToSubpages' => '0'], 'changeSet' => [(optional)'hidden' => '1', 'extendToSubpages' => '1']
60
        'hiddenIsEnabledAndExtendToSubPagesWasAdded' => [
61
            'currentState' =>  ['hidden' => '1'],
62
            'changeSet' => ['extendToSubpages' => '1']
63
        ],
64
        // the field "no_search_sub_entries" of current page was set to 1
65
        'no_search_sub_entriesFlagWasAdded' => [
66
            'changeSet' => ['no_search_sub_entries' => '1']
67
        ],
68
    ];
69
70
    /**
71
     * Tracks down index documents belonging to a particular record or page and
72
     * removes them from the index and the Index Queue.
73
     *
74
     * @param string $table The record's table name.
75
     * @param int $uid The record's uid.
76
     * @throws \UnexpectedValueException if a hook object does not implement interface
77
     *                                   \ApacheSolrForTypo3\Solr\GarbageCollectorPostProcessor
78
     */
79
    public function collectGarbage($table, $uid): void
80
    {
81
        $garbageRemoverStrategy = StrategyFactory::getByTable($table);
82
        $garbageRemoverStrategy->removeGarbageOf($table, $uid);
83
    }
84
85
    /**
86
     * Handles moved pages
87
     *
88
     * @param int $uid
89
     */
90
    public function handlePageMovement(int $uid): void
91
    {
92
        // TODO the below comment is not valid anymore, pid has been removed from doc ID
93
        // ...still needed?
94
95
        // must be removed from index since the pid changes and
96
        // is part of the Solr document ID
97
        $this->collectGarbage('pages', $uid);
98
99
        // now re-index with new properties
100
        $this->indexQueue->updateItem('pages', $uid);
101
    }
102
103
    /**
104
     * Performs a record garbage check
105
     *
106
     * @param int $uid
107
     * @param string $table
108
     * @param array $updatedFields
109
     * @param bool $frontendGroupsRemoved
110
     */
111
    public function performRecordGarbageCheck(
112
        int $uid,
113
        string $table,
114
        array $updatedFields,
115
        bool $frontendGroupsRemoved
116
    ): void {
117
        $record = $this->getRecordWithFieldRelevantForGarbageCollection($table, $uid);
118
119
        // If no record could be found skip further processing
120
        if (empty($record)) {
121
            return;
122
        }
123
124
        if ($table === 'pages') {
125
            $this->deleteSubEntriesWhenRecursiveTriggerIsRecognized($table, $uid, $updatedFields);
126
        }
127
128
        $record = $this->tcaService->normalizeFrontendGroupField($table, $record);
129
        $isGarbage = $this->getIsGarbageRecord($table, $record, $frontendGroupsRemoved);
130
        if (!$isGarbage) {
131
            return;
132
        }
133
134
        $this->collectGarbage($table, $uid);
135
    }
136
137
    /**
138
     * @param string $table
139
     * @param int $uid
140
     * @param array $updatedFields
141
     */
142
    protected function deleteSubEntriesWhenRecursiveTriggerIsRecognized(
143
        string $table,
144
        int $uid,
145
        array $updatedFields
146
    ): void {
147
        if (!$this->isRecursivePageUpdateRequired($uid, $updatedFields)) {
148
            return;
149
        }
150
151
        // get affected subpages when "extendToSubpages" flag was set
152
        $pagesToDelete = $this->getSubPageIds($uid);
153
        // we need to at least remove this page
154
        foreach ($pagesToDelete as $pageToDelete) {
155
            $this->collectGarbage($table, $pageToDelete);
156
        }
157
    }
158
159
    /**
160
     * Determines if a record is garbage and can be deleted.
161
     *
162
     * @param string $table
163
     * @param array $record
164
     * @param bool $frontendGroupsRemoved
165
     * @return bool
166
     */
167
    protected function getIsGarbageRecord(string $table, array $record, bool $frontendGroupsRemoved): bool
168
    {
169
        return $frontendGroupsRemoved
170
            || $this->tcaService->isHidden($table, $record)
171
            || $this->isInvisibleByStartOrEndtime($table, $record)
172
            || ($table === 'pages' && $this->isPageExcludedFromSearch($record))
173
            || ($table === 'pages' && !$this->isIndexablePageType($record));
174
    }
175
176
    /**
177
     * Checks whether a page has a page type that can be indexed.
178
     * Currently standard pages and mount pages can be indexed.
179
     *
180
     * @param array $record A page record
181
     * @return bool TRUE if the page can be indexed according to its page type, FALSE otherwise
182
     */
183
    protected function isIndexablePageType(array $record): bool
184
    {
185
        return $this->frontendEnvironment->isAllowedPageType($record);
186
    }
187
188
    /**
189
     * Checks whether the page has been excluded from searching.
190
     *
191
     * @param array $record An array with record fields that may affect visibility.
192
     * @return bool True if the page has been excluded from searching, FALSE otherwise
193
     */
194
    protected function isPageExcludedFromSearch(array $record): bool
195
    {
196
        return (bool)$record['no_search'];
197
    }
198
199
200
    /**
201
     * Check if a record is getting invisible due to changes in start or endtime. In addition it is checked that the related
202
     * queue item was marked as indexed.
203
     *
204
     * @param string $table
205
     * @param array $record
206
     * @return bool
207
     */
208
    protected function isInvisibleByStartOrEndtime(string $table, array $record): bool
209
    {
210
        return (
211
            ($this->tcaService->isStartTimeInFuture($table, $record)
212
                || $this->tcaService->isEndTimeInPast($table, $record))
213
            && $this->isRelatedQueueRecordMarkedAsIndexed($table, $record)
214
        );
215
    }
216
217
    /**
218
     * Checks if the related index queue item is indexed.
219
     *
220
     * * For tt_content the page from the pid is checked
221
     * * For all other records the table it's self is checked
222
     *
223
     * @param string $table The table name.
224
     * @param array $record An array with record fields that may affect visibility.
225
     * @return bool True if the record is marked as being indexed
226
     */
227
    protected function isRelatedQueueRecordMarkedAsIndexed(string $table, array $record): bool
228
    {
229
        if ($table === 'tt_content') {
230
            $table = 'pages';
231
            $uid = $record['pid'];
232
        } else {
233
            $uid = $record['uid'];
234
        }
235
236
        return $this->indexQueue->containsIndexedItem($table, $uid);
237
    }
238
239
    /**
240
     * Returns a record with all visibility affecting fields.
241
     *
242
     * @param string $table
243
     * @param int $uid
244
     * @return array
245
     */
246
    public function getRecordWithFieldRelevantForGarbageCollection(string $table, int $uid): array
247
    {
248
        $garbageCollectionRelevantFields = $this->tcaService->getVisibilityAffectingFieldsByTable($table);
249
        return (array)BackendUtility::getRecord($table, $uid, $garbageCollectionRelevantFields, '', false);
250
    }
251
}
252