Completed
Push — master ( 4904bb...48f77b )
by Timo
11s
created

Queue   D

Complexity

Total Complexity 77

Size/Duplication

Total Lines 912
Duplicated Lines 0 %

Coupling/Cohesion

Components 3
Dependencies 12

Test Coverage

Coverage 77.08%

Importance

Changes 0
Metric Value
wmc 77
lcom 3
cbo 12
dl 0
loc 912
ccs 259
cts 336
cp 0.7708
rs 4.2105
c 0
b 0
f 0

31 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 3
A getLastIndexTime() 0 19 2
A getLastIndexedItemId() 0 18 2
B initialize() 0 41 6
A initializeIndexingConfiguration() 0 23 1
A updateItem() 0 21 4
A updateExistingItem() 0 17 3
B addNewItem() 0 25 5
A getRecordCached() 0 13 2
B getItemChangedTime() 0 45 5
A getPageItemChangedTime() 0 16 2
A getLocalizableItemChangedTime() 0 19 2
A containsItem() 0 12 1
A containsItemWithRootPageId() 0 12 1
A containsIndexedItem() 0 13 1
B deleteItem() 0 28 3
B deleteItemsByType() 0 29 3
B deleteItemsBySite() 0 50 5
A deleteAllItems() 0 4 1
A getItem() 0 21 2
A getItems() 0 12 1
A getItemsCountBySite() 0 6 1
A getRemainingItemsCountBySite() 0 6 1
A getAllItemsCount() 0 4 1
A getItemCount() 0 7 1
A buildIndexConfigurationConstraint() 0 9 2
A getItemsToIndex() 0 23 2
B getIndexQueueItemObjectsFromRecords() 0 52 7
A markItemAsFailed() 0 21 3
A getIndexingConfigurationsByItem() 0 16 2
A getIndexingConfigurationByItem() 0 15 2

How to fix   Complexity   

Complex Class

Complex classes like Queue often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Queue, and based on these observations, apply Extract Interface, too.

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\Domain\Index\Queue\RecordMonitor\Helper\ConfigurationAwareRecordService;
28
use ApacheSolrForTypo3\Solr\Domain\Index\Queue\RecordMonitor\Helper\RootPageResolver;
29
use ApacheSolrForTypo3\Solr\Site;
30
use ApacheSolrForTypo3\Solr\System\Cache\TwoLevelCache;
31
use ApacheSolrForTypo3\Solr\Util;
32
use ApacheSolrForTypo3\Solr\Utility\DatabaseUtility;
33
use TYPO3\CMS\Backend\Utility\BackendUtility;
34
use TYPO3\CMS\Core\Utility\GeneralUtility;
35
36
/**
37
 * The Indexing Queue. It allows us to decouple from frontend indexing and
38
 * reacting to changes faster.
39
 *
40
 * @author Ingo Renner <[email protected]>
41
 */
42
class Queue
43
{
44
    /**
45
     * @var RootPageResolver
46
     */
47
    protected $rootPageResolver;
48
49
    /**
50
     * @var ConfigurationAwareRecordService
51
     */
52
    protected $recordService;
53
54
    /**
55
     * Queue constructor.
56
     * @param RootPageResolver|null $rootPageResolver
57
     * @param ConfigurationAwareRecordService|null $recordService
58
     */
59 67
    public function __construct(RootPageResolver $rootPageResolver = null, ConfigurationAwareRecordService $recordService = null)
60
    {
61 67
        $this->rootPageResolver = isset($rootPageResolver) ? $rootPageResolver : GeneralUtility::makeInstance(RootPageResolver::class);
62 67
        $this->recordService = isset($recordService) ? $recordService : GeneralUtility::makeInstance(ConfigurationAwareRecordService::class);
63 67
    }
64
65
    // FIXME some of the methods should be renamed to plural forms
66
    // FIXME singular form methods should deal with exactly one item only
67
68
    /**
69
     * Returns the timestamp of the last indexing run.
70
     *
71
     * @param int $rootPageId The root page uid for which to get
72
     *      the last indexed item id
73
     * @return int Timestamp of last index run.
74
     */
75
    public function getLastIndexTime($rootPageId)
76
    {
77
        $lastIndexTime = 0;
78
79
        $lastIndexedRow = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
80
            'indexed',
81
            'tx_solr_indexqueue_item',
82
            'root = ' . (int)$rootPageId,
83
            '',
84
            'indexed DESC',
85
            1
86
        );
87
88
        if ($lastIndexedRow[0]['indexed']) {
89
            $lastIndexTime = $lastIndexedRow[0]['indexed'];
90
        }
91
92
        return $lastIndexTime;
93
    }
94
95
    /**
96
     * Returns the uid of the last indexed item in the queue
97
     *
98
     * @param int $rootPageId The root page uid for which to get
99
     *      the last indexed item id
100
     * @return int The last indexed item's ID.
101
     */
102
    public function getLastIndexedItemId($rootPageId)
