Passed
Pull Request — master (#1249)
by
unknown
19:13
created

Queue::updateItem()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 21
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 4.0047

Importance

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