Passed
Push — master ( bd1cec...16f071 )
by Timo
22:19
created

GarbageCollector::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1.0156

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 3
cts 4
cp 0.75
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
crap 1.0156
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 ApacheSolrForTypo3\Solr\IndexQueue\Queue;
28
use ApacheSolrForTypo3\Solr\System\TCA\TCAService;
29
use TYPO3\CMS\Backend\Utility\BackendUtility;
30
use TYPO3\CMS\Core\DataHandling\DataHandler;
31
use TYPO3\CMS\Core\SingletonInterface;
32
use TYPO3\CMS\Core\Utility\GeneralUtility;
33
34
/**
35
 * Garbage Collector, removes related documents from the index when a record is
36
 * set to hidden, is deleted or is otherwise made invisible to website visitors.
37
 *
38
 * Garbage collection will happen for online/LIVE workspaces only.
39
 *
40
 * @author Ingo Renner <[email protected]>
41
 * @author Timo Schmidt <[email protected]>
42
 */
43
class GarbageCollector extends AbstractDataHandlerListener implements SingletonInterface
44
{
45
    /**
46
     * @var array
47
     */
48
    protected $trackedRecords = [];
49
50
    /**
51
     * @var TCAService
52
     */
53
    protected $tcaService;
54
55
    /**
56
     * GarbageCollector constructor.
57
     * @param TCAService|null $TCAService
58
     */
59 11
    public function __construct(TCAService $TCAService = null)
60
    {
61 11
        parent::__construct();
62 11
        $this->tcaService = $TCAService ?? GeneralUtility::makeInstance(TCAService::class);
63 11
    }
64
65
    /**
66
     * Hooks into TCE main and tracks record deletion commands.
67
     *
68
     * @param string $command The command.
69
     * @param string $table The table the record belongs to
70
     * @param int $uid The record's uid
71
     * @param string $value Not used
72
     * @param DataHandler $tceMain TYPO3 Core Engine parent object, not used
73
     * @return void
74
     */
75 2
    public function processCmdmap_preProcess(
76
        $command,
77
        $table,
78
        $uid,
79
        /** @noinspection PhpUnusedParameterInspection */
80
        $value,
0 ignored issues
show
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

80
        /** @scrutinizer ignore-unused */ $value,

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...
81
        /** @noinspection PhpUnusedParameterInspection */
82
        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

82
        /** @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...
83
    ) {
84
        // workspaces: collect garbage only for LIVE workspace
85 2
        if ($command === 'delete' && $GLOBALS['BE_USER']->workspace == 0) {
86 2
            $this->collectGarbage($table, $uid);
87
88 2
            if ($table === 'pages') {
89 1
                $this->getIndexQueue()->deleteItem($table, $uid);
90
            }
91
        }
92 2
    }
93
94
    /**
95
     * Holds the configuration when a recursive page queing should be triggered.
96
     *
97
     * @var array
98
     * @return array
99
     */
100 3
    protected function getUpdateSubPagesRecursiveTriggerConfiguration()
101
    {
102
        return [
103
            // the current page has the field "extendToSubpages" enabled and the field "hidden" was set to 1
104 3
            'extendToSubpageEnabledAndHiddenFlagWasAdded' => [
105
                'currentState' =>  ['extendToSubpages' => '1'],
106
                'changeSet' => ['hidden' => '1']
107
            ],
108
            // the current page has the field "hidden" enabled and the field "extendToSubpages" was set to 1
109
            'hiddenIsEnabledAndExtendToSubPagesWasAdded' => [
110
                'currentState' =>  ['hidden' => '1'],
111
                'changeSet' => ['extendToSubpages' => '1']
112
            ]
113
        ];
114
    }
115
116
    /**
117
     * Tracks down index documents belonging to a particular record or page and
118
     * removes them from the index and the Index Queue.
119
     *
120
     * @param string $table The record's table name.
121
     * @param int $uid The record's uid.
122
     * @throws \UnexpectedValueException if a hook object does not implement interface \ApacheSolrForTypo3\Solr\GarbageCollectorPostProcessor
123
     */
124 10
    public function collectGarbage($table, $uid)
125
    {
126 10
        if ($table === 'tt_content' || $table === 'pages' || $table === 'pages_language_overlay') {
127 10
            $this->collectPageGarbage($table, $uid);
128
        } else {
129
            $this->collectRecordGarbage($table, $uid);
130
        }
131
132 10
        if (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['postProcessGarbageCollector'])) {
133
            foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['postProcessGarbageCollector'] as $classReference) {
134
                $garbageCollectorPostProcessor = GeneralUtility::makeInstance($classReference);
135
136
                if ($garbageCollectorPostProcessor instanceof GarbageCollectorPostProcessor) {
137
                    $garbageCollectorPostProcessor->postProcessGarbageCollector($table,
138
                        $uid);
139
                } else {
140
                    throw new \UnexpectedValueException(
141
                        get_class($garbageCollectorPostProcessor) . ' must implement interface ' . GarbageCollectorPostProcessor::class,
142
                        1345807460
143
                    );
144
                }
145
            }
146
        }
147 10
    }
