Passed
Push — task/2976_TYPO3.11_compatibili... ( 803400...b598e4 )
by Rafael
33:58 queued 15:41
created

processDatamap_afterDatabaseOperations()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 25
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 4.0466

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 15
dl 0
loc 25
ccs 12
cts 14
cp 0.8571
rs 9.7666
c 1
b 0
f 0
cc 4
nc 4
nop 5
crap 4.0466
1
<?php
2
namespace ApacheSolrForTypo3\Solr;
3
4
/***************************************************************
5
 *  Copyright notice
6
 *
7
 *  (c) 2010-2015 Ingo Renner <[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 Psr\EventDispatcher\EventDispatcherInterface;
28
use ApacheSolrForTypo3\Solr\System\TCA\TCAService;
29
use TYPO3\CMS\Core\DataHandling\DataHandler;
30
use TYPO3\CMS\Core\SingletonInterface;
31
use TYPO3\CMS\Core\Utility\GeneralUtility;
32
use ApacheSolrForTypo3\Solr\Domain\Index\Queue\UpdateHandler\GarbageHandler;
33
use ApacheSolrForTypo3\Solr\Domain\Index\Queue\UpdateHandler\Events\RecordDeletedEvent;
34
use ApacheSolrForTypo3\Solr\Domain\Index\Queue\UpdateHandler\Events\PageMovedEvent;
35
use ApacheSolrForTypo3\Solr\Domain\Index\Queue\UpdateHandler\Events\RecordGarbageCheckEvent;
36
37
/**
38
 * Garbage Collector, removes related documents from the index when a record is
39
 * set to hidden, is deleted or is otherwise made invisible to website visitors.
40
 *
41
 * Garbage collection will happen for online/LIVE workspaces only.
42
 *
43
 * @author Ingo Renner <[email protected]>
44
 * @author Timo Schmidt <[email protected]>
45
 */
46
class GarbageCollector implements SingletonInterface
47
{
48
    /**
49
     * @var array
50
     */
51
    protected $trackedRecords = [];
52
53
    /**
54
     * @var TCAService
55
     */
56
    protected $tcaService;
57
58
    /**
59
     * @var EventDispatcherInterface
60
     */
61
    protected $eventDispatcher;
62
63
    /**
64
     * GarbageCollector constructor.
65
     *
66
     * @param TCAService|null $TCAService
67
     * @param EventDispatcherInterface|null $eventDispatcher
68
     */
69 32
    public function __construct(TCAService $TCAService = null, EventDispatcherInterface $eventDispatcher = null)
70
    {
71 32
        $this->tcaService = $TCAService ?? GeneralUtility::makeInstance(TCAService::class);
72 32
        $this->eventDispatcher = $eventDispatcher ?? GeneralUtility::makeInstance(EventDispatcherInterface::class);
73 32
    }
74
75
    /**
76
     * Hooks into TCE main and tracks record deletion commands.
77
     *
78
     * @param string $command The command.
79
     * @param string $table The table the record belongs to
80
     * @param int $uid The record's uid
81
     * @param string $value Not used
82
     * @param DataHandler $tceMain TYPO3 Core Engine parent object, not used
83
     */
84 8
    public function processCmdmap_preProcess($command, $table, $uid, $value, DataHandler $tceMain): void
0 ignored issues
show
Unused Code introduced by
The parameter $tceMain is not used and could be removed. ( Ignorable by Annotation )

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

84
    public function processCmdmap_preProcess($command, $table, $uid, $value, /** @scrutinizer ignore-unused */ DataHandler $tceMain): void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $value is not used and could be removed. ( Ignorable by Annotation )

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

84
    public function processCmdmap_preProcess($command, $table, $uid, /** @scrutinizer ignore-unused */ $value, DataHandler $tceMain): void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
85
    {
86
        // workspaces: collect garbage only for LIVE workspace
87 8
        if ($command === 'delete' && ($GLOBALS['BE_USER']->workspace ?? null) == 0) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $GLOBALS['BE_USER']->workspace ?? null of type mixed|null to 0; this is ambiguous as not only 0 == 0 is true, but null == 0 is true, too. Consider using a strict comparison ===.
Loading history...
88 7
            $this->eventDispatcher->dispatch(
89 7
                new RecordDeletedEvent((int)$uid, (string)$table)
90
            );
91
        }
92 8
    }