103
    {
104
        $lastIndexedItemId = 0;
105
106
        $lastIndexedItemRow = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
107
            'uid',
108
            'tx_solr_indexqueue_item',
109
            'root = ' . (int)$rootPageId,
110
            '',
111
            'indexed DESC',
112
            1
113
        );
114
        if ($lastIndexedItemRow[0]['uid']) {
115
            $lastIndexedItemId = $lastIndexedItemRow[0]['uid'];
116
        }
117
118
        return $lastIndexedItemId;
119
    }
120
121
    /**
122
     * Truncate and rebuild the tx_solr_indexqueue_item table. This is the most
123
     * complete way to force reindexing, or to build the Index Queue for the
124
     * first time. The Index Queue initialization is site-specific.
125
     *
126
     * @param Site $site The site to initialize
127
     * @param string $indexingConfigurationName Name of a specific
128
     *      indexing configuration
129
     * @return array An array of booleans, each representing whether the
130
     *      initialization for an indexing configuration was successful
131
     */
132 5
    public function initialize(Site $site, $indexingConfigurationName = '')
133
    {
134 5
        $indexingConfigurations = [];
135 5
        $initializationStatus = [];
136
137 5
        if (empty($indexingConfigurationName)) {
138
            $solrConfiguration = $site->getSolrConfiguration();
139
            $indexingConfigurations = $solrConfiguration->getEnabledIndexQueueConfigurationNames();
140
        } else {
141 5
            $indexingConfigurations[] = $indexingConfigurationName;
142
        }
143
144 5
        foreach ($indexingConfigurations as $indexingConfigurationName) {
145 5
            $initializationStatus[$indexingConfigurationName] = $this->initializeIndexingConfiguration(
146
                $site,
147
                $indexingConfigurationName
148
            );
149
        }
150
151 5
        if (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['postProcessIndexQueueInitialization'])) {
152
            foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['postProcessIndexQueueInitialization'] as $classReference) {
153
                $indexQueueInitializationPostProcessor = GeneralUtility::getUserObj($classReference);
154
155
                if ($indexQueueInitializationPostProcessor instanceof InitializationPostProcessor) {
156
                    $indexQueueInitializationPostProcessor->postProcessIndexQueueInitialization(
157
                        $site,
158
                        $indexingConfigurations,
159
                        $initializationStatus
160
                    );
161
                } else {
162
                    throw new \UnexpectedValueException(
163
                        get_class($indexQueueInitializationPostProcessor) .
164
                        ' must implement interface ' . InitializationPostProcessor::class,
165
                        1345815561
166
                    );
167
                }
168
            }
169
        }
170
171 5
        return $initializationStatus;
172
    }
173
174
    /**
175
     * Initializes the Index Queue for a specific indexing configuration.
176
     *
177
     * @param Site $site The site to initialize
178
     * @param string $indexingConfigurationName name of a specific
179
     *      indexing configuration
180
     * @return bool TRUE if the initialization was successful, FALSE otherwise
181
     */
182 5
    protected function initializeIndexingConfiguration(
183
        Site $site,
184
        $indexingConfigurationName
185
    ) {
186
        // clear queue
187 5
        $this->deleteItemsBySite($site, $indexingConfigurationName);
188
189 5
        $solrConfiguration = $site->getSolrConfiguration();
190
191 5
        $tableToIndex = $solrConfiguration->getIndexQueueTableNameOrFallbackToConfigurationName($indexingConfigurationName);
192 5
        $initializerClass = $solrConfiguration->getIndexQueueInitializerClassByConfigurationName($indexingConfigurationName);
193
194 5
        $initializer = GeneralUtility::makeInstance($initializerClass);
195
        /** @var $initializer \ApacheSolrForTypo3\Solr\IndexQueue\Initializer\AbstractInitializer */
196 5
        $initializer->setSite($site);
197 5
        $initializer->setType($tableToIndex);
198 5
        $initializer->setIndexingConfigurationName($indexingConfigurationName);
199
200 5
        $indexConfiguration = $solrConfiguration->getIndexQueueConfigurationByName($indexingConfigurationName);
201 5
        $initializer->setIndexingConfiguration($indexConfiguration);
202
203 5
        return $initializer->initialize();
204
    }
205
206
    /**
207
     * Gets the indexing configuration to use for an item.
208
     * Sometimes, when there are multiple configurations for a certain item type
209
     * (table) it can be hard or even impossible to find which one to use
210
     * though.
211
     * Currently selects the first indexing configuration where the name matches
212
     * the itemType or where the configured tbale is the same as the itemType.
213
     *
214
     * !!! Might return incorrect results for complex configurations !!!
215
     * Try to set the indexingConfiguration directly when using the updateItem()
216
     * method in such situations.
217
     *
218
     * @param string $itemType The item's type, usually a table name.
219
     * @param string $itemUid The item's uid, usually an integer uid, could be a
220
     *      different value for non-database-record types.
221
     * @param int $rootPageId The configuration's page tree's root page id.
222
     *      Optional, not needed for all types.
223
     * @return string The indexing configuration's name to use when indexing
224
     * @deprecated since 6.1 will be removed in 7.0
225
     * Use getIndexingConfigurationsByItem() now, which behaves
226
     * almost the same way but returns an array of configurations
227
     */
