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

GarbageHandler::getIsGarbageRecord()   B

Complexity

Conditions 7
Paths 15

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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

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

258
                ->where(/** @scrutinizer ignore-type */ $queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)))
Loading history...
259
                ->executeQuery()
260
                ->fetchAssociative();
261
        } catch (\Throwable $e) {
262
            $row = false;
263
        }
264
265
        return is_array($row) ? $row : null;
266
    }
267
}
268