Completed
Pull Request — master (#759)
by Timo
23:57
created

processDatamap_afterDatabaseOperations()   D

Complexity

Conditions 14
Paths 166

Size

Total Lines 80
Code Lines 42

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 25
CRAP Score 52.8984

Importance

Changes 0
Metric Value
dl 0
loc 80
ccs 25
cts 60
cp 0.4167
rs 4.7138
c 0
b 0
f 0
cc 14
eloc 42
nc 166
nop 5
crap 52.8984

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
namespace ApacheSolrForTypo3\Solr\IndexQueue;
3
4
/***************************************************************
5
 *  Copyright notice
6
 *
7
 *  (c) 2009-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 2 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\AbstractDataHandlerListener;
28
use ApacheSolrForTypo3\Solr\Site;
29
use ApacheSolrForTypo3\Solr\System\Configuration\TypoScriptConfiguration;
30
use ApacheSolrForTypo3\Solr\Util;
31
use TYPO3\CMS\Backend\Utility\BackendUtility;
32
use TYPO3\CMS\Core\DataHandling\DataHandler;
33
use TYPO3\CMS\Core\Utility\GeneralUtility;
34
35
/**
36
 * A class that monitors changes to records so that the changed record gets
37
 * passed to the index queue to update the according index document.
38
 *
39
 * @author Ingo Renner <[email protected]>
40
 */
41
class RecordMonitor extends AbstractDataHandlerListener
42
{
43
44
    /**
45
     * Solr TypoScript configuration
46
     *
47
     * @var TypoScriptConfiguration
48
     */
49
    protected $solrConfiguration;
50
51
    /**
52
     * Index Queue
53
     *
54
     * @var Queue
55
     */
56
    protected $indexQueue;
57
58
    /**
59
     * Constructor
60
     *
61
     */
62 8
    public function __construct()
63
    {
64 8
        $this->indexQueue = GeneralUtility::makeInstance('ApacheSolrForTypo3\\Solr\\IndexQueue\\Queue');
65 8
    }
66
67
    /**
68
     * Holds the configuration when a recursive page queing should be triggered.
69
     *
70
     * @var array
71
     * @return array
72
     */
73 4
    protected function getUpdateSubPagesRecursiveTriggerConfiguration()
74
    {
75
        return array(
76
            // the current page has the field "extendToSubpages" enabled and the field "hidden" was set to 0 => requeue subpages
77
            'extendToSubpageEnabledAndHiddenFlagWasRemoved' => array(
78
                'currentState' =>  array('extendToSubpages' => '1'),
79
                'changeSet' => array('hidden' => '0')
80 4
            ),
81
            // the current page has the field "hidden" enabled and the field "extendToSubpages" was set to 0 => requeue subpages
82
            'hiddenIsEnabledAndExtendToSubPagesWasRemoved' => array(
83
                'currentState' =>  array('hidden' => '1'),
84
                'changeSet' => array('extendToSubpages' => '0')
85
            )
86
        );
87
    }
88
89
    /**
90
     * Hooks into TCE main and tracks record deletion commands.
91
     *
92
     * @param string $command The command.
93
     * @param string $table The table the record belongs to
94
     * @param int $uid The record's uid
95
     * @param string $value
96
     * @param DataHandler $tceMain TYPO3 Core Engine parent object
97
     */
98
    public function processCmdmap_preProcess(
99
        $command,
100
        $table,
101
        $uid,
102
        $value,
0 ignored issues
show
Unused Code introduced by
The parameter $value is not used and could be removed.

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

Loading history...
103
        DataHandler $tceMain
104
    ) {
105
        if ($command == 'delete' && $table == 'tt_content' && $GLOBALS['BE_USER']->workspace == 0) {
106
            // skip workspaces: index only LIVE workspace
107
            $this->indexQueue->updateItem('pages',
108
                $this->getValidatedPid($tceMain, $table, $uid),
109
                null,
110
                time()
111
            );
112
        }
113
    }
114
115
    /**
116
     * Hooks into TCE main and tracks workspace publish/swap events and
117
     * page move commands in LIVE workspace.
118
     *
119
     * @param string $command The command.
120
     * @param string $table The table the record belongs to
121
     * @param int $uid The record's uid
122
     * @param string $value
123
     * @param DataHandler $tceMain TYPO3 Core Engine parent object
124
     */
125 1
    public function processCmdmap_postProcess(
126
        $command,
127
        $table,
128
        $uid,
129
        $value,
130
        DataHandler $tceMain
131
    ) {
132 1
        if (Util::isDraftRecord($table, $uid)) {
133
            // skip workspaces: index only LIVE workspace
134
            return;
135
        }
136
137
        // track publish / swap events for records (workspace support)
138
        // command "version"
139 1
        if ($command == 'version' && $value['action'] == 'swap') {
140
            switch ($table) {
141
                /** @noinspection PhpMissingBreakStatementInspection */
142 1
                case 'tt_content':
143
                    $uid = $this->getValidatedPid($tceMain, $table, $uid);
144
                    $table = 'pages';
145 1
                case 'pages':
146 1
                    $this->solrConfiguration = Util::getSolrConfigurationFromPageId($uid);
147 1
                    $record = $this->getRecord($table, $uid);
148
149 1
                    if (!empty($record) && $this->isEnabledRecord($table,
150
                            $record)
151
                    ) {
152 1
                        $this->updateMountPages($uid);
153
154 1
                        $this->indexQueue->updateItem($table, $uid);
155
                    } else {
156
                        // TODO should be moved to garbage collector
157
                        if ($this->indexQueue->containsItem($table, $uid)) {
158
                            $this->removeFromIndexAndQueue($table, $uid);
159
                        }
160
                    }
161 1
                    break;
162
                default:
163
                    $recordPageId = $this->getValidatedPid($tceMain, $table, $uid);
164
                    $this->solrConfiguration = Util::getSolrConfigurationFromPageId($recordPageId);
165
                    $isMonitoredTable = $this->solrConfiguration->getIndexQueueIsMonitoredTable($table);
166
167
                    if ($isMonitoredTable) {
168
                        $record = $this->getRecord($table, $uid);
169
170
                        if (!empty($record) && $this->isEnabledRecord($table,
171
                                $record)
172
                        ) {
173
                            if (Util::isLocalizedRecord($table, $record)) {
174
                                // if it's a localization overlay, update the original record instead
175
                                $uid = $record[$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']];
176
                            }
177
178
                            $configurationName = $this->getIndexingConfigurationName($table,
179
                                $uid);
180
                            $this->indexQueue->updateItem($table, $uid,
181
                                $configurationName);
182
                        } else {
183
                            // TODO should be moved to garbage collector
184
                            if ($this->indexQueue->containsItem($table, $uid)) {
185
                                $this->removeFromIndexAndQueue($table, $uid);
186
                            }
187
                        }
188
                    }
189
            }
190
        }
191
192 1
        if ($command == 'move' && $table == 'pages' && $GLOBALS['BE_USER']->workspace == 0) {
193
            // moving pages in LIVE workspace
194
            $this->solrConfiguration = Util::getSolrConfigurationFromPageId($uid);
195
            $record = $this->getRecord('pages', $uid);
196
            if (!empty($record) && $this->isEnabledRecord($table, $record)) {
197
                $this->indexQueue->updateItem('pages', $uid);
198
            } else {
199
                // check if the item should be removed from the index because it no longer matches the conditions
200
                if ($this->indexQueue->containsItem('pages', $uid)) {
201
                    $this->removeFromIndexAndQueue('pages', $uid);
202
                }
203
            }
204
        }
205 1
    }
206
207
    /**
208
     * Hooks into TCE Main and watches all record creations and updates. If it
209
     * detects that the new/updated record belongs to a table configured for
210
     * indexing through Solr, we add the record to the index queue.
211
     *
212
     * @param string $status Status of the current operation, 'new' or 'update'
213
     * @param string $table The table the record belongs to
214
     * @param mixed $uid The record's uid, [integer] or [string] (like 'NEW...')
215
     * @param array $fields The record's data
216
     * @param DataHandler $tceMain TYPO3 Core Engine parent object
217
     * @return void
218
     */
219 5
    public function processDatamap_afterDatabaseOperations(
220
        $status,
221
        $table,
222
        $uid,
223
        array $fields,
224
        DataHandler $tceMain
225
    ) {
226 5
        $recordTable = $table;
227 5
        $recordUid = $uid;
228
229 5
        if ($status == 'new') {
230 1
            $recordUid = $tceMain->substNEWwithIDs[$recordUid];
231
        }
232
233 5
        if (Util::isDraftRecord($table, $recordUid)) {
234
            // skip workspaces: index only LIVE workspace
235
            return;
236
        }
237
238 5
        $recordPageId = $this->getRecordPageId($status, $recordTable, $recordUid, $uid, $fields, $tceMain);
239 4
240 4
        // when a content element changes we need to updated the page instead
241 4
        if ($recordTable == 'tt_content') {
242
            $recordTable = 'pages';
243
            $recordUid = $recordPageId;
244 1
        }
245
246
        $this->solrConfiguration = Util::getSolrConfigurationFromPageId($recordPageId);
247
        $isMonitoredRecord = $this->solrConfiguration->getIndexQueueIsMonitoredTable($recordTable);
248 5
249
        if (!$isMonitoredRecord) {
250
            // when it is a non monitored record, we can skip it.
251
            return;
252
        }
253 5
254 5
        $record = $this->getRecord($recordTable, $recordUid);
255
        if (empty($record)) {
256 5
            // TODO move this part to the garbage collector
257
            // check if the item should be removed from the index because it no longer matches the conditions
258
            if ($this->indexQueue->containsItem($recordTable, $recordUid)) {
259
                $this->removeFromIndexAndQueue($recordTable, $recordUid);
260
            }
261 5
            return;
262 5
        }
263
264
        // only update/insert the item if we actually found a record
265 5
        if (Util::isLocalizedRecord($recordTable, $record)) {
266
            // if it's a localization overlay, update the original record instead
267
            $recordUid = $record[$GLOBALS['TCA'][$recordTable]['ctrl']['transOrigPointerField']];
268
269
            if ($recordTable == 'pages_language_overlay') {
270
                $recordTable = 'pages';
271
            }
272
273
            $tableEnableFields = implode(', ', $GLOBALS['TCA'][$recordTable]['ctrl']['enablecolumns']);
274
            $l10nParentRecord = BackendUtility::getRecord($recordTable, $recordUid, $tableEnableFields, '', false);
275
            if (!$this->isEnabledRecord($recordTable, $l10nParentRecord)) {
0 ignored issues
show
Bug introduced by
It seems like $l10nParentRecord defined by \TYPO3\CMS\Backend\Utili...nableFields, '', false) on line 274 can also be of type null; however, ApacheSolrForTypo3\Solr\...itor::isEnabledRecord() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
276
                // the l10n parent record must be visible to have it's translation indexed
277
                return;
278
            }
279
        }
280
281
        // Clear existing index queue items to prevent mount point duplicates.
282
        if ($recordTable == 'pages') {
283
            $this->indexQueue->deleteItem('pages', $recordUid);
284
        }
285
286 5
        if ($this->isEnabledRecord($recordTable, $record)) {
287 4
            $configurationName = null;
288
            if ($recordTable !== 'pages') {
289
                $configurationName = $this->getIndexingConfigurationName($recordTable, $recordUid);
290 5
            }
291 3
292 3
            $this->indexQueue->updateItem($recordTable, $recordUid, $configurationName);
293 1
        }
294
295
        if ($recordTable == 'pages') {
296 3
            $this->doPagesPostUpdateOperations($fields, $recordUid);
297
        }
298
    }
299
300 5
    /**
301 4
     * Applies needed updates when a pages record was processed by the RecordMonitor.
302 4
     *
303
     * @param array $fields
304 4
     * @param int $recordUid
305 2
     */
306 2
    protected function doPagesPostUpdateOperations(array $fields, $recordUid)
307 5
    {
308
        $this->updateCanonicalPages($recordUid);
309
        $this->updateMountPages($recordUid);
310
311
        if ($this->isRecursiveUpdateRequired($recordUid, $fields)) {
312
            $treePageIds = $this->getSubPageIds($recordUid);
313
            $this->updatePageIdItems($treePageIds);
314
        }
315
    }
316
317
    /**
318 5
     * Determines the recordPageId (pid) of a record.
319
     *
320
     * @param string $status
321
     * @param string $recordTable
322
     * @param int $recordUid
323
     * @param int $originalUid
324
     * @param array $fields
325
     * @param DataHandler $tceMain
326
     * @return int
327
     */
328
    protected function getRecordPageId($status, $recordTable, $recordUid, $originalUid, array $fields, DataHandler $tceMain)
329
    {
330
        if ($status == 'update' && !isset($fields['pid'])) {
331
            $recordPageId = $this->getValidatedPid($tceMain, $recordTable, $recordUid);
332
            if ($recordTable == 'pages' && Util::isRootPage($recordUid)) {
333
                $recordPageId = $originalUid;
334
            }
335
336
            return $recordPageId;
337
        }
338
339
        return $fields['pid'];
340 6
    }
341
342 6
    /**
343
     * Applies the updateItem instruction on a collection of pageIds.
344 6
     *
345 6
     * @param array $treePageIds
346 6
     */
347
    protected function updatePageIdItems(array $treePageIds)
348 6
    {
349 6
        foreach ($treePageIds as $treePageId) {
350 6
            $this->indexQueue->updateItem('pages', $treePageId);
351 6
        }
352
    }
353 6
354
355
    /**
356
     * Removes record from the index queue and from the solr index
357
     *
358 6
     * @param string $recordTable Name of table where the record lives
359
     * @param int $recordUid Id of record
360
     */
361
    protected function removeFromIndexAndQueue($recordTable, $recordUid)
362
    {
363
        $garbageCollector = GeneralUtility::makeInstance('ApacheSolrForTypo3\\Solr\\GarbageCollector');
364
        $garbageCollector->collectGarbage($recordTable, $recordUid);
365
    }
366
367
    /**
368
     * Retrieves a record, taking into account the additionalWhereClauses of the
369 4
     * Indexing Queue configurations.
370
     *
371 4
     * @param string $recordTable Table to read from
372 4
     * @param int $recordUid Id of the record
373 4
     * @return array Record if found, otherwise empty array
374 4
     */
375 4
    protected function getRecord($recordTable, $recordUid)
376
    {
377
        $record = array();
378 4
379
        $indexingConfigurations = $this->solrConfiguration->getEnabledIndexQueueConfigurationNames();
380
        foreach ($indexingConfigurations as $indexingConfigurationName) {
381 4
            $tableToIndex = $this->solrConfiguration->getIndexQueueTableNameOrFallbackToConfigurationName($indexingConfigurationName);
382
383
            if ($tableToIndex === $recordTable) {
384
                $recordWhereClause = $this->solrConfiguration->getIndexQueueAdditionalWhereClauseByConfigurationName($indexingConfigurationName);
385
                $record = BackendUtility::getRecord($recordTable, $recordUid, '*', $recordWhereClause);
386
                if (!empty($record)) {
387
                    // if we found a record which matches the conditions, we can continue
388
                    break;
389
                }
390
            }
391 5
        }
392
393
        return $record;
394 5
    }
395 5
396
    // Handle pages showing content from another page
397 5
398
    /**
399 5
     * Triggers Index Queue updates for other pages showing content from the
400
     * page currently being updated.
401
     *
402
     * @param int $pageId UID of the page currently being updated
403
     */
404
    protected function updateCanonicalPages($pageId)
405 5
    {
406
        $canonicalPages = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
407
            'uid',
408
            'pages',
409
            'content_from_pid = ' . $pageId
410
            . BackendUtility::deleteClause('pages')
411
        );
412
413 5
        foreach ($canonicalPages as $page) {
414
            $this->indexQueue->updateItem('pages', $page['uid']);
415 5
        }
416 5
    }
417
418 5
    // Mount Page Handling
419 5
420
    /**
421 5
     * Handles updates of the Index Queue in case a newly created or changed
422
     * page is part of a tree that is mounted into a another site.
423
     *
424
     * @param int $pageId Page Id (uid).
425
     */
426
    protected function updateMountPages($pageId)
427
    {
428
        // get the root line of the page, every parent page could be a Mount Page source
429
        $pageSelect = GeneralUtility::makeInstance('TYPO3\\CMS\\Frontend\\Page\\PageRepository');
430
        $rootLine = $pageSelect->getRootLine($pageId);
431 5
432
        $destinationMountProperties = $this->getDestinationMountPropertiesByRootLine($rootLine);
433
434
        if (!empty($destinationMountProperties)) {
435 5
            foreach ($destinationMountProperties as $destinationMount) {
436 5
                $this->addPageToMountingSiteIndexQueue($pageId,
437
                    $destinationMount);
438
            }
439
        }
440 5
    }
441 5
442
    /**
443 5
     * Finds Mount Pages that mount pages in a given root line.
444
     *
445 5
     * @param array $rootLine Root line of pages to check for usage as mount source
446 5
     * @return array Array of pages found to be mounting pages from the root line.
447 5
     */
448 5
    protected function getDestinationMountPropertiesByRootLine(array $rootLine)
449 5
    {
450
        $mountPages = array();
451
        $rootLineParentPageIds = array();
452 5
453
        $currentPage = array_shift($rootLine);
454
        $currentPageUid = (int)$currentPage['uid'];
455
456
        if (!empty($rootLine)) {
457
            foreach ($rootLine as $pageRecord) {
458
                $rootLineParentPageIds[] = $pageRecord['uid'];
459
460
                if ($pageRecord['is_siteroot']) {
461
                    break;
462
                }
463
            }
464
        }
465
466
        if (empty($rootLine) && $currentPageUid === 0) {
467
            return $mountPages;
468
        }
469
470
        $pageQueryConditions = array();
471
        if (!empty($rootLineParentPageIds)) {
472
            $pageQueryConditions[] = '(mount_pid IN(' . implode(',', $rootLineParentPageIds) . '))';
473
        }
474
475
        if ($currentPageUid !== 0) {
476
            $pageQueryConditions[] = '(mount_pid=' . $currentPageUid . ' AND mount_pid_ol=1)';
477
        }
478
        $pageQueryCondition = implode(' OR ', $pageQueryConditions);
479
480
        $mountPages = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
481
            'uid, uid AS mountPageDestination, mount_pid AS mountPageSource, mount_pid_ol AS mountPageOverlayed',
482
            'pages',
483
            '(' . $pageQueryCondition . ') AND doktype = 7 AND no_search = 0'
484
            . BackendUtility::deleteClause('pages')
485
        );
486 6
487
        return $mountPages;
488 6
    }