228
    protected function getIndexingConfigurationByItem(
229
        $itemType,
230
        $itemUid,
0 ignored issues
show
Unused Code introduced by
The parameter $itemUid 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...
231
        $rootPageId = null
232
    ) {
233
        GeneralUtility::logDeprecatedFunction();
234
        $indexingConfigurationName = '';
235
236
        $configurations = $this->getIndexingConfigurationsByItem($itemType, $rootPageId);
0 ignored issues
show
Deprecated Code introduced by
The method ApacheSolrForTypo3\Solr\...gConfigurationsByItem() has been deprecated with message: since 6.1 will be removed in 7.0

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
237
        if (count($configurations) > 0) {
238
            $indexingConfigurationName = $configurations[0];
239
        }
240
241
        return $indexingConfigurationName;
242
    }
243
244
    /**
245
     * Gets the indexing configurations to use for an item.
246
     * Multiple configurations for a certain item type (table) might be available.
247
     *
248
     * @param string $itemType The item's type, usually a table name.
249
     * @param int $rootPageId The configuration's page tree's root page id.
250
     *      Optional, not needed for all types.
251
     * @return array<string> The indexing configurations names to use when indexing
252
     * @deprecated since 6.1 will be removed in 7.0
253
     */
254
    protected function getIndexingConfigurationsByItem(
255
        $itemType,
256
        $rootPageId = null
257
    ) {
258
        GeneralUtility::logDeprecatedFunction();
259
260
        $possibleIndexingConfigurationNames = [];
261
262
        if (!is_null($rootPageId)) {
263
            // get configuration for the root's branch
264
            $solrConfiguration = Util::getSolrConfigurationFromPageId($rootPageId);
265
            $possibleIndexingConfigurationNames = $solrConfiguration->getIndexQueueConfigurationNamesByTableName($itemType);
266
        }
267
268
        return $possibleIndexingConfigurationNames;
269
    }
270
271
    /**
272
     * Marks an item as needing (re)indexing.
273
     *
274
     * Like with Solr itself, there's no add method, just a simple update method
275
     * that handles the adds, too.
276
     *
277
     * The method creates or updates the index queue items for all related rootPageIds.
278
     *
279
     * @param string $itemType The item's type, usually a table name.
280
     * @param string $itemUid The item's uid, usually an integer uid, could be a
281
     *      different value for non-database-record types.
282
     * @param int $forcedChangeTime The change time for the item if set, otherwise
283
     *          value from getItemChangedTime() is used.
284
     */
285 47
    public function updateItem($itemType, $itemUid, $forcedChangeTime = 0)
286
    {
287 47
        $rootPageIds = $this->rootPageResolver->getResponsibleRootPageIds($itemType, $itemUid);
288 46
        foreach ($rootPageIds as $rootPageId) {
289 46
            $skipInvalidRootPage = $rootPageId === 0;
290 46
            if ($skipInvalidRootPage) {
291
                continue;
292
            }
293
294 46
            $solrConfiguration = Util::getSolrConfigurationFromPageId($rootPageId);
295 46
            $indexingConfiguration = $this->recordService->getIndexingConfigurationName($itemType, $itemUid, $solrConfiguration);
296 46
            $itemInQueueForRootPage = $this->containsItemWithRootPageId($itemType, $itemUid, $rootPageId);
297 46
            if ($itemInQueueForRootPage) {
298
                // update the existing queue item
299 10
                $this->updateExistingItem($itemType, $itemUid, $indexingConfiguration, $rootPageId, $forcedChangeTime);
300
            } else {
301
                // add the item since it's not in the queue yet
302 46
                $this->addNewItem($itemType, $itemUid, $indexingConfiguration, $rootPageId);
303
            }
304
        }
305 46
    }
306
307
    /**
308
     * Updates an existing queue entry by $itemType $itemUid and $rootPageId.
309
     *
310
     * @param string $itemType  The item's type, usually a table name.
311
     * @param int $itemUid The item's uid, usually an integer uid, could be a
312
     *      different value for non-database-record types.
313
     * @param string $indexingConfiguration The name of the related indexConfiguration
314
     * @param int $rootPageId The uid of the rootPage
315
     * @param int $forcedChangeTime The forced change time that should be used for updating
316
     */
317 10
    protected function updateExistingItem($itemType, $itemUid, $indexingConfiguration, $rootPageId, $forcedChangeTime)
318
    {
319
        // update if that item is in the queue already
320
        $changes = [
321 10
            'changed' => ($forcedChangeTime > 0) ? $forcedChangeTime : $this->getItemChangedTime($itemType, $itemUid)
322
        ];
323
324 10
        if (!empty($indexingConfiguration)) {
325 10
            $changes['indexing_configuration'] = $indexingConfiguration;
326
        }
327
328 10
        $GLOBALS['TYPO3_DB']->exec_UPDATEquery(
329 10
            'tx_solr_indexqueue_item',
330 10
            'item_type = ' . $GLOBALS['TYPO3_DB']->fullQuoteStr($itemType, 'tx_solr_indexqueue_item') .
331 10
            ' AND item_uid = ' . (int)$itemUid . ' AND root = ' . (int)$rootPageId,
332
            $changes);
333 10
    }