148
149
    /**
150
     * Tracks down index documents belonging to a particular page and
151
     * removes them from the index and the Index Queue.
152
     *
153
     * @param string $table The record's table name.
154
     * @param int $uid The record's uid.
155
     */
156 10
    protected function collectPageGarbage($table, $uid)
157
    {
158
        switch ($table) {
159 10
            case 'tt_content':
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
160 4
                $contentElement = BackendUtility::getRecord('tt_content', $uid, 'uid, pid', '', false);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $contentElement is correct as TYPO3\CMS\Backend\Utilit... 'uid, pid', '', false) targeting TYPO3\CMS\Backend\Utilit...endUtility::getRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
161
162 4
                $table = 'pages';
163 4
                $uid = $contentElement['pid'];
164
165 4
                $this->deleteIndexDocuments($table, $uid);
166
                // only a content element was removed, now update/re-index the page
167 4
                $this->getIndexQueue()->updateItem($table, $uid);
168 4
                break;
169
            // @todo This case can be deleted when TYPO3 8 compatibility is dropped
170 6
            case 'pages_language_overlay':
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
171
                $pageOverlayRecord = BackendUtility::getRecord('pages_language_overlay', $uid, 'uid, pid', '', false);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $pageOverlayRecord is correct as TYPO3\CMS\Backend\Utilit... 'uid, pid', '', false) targeting TYPO3\CMS\Backend\Utilit...endUtility::getRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
172
173
                $table = 'pages';
174
                $uid = $pageOverlayRecord['pid'];
175
176
                $this->deleteIndexDocuments($table, $uid);
177
                // only a page overlay was removed, now update/re-index the page
178
                $this->getIndexQueue()->updateItem($table, $uid);
179
                break;
180 6
            case 'pages':
181
                // @todo The content of this if statement can allways be executed when TYPO3 8 support is dropped
182 6
                if (!Util::getIsTYPO3VersionBelow9()) {
183
                    $pageOverlay = BackendUtility::getRecord('pages', $uid, 'l10n_parent', '', false);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $pageOverlay is correct as TYPO3\CMS\Backend\Utilit...10n_parent', '', false) targeting TYPO3\CMS\Backend\Utilit...endUtility::getRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
184
                    $uid = empty($pageOverlay['l10n_parent']) ? $uid : $pageOverlay['l10n_parent'];
185
                }
186
187 6
                $this->deleteIndexDocuments($table, $uid);
188 6
                $this->getIndexQueue()->deleteItem($table, $uid);
189
190 6
                break;
191
        }
192 10
    }
193
194
    /**
195
     * @param string $table
196
     * @param int $uid
197
     * @param array $changedFields
198
     */
199 3
    protected function deleteSubpagesWhenExtendToSubpagesIsSet($table, $uid, $changedFields)
