Passed
Pull Request — master (#1140)
by
unknown
34:11
created

Queue::updateIndexTimeByItem()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 5
nc 1
nop 1
dl 0
loc 8
ccs 0
cts 0
cp 0
crap 2
rs 9.4285
c 0
b 0
f 0
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\Domain\Index\Queue\Statistic\QueueStatistic;
30
use ApacheSolrForTypo3\Solr\Site;
31
use ApacheSolrForTypo3\Solr\System\Cache\TwoLevelCache;
32
use ApacheSolrForTypo3\Solr\System\Logging\SolrLogManager;
33
use ApacheSolrForTypo3\Solr\Util;
34
use ApacheSolrForTypo3\Solr\Utility\DatabaseUtility;
35
use TYPO3\CMS\Backend\Utility\BackendUtility;
36
use TYPO3\CMS\Core\Utility\GeneralUtility;
37
38
/**
39
 * The Indexing Queue. It allows us to decouple from frontend indexing and
40
 * reacting to changes faster.
41
 *
42
 * @author Ingo Renner <[email protected]>
43
 */
44
class Queue
45
{
46
    /**
47
     * @var RootPageResolver
48
     */
49
    protected $rootPageResolver;
50
51
    /**
52
     * @var ConfigurationAwareRecordService
53
     */
54
    protected $recordService;
55
56
    /**
57
     * @var \ApacheSolrForTypo3\Solr\System\Logging\SolrLogManager
58
     */
59
    protected $logger = null;
60
61
    /**
62
     * Queue constructor.
63
     * @param RootPageResolver|null $rootPageResolver
64
     * @param ConfigurationAwareRecordService|null $recordService
65
     */
66 72
    public function __construct(RootPageResolver $rootPageResolver = null, ConfigurationAwareRecordService $recordService = null)
67
    {
68 72
        $this->logger = GeneralUtility::makeInstance(SolrLogManager::class, __CLASS__);
69 72
        $this->rootPageResolver = isset($rootPageResolver) ? $rootPageResolver : GeneralUtility::makeInstance(RootPageResolver::class);
70 72
        $this->recordService = isset($recordService) ? $recordService : GeneralUtility::makeInstance(ConfigurationAwareRecordService::class);
71 72
    }
72
73
    // FIXME some of the methods should be renamed to plural forms
74
    // FIXME singular form methods should deal with exactly one item only
75
76
    /**
77
     * Returns the timestamp of the last indexing run.
78
     *
79
     * @param int $rootPageId The root page uid for which to get
80
     *      the last indexed item id
81
     * @return int Timestamp of last index run.
82
     */
83
    public function getLastIndexTime($rootPageId)
84
    {
85
        $lastIndexTime = 0;
86
87
        $lastIndexedRow = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
88
            'indexed',
89
            'tx_solr_indexqueue_item',
90
            'root = ' . (int)$rootPageId,
91
            '',
92
            'indexed DESC',
93
            1
94
        );
95
96
        if ($lastIndexedRow[0]['indexed']) {
97
            $lastIndexTime = $lastIndexedRow[0]['indexed'];
98
        }
99
100
        return $lastIndexTime;
101
    }
102
103
    /**
104
     * Returns the uid of the last indexed item in the queue
105
     *
106
     * @param int $rootPageId The root page uid for which to get
107
     *      the last indexed item id
108
     * @return int The last indexed item's ID.
109
     */
110
    public function getLastIndexedItemId($rootPageId)
111
    {
112
        $lastIndexedItemId = 0;
113
114
        $lastIndexedItemRow = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
115
            'uid',
116
            'tx_solr_indexqueue_item',
117
            'root = ' . (int)$rootPageId,
118
            '',
119
            'indexed DESC',
120
            1
121
        );
122
        if ($lastIndexedItemRow[0]['uid']) {
123
            $lastIndexedItemId = $lastIndexedItemRow[0]['uid'];
124
        }
125
126
        return $lastIndexedItemId;
127
    }
128
129
    /**
130
     * Truncate and rebuild the tx_solr_indexqueue_item table. This is the most
131
     * complete way to force reindexing, or to build the Index Queue for the
132
     * first time. The Index Queue initialization is site-specific.
133
     *
134
     * @param Site $site The site to initialize
135
     * @param string $indexingConfigurationName Name of a specific
136
     *      indexing configuration
137
     * @return array An array of booleans, each representing whether the
138
     *      initialization for an indexing configuration was successful
139
     */
140 6
    public function initialize(Site $site, $indexingConfigurationName = '')