334
335
    /**
336
     * Adds an item to the index queue.
337
     *
338
     * Not meant for public use.
339
     *
340
     * @param string $itemType The item's type, usually a table name.
341
     * @param string $itemUid The item's uid, usually an integer uid, could be a
342
     *      different value for non-database-record types.
343
     * @param string $indexingConfiguration The item's indexing configuration to use.
344
     *      Optional, overwrites existing / determined configuration.
345
     * @return void
346
     */
347 40
    private function addNewItem($itemType, $itemUid, $indexingConfiguration, $rootPageId)
348
    {
349 40
        $additionalRecordFields = '';
350 40
        if ($itemType == 'pages') {
351 27
            $additionalRecordFields = ', doktype, uid';
352
        }
353
354 40
        $record = $this->getRecordCached($itemType, $itemUid, $additionalRecordFields);
355
356 40
        if (empty($record) || ($itemType == 'pages' && !Util::isAllowedPageType($record, $indexingConfiguration))) {
357 1
            return;
358
        }
359
360
        $item = [
361 39
            'root' => $rootPageId,
362 39
            'item_type' => $itemType,
363 39
            'item_uid' => $itemUid,
364 39
            'changed' => $this->getItemChangedTime($itemType, $itemUid),
365 39
            'errors' => ''
366
        ];
367
368
        // make a backup of the current item
369 39
        $item['indexing_configuration'] = $indexingConfiguration;
370 39
        $GLOBALS['TYPO3_DB']->exec_INSERTquery('tx_solr_indexqueue_item', $item);
371 39
    }
372
373
    /**
374
     * Get record to be added in addNewItem
375
     *
376
     * @param string $itemType The item's type, usually a table name.
377
     * @param string $itemUid The item's uid, usually an integer uid, could be a
378
     *      different value for non-database-record types.
379
     * @param string $additionalRecordFields for sql-query
380
     *
381
     * @return array|NULL
382
     */
383 40
    protected function getRecordCached($itemType, $itemUid, $additionalRecordFields)
384
    {
385 40
        $cache = GeneralUtility::makeInstance(TwoLevelCache::class, 'cache_runtime');
386 40
        $cacheId = md5('Queue' . ':' . 'getRecordCached' . ':' . $itemType . ':' . $itemUid . ':' . 'pid' . $additionalRecordFields);
387
388 40
        $record = $cache->get($cacheId);
389 40
        if (empty($record)) {
390 40
            $record = BackendUtility::getRecord($itemType, $itemUid, 'pid' . $additionalRecordFields);
391 40
            $cache->set($cacheId, $record);
392
        }
393
394 40
        return $record;
395
    }
396
397
    /**
398
     * Determines the time for when an item should be indexed. This timestamp
399
     * is then stored in the changed column in the Index Queue.
400
     *
401
     * The changed timestamp usually is now - time(). For records which are set
402
     * to published at a later time, this timestamp is the start time. So if a
403
     * future start time has been set, that will be used to delay indexing
404
     * of an item.
405
     *
406
     * @param string $itemType The item's table name.
407
     * @param string $itemUid The item's uid, usually an integer uid, could be a
408
     *      different value for non-database-record types.
409
     * @return int Timestamp of the item's changed time or future start time
410
     */
411 45
    protected function getItemChangedTime($itemType, $itemUid)
412
    {
413 45
        $itemTypeHasStartTimeColumn = false;
414 45
        $changedTimeColumns = $GLOBALS['TCA'][$itemType]['ctrl']['tstamp'];
415 45
        $startTime = 0;
416 45
        $pageChangedTime = 0;
417
418 45
        if (!empty($GLOBALS['TCA'][$itemType]['ctrl']['enablecolumns']['starttime'])) {
419 45
            $itemTypeHasStartTimeColumn = true;
420 45
            $changedTimeColumns .= ', ' . $GLOBALS['TCA'][$itemType]['ctrl']['enablecolumns']['starttime'];
421
        }
422 45
        if ($itemType == 'pages') {
423
            // does not carry time information directly, but needed to support
424
            // canonical pages
425 32
            $changedTimeColumns .= ', content_from_pid';
426
        }
427
428 45
        $record = BackendUtility::getRecord($itemType, $itemUid, $changedTimeColumns);
429 45
        $itemChangedTime = $record[$GLOBALS['TCA'][$itemType]['ctrl']['tstamp']];
430
431 45
        if ($itemTypeHasStartTimeColumn) {
432 45
            $startTime = $record[$GLOBALS['TCA'][$itemType]['ctrl']['enablecolumns']['starttime']];
433
        }
434
435 45
        if ($itemType == 'pages') {
436 32
            $record['uid'] = $itemUid;
437
            // overrule the page's last changed time with the most recent
438
            //content element change
439 32
            $pageChangedTime = $this->getPageItemChangedTime($record);
440
        }
441
442 45
        $localizationsChangedTime = $this->getLocalizableItemChangedTime($itemType, $itemUid);
443
444
        // if start time exists and start time is higher than last changed timestamp
445
        // then set changed to the future start time to make the item
446
        // indexed at a later time
447 45
        $changedTime = max(
448
            $itemChangedTime,
449
            $pageChangedTime,
450
            $localizationsChangedTime,
451
            $startTime
452
        );
453
454 45
        return $changedTime;
455
    }