489
490
    /**
491 6
     * Adds a page to the Index Queue of a site mounting the page.
492
     *
493 4
     * @param int $mountedPageId ID (uid) of the mounted page.
494
     * @param array $mountProperties Array of mount point properties mountPageSource, mountPageDestination, and mountPageOverlayed
495 6
     */
496
    protected function addPageToMountingSiteIndexQueue(
497 2
        $mountedPageId,
498
        array $mountProperties
499
    ) {
500 6
        $mountingSite = Site::getSiteByPageId($mountProperties['mountPageDestination']);
501
502
        $pageInitializer = GeneralUtility::makeInstance('ApacheSolrForTypo3\\Solr\\IndexQueue\\Initializer\\Page');
503
        $pageInitializer->setSite($mountingSite);
504
505
        $pageInitializer->initializeMountedPage($mountProperties,
506
            $mountedPageId);
507
    }
508
509
    /**
510 1
     * Retrieves the pid of a record and throws an exception when getPid returns false.
511
     *
512 1
     * @param DataHandler $tceMain
513 1
     * @param string $table
514 1
     * @param integer $uid
515 1
     * @throws NoPidException
516
     * @return integer
517
     */
518
    protected function getValidatedPid(DataHandler $tceMain, $table, $uid)
519
    {
520 1
        $pid = $tceMain->getPID($table, $uid);
521
        if ($pid === false) {
522 1
            throw new NoPidException('Pid should not be false');
523 1
        }
524 1
525
        $pid = intval($pid);
526 1
        return $pid;
527
    }