200
    {
201 3
        if (!$this->isRecursivePageUpdateRequired($uid, $changedFields)) {
202 1
            return;
203
        }
204
205 2
        $indexQueue = $this->getIndexQueue();
206
        // get affected subpages when "extendToSubpages" flag was set
207 2
        $pagesToDelete = $this->getSubPageIds($uid);
208
        // we need to at least remove this page
209 2
        foreach ($pagesToDelete as $pageToDelete) {
210 2
            $this->deleteIndexDocuments($table, $pageToDelete);
211 2
            $indexQueue->deleteItem($table, $pageToDelete);
212
        }
213 2
    }
214
215
    /**
216
     * Deletes index documents for a given record identification.
217
     *
218
     * @param string $table The record's table name.
219
     * @param int $uid The record's uid.
220
     */
221 10
    protected function deleteIndexDocuments($table, $uid)
222
    {
223
        /** @var $connectionManager ConnectionManager */
224 10
        $connectionManager = GeneralUtility::makeInstance(ConnectionManager::class);
225
226
        // record can be indexed for multiple sites
227 10
        $indexQueueItems = $this->getIndexQueue()->getItems($table, $uid);
228 10
        foreach ($indexQueueItems as $indexQueueItem) {
229 9
            $site = $indexQueueItem->getSite();
230 9
            $solrConfiguration = $site->getSolrConfiguration();
231 9
            $enableCommitsSetting = $solrConfiguration->getEnableCommits();
232
233
            // a site can have multiple connections (cores / languages)
234 9
            $solrConnections = $connectionManager->getConnectionsBySite($site);
235 9
            foreach ($solrConnections as $solr) {
236 9
                $solr->getWriteService()->deleteByQuery('type:' . $table . ' AND uid:' . intval($uid));
237 9
                if ($enableCommitsSetting) {
238 9
                    $solr->getWriteService()->commit(false, false, false);
239
                }
240
            }
241
        }
242 10
    }
243
244
    /**
245
     * Tracks down index documents belonging to a particular record and
246
     * removes them from the index and the Index Queue.
247
     *
248
     * @param string $table The record's table name.
249
     * @param int $uid The record's uid.
250
     */
251
    protected function collectRecordGarbage($table, $uid)
252
    {
253
        $this->deleteIndexDocuments($table, $uid);
254
        $this->getIndexQueue()->deleteItem($table, $uid);
255
    }
256
257
    // methods checking whether to trigger garbage collection
258
259
    /**
260
     * Hooks into TCE main and tracks page move commands.
261
     *
262
     * @param string $command The command.
263
     * @param string $table The table the record belongs to
264
     * @param int $uid The record's uid
265
     * @param string $value Not used
266
     * @param DataHandler $tceMain TYPO3 Core Engine parent object, not used
267
     */