141
    {
142 6
        $indexingConfigurations = [];
143 6
        $initializationStatus = [];
144
145 6
        if (empty($indexingConfigurationName)) {
146 1
            $solrConfiguration = $site->getSolrConfiguration();
147 1
            $indexingConfigurations = $solrConfiguration->getEnabledIndexQueueConfigurationNames();
148
        } else {
149 5
            $indexingConfigurations[] = $indexingConfigurationName;
150
        }
151
152 6
        foreach ($indexingConfigurations as $indexingConfigurationName) {
153 6
            $initializationStatus[$indexingConfigurationName] = $this->initializeIndexingConfiguration(
154
                $site,
155
                $indexingConfigurationName
156
            );
157
        }
158
159 6
        if (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['postProcessIndexQueueInitialization'])) {
160
            foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['postProcessIndexQueueInitialization'] as $classReference) {
161
                $indexQueueInitializationPostProcessor = GeneralUtility::getUserObj($classReference);
162
163
                if ($indexQueueInitializationPostProcessor instanceof InitializationPostProcessor) {
164
                    $indexQueueInitializationPostProcessor->postProcessIndexQueueInitialization(
165
                        $site,
166
                        $indexingConfigurations,
167
                        $initializationStatus
168
                    );
169
                } else {
170
                    throw new \UnexpectedValueException(
171
                        get_class($indexQueueInitializationPostProcessor) .
172
                        ' must implement interface ' . InitializationPostProcessor::class,
173
                        1345815561
174
                    );
175
                }
176
            }
177
        }
178
179 6
        return $initializationStatus;
180
    }
181
182
    /**
183
     * Initializes the Index Queue for a specific indexing configuration.
184
     *
185
     * @param Site $site The site to initialize
186
     * @param string $indexingConfigurationName name of a specific
187
     *      indexing configuration
188
     * @return bool TRUE if the initialization was successful, FALSE otherwise
189
     */
190 6
    protected function initializeIndexingConfiguration(
191
        Site $site,
192
        $indexingConfigurationName
193
    ) {
194
        // clear queue
195 6
        $this->deleteItemsBySite($site, $indexingConfigurationName);
196
197 6
        $solrConfiguration = $site->getSolrConfiguration();
198
199 6
        $tableToIndex = $solrConfiguration->getIndexQueueTableNameOrFallbackToConfigurationName($indexingConfigurationName);
200 6
        $initializerClass = $solrConfiguration->getIndexQueueInitializerClassByConfigurationName($indexingConfigurationName);
201
202 6
        $initializer = GeneralUtility::makeInstance($initializerClass);
203
        /** @var $initializer \ApacheSolrForTypo3\Solr\IndexQueue\Initializer\AbstractInitializer */
204 6
        $initializer->setSite($site);
205 6
        $initializer->setType($tableToIndex);
206 6
        $initializer->setIndexingConfigurationName($indexingConfigurationName);
207
208 6
        $indexConfiguration = $solrConfiguration->getIndexQueueConfigurationByName($indexingConfigurationName);
209 6
        $initializer->setIndexingConfiguration($indexConfiguration);
210
211 6
        return $initializer->initialize();
212
    }
213
214
    /**
215
     * Gets the indexing configuration to use for an item.
216
     * Sometimes, when there are multiple configurations for a certain item type
217
     * (table) it can be hard or even impossible to find which one to use
218
     * though.
219
     * Currently selects the first indexing configuration where the name matches
220
     * the itemType or where the configured tbale is the same as the itemType.
221
     *
222
     * !!! Might return incorrect results for complex configurations !!!
223
     * Try to set the indexingConfiguration directly when using the updateItem()
224
     * method in such situations.
225
     *
226
     * @param string $itemType The item's type, usually a table name.
227
     * @param string $itemUid The item's uid, usually an integer uid, could be a
228
     *      different value for non-database-record types.
229
     * @param int $rootPageId The configuration's page tree's root page id.
230
     *      Optional, not needed for all types.
231
     * @return string The indexing configuration's name to use when indexing
232
     * @deprecated since 6.1 will be removed in 7.0
233
     * Use getIndexingConfigurationsByItem() now, which behaves
234
     * almost the same way but returns an array of configurations
235
     */
236
    protected function getIndexingConfigurationByItem(
237
        $itemType,
238
        $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...
239
        $rootPageId = null
240
    ) {
241
        GeneralUtility::logDeprecatedFunction();
242
        $indexingConfigurationName = '';
243
244
        $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...
245
        if (count($configurations) > 0) {
246
            $indexingConfigurationName = $configurations[0];
247
        }
248
249
        return $indexingConfigurationName;
250
    }
251
252
    /**
253
     * Gets the indexing configurations to use for an item.
254
     * Multiple configurations for a certain item type (table) might be available.
255
     *
256
     * @param string $itemType The item's type, usually a table name.
257
     * @param int $rootPageId The configuration's page tree's root page id.
258
     *      Optional, not needed for all types.
259
     * @return array<string> The indexing configurations names to use when indexing
260
     * @deprecated since 6.1 will be removed in 7.0
261
     */
262
    protected function getIndexingConfigurationsByItem(
263
        $itemType,
264
        $rootPageId = null
265
    ) {
266
        GeneralUtility::logDeprecatedFunction();
267
268
        $possibleIndexingConfigurationNames = [];
269
270
        if (!is_null($rootPageId)) {
271
            // get configuration for the root's branch
272
            $solrConfiguration = Util::getSolrConfigurationFromPageId($rootPageId);
273
            $possibleIndexingConfigurationNames = $solrConfiguration->getIndexQueueConfigurationNamesByTableName($itemType);
274
        }
275
276
        return $possibleIndexingConfigurationNames;
277
    }