456
457
    /**
458
     * Gets the most recent changed time of a page's content elements
459
     *
460
     * @param array $page Partial page record
461
     * @return int Timestamp of the most recent content element change
462
     */
463 32
    protected function getPageItemChangedTime(array $page)
464
    {
465 32
        if (!empty($page['content_from_pid'])) {
466
            // canonical page, get the original page's last changed time
467
            $pageContentLastChangedTime = $this->getPageItemChangedTime(['uid' => $page['content_from_pid']]);
468
        } else {
469 32
            $pageContentLastChangedTime = $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow(
470 32
                'MAX(tstamp) AS changed_time',
471 32
                'tt_content',
472 32
                'pid = ' . (int)$page['uid']
473
            );
474 32
            $pageContentLastChangedTime = $pageContentLastChangedTime['changed_time'];
475
        }
476
477 32
        return $pageContentLastChangedTime;
478
    }
479
480
    /**
481
     * Gets the most recent changed time for an item taking into account
482
     * localized records.
483
     *
484
     * @param string $itemType The item's type, usually a table name.
485
     * @param string $itemUid The item's uid, usually an integer uid, could be a
486
     *      different value for non-database-record types.
487
     * @return int Timestamp of the most recent content element change
488
     */
489 45
    protected function getLocalizableItemChangedTime($itemType, $itemUid)
490
    {
491 45
        $localizedChangedTime = 0;
492
493 45
        if (isset($GLOBALS['TCA'][$itemType]['ctrl']['transOrigPointerField'])) {
494
            // table is localizable
495 13
            $translationOriginalPointerField = $GLOBALS['TCA'][$itemType]['ctrl']['transOrigPointerField'];
496
497 13
            $itemUid = intval($itemUid);
498 13
            $localizedChangedTime = $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow(
499 13
                'MAX(tstamp) AS changed_time',
500
                $itemType,
501 13
                "uid = $itemUid OR $translationOriginalPointerField = $itemUid"
502
            );
503 13
            $localizedChangedTime = $localizedChangedTime['changed_time'];
504
        }
505
506 45
        return $localizedChangedTime;
507
    }
508
509
    /**
510
     * Checks whether the Index Queue contains a specific item.
511
     *
512
     * @param string $itemType The item's type, usually a table name.
513
     * @param string $itemUid The item's uid, usually an integer uid, could be a
514
     *      different value for non-database-record types.
515
     * @return bool TRUE if the item is found in the queue, FALSE otherwise
516
     */
517 3
    public function containsItem($itemType, $itemUid)
518
    {
519 3
        $itemIsInQueue = (boolean)$GLOBALS['TYPO3_DB']->exec_SELECTcountRows(
520 3
            'uid',
521 3
            'tx_solr_indexqueue_item',
522 3
            'item_type = ' . $GLOBALS['TYPO3_DB']->fullQuoteStr($itemType,
523 3
                'tx_solr_indexqueue_item') .
524 3
            ' AND item_uid = ' . (int)$itemUid
525
        );
526
527 3
        return $itemIsInQueue;
528
    }
529
530
    /**
531
     * Checks whether the Index Queue contains a specific item.
532
     *
533
     * @param string $itemType The item's type, usually a table name.
534
     * @param string $itemUid The item's uid, usually an integer uid, could be a
535
     *      different value for non-database-record types.
536
     * @param integer $rootPageId
537
     * @return bool TRUE if the item is found in the queue, FALSE otherwise
538
     */
539 46
    public function containsItemWithRootPageId($itemType, $itemUid, $rootPageId)
540
    {
541 46
        $itemIsInQueue = (boolean)$GLOBALS['TYPO3_DB']->exec_SELECTcountRows(
542 46
            'uid',
543 46
            'tx_solr_indexqueue_item',
544 46
            'item_type = ' . $GLOBALS['TYPO3_DB']->fullQuoteStr($itemType,
545 46
                'tx_solr_indexqueue_item') .
546 46
            ' AND item_uid = ' . (int)$itemUid . ' AND root = ' . (int)$rootPageId
547
        );
548
549 46
        return $itemIsInQueue;
550
    }
551
552
    /**
553
     * Checks whether the Index Queue contains a specific item that has been
554
     * marked as indexed.
555
     *
556
     * @param string $itemType The item's type, usually a table name.
557
     * @param string $itemUid The item's uid, usually an integer uid, could be a
558
     *      different value for non-database-record types.
559
     * @return bool TRUE if the item is found in the queue and marked as
560
     *      indexed, FALSE otherwise
561
     */
562 3
    public function containsIndexedItem($itemType, $itemUid)
