Passed
Pull Request — master (#1162)
by
unknown
21:35
created

Queue::getLastIndexedRow()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 17
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 2

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 17
ccs 11
cts 11
cp 1
rs 9.4285
cc 2
eloc 11
nc 2
nop 1
crap 2
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 76
    public function __construct(RootPageResolver $rootPageResolver = null, ConfigurationAwareRecordService $recordService = null)
67
    {
68 76
        $this->logger = GeneralUtility::makeInstance(SolrLogManager::class, __CLASS__);
69 76
        $this->rootPageResolver = isset($rootPageResolver) ? $rootPageResolver : GeneralUtility::makeInstance(RootPageResolver::class);
70 76
        $this->recordService = isset($recordService) ? $recordService : GeneralUtility::makeInstance(ConfigurationAwareRecordService::class);
71 76
    }
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 2
    public function getLastIndexedItemId($rootPageId)
104
    {
105 2
        $lastIndexedItemId = 0;
106
107 2
        $lastIndexedItemRow = $this->getLastIndexedRow($rootPageId);
108 2
        if ($lastIndexedItemRow[0]['uid']) {
109 1
            $lastIndexedItemId = $lastIndexedItemRow[0]['uid'];
110 1
        }
111
112 2
        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 4
    protected function getLastIndexedRow($rootPageId)
122
    {
123 4
        $row = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
124 4
            'uid, indexed',
125 4
            'tx_solr_indexqueue_item',
126 4
            'root = ' . (int)$rootPageId,
127 4
            '',
128 4
            'indexed DESC',
129
            1
130 4
        );
131
132 4
        if ($row[0]['uid']) {
133 2
            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 49
    public function updateItem($itemType, $itemUid, $forcedChangeTime = 0)
304
    {
305 49
        $rootPageIds = $this->rootPageResolver->getResponsibleRootPageIds($itemType, $itemUid);
306 48
        foreach ($rootPageIds as $rootPageId) {
307 48
            $skipInvalidRootPage = $rootPageId === 0;
308 48
            if ($skipInvalidRootPage) {
309
                continue;
310
            }
311
312 48
            $solrConfiguration = Util::getSolrConfigurationFromPageId($rootPageId);
313 48
            $indexingConfiguration = $this->recordService->getIndexingConfigurationName($itemType, $itemUid, $solrConfiguration);
314 48
            $itemInQueueForRootPage = $this->containsItemWithRootPageId($itemType, $itemUid, $rootPageId);
315 48
            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 42
                $this->addNewItem($itemType, $itemUid, $indexingConfiguration, $rootPageId);
321
            }
322 48
        }
323 48
    }
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 42
    private function addNewItem($itemType, $itemUid, $indexingConfiguration, $rootPageId)
395
    {
396 42
        $additionalRecordFields = '';
397 42
        if ($itemType == 'pages') {
398 29
            $additionalRecordFields = ', doktype, uid';
399 29
        }
400
401 42
        $record = $this->getRecordCached($itemType, $itemUid, $additionalRecordFields);
402
403 42
        if (empty($record) || ($itemType == 'pages' && !Util::isAllowedPageType($record, $indexingConfiguration))) {
404 1
            return;
405
        }
406
407
        $item = [
408 41
            'root' => $rootPageId,
409 41
            'item_type' => $itemType,
410 41
            'item_uid' => $itemUid,
411 41
            'changed' => $this->getItemChangedTime($itemType, $itemUid),
412
            'errors' => ''
413 41
        ];
414
415
        // make a backup of the current item
416 41
        $item['indexing_configuration'] = $indexingConfiguration;
417 41
        $GLOBALS['TYPO3_DB']->exec_INSERTquery('tx_solr_indexqueue_item', $item);
418 41
    }
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 42
    protected function getRecordCached($itemType, $itemUid, $additionalRecordFields)
431
    {
432 42
        $cache = GeneralUtility::makeInstance(TwoLevelCache::class, 'cache_runtime');
433 42
        $cacheId = md5('Queue' . ':' . 'getRecordCached' . ':' . $itemType . ':' . $itemUid . ':' . 'pid' . $additionalRecordFields);
434
435 42
        $record = $cache->get($cacheId);
436 42
        if (empty($record)) {
437 42
            $record = BackendUtility::getRecord($itemType, $itemUid, 'pid' . $additionalRecordFields);
438 42
            $cache->set($cacheId, $record);
439 42
        }
440
441 42
        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 47
    protected function getItemChangedTime($itemType, $itemUid)
459
    {
460 47
        $itemTypeHasStartTimeColumn = false;
461 47
        $changedTimeColumns = $GLOBALS['TCA'][$itemType]['ctrl']['tstamp'];
462 47
        $startTime = 0;
463 47
        $pageChangedTime = 0;
464
465 47
        if (!empty($GLOBALS['TCA'][$itemType]['ctrl']['enablecolumns']['starttime'])) {
466 47
            $itemTypeHasStartTimeColumn = true;
467 47
            $changedTimeColumns .= ', ' . $GLOBALS['TCA'][$itemType]['ctrl']['enablecolumns']['starttime'];
468 47
        }
469 47
        if ($itemType == 'pages') {
470
            // does not carry time information directly, but needed to support
471
            // canonical pages
472 34
            $changedTimeColumns .= ', content_from_pid';
473 34
        }
474
475 47
        $record = BackendUtility::getRecord($itemType, $itemUid, $changedTimeColumns);
476 47
        $itemChangedTime = $record[$GLOBALS['TCA'][$itemType]['ctrl']['tstamp']];
477
478 47
        if ($itemTypeHasStartTimeColumn) {
479 47
            $startTime = $record[$GLOBALS['TCA'][$itemType]['ctrl']['enablecolumns']['starttime']];
480 47
        }
481
482 47
        if ($itemType == 'pages') {
483 34
            $record['uid'] = $itemUid;
484
            // overrule the page's last changed time with the most recent
485
            //content element change
486 34
            $pageChangedTime = $this->getPageItemChangedTime($record);
487 34
        }
488
489 47
        $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 47
        $changedTime = max(
495 47
            $itemChangedTime,
496 47
            $pageChangedTime,
497 47
            $localizationsChangedTime,
498
            $startTime
499 47
        );
500
501 47
        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 34
    protected function getPageItemChangedTime(array $page)
511
    {
512 34
        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 34
            $pageContentLastChangedTime = $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow(
517 34
                'MAX(tstamp) AS changed_time',
518 34
                'tt_content',
519 34
                'pid = ' . (int)$page['uid']
520 34
            );
521 34
            $pageContentLastChangedTime = $pageContentLastChangedTime['changed_time'];
522
        }
523
524 34
        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 47
    protected function getLocalizableItemChangedTime($itemType, $itemUid)
537
    {
538 47
        $localizedChangedTime = 0;
539
540 47
        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 47
        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 48
    public function containsItemWithRootPageId($itemType, $itemUid, $rootPageId)
587
    {
588 48
        $itemIsInQueue = (boolean)$GLOBALS['TYPO3_DB']->exec_SELECTcountRows(
589 48
            'uid',
590 48
            'tx_solr_indexqueue_item',
591 48
            'item_type = ' . $GLOBALS['TYPO3_DB']->fullQuoteStr($itemType,
592 48
                'tx_solr_indexqueue_item') .
593 48
            ' AND item_uid = ' . (int)$itemUid . ' AND root = ' . (int)$rootPageId
594 48
        );
595
596 48
        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 28
    public function deleteItem($itemType, $itemUid)
630
    {
631 28
        $uidList = [];
632
633
        // get the item uids to use them in the deletes afterwards
634 28
        $items = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
635 28
            'uid',
636 28
            'tx_solr_indexqueue_item',
637 28
            'item_type = ' . $GLOBALS['TYPO3_DB']->fullQuoteStr($itemType,
638 28
                'tx_solr_indexqueue_item') .
639 28
            ' AND item_uid = ' . intval($itemUid)
640 28
        );
641
642 28
        if (count($items)) {
643 9
            foreach ($items as $item) {
644 9
                $uidList[] = $item['uid'];
645 9
            }
646
647 9
            $GLOBALS['TYPO3_DB']->exec_DELETEquery(
648 9
                'tx_solr_indexqueue_item',
649 9
                'uid IN(' . implode(',', $uidList) . ')'
650 9
            );
651 9
            $GLOBALS['TYPO3_DB']->exec_DELETEquery(
652 9
                'tx_solr_indexqueue_indexing_property',
653 9
                'item_id IN(' . implode(',', $uidList) . ')'
654 9
            );
655 9
        }
656 28
    }
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 11
    public function getItem($itemId)
769
    {
770 11
        $item = null;
771
772 11
        $indexQueueItemRecord = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
773 11
            '*',
774 11
            'tx_solr_indexqueue_item',
775 11
            'uid = ' . intval($itemId)
776 11
        );
777
778 11
        if (count($indexQueueItemRecord) == 1) {
779 11
            $indexQueueItemRecord = $indexQueueItemRecord[0];
780 11
            $item = GeneralUtility::makeInstance(
781 11
                Item::class,
782
                $indexQueueItemRecord
783 11
            );
784 11
        }
785
786 11
        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 21
    public function getItems($itemType, $itemUid)
797
    {
798 21
        $indexQueueItemRecords = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
799 21
            '*',
800 21
            'tx_solr_indexqueue_item',
801 21
            'item_type = ' . $GLOBALS['TYPO3_DB']->fullQuoteStr($itemType,
802 21
                'tx_solr_indexqueue_item') .
803 21
            ' AND item_uid = ' . intval($itemUid)
804 21
        );
805
806 21
        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 55
    public function getAllItemsCount()
847
    {
848 55
        return $this->getItemCount();
849
    }
850
851
    /**
852
     * @param string $where
853
     * @return int
854
     */
855 55
    private function getItemCount($where = '1=1')
856
    {
857
        /**  @var $db \TYPO3\CMS\Core\Database\DatabaseConnection */
858 55
        $db = $GLOBALS['TYPO3_DB'];
859
860 55
        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 26
    protected function getIndexQueueItemObjectsFromRecords(
956
        array $indexQueueItemRecords
957
    ) {
958 26
        $indexQueueItems = [];
959 26
        $tableUids = [];
960 26
        $tableRecords = [];
961
962
        // grouping records by table
963 26
        foreach ($indexQueueItemRecords as $indexQueueItemRecord) {
964 26
            $tableUids[$indexQueueItemRecord['item_type']][] = $indexQueueItemRecord['item_uid'];
965 26
        }
966
967
        // fetching records by table, saves us a lot of single queries
968 26
        foreach ($tableUids as $table => $uids) {
969 26
            $uidList = implode(',', $uids);
970 26
            $records = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
971 26
                '*',
972 26
                $table,
973 26
                'uid IN(' . $uidList . ')',
974 26
                '', '', '', // group, order, limit
975
                'uid'
976 26
            );
977 26
            $tableRecords[$table] = $records;
978
979 26
            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 26
        }
987
988
        // creating index queue item objects and assigning / mapping
989
        // records to index queue items
990 26
        foreach ($indexQueueItemRecords as $indexQueueItemRecord) {
991 26
            if (isset($tableRecords[$indexQueueItemRecord['item_type']][$indexQueueItemRecord['item_uid']])) {
992 25
                $indexQueueItems[] = GeneralUtility::makeInstance(
993 25
                    Item::class,
994 25
                    $indexQueueItemRecord,
995 25
                    $tableRecords[$indexQueueItemRecord['item_type']][$indexQueueItemRecord['item_uid']]
996 25
                );
997 25
            } 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 26
        }
1009
1010 26
        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
    public function markItemAsFailed($item, $errorMessage = '')
1021
    {
1022
        if ($item instanceof Item) {
1023
            $itemUid = $item->getIndexQueueUid();
1024
        } else {
1025
            $itemUid = (int)$item;
1026
        }
1027
1028
        if (empty($errorMessage)) {
1029
            // simply set to "TRUE"
1030
            $errorMessage = '1';
1031
        }
1032
1033
        $GLOBALS['TYPO3_DB']->exec_UPDATEquery(
1034
            'tx_solr_indexqueue_item',
1035
            'uid = ' . $itemUid,
1036
            [
1037
                'errors' => $errorMessage
1038
            ]
1039
        );
1040
    }
1041
1042
    /**
1043
     * Sets the timestamp of when an item last has been indexed.
1044
     *
1045
     * @param Item $item
1046
     */
1047 5
    public function updateIndexTimeByItem(Item $item)
1048
    {
1049 5
        $GLOBALS['TYPO3_DB']->exec_UPDATEquery(
1050 5
            'tx_solr_indexqueue_item',
1051 5
            'uid = ' . (int)$item->getIndexQueueUid(),
1052 5
            ['indexed' => time()]
1053 5
        );
1054 5
    }
1055
}
1056