278
279
    /**
280
     * Marks an item as needing (re)indexing.
281
     *
282
     * Like with Solr itself, there's no add method, just a simple update method
283
     * that handles the adds, too.
284
     *
285
     * The method creates or updates the index queue items for all related rootPageIds.
286
     *
287
     * @param string $itemType The item's type, usually a table name.
288
     * @param string $itemUid The item's uid, usually an integer uid, could be a
289
     *      different value for non-database-record types.
290
     * @param int $forcedChangeTime The change time for the item if set, otherwise
291
     *          value from getItemChangedTime() is used.
292
     */
293 49
    public function updateItem($itemType, $itemUid, $forcedChangeTime = 0)
294
    {
295 49
        $rootPageIds = $this->rootPageResolver->getResponsibleRootPageIds($itemType, $itemUid);
296 48
        foreach ($rootPageIds as $rootPageId) {
297 48
            $skipInvalidRootPage = $rootPageId === 0;
298 48
            if ($skipInvalidRootPage) {
299
                continue;
300
            }
301
302 48
            $solrConfiguration = Util::getSolrConfigurationFromPageId($rootPageId);
303 48
            $indexingConfiguration = $this->recordService->getIndexingConfigurationName($itemType, $itemUid, $solrConfiguration);
304 48
            $itemInQueueForRootPage = $this->containsItemWithRootPageId($itemType, $itemUid, $rootPageId);
305 48
            if ($itemInQueueForRootPage) {
306
                // update the existing queue item
307 10
                $this->updateExistingItem($itemType, $itemUid, $indexingConfiguration, $rootPageId, $forcedChangeTime);
308
            } else {
309
                // add the item since it's not in the queue yet
310 48
                $this->addNewItem($itemType, $itemUid, $indexingConfiguration, $rootPageId);
311
            }
312
        }
313 48
    }
314
315
    /**
316
     * Finds indexing errors for the current site
317
     *
318
     * @param Site $site
319
     * @return array Error items for the current site's Index Queue
320
     */
321
    public function getErrorsBySite(Site $site)
322
    {
323
        return $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
324
            'uid, item_type, item_uid, errors',
325
            'tx_solr_indexqueue_item',
326
            'errors NOT LIKE "" AND root = ' . $site->getRootPageId()
327
        );
328
    }
329
330
    /**
331
     * Resets all the errors for all index queue items.
332
     *
333
     * @return mixed
334
     */
335
    public function resetAllErrors()
336
    {
337
        return $GLOBALS['TYPO3_DB']->exec_UPDATEquery(
338
            'tx_solr_indexqueue_item',
339
            'errors NOT LIKE ""',
340
            ['errors' => '']
341
        );
342
    }
343
344
    /**
345
     * Updates an existing queue entry by $itemType $itemUid and $rootPageId.
346
     *
347
     * @param string $itemType  The item's type, usually a table name.
348
     * @param int $itemUid The item's uid, usually an integer uid, could be a
349
     *      different value for non-database-record types.
350
     * @param string $indexingConfiguration The name of the related indexConfiguration
351
     * @param int $rootPageId The uid of the rootPage
352
     * @param int $forcedChangeTime The forced change time that should be used for updating
353
     */
354 10
    protected function updateExistingItem($itemType, $itemUid, $indexingConfiguration, $rootPageId, $forcedChangeTime)
355
    {
356
        // update if that item is in the queue already
357
        $changes = [
358 10
            'changed' => ($forcedChangeTime > 0) ? $forcedChangeTime : $this->getItemChangedTime($itemType, $itemUid)
359
        ];
360
361 10
        if (!empty($indexingConfiguration)) {
362 10
            $changes['indexing_configuration'] = $indexingConfiguration;
363
        }
364
365 10
        $GLOBALS['TYPO3_DB']->exec_UPDATEquery(
366 10
            'tx_solr_indexqueue_item',
367 10
            'item_type = ' . $GLOBALS['TYPO3_DB']->fullQuoteStr($itemType, 'tx_solr_indexqueue_item') .
368 10
            ' AND item_uid = ' . (int)$itemUid . ' AND root = ' . (int)$rootPageId,
369
            $changes);
370 10
    }
371
372
    /**
373
     * Adds an item to the index queue.
374
     *
375
     * Not meant for public use.
376
     *
377
     * @param string $itemType The item's type, usually a table name.
378
     * @param string $itemUid The item's uid, usually an integer uid, could be a
379
     *      different value for non-database-record types.
380
     * @param string $indexingConfiguration The item's indexing configuration to use.
381
     *      Optional, overwrites existing / determined configuration.
382
     * @return void
383
     */
384 42
    private function addNewItem($itemType, $itemUid, $indexingConfiguration, $rootPageId)
385
    {
386 42
        $additionalRecordFields = '';
387 42
        if ($itemType == 'pages') {
388 29
            $additionalRecordFields = ', doktype, uid';
389
        }
390
391 42
        $record = $this->getRecordCached($itemType, $itemUid, $additionalRecordFields);
392
393 42
        if (empty($record) || ($itemType == 'pages' && !Util::isAllowedPageType($record, $indexingConfiguration))) {
394 1
            return;
395
        }
396
397
        $item = [
398 41
            'root' => $rootPageId,
399 41
            'item_type' => $itemType,
400 41
            'item_uid' => $itemUid,
401 41
            'changed' => $this->getItemChangedTime($itemType, $itemUid),
402 41
            'errors' => ''
403
        ];
404
405
        // make a backup of the current item
406 41
        $item['indexing_configuration'] = $indexingConfiguration;
407 41
        $GLOBALS['TYPO3_DB']->exec_INSERTquery('tx_solr_indexqueue_item', $item);
408 41
    }