563
    {
564 3
        $itemIsInQueue = (boolean)$GLOBALS['TYPO3_DB']->exec_SELECTcountRows(
565 3
            'uid',
566 3
            'tx_solr_indexqueue_item',
567 3
            'item_type = ' . $GLOBALS['TYPO3_DB']->fullQuoteStr($itemType,
568 3
                'tx_solr_indexqueue_item') .
569 3
            ' AND item_uid = ' . (int)$itemUid .
570 3
            ' AND indexed > 0'
571
        );
572
573 3
        return $itemIsInQueue;
574
    }
575
576
    /**
577
     * Removes an item from the Index Queue.
578
     *
579
     * @param string $itemType The type of the item to remove, usually a table name.
580
     * @param int $itemUid The uid of the item to remove
581
     */
582 26
    public function deleteItem($itemType, $itemUid)
583
    {
584 26
        $uidList = [];
585
586
        // get the item uids to use them in the deletes afterwards
587 26
        $items = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
588 26
            'uid',
589 26
            'tx_solr_indexqueue_item',
590 26
            'item_type = ' . $GLOBALS['TYPO3_DB']->fullQuoteStr($itemType,
591 26
                'tx_solr_indexqueue_item') .
592 26
            ' AND item_uid = ' . intval($itemUid)
593
        );
594
595 26
        if (count($items)) {
596 9
            foreach ($items as $item) {
597 9
                $uidList[] = $item['uid'];
598
            }
599
600 9
            $GLOBALS['TYPO3_DB']->exec_DELETEquery(
601 9
                'tx_solr_indexqueue_item',
602 9
                'uid IN(' . implode(',', $uidList) . ')'
603
            );
604 9
            $GLOBALS['TYPO3_DB']->exec_DELETEquery(
605 9
                'tx_solr_indexqueue_indexing_property',
606 9
                'item_id IN(' . implode(',', $uidList) . ')'
607
            );
608
        }
609 26
    }
610
611
    /**
612
     * Removes all items of a certain type from the Index Queue.
613
     *
614
     * @param string $itemType The type of items to remove, usually a table name.
615
     */
616 1
    public function deleteItemsByType($itemType)
617
    {
618 1
        $uidList = [];
619
620
        // get the item uids to use them in the deletes afterwards
621 1
        $items = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
622 1
            'uid',
623 1
            'tx_solr_indexqueue_item',
624 1
            'item_type = ' . $GLOBALS['TYPO3_DB']->fullQuoteStr(
625
                $itemType,
626 1
                'tx_solr_indexqueue_item'
627
            )
628
        );
629
630 1
        if (count($items)) {
631 1
            foreach ($items as $item) {
632 1
                $uidList[] = $item['uid'];
633
            }
634
635 1
            $GLOBALS['TYPO3_DB']->exec_DELETEquery(
636 1
                'tx_solr_indexqueue_item',
637 1
                'uid IN(' . implode(',', $uidList) . ')'
638
            );
639 1
            $GLOBALS['TYPO3_DB']->exec_DELETEquery(
640 1
                'tx_solr_indexqueue_indexing_property',
641 1
                'item_id IN(' . implode(',', $uidList) . ')'
642
            );
643
        }
644 1
    }
645
646
    /**
647
     * Removes all items of a certain site from the Index Queue. Accepts an
648
     * optional parameter to limit the deleted items by indexing configuration.
649
     *
650
     * @param Site $site The site to remove items for.
651
     * @param string $indexingConfigurationName Name of a specific indexing
652
     *      configuration
653
     */
654 5
    public function deleteItemsBySite(
655
        Site $site,
656
        $indexingConfigurationName = ''
657
    ) {
658 5
        $rootPageConstraint = 'tx_solr_indexqueue_item.root = ' . $site->getRootPageId();
659
660 5
        $indexingConfigurationConstraint = '';
661 5
        if (!empty($indexingConfigurationName)) {
662
            $indexingConfigurationConstraint =
663
                ' AND tx_solr_indexqueue_item.indexing_configuration = \'' .
664 5
                $indexingConfigurationName . '\'';
665
        }
666
667 5
        DatabaseUtility::transactionStart();
668
        try {
669
            // reset Index Queue
670 5
            $result = $GLOBALS['TYPO3_DB']->exec_DELETEquery(
671 5
                'tx_solr_indexqueue_item',
672 5
                $rootPageConstraint . $indexingConfigurationConstraint
673
            );
674 5
            if (!$result) {
675
                throw new \RuntimeException(
676
                    'Failed to reset Index Queue for site ' . $site->getLabel(),
677
                    1412986560
678
                );
679
            }
680
681
            // reset Index Queue Properties
682
            $indexQueuePropertyResetQuery = '
683
                DELETE tx_solr_indexqueue_indexing_property.*
684
                FROM tx_solr_indexqueue_indexing_property
685
                INNER JOIN tx_solr_indexqueue_item
686
                    ON tx_solr_indexqueue_item.uid = tx_solr_indexqueue_indexing_property.item_id
687
                    AND ' .
688 5
                $rootPageConstraint .
689 5
                $indexingConfigurationConstraint;
690
691 5
            $result = $GLOBALS['TYPO3_DB']->sql_query($indexQueuePropertyResetQuery);
692 5
            if (!$result) {
693
                throw new \RuntimeException(
694
                    'Failed to reset Index Queue properties for site ' . $site->getLabel(),
695
                    1412986604
696
                );
697
            }
698
699 5
            DatabaseUtility::transactionCommit();
700
        } catch (\RuntimeException $e) {
701
            DatabaseUtility::transactionRollback();
702
        }
703 5
    }