528 1
529
    /**
530 1
     * Checks if a record is "enabled"
531
     *
532
     * A record is considered "enabled" if
533
     *  - it is not hidden
534
     *  - it is not deleted
535 1
     *  - as a page it is not set to be excluded from search
536
     *
537
     * @param string $table The record's table name
538
     * @param array $record The record to check
539
     * @return bool TRUE if the record is enabled, FALSE otherwise
540
     */
541
    protected function isEnabledRecord($table, $record)
542
    {
543
        $recordEnabled = true;
544
545
        if (
546
            (isset($GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['disabled']) && !empty($record[$GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['disabled']]))
547
            ||
548
            (isset($GLOBALS['TCA'][$table]['ctrl']['delete']) && !empty($record[$GLOBALS['TCA'][$table]['ctrl']['delete']]))
549
            ||
550
            ($table == 'pages' && !empty($record['no_search']))
551
        ) {
552
            $recordEnabled = false;
553
        }
554
555
        return $recordEnabled;
556
    }
557
558
    /**
559
     * Retrieves the name of the  Indexing Queue Configuration for a record
560
     *
561
     * @param string $recordTable Table to read from
562
     * @param int $recordUid Id of the record
563
     * @return string Name of indexing configuration
564
     */
565
    protected function getIndexingConfigurationName($recordTable, $recordUid)
566
    {
567
        $name = $recordTable;
568
        $indexingConfigurations = $this->solrConfiguration->getEnabledIndexQueueConfigurationNames();
569
        foreach ($indexingConfigurations as $indexingConfigurationName) {
570
            if (!$this->solrConfiguration->getIndexQueueConfigurationIsEnabled($indexingConfigurationName)) {
571
                // ignore disabled indexing configurations
572
                continue;
573
            }
574
575
            $tableToIndex = $this->solrConfiguration->getIndexQueueTableNameOrFallbackToConfigurationName($indexingConfigurationName);
576
577
            if ($tableToIndex === $recordTable) {
578
                $recordWhereClause = $this->solrConfiguration->getIndexQueueAdditionalWhereClauseByConfigurationName($indexingConfigurationName);
579
                $record = BackendUtility::getRecord($recordTable, $recordUid, '*', $recordWhereClause);
580
581
                if (!empty($record)) {
582
                    // we found a record which matches the conditions
583
                    $name = $indexingConfigurationName;
584
                    // FIXME currently returns after the first configuration match
585
                    break;
586
                }
587
            }
588
        }
589
590
        return $name;
591
    }
592
}
593