409
410
    /**
411
     * Get record to be added in addNewItem
412
     *
413
     * @param string $itemType The item's type, usually a table name.
414
     * @param string $itemUid The item's uid, usually an integer uid, could be a
415
     *      different value for non-database-record types.
416
     * @param string $additionalRecordFields for sql-query
417
     *
418
     * @return array|NULL
419
     */
420 42
    protected function getRecordCached($itemType, $itemUid, $additionalRecordFields)
421
    {
422 42
        $cache = GeneralUtility::makeInstance(TwoLevelCache::class, 'cache_runtime');
423 42
        $cacheId = md5('Queue' . ':' . 'getRecordCached' . ':' . $itemType . ':' . $itemUid . ':' . 'pid' . $additionalRecordFields);
424
425 42
        $record = $cache->get($cacheId);
426 42
        if (empty($record)) {
427 42
            $record = BackendUtility::getRecord($itemType, $itemUid, 'pid' . $additionalRecordFields);
428 42
            $cache->set($cacheId, $record);
429
        }
430
431 42
        return $record;
432
    }
433
434
    /**
435
     * Determines the time for when an item should be indexed. This timestamp
436
     * is then stored in the changed column in the Index Queue.
437
     *
438
     * The changed timestamp usually is now - time(). For records which are set
439
     * to published at a later time, this timestamp is the start time. So if a
440
     * future start time has been set, that will be used to delay indexing
441
     * of an item.
442
     *
443
     * @param string $itemType The item's table name.
444
     * @param string $itemUid The item's uid, usually an integer uid, could be a
445
     *      different value for non-database-record types.
446
     * @return int Timestamp of the item's changed time or future start time
447
     */
448 47
    protected function getItemChangedTime($itemType, $itemUid)
449
    {
450 47
        $itemTypeHasStartTimeColumn = false;
451 47
        $changedTimeColumns = $GLOBALS['TCA'][$itemType]['ctrl']['tstamp'];
452 47
        $startTime = 0;
453 47
        $pageChangedTime = 0;
454
455 47
        if (!empty($GLOBALS['TCA'][$itemType]['ctrl']['enablecolumns']['starttime'])) {
456 47
            $itemTypeHasStartTimeColumn = true;
457 47
            $changedTimeColumns .= ', ' . $GLOBALS['TCA'][$itemType]['ctrl']['enablecolumns']['starttime'];
458
        }
459 47
        if ($itemType == 'pages') {
460
            // does not carry time information directly, but needed to support
461
            // canonical pages
462 34
            $changedTimeColumns .= ', content_from_pid';
463
        }
464
465 47
        $record = BackendUtility::getRecord($itemType, $itemUid, $changedTimeColumns);
466 47
        $itemChangedTime = $record[$GLOBALS['TCA'][$itemType]['ctrl']['tstamp']];
467
468 47
        if ($itemTypeHasStartTimeColumn) {
469 47
            $startTime = $record[$GLOBALS['TCA'][$itemType]['ctrl']['enablecolumns']['starttime']];
470
        }
471
472 47
        if ($itemType == 'pages') {
473 34
            $record['uid'] = $itemUid;
474
            // overrule the page's last changed time with the most recent
475
            //content element change
476 34
            $pageChangedTime = $this->getPageItemChangedTime($record);
477
        }
478
479 47
        $localizationsChangedTime = $this->getLocalizableItemChangedTime($itemType, $itemUid);
480
481
        // if start time exists and start time is higher than last changed timestamp
482
        // then set changed to the future start time to make the item
483
        // indexed at a later time
484 47
        $changedTime = max(
485
            $itemChangedTime,
486
            $pageChangedTime,
487
            $localizationsChangedTime,
488
            $startTime
489
        );
490
491 47
        return $changedTime;
492
    }
493
494
    /**
495
     * Gets the most recent changed time of a page's content elements
496
     *
497
     * @param array $page Partial page record
498
     * @return int Timestamp of the most recent content element change
499
     */
500 34
    protected function getPageItemChangedTime(array $page)
501
    {
502 34
        if (!empty($page['content_from_pid'])) {
503
            // canonical page, get the original page's last changed time
504
            $pageContentLastChangedTime = $this->getPageItemChangedTime(['uid' => $page['content_from_pid']]);
505
        } else {
506 34
            $pageContentLastChangedTime = $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow(
507 34
                'MAX(tstamp) AS changed_time',
508 34
                'tt_content',
509 34
                'pid = ' . (int)$page['uid']
510
            );
511 34
            $pageContentLastChangedTime = $pageContentLastChangedTime['changed_time'];
512
        }
513
514 34
        return $pageContentLastChangedTime;
515
    }
516
517
    /**
518
     * Gets the most recent changed time for an item taking into account
519
     * localized records.
520
     *
521
     * @param string $itemType The item's type, usually a table name.
522
     * @param string $itemUid The item's uid, usually an integer uid, could be a
523
     *      different value for non-database-record types.
524
     * @return int Timestamp of the most recent content element change
525
     */