268 2
    public function processCmdmap_postProcess(
269
        $command,
270
        $table,
271
        $uid,
272
        /** @noinspection PhpUnusedParameterInspection */
273
        $value,
0 ignored issues
show
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

273
        /** @scrutinizer ignore-unused */ $value,

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...
274
        /** @noinspection PhpUnusedParameterInspection */
275
        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

275
        /** @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...
276
    ) {
277
        // workspaces: collect garbage only for LIVE workspace
278 2
        if ($command === 'move' && $table === 'pages' && $GLOBALS['BE_USER']->workspace == 0) {
279
            // TODO the below comment is not valid anymore, pid has been removed from doc ID
280
            // ...still needed?
281
282
            // must be removed from index since the pid changes and
283
            // is part of the Solr document ID
284
            $this->collectGarbage($table, $uid);
285
286
            // now re-index with new properties
287
            $this->getIndexQueue()->updateItem($table, $uid);
288
        }
289 2
    }
290
291
    /**
292
     * Hooks into TCE main and tracks changed records. In this case the current
293
     * record's values are stored to do a change comparison later on for fields
294
     * like fe_group.
295
     *
296
     * @param array $incomingFields An array of incoming fields, new or changed, not used
297
     * @param string $table The table the record belongs to
298
     * @param mixed $uid The record's uid, [integer] or [string] (like 'NEW...')
299
     * @param DataHandler $tceMain TYPO3 Core Engine parent object, not used
300
     */
301 5
    public function processDatamap_preProcessFieldArray(
302
        /** @noinspection PhpUnusedParameterInspection */
303
        $incomingFields,
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

303
        /** @scrutinizer ignore-unused */ $incomingFields,

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...
304
        $table,
305
        $uid,
306
        /** @noinspection PhpUnusedParameterInspection */
307
        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

307
        /** @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...
308
    ) {
309 5
        if (!is_int($uid)) {
310
            // a newly created record, skip
311
            return;
312
        }
313
314 5
        if (Util::isDraftRecord($table, $uid)) {
315
            // skip workspaces: collect garbage only for LIVE workspace
316
            return;
317
        }
318
319 5
        $hasConfiguredEnableColumnForFeGroup = $this->tcaService->isEnableColumn($table, 'fe_group');
320
321 5
        if ($hasConfiguredEnableColumnForFeGroup) {
322 5
            $visibilityAffectingFields = $this->tcaService->getVisibilityAffectingFieldsByTable($table);
323 5
            $record = (array)BackendUtility::getRecord(
0 ignored issues
show
Bug introduced by
Are you sure the usage of TYPO3\CMS\Backend\Utilit...ctingFields, '', false) targeting TYPO3\CMS\Backend\Utilit...endUtility::getRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
324 5
                $table,
325 5
                $uid,
326 5
                $visibilityAffectingFields,
327 5
                '',
328 5
                false
329
            );
330
331
            // If no record could be found skip further processing
332 5
            if (empty($record)) {
333
                return;
334
            }
335
336 5
            $record = $this->tcaService->normalizeFrontendGroupField($table, $record);
337
338
            // keep previous state of important fields for later comparison
339 5
            $this->trackedRecords[$table][$uid] = $record;
340
        }
341 5
    }
342
343
    /**
344
     * Hooks into TCE Main and watches all record updates. If a change is
345
     * detected that would remove the record from the website, we try to find
346
     * related documents and remove them from the index.
347
     *
348
     * @param string $status Status of the current operation, 'new' or 'update'
349
     * @param string $table The table the record belongs to
350
     * @param mixed $uid The record's uid, [integer] or [string] (like 'NEW...')
351
     * @param array $fields The record's data, not used
352
     * @param DataHandler $tceMain TYPO3 Core Engine parent object, not used
353
     */
354 7
    public function processDatamap_afterDatabaseOperations(
355
        $status,
356
        $table,
357
        $uid,
358
        array $fields,
359
        /** @noinspection PhpUnusedParameterInspection */
360
        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

360
        /** @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...
361
    ) {
362 7
        if ($status === 'new') {
363
            // a newly created record, skip
364
            return;
365
        }
366
367 7
        if (Util::isDraftRecord($table, $uid)) {
368
            // skip workspaces: collect garbage only for LIVE workspace
369
            return;
370
        }
371
372 7
        $garbageCollectionRelevantFields = $this->tcaService->getVisibilityAffectingFieldsByTable($table);
373
374 7
        $record = (array)BackendUtility::getRecord($table, $uid, $garbageCollectionRelevantFields, '', false);
0 ignored issues
show
Bug introduced by
Are you sure the usage of TYPO3\CMS\Backend\Utilit...evantFields, '', false) targeting TYPO3\CMS\Backend\Utilit...endUtility::getRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
375
376
        // If no record could be found skip further processing
377 7
        if (empty($record)) {
378
            return;
379
        }
380
381 7
        $record = $this->tcaService->normalizeFrontendGroupField($table, $record);
382
383 7
        if ($this->tcaService->isHidden($table, $record)
384 3
            || $this->isInvisibleByStartOrEndtime($table, $record)
385 1
            || $this->hasFrontendGroupsRemoved($table, $record)
386 1
            || ($table === 'pages' && $this->isPageExcludedFromSearch($record))
387 7
            || ($table === 'pages' && !$this->isIndexablePageType($record))
388
        ) {
389 6
            $this->collectGarbage($table, $uid);
390
391 6
            if ($table === 'pages') {
392 3
                $this->deleteSubpagesWhenExtendToSubpagesIsSet($table, $uid, $fields);
393
            }
394
        }
395 7
    }
396
397
    /**
398
     * Check if a record is getting invisible due to changes in start or endtime. In addition it is checked that the related
399
     * queue item was marked as indexed.
400
     *
401
     * @param string $table
402
     * @param array $record
403
     * @return bool
404
     */
405 3
    protected function isInvisibleByStartOrEndtime($table, $record)
406
    {
407
        return (
408 3
            ($this->tcaService->isStartTimeInFuture($table, $record) || $this->tcaService->isEndTimeInPast($table, $record)) &&
409 3
            $this->isRelatedQueueRecordMarkedAsIndexed($table, $record)
410
        );
411
    }
412
413
    /**
414
     * Checks if the related index queue item is indexed.
415
     *
416
     * * For tt_content and pages_language_overlay the page from the pid is checked
417
     * * For all other records the table it's self is checked
418
     *
419
     * @param string $table The table name.
420
     * @param array $record An array with record fields that may affect visibility.
421
     * @return bool True if the record is marked as being indexed
422
     */
423 2
    protected function isRelatedQueueRecordMarkedAsIndexed($table, $record)
424
    {
425
        //@todo check for pages_language_overlay can be dropped when TYPO3 8 compatibility is dropped.
426 2
        if ($table === 'tt_content' || $table === 'pages_language_overlay') {
427 2
            $table = 'pages';
428 2
            $uid = $record['pid'];
429
        } else {
430
            $uid = $record['uid'];
431
        }
432
433 2
        return $this->getIndexQueue()->containsIndexedItem($table, $uid);
434
    }
435
436
    /**
437
     * @return Queue
438
     */
439 10
    private function getIndexQueue()
440
    {
441 10
        return GeneralUtility::makeInstance(Queue::class);
442
    }
443
444
    /**
445
     * Checks whether the a frontend group field exists for the record and if so
446
     * whether groups have been removed from accessing the record thus making
447
     * the record invisible to at least some people.
448
     *
449
     * @param string $table The table name.
450
     * @param array $record An array with record fields that may affect visibility.
451
     * @return bool TRUE if frontend groups have been removed from access to the record, FALSE otherwise.
452
     */
453 1
    protected function hasFrontendGroupsRemoved($table, $record)
454
    {
455 1
        $frontendGroupsRemoved = false;
456
457 1
        if (isset($GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['fe_group'])) {
458 1
            $frontendGroupsField = $GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['fe_group'];
459
460 1
            $previousGroups = explode(',',
461 1
                (string)$this->trackedRecords[$table][$record['uid']][$frontendGroupsField]);
462 1
            $currentGroups = explode(',',
463 1
                (string)$record[$frontendGroupsField]);
464
465 1
            $removedGroups = array_diff($previousGroups, $currentGroups);
466
467 1
            $frontendGroupsRemoved = (boolean)count($removedGroups);
468
        }
469
470 1
        return $frontendGroupsRemoved;
471
    }
472
473
    /**
474
     * Checks whether the page has been excluded from searching.
475
     *
476
     * @param array $record An array with record fields that may affect visibility.
477
     * @return bool True if the page has been excluded from searching, FALSE otherwise
478
     */
479
    protected function isPageExcludedFromSearch($record)
480
    {
481
        return (boolean)$record['no_search'];
482
    }
483
484
    /**
485
     * Checks whether a page has a page type that can be indexed.
486
     * Currently standard pages and mount pages can be indexed.
487
     *
488
     * @param array $record A page record
489
     * @return bool TRUE if the page can be indexed according to its page type, FALSE otherwise
490
     */
491
    protected function isIndexablePageType(array $record)
492
    {
493
        return Util::isAllowedPageType($record);
494
    }
495
}
496