704
705
    /**
706
     * Removes all items from the Index Queue.
707
     *
708
     */
709
    public function deleteAllItems()
710
    {
711
        $GLOBALS['TYPO3_DB']->exec_TRUNCATEquery('tx_solr_indexqueue_item', '');
712
    }
713
714
    /**
715
     * Gets a single Index Queue item by its uid.
716
     *
717
     * @param int $itemId Index Queue item uid
718
     * @return Item The request Index Queue item or NULL
719
     *      if no item with $itemId was found
720
     */
721 11
    public function getItem($itemId)
722
    {
723 11
        $item = null;
724
725 11
        $indexQueueItemRecord = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
726 11
            '*',
727 11
            'tx_solr_indexqueue_item',
728 11
            'uid = ' . intval($itemId)
729
        );
730
731 11
        if (count($indexQueueItemRecord) == 1) {
732 11
            $indexQueueItemRecord = $indexQueueItemRecord[0];
733
734 11
            $item = GeneralUtility::makeInstance(
735 11
                Item::class,
736
                $indexQueueItemRecord
737
            );
738
        }
739
740 11
        return $item;
741
    }
742
743
    /**
744
     * Gets Index Queue items by type and uid.
745
     *
746
     * @param string $itemType item type, usually  the table name
747
     * @param int $itemUid item uid
748
     * @return Item[] An array of items matching $itemType and $itemUid
749
     */
750 20
    public function getItems($itemType, $itemUid)
751
    {
752 20
        $indexQueueItemRecords = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
753 20
            '*',
754 20
            'tx_solr_indexqueue_item',
755 20
            'item_type = ' . $GLOBALS['TYPO3_DB']->fullQuoteStr($itemType,
756 20
                'tx_solr_indexqueue_item') .
757 20
            ' AND item_uid = ' . intval($itemUid)
758
        );
759
760 20
        return $this->getIndexQueueItemObjectsFromRecords($indexQueueItemRecords);
761
    }
762
763
    /**
764
     * Gets number of Index Queue items for a specific site / indexing configuration
765
     * optional parameter to limit the counted items by indexing configuration.
766
     *
767
     * @param Site $site The site to search for.
768
     * @param string $indexingConfigurationName name of a specific indexing
769
     *      configuration
770
     * @return int Number of items
771
     */
772 2
    public function getItemsCountBySite(Site $site, $indexingConfigurationName = '')
773
    {
774 2
        $indexingConfigurationConstraint = $this->buildIndexConfigurationConstraint($indexingConfigurationName);
775 2
        $where = 'root = ' . $site->getRootPageId() . $indexingConfigurationConstraint;
776 2
        return (int)$this->getItemCount($where);
777
    }
778
779
    /**
780
     * Gets number of unprocessed Index Queue items for a specific site / indexing configuration
781
     * optional parameter to limit the counted items by indexing configuration.
782
     *
783
     * @param Site $site The site to search for.
784
     * @param string $indexingConfigurationName name of a specific indexing
785
     *      configuration
786
     * @return int Number of items.
787
     */
788 2
    public function getRemainingItemsCountBySite(Site $site, $indexingConfigurationName = '')
789
    {
790 2
        $indexingConfigurationConstraint = $this->buildIndexConfigurationConstraint($indexingConfigurationName);
791 2
        $where = 'changed > indexed AND root = ' . $site->getRootPageId() . $indexingConfigurationConstraint;
792 2
        return (int)$this->getItemCount($where);
793
    }
794
795
    /**
796
     * Returns the number of items for all queues.
797
     *
798
     * @return int
799
     */
800 46
    public function getAllItemsCount()
801
    {
802 46
        return $this->getItemCount();
803
    }
804
805
    /**
806
     * @param string $where
807
     * @return int
808
     */
809 49
    private function getItemCount($where = '1=1')
810
    {
811
        /**  @var $db \TYPO3\CMS\Core\Database\DatabaseConnection */
812 49
        $db = $GLOBALS['TYPO3_DB'];
813
814 49
        return (int)$db->exec_SELECTcountRows('uid', 'tx_solr_indexqueue_item', $where);
815
    }
816
817
    /**
818
     * Build a database constraint that limits to a certain indexConfigurationName
819
     *
820
     * @param string $indexingConfigurationName
821
     * @return string
822
     */
823 3
    protected function buildIndexConfigurationConstraint($indexingConfigurationName)