526 47
    protected function getLocalizableItemChangedTime($itemType, $itemUid)
527
    {
528 47
        $localizedChangedTime = 0;
529
530 47
        if (isset($GLOBALS['TCA'][$itemType]['ctrl']['transOrigPointerField'])) {
531
            // table is localizable
532 13
            $translationOriginalPointerField = $GLOBALS['TCA'][$itemType]['ctrl']['transOrigPointerField'];
533
534 13
            $itemUid = intval($itemUid);
535 13
            $localizedChangedTime = $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow(
536 13
                'MAX(tstamp) AS changed_time',
537
                $itemType,
538 13
                "uid = $itemUid OR $translationOriginalPointerField = $itemUid"
539
            );
540 13
            $localizedChangedTime = $localizedChangedTime['changed_time'];
541
        }
542
543 47
        return $localizedChangedTime;
544
    }
545
546
    /**
547
     * Checks whether the Index Queue contains a specific item.
548
     *
549
     * @param string $itemType The item's type, usually a table name.
550
     * @param string $itemUid The item's uid, usually an integer uid, could be a
551
     *      different value for non-database-record types.
552
     * @return bool TRUE if the item is found in the queue, FALSE otherwise
553
     */
554 3
    public function containsItem($itemType, $itemUid)
555
    {
556 3
        $itemIsInQueue = (boolean)$GLOBALS['TYPO3_DB']->exec_SELECTcountRows(
557 3
            'uid',
558 3
            'tx_solr_indexqueue_item',
559 3
            'item_type = ' . $GLOBALS['TYPO3_DB']->fullQuoteStr($itemType,
560 3
                'tx_solr_indexqueue_item') .
561 3
            ' AND item_uid = ' . (int)$itemUid
562
        );
563
564 3
        return $itemIsInQueue;
565
    }
566
567
    /**
568
     * Checks whether the Index Queue contains a specific item.
569
     *
570
     * @param string $itemType The item's type, usually a table name.
571
     * @param string $itemUid The item's uid, usually an integer uid, could be a
572
     *      different value for non-database-record types.
573
     * @param integer $rootPageId
574
     * @return bool TRUE if the item is found in the queue, FALSE otherwise
575
     */
576 48
    public function containsItemWithRootPageId($itemType, $itemUid, $rootPageId)
577
    {
578 48
        $itemIsInQueue = (boolean)$GLOBALS['TYPO3_DB']->exec_SELECTcountRows(
579 48
            'uid',
580 48
            'tx_solr_indexqueue_item',
581 48
            'item_type = ' . $GLOBALS['TYPO3_DB']->fullQuoteStr($itemType,
582 48
                'tx_solr_indexqueue_item') .
583 48
            ' AND item_uid = ' . (int)$itemUid . ' AND root = ' . (int)$rootPageId
584
        );
585
586 48
        return $itemIsInQueue;
587
    }
588
589
    /**
590
     * Checks whether the Index Queue contains a specific item that has been
591
     * marked as indexed.
592
     *
593
     * @param string $itemType The item's type, usually a table name.
594
     * @param string $itemUid The item's uid, usually an integer uid, could be a
595
     *      different value for non-database-record types.
596
     * @return bool TRUE if the item is found in the queue and marked as
597
     *      indexed, FALSE otherwise
598
     */
599 3
    public function containsIndexedItem($itemType, $itemUid)
600
    {
601 3
        $itemIsInQueue = (boolean)$GLOBALS['TYPO3_DB']->exec_SELECTcountRows(
602 3
            'uid',
603 3
            'tx_solr_indexqueue_item',
604 3
            'item_type = ' . $GLOBALS['TYPO3_DB']->fullQuoteStr($itemType,
605 3
                'tx_solr_indexqueue_item') .
606 3
            ' AND item_uid = ' . (int)$itemUid .
607 3
            ' AND indexed > 0'
608
        );
609
610 3
        return $itemIsInQueue;
611
    }
612
613
    /**
614
     * Removes an item from the Index Queue.
615
     *
616
     * @param string $itemType The type of the item to remove, usually a table name.
617
     * @param int $itemUid The uid of the item to remove
618
     */
619 28
    public function deleteItem($itemType, $itemUid)
620
    {
621 28
        $uidList = [];
622
623
        // get the item uids to use them in the deletes afterwards
624 28
        $items = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
625 28
            'uid',
626 28
            'tx_solr_indexqueue_item',
627 28
            'item_type = ' . $GLOBALS['TYPO3_DB']->fullQuoteStr($itemType,
628 28
                'tx_solr_indexqueue_item') .
629 28
            ' AND item_uid = ' . intval($itemUid)
630
        );
631
632 28
        if (count($items)) {
633 9
            foreach ($items as $item) {
634 9
                $uidList[] = $item['uid'];
635
            }
636
637 9
            $GLOBALS['TYPO3_DB']->exec_DELETEquery(
638 9
                'tx_solr_indexqueue_item',
639 9
                'uid IN(' . implode(',', $uidList) . ')'
640
            );
641 9
            $GLOBALS['TYPO3_DB']->exec_DELETEquery(
642 9
                'tx_solr_indexqueue_indexing_property',
643 9
                'item_id IN(' . implode(',', $uidList) . ')'
644
            );
645
        }
