Passed
Push — master ( 90cea8...3d6c73 )
by Timo
01:26
created

Queue::getIndexingConfigurationsByItem()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 16
ccs 0
cts 0
cp 0
rs 9.4285
cc 2
eloc 9
nc 2
nop 2
crap 6

1 Method

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