93
94
    /**
95
     * Tracks down index documents belonging to a particular record or page and
96
     * removes them from the index and the Index Queue.
97
     *
98
     * @param string $table The record's table name.
99
     * @param int $uid The record's uid.
100
     * @throws \UnexpectedValueException if a hook object does not implement interface \ApacheSolrForTypo3\Solr\GarbageCollectorPostProcessor
101
     */
102 2
    public function collectGarbage($table, $uid): void
103
    {
104 2
        $this->getGarbageHandler()->collectGarbage((string)$table, (int)$uid);
105 2
    }
106
107
    /**
108
     * Hooks into TCE main and tracks page move commands.
109
     *
110
     * @param string $command The command.
111
     * @param string $table The table the record belongs to
112
     * @param int $uid The record's uid
113
     * @param string $value Not used
114
     * @param DataHandler $tceMain TYPO3 Core Engine parent object, not used
115
     */
116 8
    public function processCmdmap_postProcess($command, $table, $uid, $value, DataHandler $tceMain) {
0 ignored issues
show
Unused Code introduced by
The parameter $tceMain is not used and could be removed. ( Ignorable by Annotation )

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

116
    public function processCmdmap_postProcess($command, $table, $uid, $value, /** @scrutinizer ignore-unused */ DataHandler $tceMain) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $value is not used and could be removed. ( Ignorable by Annotation )

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

116
    public function processCmdmap_postProcess($command, $table, $uid, /** @scrutinizer ignore-unused */ $value, DataHandler $tceMain) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
117
        // workspaces: collect garbage only for LIVE workspace
118 8
        if ($command === 'move' && $table === 'pages' && ($GLOBALS['BE_USER']->workspace ?? null) == 0) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $GLOBALS['BE_USER']->workspace ?? null of type mixed|null to 0; this is ambiguous as not only 0 == 0 is true, but null == 0 is true, too. Consider using a strict comparison ===.
Loading history...
119 1
            $this->eventDispatcher->dispatch(
120 1
                new PageMovedEvent((int)$uid)
121
            );
122
        }
123 8
    }
124
125
    /**
126
     * Hooks into TCE main and tracks changed records. In this case the current
127
     * record's values are stored to do a change comparison later on for fields
128
     * like fe_group.
129
     *
130
     * @param array $incomingFields An array of incoming fields, new or changed, not used
131
     * @param string $table The table the record belongs to
132
     * @param mixed $uid The record's uid, [integer] or [string] (like 'NEW...')
133
     * @param DataHandler $tceMain TYPO3 Core Engine parent object, not used
134
     */
135 13
    public function processDatamap_preProcessFieldArray($incomingFields, $table, $uid, DataHandler $tceMain): void
0 ignored issues
show
Unused Code introduced by
The parameter $incomingFields is not used and could be removed. ( Ignorable by Annotation )

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

135
    public function processDatamap_preProcessFieldArray(/** @scrutinizer ignore-unused */ $incomingFields, $table, $uid, DataHandler $tceMain): void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $tceMain is not used and could be removed. ( Ignorable by Annotation )

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

135
    public function processDatamap_preProcessFieldArray($incomingFields, $table, $uid, /** @scrutinizer ignore-unused */ DataHandler $tceMain): void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