646 28
    }
647
648
    /**
649
     * Removes all items of a certain type from the Index Queue.
650
     *
651
     * @param string $itemType The type of items to remove, usually a table name.
652
     */
653 1
    public function deleteItemsByType($itemType)
654
    {
655 1
        $uidList = [];
656
657
        // get the item uids to use them in the deletes afterwards
658 1
        $items = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
659 1
            'uid',
660 1
            'tx_solr_indexqueue_item',
661 1
            'item_type = ' . $GLOBALS['TYPO3_DB']->fullQuoteStr(
662
                $itemType,
663 1
                'tx_solr_indexqueue_item'
664
            )
665
        );
666
667 1
        if (count($items)) {
668 1
            foreach ($items as $item) {
669 1
                $uidList[] = $item['uid'];
670
            }
671
672 1
            $GLOBALS['TYPO3_DB']->exec_DELETEquery(
673 1
                'tx_solr_indexqueue_item',
674 1
                'uid IN(' . implode(',', $uidList) . ')'
675
            );
676 1
            $GLOBALS['TYPO3_DB']->exec_DELETEquery(
677 1
                'tx_solr_indexqueue_indexing_property',
678 1
                'item_id IN(' . implode(',', $uidList) . ')'
679
            );
680
        }
681 1
    }
682
683
    /**
684
     * Removes all items of a certain site from the Index Queue. Accepts an
685
     * optional parameter to limit the deleted items by indexing configuration.
686
     *
687
     * @param Site $site The site to remove items for.
688
     * @param string $indexingConfigurationName Name of a specific indexing
689
     *      configuration
690
     */
691 6
    public function deleteItemsBySite(
692
        Site $site,
693
        $indexingConfigurationName = ''
694
    ) {
695 6
        $rootPageConstraint = 'tx_solr_indexqueue_item.root = ' . $site->getRootPageId();
696
697 6
        $indexingConfigurationConstraint = '';
698 6
        if (!empty($indexingConfigurationName)) {
699
            $indexingConfigurationConstraint =
700
                ' AND tx_solr_indexqueue_item.indexing_configuration = \'' .
701 6
                $indexingConfigurationName . '\'';
702
        }
703
704 6
        DatabaseUtility::transactionStart();
705
        try {
706
            // reset Index Queue
707 6
            $result = $GLOBALS['TYPO3_DB']->exec_DELETEquery(
708 6
                'tx_solr_indexqueue_item',
709 6
                $rootPageConstraint . $indexingConfigurationConstraint
710
            );
711 6
            if (!$result) {
712
                throw new \RuntimeException(
713
                    'Failed to reset Index Queue for site ' . $site->getLabel(),
714
                    1412986560
715
                );
716
            }
717
718
            // reset Index Queue Properties
719
            $indexQueuePropertyResetQuery = '
720
                DELETE tx_solr_indexqueue_indexing_property.*
721
                FROM tx_solr_indexqueue_indexing_property
722
                INNER JOIN tx_solr_indexqueue_item
723
                    ON tx_solr_indexqueue_item.uid = tx_solr_indexqueue_indexing_property.item_id
724
                    AND ' .
725 6
                $rootPageConstraint .
726 6
                $indexingConfigurationConstraint;
727
728 6
            $result = $GLOBALS['TYPO3_DB']->sql_query($indexQueuePropertyResetQuery);
729 6
            if (!$result) {
730
                throw new \RuntimeException(
731
                    'Failed to reset Index Queue properties for site ' . $site->getLabel(),
732
                    1412986604
733
                );
734
            }
735
736 6
            DatabaseUtility::transactionCommit();
737
        } catch (\RuntimeException $e) {
738
            DatabaseUtility::transactionRollback();
739
        }
740 6
    }
741
742
    /**
743
     * Removes all items from the Index Queue.
744
     *
745
     */
746 1
    public function deleteAllItems()
747
    {
748 1
        $GLOBALS['TYPO3_DB']->exec_TRUNCATEquery('tx_solr_indexqueue_item', '');
749 1
    }
750
751
    /**
752
     * Gets a single Index Queue item by its uid.
753
     *
754
     * @param int $itemId Index Queue item uid
755
     * @return Item The request Index Queue item or NULL
756
     *      if no item with $itemId was found
757
     */
758 11
    public function getItem($itemId)
759
    {
760 11
        $item = null;
761
762 11
        $indexQueueItemRecord = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
763 11
            '*',
764 11
            'tx_solr_indexqueue_item',
765 11
            'uid = ' . intval($itemId)
766
        );
767
768 11
        if (count($indexQueueItemRecord) == 1) {
769 11
            $indexQueueItemRecord = $indexQueueItemRecord[0];
770 11
            $item = GeneralUtility::makeInstance(
771 11
                Item::class,
772
                $indexQueueItemRecord
773
            );
774
        }
775
776 11
        return $item;
777
    }
