Completed
Push — master ( cca011...7d5329 )
by Timo
16:43
created

Queue::initializeIndexingConfiguration()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 23
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 1

Importance

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