136
    {
137 13
        if (!is_int($uid)) {
138
            // a newly created record, skip
139 2
            return;
140
        }
141
142 12
        $uid = (int)$uid;
143 12
        $table = (string)$table;
144 12
        if (Util::isDraftRecord($table, $uid)) {
145
            // skip workspaces: collect garbage only for LIVE workspace
146
            return;
147
        }
148
149 12
        $hasConfiguredEnableColumnForFeGroup = $this->tcaService->isEnableColumn($table, 'fe_group');
150 12
        if (!$hasConfiguredEnableColumnForFeGroup) {
151
            return;
152
        }
153
154 12
        $record = $this->getGarbageHandler()->getRecordWithFieldRelevantForGarbageCollection($table, $uid);
155
        // If no record could be found skip further processing
156 12
        if (empty($record)) {
157
            return;
158
        }
159
160 12
        $record = $this->tcaService->normalizeFrontendGroupField($table, $record);
161
162
        // keep previous state of important fields for later comparison
163 12
        $this->trackedRecords[$table][$uid] = $record;
164 12
    }
165
166
    /**
167
     * Hooks into TCE Main and watches all record updates. If a change is
168
     * detected that would remove the record from the website, we try to find
169
     * related documents and remove them from the index.
170
     *
171
     * @param string $status Status of the current operation, 'new' or 'update'
172
     * @param string $table The table the record belongs to
173
     * @param mixed $uid The record's uid, [integer] or [string] (like 'NEW...')
174
     * @param array $fields The record's data, not used
175
     * @param DataHandler $tceMain TYPO3 Core Engine parent object, not used
176
     */
177 19
    public function processDatamap_afterDatabaseOperations($status, $table, $uid, array $fields, DataHandler $tceMain): void
0 ignored issues
show
Unused Code introduced by
The parameter $tceMain is not used and could be removed. ( Ignorable by Annotation )

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

177
    public function processDatamap_afterDatabaseOperations($status, $table, $uid, array $fields, /** @scrutinizer ignore-unused */ DataHandler $tceMain): void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
178
    {
179 19
        if ($status === 'new') {
180
            // a newly created record, skip
181 2
            return;
182
        }
183
184 18
        $uid = (int)$uid;
185 18
        $table = (string)$table;
186 18
        if (Util::isDraftRecord($table, $uid)) {
187
            // skip workspaces: collect garbage only for LIVE workspace
188
            return;
189
        }
190
191 18
        $updatedRecord = $this->getGarbageHandler()->getRecordWithFieldRelevantForGarbageCollection($table, $uid);
192 18
        if (empty($updatedRecord)) {
193
            return;
194
        }
195
196 18
        $this->eventDispatcher->dispatch(
197 18
            new RecordGarbageCheckEvent(
198 18
                $uid,
199
                $table,
200
                $fields,
201 18
                $this->hasFrontendGroupsRemoved($table, $updatedRecord)
202
            )
203
        );
204 18
    }
205
206
    /**
207
     * Checks whether the a frontend group field exists for the record and if so
208
     * whether groups have been removed from accessing the record thus making
209
     * the record invisible to at least some people.
210
     *
211
     * @param string $table The table name.
212
     * @param array $updatedRecord An array with fields of the updated record that may affect visibility.
213
     * @return bool TRUE if frontend groups have been removed from access to the record, FALSE otherwise.
214
     */
215 18
    protected function hasFrontendGroupsRemoved(string $table, array $updatedRecord): bool
216
    {
217 18
        if (!isset($GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['fe_group'])) {
218
            return false;
219
        }
220
221 18
        $frontendGroupsField = $GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['fe_group'];
222
223 18
        $previousGroups = explode(',', (string)$this->trackedRecords[$table][$updatedRecord['uid']][$frontendGroupsField]);
224 18
        $currentGroups = explode(',', (string)$updatedRecord[$frontendGroupsField]);
225 18
        $removedGroups = array_diff($previousGroups, $currentGroups);
226
227 18
        return !empty($removedGroups);
228
    }
229
230
    /**
231
     * Returns the GarbageHandler
232
     *
233
     * @return GarbageHandler
234
     */
235 21
    protected function getGarbageHandler(): GarbageHandler
236
    {
237 21
        return GeneralUtility::makeInstance(GarbageHandler::class);
238
    }
239
}
240