778
779
    /**
780
     * Gets Index Queue items by type and uid.
781
     *
782
     * @param string $itemType item type, usually  the table name
783
     * @param int $itemUid item uid
784
     * @return Item[] An array of items matching $itemType and $itemUid
785
     */
786 21
    public function getItems($itemType, $itemUid)
787
    {
788 21
        $indexQueueItemRecords = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
789 21
            '*',
790 21
            'tx_solr_indexqueue_item',
791 21
            'item_type = ' . $GLOBALS['TYPO3_DB']->fullQuoteStr($itemType,
792 21
                'tx_solr_indexqueue_item') .
793 21
            ' AND item_uid = ' . intval($itemUid)
794
        );
795
796 21
        return $this->getIndexQueueItemObjectsFromRecords($indexQueueItemRecords);
797
    }
798
799
    /**
800
     * Gets number of Index Queue items for a specific site / indexing configuration
801
     * optional parameter to limit the counted items by indexing configuration.
802
     *
803
     * @param Site $site The site to search for.
804
     * @param string $indexingConfigurationName name of a specific indexing
805
     *      configuration
806
     * @deprecated since 6.1 will be removed in 7.0 use getStatisticsBySite()->getTotalCount() please
807
     * @return int Number of items
808
     */
809
    public function getItemsCountBySite(Site $site, $indexingConfigurationName = '')
810
    {
811
        GeneralUtility::logDeprecatedFunction();
812
        return (int) $this->getStatisticsBySite($site, $indexingConfigurationName)->getTotalCount();
813
    }
814
815
    /**
816
     * Gets number of unprocessed Index Queue items for a specific site / indexing configuration
817
     * optional parameter to limit the counted items by indexing configuration.
818
     *
819
     * @param Site $site The site to search for.
820
     * @param string $indexingConfigurationName name of a specific indexing
821
     *      configuration
822
     * @deprecated since 6.1 will be removed in 7.0 use getStatisticsBySite()->getPendingCount() please
823
     * @return int Number of items.
824
     */
825
    public function getRemainingItemsCountBySite(Site $site, $indexingConfigurationName = '')
826
    {
827
        GeneralUtility::logDeprecatedFunction();
828
        return (int) $this->getStatisticsBySite($site, $indexingConfigurationName)->getPendingCount();
829
    }
830
831
    /**
832
     * Returns the number of items for all queues.
833
     *
834
     * @return int
835
     */
836 51
    public function getAllItemsCount()
837
    {
838 51
        return $this->getItemCount();
839
    }
840
841
    /**
842
     * @param string $where
843
     * @return int
844
     */
845 51
    private function getItemCount($where = '1=1')
846
    {
847
        /**  @var $db \TYPO3\CMS\Core\Database\DatabaseConnection */
848 51
        $db = $GLOBALS['TYPO3_DB'];
849
850 51
        return (int)$db->exec_SELECTcountRows('uid', 'tx_solr_indexqueue_item', $where);
851
    }
852
853
    /**
854
     * Extracts the number of pending, indexed and erroneous items from the
855
     * Index Queue.
856
     *
857
     * @param Site $site
858
     * @param string $indexingConfigurationName
859
     *
860
     * @return QueueStatistic
861
     */
862 5
    public function getStatisticsBySite(Site $site, $indexingConfigurationName = '')
863
    {
864 5
        $indexingConfigurationConstraint = $this->buildIndexConfigurationConstraint($indexingConfigurationName);
865 5
        $where = 'root = ' . (int)$site->getRootPageId() . $indexingConfigurationConstraint;
866
867 5
        $indexQueueStats = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
868
            'indexed < changed as pending,'
869
            . '(errors not like "") as failed,'
870 5
            . 'COUNT(*) as count',
871 5
            'tx_solr_indexqueue_item',
872
            $where,
873 5
            'pending, failed'
874
        );
875
            /** @var $statistic QueueStatistic */
876 5
        $statistic = GeneralUtility::makeInstance(QueueStatistic::class);
877
878 5
        foreach ($indexQueueStats as $row) {
879 5
            if ($row['failed'] == 1) {
880 1
                $statistic->setFailedCount((int) $row['count']);
881 5
            } elseif ($row['pending'] == 1) {
882 5
                $statistic->setPendingCount((int) $row['count']);
883
            } else {
884 5
                $statistic->setSuccessCount((int) $row['count']);
885
            }
886
        }
887
888 5
        return $statistic;
889
    }
890
891
    /**
892
     * Build a database constraint that limits to a certain indexConfigurationName
893
     *
894
     * @param string $indexingConfigurationName
895
     * @return string
896
     */
897 5
    protected function buildIndexConfigurationConstraint($indexingConfigurationName)
898
    {
899 5
        $indexingConfigurationConstraint = '';
900 5
        if (!empty($indexingConfigurationName)) {
901
            $indexingConfigurationConstraint = ' AND indexing_configuration = \'' . $indexingConfigurationName . '\'';
902
            return $indexingConfigurationConstraint;
903
        }
904 5
        return $indexingConfigurationConstraint;
905
    }
906
907
    /**
908
     * Gets $limit number of items to index for a particular $site.
909
     *
910
     * @param Site $site TYPO3 site
911
     * @param int $limit Number of items to get from the queue
912
     * @return Item[] Items to index to the given solr server
913
     */