824
    {
825 3
        $indexingConfigurationConstraint = '';
826 3
        if (!empty($indexingConfigurationName)) {
827
            $indexingConfigurationConstraint = ' AND indexing_configuration = \'' . $indexingConfigurationName . '\'';
828
            return $indexingConfigurationConstraint;
829
        }
830 3
        return $indexingConfigurationConstraint;
831
    }
832
833
    /**
834
     * Gets $limit number of items to index for a particular $site.
835
     *
836
     * @param Site $site TYPO3 site
837
     * @param int $limit Number of items to get from the queue
838
     * @return Item[] Items to index to the given solr server
839
     */
840 7
    public function getItemsToIndex(Site $site, $limit = 50)
841
    {
842 7
        $itemsToIndex = [];
843
844
        // determine which items to index with this run
845 7
        $indexQueueItemRecords = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
846 7
            '*',
847 7
            'tx_solr_indexqueue_item',
848 7
            'root = ' . $site->getRootPageId() .
849 7
            ' AND changed > indexed' .
850 7
            ' AND changed <= ' . time() .
851 7
            ' AND errors = \'\'',
852 7
            '',
853 7
            'indexing_priority DESC, changed DESC, uid DESC',
854
            intval($limit)
855
        );
856 7
        if (!empty($indexQueueItemRecords)) {
857
            // convert queued records to index queue item objects
858 7
            $itemsToIndex = $this->getIndexQueueItemObjectsFromRecords($indexQueueItemRecords);
859
        }
860
861 7
        return $itemsToIndex;
862
    }
863
864
    /**
865
     * Creates an array of ApacheSolrForTypo3\Solr\IndexQueue\Item objects from an array of
866
     * index queue records.
867
     *
868
     * @param array $indexQueueItemRecords Array of plain index queue records
869
     * @return array Array of ApacheSolrForTypo3\Solr\IndexQueue\Item objects
870
     */
871 25
    protected function getIndexQueueItemObjectsFromRecords(
872
        array $indexQueueItemRecords
873
    ) {
874 25
        $indexQueueItems = [];
875 25
        $tableUids = [];
876 25
        $tableRecords = [];
877
878
        // grouping records by table
879 25
        foreach ($indexQueueItemRecords as $indexQueueItemRecord) {
880 25
            $tableUids[$indexQueueItemRecord['item_type']][] = $indexQueueItemRecord['item_uid'];
881
        }
882
883
        // fetching records by table, saves us a lot of single queries
884 25
        foreach ($tableUids as $table => $uids) {
885 25
            $uidList = implode(',', $uids);
886 25
            $records = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
887 25
                '*',
888
                $table,
889 25
                'uid IN(' . $uidList . ')',
890 25
                '', '', '', // group, order, limit
891 25
                'uid'
892
            );
893 25
            $tableRecords[$table] = $records;
894
895 25
            if (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['postProcessFetchRecordsForIndexQueueItem'])) {
896
                $params = ['table' => $table, 'uids' => $uids, 'tableRecords' => &$tableRecords];
897
                foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['postProcessFetchRecordsForIndexQueueItem'] as $reference) {
898
                    GeneralUtility::callUserFunction($reference, $params, $this);
899
                }
900 25
                unset($params);
901
            }
902
        }
903
904
        // creating index queue item objects and assigning / mapping
905
        // records to index queue items
906 25
        foreach ($indexQueueItemRecords as $indexQueueItemRecord) {
907 25
            if (isset($tableRecords[$indexQueueItemRecord['item_type']][$indexQueueItemRecord['item_uid']])) {
908 24
                $indexQueueItems[] = GeneralUtility::makeInstance(
909 24
                    Item::class,
910
                    $indexQueueItemRecord,
911 24
                    $tableRecords[$indexQueueItemRecord['item_type']][$indexQueueItemRecord['item_uid']]
912
                );
913
            } else {
914 1
                GeneralUtility::devLog('Record missing for Index Queue item. Item removed.',
915 1
                    'solr', 3, [$indexQueueItemRecord]);
916 1
                $this->deleteItem($indexQueueItemRecord['item_type'],
917 25
                    $indexQueueItemRecord['item_uid']);
918
            }
919
        }
920
921 25
        return $indexQueueItems;
922
    }
923
924
    /**
925
     * Marks an item as failed and causes the indexer to skip the item in the
926
     * next run.
927
     *
928
     * @param int|Item $item Either the item's Index Queue
929
     *      uid or the complete item
930
     * @param string $errorMessage Error message
931
     */
932
    public function markItemAsFailed($item, $errorMessage = '')
933
    {
934
        if ($item instanceof Item) {
935
            $itemUid = $item->getIndexQueueUid();
936
        } else {
937
            $itemUid = (int)$item;
938
        }
939
940
        if (empty($errorMessage)) {
941
            // simply set to "TRUE"
942
            $errorMessage = '1';
943
        }
944
945
        $GLOBALS['TYPO3_DB']->exec_UPDATEquery(
946
            'tx_solr_indexqueue_item',
947
            'uid = ' . $itemUid,
948
            [
949
                'errors' => $errorMessage
950
            ]
951
        );
952
    }
953
}
954