914 7
    public function getItemsToIndex(Site $site, $limit = 50)
915
    {
916 7
        $itemsToIndex = [];
917
918
        // determine which items to index with this run
919 7
        $indexQueueItemRecords = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
920 7
            '*',
921 7
            'tx_solr_indexqueue_item',
922 7
            'root = ' . $site->getRootPageId() .
923 7
            ' AND changed > indexed' .
924 7
            ' AND changed <= ' . time() .
925 7
            ' AND errors = \'\'',
926 7
            '',
927 7
            'indexing_priority DESC, changed DESC, uid DESC',
928
            intval($limit)
929
        );
930 7
        if (!empty($indexQueueItemRecords)) {
931
            // convert queued records to index queue item objects
932 7
            $itemsToIndex = $this->getIndexQueueItemObjectsFromRecords($indexQueueItemRecords);
933
        }
934
935 7
        return $itemsToIndex;
936
    }
937
938
    /**
939
     * Creates an array of ApacheSolrForTypo3\Solr\IndexQueue\Item objects from an array of
940
     * index queue records.
941
     *
942
     * @param array $indexQueueItemRecords Array of plain index queue records
943
     * @return array Array of ApacheSolrForTypo3\Solr\IndexQueue\Item objects
944
     */
945 26
    protected function getIndexQueueItemObjectsFromRecords(
946
        array $indexQueueItemRecords
947
    ) {
948 26
        $indexQueueItems = [];
949 26
        $tableUids = [];
950 26
        $tableRecords = [];
951
952
        // grouping records by table
953 26
        foreach ($indexQueueItemRecords as $indexQueueItemRecord) {
954 26
            $tableUids[$indexQueueItemRecord['item_type']][] = $indexQueueItemRecord['item_uid'];
955
        }
956
957
        // fetching records by table, saves us a lot of single queries
958 26
        foreach ($tableUids as $table => $uids) {
959 26
            $uidList = implode(',', $uids);
960 26
            $records = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
961 26
                '*',
962
                $table,
963 26
                'uid IN(' . $uidList . ')',
964 26
                '', '', '', // group, order, limit
965 26
                'uid'
966
            );
967 26
            $tableRecords[$table] = $records;
968
969 26
            if (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['postProcessFetchRecordsForIndexQueueItem'])) {
970
                $params = ['table' => $table, 'uids' => $uids, 'tableRecords' => &$tableRecords];
971
                foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['postProcessFetchRecordsForIndexQueueItem'] as $reference) {
972
                    GeneralUtility::callUserFunction($reference, $params, $this);
973
                }
974 26
                unset($params);
975
            }
976
        }
977
978
        // creating index queue item objects and assigning / mapping
979
        // records to index queue items
980 26
        foreach ($indexQueueItemRecords as $indexQueueItemRecord) {
981 26
            if (isset($tableRecords[$indexQueueItemRecord['item_type']][$indexQueueItemRecord['item_uid']])) {
982 25
                $indexQueueItems[] = GeneralUtility::makeInstance(
983 25
                    Item::class,
984
                    $indexQueueItemRecord,
985 25
                    $tableRecords[$indexQueueItemRecord['item_type']][$indexQueueItemRecord['item_uid']]
986
                );
987
            } else {
988 1
                $this->logger->log(
989 1
                    SolrLogManager::ERROR,
990 1
                    'Record missing for Index Queue item. Item removed.',
991
                    [
992 1
                        $indexQueueItemRecord
993
                    ]
994
                );
995 1
                $this->deleteItem($indexQueueItemRecord['item_type'],
996 26
                    $indexQueueItemRecord['item_uid']);
997
            }
998
        }
999
1000 26
        return $indexQueueItems;
1001
    }
1002
1003
    /**
1004
     * Marks an item as failed and causes the indexer to skip the item in the
1005
     * next run.
1006
     *
1007
     * @param int|Item $item Either the item's Index Queue uid or the complete item
1008
     * @param string $errorMessage Error message
1009
     */
1010
    public function markItemAsFailed($item, $errorMessage = '')
1011
    {
1012
        if ($item instanceof Item) {
1013
            $itemUid = $item->getIndexQueueUid();
1014
        } else {
1015
            $itemUid = (int)$item;
1016
        }
1017
1018
        if (empty($errorMessage)) {
1019
            // simply set to "TRUE"
1020
            $errorMessage = '1';
1021
        }
1022
1023
        $GLOBALS['TYPO3_DB']->exec_UPDATEquery(
1024
            'tx_solr_indexqueue_item',
1025
            'uid = ' . $itemUid,
1026
            [
1027
                'errors' => $errorMessage
1028
            ]
1029
        );
1030
    }
1031
1032
    /**
1033
     * Sets the timestamp of when an item last has been indexed.
1034
     *
1035
     * @param Item $item
1036
     */
1037
    public function updateIndexTimeByItem(Item $item)
1038
    {
1039
        $GLOBALS['TYPO3_DB']->exec_UPDATEquery(
1040
            'tx_solr_indexqueue_item',
1041
            'uid = ' . (int)$item->getIndexQueueUid(),
1042
            ['indexed' => time()]
1043
        );
1044
    }
1045
}
1046