Passed
Push — master ( c306d6...ceddc6 )
by Timo
51:27 queued 28:43
created

Queue::getLastIndexedItemId()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 11
ccs 7
cts 7
cp 1
rs 9.4285
cc 2
eloc 6
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\QueueItemRepository;
28
use ApacheSolrForTypo3\Solr\Domain\Index\Queue\RecordMonitor\Helper\ConfigurationAwareRecordService;
29
use ApacheSolrForTypo3\Solr\Domain\Index\Queue\RecordMonitor\Helper\RootPageResolver;
30
use ApacheSolrForTypo3\Solr\Domain\Index\Queue\Statistic\QueueStatistic;
31
use ApacheSolrForTypo3\Solr\Domain\Index\Queue\Statistic\QueueStatisticsRepository;
32
use ApacheSolrForTypo3\Solr\Site;
33
use ApacheSolrForTypo3\Solr\System\Cache\TwoLevelCache;
34
use ApacheSolrForTypo3\Solr\System\Logging\SolrLogManager;
35
use ApacheSolrForTypo3\Solr\Util;
36
use TYPO3\CMS\Backend\Utility\BackendUtility;
37
use TYPO3\CMS\Core\Utility\GeneralUtility;
38
39
/**
40
 * The Indexing Queue. It allows us to decouple from frontend indexing and
41
 * reacting to changes faster.
42
 *
43
 * @author Ingo Renner <[email protected]>
44
 */
45
class Queue
46
{
47
    /**
48
     * @var RootPageResolver
49
     */
50
    protected $rootPageResolver;
51
52
    /**
53
     * @var ConfigurationAwareRecordService
54
     */
55
    protected $recordService;
56
57
    /**
58
     * @var \ApacheSolrForTypo3\Solr\System\Logging\SolrLogManager
59
     */
60
    protected $logger = null;
61
62
    /**
63
     * @var QueueItemRepository
64
     */
65
    protected $queueItemRepository;
66 102
67
    /**
68 102
     * @var QueueStatisticsRepository
69 102
     */
70 102
    protected $queueStatisticsRepository;
71 102
72
    /**
73
     * Queue constructor.
74
     * @param RootPageResolver|null $rootPageResolver
75
     * @param ConfigurationAwareRecordService|null $recordService
76
     * @param QueueItemRepository|null $queueItemRepository
77
     * @param QueueStatisticsRepository|null $queueStatisticsRepository
78
     */
79
    public function __construct(RootPageResolver $rootPageResolver = null, ConfigurationAwareRecordService $recordService = null, QueueItemRepository $queueItemRepository = null, QueueStatisticsRepository $queueStatisticsRepository = null)
80
    {
81
        $this->logger = GeneralUtility::makeInstance(SolrLogManager::class, __CLASS__);
82
        $this->rootPageResolver = isset($rootPageResolver) ? $rootPageResolver : GeneralUtility::makeInstance(RootPageResolver::class);
83 2
        $this->recordService = isset($recordService) ? $recordService : GeneralUtility::makeInstance(ConfigurationAwareRecordService::class);
84
        $this->queueItemRepository = isset($queueItemRepository) ? $queueItemRepository : GeneralUtility::makeInstance(QueueItemRepository::class);
85 2
        $this->queueStatisticsRepository = isset($queueStatisticsRepository) ? $queueStatisticsRepository : GeneralUtility::makeInstance(QueueStatisticsRepository::class);
86
    }
87 2
88
    // FIXME some of the methods should be renamed to plural forms
89 2
    // FIXME singular form methods should deal with exactly one item only
90 1
91
    /**
92
     * Returns the timestamp of the last indexing run.
93 2
     *
94
     * @param int $rootPageId The root page uid for which to get
95
     *      the last indexed item id
96
     * @return int Timestamp of last index run.
97
     */
98
    public function getLastIndexTime($rootPageId)
99
    {
100
        $lastIndexTime = 0;
101
102
        $lastIndexedRow = $this->queueItemRepository->findLastIndexedRow($rootPageId);
103 3
104
        if ($lastIndexedRow[0]['indexed']) {
105 3
            $lastIndexTime = $lastIndexedRow[0]['indexed'];
106
        }
107 3
108 3
        return $lastIndexTime;
109 2
    }
110
111
    /**
112 3
     * Returns the uid of the last indexed item in the queue
113
     *
114
     * @param int $rootPageId The root page uid for which to get
115
     *      the last indexed item id
116
     * @return int The last indexed item's ID.
117
     */
118
    public function getLastIndexedItemId($rootPageId)
119
    {
120
        $lastIndexedItemId = 0;
121 5
122
        $lastIndexedItemRow = $this->queueItemRepository->findLastIndexedRow($rootPageId);
123 5
        if ($lastIndexedItemRow[0]['uid']) {
124 5
            $lastIndexedItemId = $lastIndexedItemRow[0]['uid'];
125 5
        }
126 5
127 5
        return $lastIndexedItemId;
128 5
    }
129 5
130
    /**
131
     * Truncate and rebuild the tx_solr_indexqueue_item table. This is the most
132 5
     * complete way to force reindexing, or to build the Index Queue for the
133 3
     * first time. The Index Queue initialization is site-specific.
134
     *
135
     * @param Site $site The site to initialize
136 2
     * @param string $indexingConfigurationName Name of a specific
137
     *      indexing configuration
138
     * @return array An array of booleans, each representing whether the
139
     *      initialization for an indexing configuration was successful
140
     */
141
    public function initialize(Site $site, $indexingConfigurationName = '')
142
    {
143
        $indexingConfigurations = [];
144
        $initializationStatus = [];
145
146
        if (empty($indexingConfigurationName)) {
147
            $solrConfiguration = $site->getSolrConfiguration();
148
            $indexingConfigurations = $solrConfiguration->getEnabledIndexQueueConfigurationNames();
149
        } else {
150 6
            $indexingConfigurations[] = $indexingConfigurationName;
151
        }
152 6
153 6
        foreach ($indexingConfigurations as $indexingConfigurationName) {
154
            $initializationStatus[$indexingConfigurationName] = $this->initializeIndexingConfiguration(
155 6
                $site,
156 1
                $indexingConfigurationName
157 1
            );
158
        }
159 5
160
        if (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['postProcessIndexQueueInitialization'])) {
161
            foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['postProcessIndexQueueInitialization'] as $classReference) {
162 6
                $indexQueueInitializationPostProcessor = GeneralUtility::getUserObj($classReference);
163 6
164
                if ($indexQueueInitializationPostProcessor instanceof InitializationPostProcessor) {
165
                    $indexQueueInitializationPostProcessor->postProcessIndexQueueInitialization(
166
                        $site,
167
                        $indexingConfigurations,
168
                        $initializationStatus
169 6
                    );
170
                } else {
171
                    throw new \UnexpectedValueException(
172
                        get_class($indexQueueInitializationPostProcessor) .
173
                        ' must implement interface ' . InitializationPostProcessor::class,
174
                        1345815561
175
                    );
176
                }
177
            }
178
        }
179
180
        return $initializationStatus;
181
    }
182
183
    /**
184
     * Initializes the Index Queue for a specific indexing configuration.
185
     *
186
     * @param Site $site The site to initialize
187
     * @param string $indexingConfigurationName name of a specific
188
     *      indexing configuration
189 6
     * @return bool TRUE if the initialization was successful, FALSE otherwise
190
     */
191
    protected function initializeIndexingConfiguration(Site $site, $indexingConfigurationName)
192
    {
193
        // clear queue
194
        $this->deleteItemsBySite($site, $indexingConfigurationName);
195
196
        $solrConfiguration = $site->getSolrConfiguration();
197
198
        $tableToIndex = $solrConfiguration->getIndexQueueTableNameOrFallbackToConfigurationName($indexingConfigurationName);
199
        $initializerClass = $solrConfiguration->getIndexQueueInitializerClassByConfigurationName($indexingConfigurationName);
200 6
201
        $initializer = GeneralUtility::makeInstance($initializerClass);
202
        /** @var $initializer \ApacheSolrForTypo3\Solr\IndexQueue\Initializer\AbstractInitializer */
203
        $initializer->setSite($site);
204
        $initializer->setType($tableToIndex);
205 6
        $initializer->setIndexingConfigurationName($indexingConfigurationName);
206
207 6
        $indexConfiguration = $solrConfiguration->getIndexQueueConfigurationByName($indexingConfigurationName);
208
        $initializer->setIndexingConfiguration($indexConfiguration);
209 6
210 6
        return $initializer->initialize();
211
    }
212 6
213
    /**
214 6
     * Marks an item as needing (re)indexing.
215 6
     *
216 6
     * Like with Solr itself, there's no add method, just a simple update method
217
     * that handles the adds, too.
218 6
     *
219 6
     * The method creates or updates the index queue items for all related rootPageIds.
220
     *
221 6
     * @param string $itemType The item's type, usually a table name.
222
     * @param string $itemUid The item's uid, usually an integer uid, could be a different value for non-database-record types.
223
     * @param int $forcedChangeTime The change time for the item if set, otherwise value from getItemChangedTime() is used.
224
     */
225
    public function updateItem($itemType, $itemUid, $forcedChangeTime = 0)
226
    {
227
        $rootPageIds = $this->rootPageResolver->getResponsibleRootPageIds($itemType, $itemUid);
228
        foreach ($rootPageIds as $rootPageId) {
229
            $skipInvalidRootPage = $rootPageId === 0;
230
            if ($skipInvalidRootPage) {
231
                continue;
232
            }
233
234
            $solrConfiguration = Util::getSolrConfigurationFromPageId($rootPageId);
235
            $indexingConfiguration = $this->recordService->getIndexingConfigurationName($itemType, $itemUid, $solrConfiguration);
236
            $itemInQueueForRootPage = $this->containsItemWithRootPageId($itemType, $itemUid, $rootPageId);
237
            if ($itemInQueueForRootPage) {
238 56
                // update changed time if that item is in the queue already
239
                $changedTime = ($forcedChangeTime > 0) ? $forcedChangeTime : $this->getItemChangedTime($itemType, $itemUid);
240 56
                $this->queueItemRepository->updateExistingItemByItemTypeAndItemUidAndRootPageId($itemType, $itemUid, $rootPageId, $changedTime, $indexingConfiguration);
241 55
            } else {
242 55
                // add the item since it's not in the queue yet
243 55
                $this->addNewItem($itemType, $itemUid, $indexingConfiguration, $rootPageId);
244
            }
245
        }
246
    }
247 55
248 55
    /**
249 55
     * Finds indexing errors for the current site
250 55
     *
251
     * @param Site $site
252 11
     * @return array Error items for the current site's Index Queue
253
     */
254
    public function getErrorsBySite(Site $site)
255 55
    {
256
        return $this->queueItemRepository->findErrorsBySite($site);
257
    }
258 55
259
    /**
260
     * Resets all the errors for all index queue items.
261
     *
262
     * @return mixed
263
     */
264
    public function resetAllErrors()
265
    {
266
        return $this->queueItemRepository->flushAllErrors();
267
    }
268
269
    /**
270
     * Adds an item to the index queue.
271
     *
272
     * Not meant for public use.
273
     *
274
     * @param string $itemType The item's type, usually a table name.
275
     * @param string $itemUid The item's uid, usually an integer uid, could be a
276
     *      different value for non-database-record types.
277
     * @param string $indexingConfiguration The item's indexing configuration to use.
278
     *      Optional, overwrites existing / determined configuration.
279
     * @param $rootPageId
280
     * @return void
281
     */
282
    private function addNewItem($itemType, $itemUid, $indexingConfiguration, $rootPageId)
283
    {
284
        $additionalRecordFields = '';
285
        if ($itemType === 'pages') {
286
            $additionalRecordFields = ', doktype, uid';
287
        }
288
289
        $record = $this->getRecordCached($itemType, $itemUid, $additionalRecordFields);
290
291
        if (empty($record) || ($itemType === 'pages' && !Util::isAllowedPageType($record, $indexingConfiguration))) {
292
            return;
293
        }
294
295
        $changedTime = $this->getItemChangedTime($itemType, $itemUid);
296
297
        $this->queueItemRepository->add($itemType, $itemUid, $rootPageId, $changedTime, $indexingConfiguration);
298
    }
299 11
300
    /**
301
     * Get record to be added in addNewItem
302
     *
303 11
     * @param string $itemType The item's type, usually a table name.
304
     * @param string $itemUid The item's uid, usually an integer uid, could be a
305
     *      different value for non-database-record types.
306 11
     * @param string $additionalRecordFields for sql-query
307 11
     *
308
     * @return array|NULL
309
     */
310 11
    protected function getRecordCached($itemType, $itemUid, $additionalRecordFields)
311 11
    {
312 11
        $cache = GeneralUtility::makeInstance(TwoLevelCache::class, 'cache_runtime');
313 11
        $cacheId = md5('Queue' . ':' . 'getRecordCached' . ':' . $itemType . ':' . $itemUid . ':' . 'pid' . $additionalRecordFields);
314
315 11
        $record = $cache->get($cacheId);
316
        if (empty($record)) {
317
            $record = BackendUtility::getRecord($itemType, $itemUid, 'pid' . $additionalRecordFields);
318
            $cache->set($cacheId, $record);
319
        }
320
321
        return $record;
322
    }
323
324
    /**
325
     * Determines the time for when an item should be indexed. This timestamp
326
     * is then stored in the changed column in the Index Queue.
327
     *
328
     * The changed timestamp usually is now - time(). For records which are set
329 49
     * to published at a later time, this timestamp is the start time. So if a
330
     * future start time has been set, that will be used to delay indexing
331 49
     * of an item.
332 49
     *
333 30
     * @param string $itemType The item's table name.
334
     * @param string $itemUid The item's uid, usually an integer uid, could be a
335
     *      different value for non-database-record types.
336 49
     * @return int Timestamp of the item's changed time or future start time
337
     */
338 49
    protected function getItemChangedTime($itemType, $itemUid)
339 1
    {
340
        $itemTypeHasStartTimeColumn = false;
341
        $changedTimeColumns = $GLOBALS['TCA'][$itemType]['ctrl']['tstamp'];
342
        $startTime = 0;
343 48
        $pageChangedTime = 0;
344 48
345 48
        if (!empty($GLOBALS['TCA'][$itemType]['ctrl']['enablecolumns']['starttime'])) {
346 48
            $itemTypeHasStartTimeColumn = true;
347 48
            $changedTimeColumns .= ', ' . $GLOBALS['TCA'][$itemType]['ctrl']['enablecolumns']['starttime'];
348
        }
349
        if ($itemType === 'pages') {
350
            // does not carry time information directly, but needed to support
351 48
            // canonical pages
352 48
            $changedTimeColumns .= ', content_from_pid';
353 48
        }
354
355
        $record = BackendUtility::getRecord($itemType, $itemUid, $changedTimeColumns);
356
        $itemChangedTime = $record[$GLOBALS['TCA'][$itemType]['ctrl']['tstamp']];
357
358
        if ($itemTypeHasStartTimeColumn) {
359
            $startTime = $record[$GLOBALS['TCA'][$itemType]['ctrl']['enablecolumns']['starttime']];
360
        }
361
362
        if ($itemType === 'pages') {
363
            $record['uid'] = $itemUid;
364
            // overrule the page's last changed time with the most recent
365 49
            //content element change
366
            $pageChangedTime = $this->getPageItemChangedTime($record);
367 49
        }
368 49
369
        $localizationsChangedTime = $this->queueItemRepository->getLocalizableItemChangedTime($itemType, (int)$itemUid);
370 49
371 49
        // if start time exists and start time is higher than last changed timestamp
372 49
        // then set changed to the future start time to make the item
373 49
        // indexed at a later time
374
        $changedTime = max(
375
            $itemChangedTime,
376 49
            $pageChangedTime,
377
            $localizationsChangedTime,
378
            $startTime
379
        );
380
381
        return $changedTime;
382
    }
383
384
    /**
385
     * Gets the most recent changed time of a page's content elements
386
     *
387
     * @param array $page Partial page record
388
     * @return int Timestamp of the most recent content element change
389
     */
390
    protected function getPageItemChangedTime(array $page)
391
    {
392
        if (!empty($page['content_from_pid'])) {
393 54
            // canonical page, get the original page's last changed time
394
            return $this->queueItemRepository->getPageItemChangedTimeByPageUid((int)$page['content_from_pid']);
395 54
        }
396 54
        return $this->queueItemRepository->getPageItemChangedTimeByPageUid((int)$page['uid']);
397 54
    }
398 54
399
    /**
400 54
     * Checks whether the Index Queue contains a specific item.
401 54
     *
402 54
     * @param string $itemType The item's type, usually a table name.
403
     * @param string $itemUid The item's uid, usually an integer uid, could be a
404 54
     *      different value for non-database-record types.
405
     * @return bool TRUE if the item is found in the queue, FALSE otherwise
406
     */
407 35
    public function containsItem($itemType, $itemUid)
408
    {
409
        return $this->queueItemRepository->containsItem($itemType, (int)$itemUid);
410 54
    }
411 54
412
    /**
413 54
     * Checks whether the Index Queue contains a specific item.
414 54
     *
415
     * @param string $itemType The item's type, usually a table name.
416
     * @param string $itemUid The item's uid, usually an integer uid, could be a
417 54
     *      different value for non-database-record types.
418 35
     * @param integer $rootPageId
419
     * @return bool TRUE if the item is found in the queue, FALSE otherwise
420
     */
421 35
    public function containsItemWithRootPageId($itemType, $itemUid, $rootPageId)
422
    {
423
        return $this->queueItemRepository->containsItemWithRootPageId($itemType, (int)$itemUid, (int)$rootPageId);
424 54
    }
425
426
    /**
427
     * Checks whether the Index Queue contains a specific item that has been
428
     * marked as indexed.
429 54
     *
430
     * @param string $itemType The item's type, usually a table name.
431
     * @param string $itemUid The item's uid, usually an integer uid, could be a
432
     *      different value for non-database-record types.
433
     * @return bool TRUE if the item is found in the queue and marked as
434
     *      indexed, FALSE otherwise
435
     */
436 54
    public function containsIndexedItem($itemType, $itemUid)
437
    {
438
        return $this->queueItemRepository->containsIndexedItem($itemType, (int)$itemUid);
439
    }
440
441
    /**
442
     * Removes an item from the Index Queue.
443
     *
444
     * @param string $itemType The type of the item to remove, usually a table name.
445 35
     * @param int $itemUid The uid of the item to remove
446
     */
447 35
    public function deleteItem($itemType, $itemUid)
448
    {
449
        $this->queueItemRepository->deleteItem($itemType, (int)$itemUid);
450
    }
451 35
452 35
    /**
453 35
     * Removes all items of a certain type from the Index Queue.
454 35
     *
455
     * @param string $itemType The type of items to remove, usually a table name.
456 35
     */
457
    public function deleteItemsByType($itemType)
458
    {
459 35
        $this->queueItemRepository->deleteItemsByType($itemType);
460
    }
461
462
    /**
463
     * Removes all items of a certain site from the Index Queue. Accepts an
464
     * optional parameter to limit the deleted items by indexing configuration.
465
     *
466
     * @param Site $site The site to remove items for.
467
     * @param string $indexingConfigurationName Name of a specific indexing
468
     *      configuration
469
     */
470
    public function deleteItemsBySite(Site $site, $indexingConfigurationName = '')
471 54
    {
472
        $this->queueItemRepository->deleteItemsBySite($site, $indexingConfigurationName);
473 54
    }
474
475 54
    /**
476 35
     * Removes all items from the Index Queue.
477
     *
478
     */
479 54
    public function deleteAllItems()
480
    {
481 54
        $this->queueItemRepository->deleteAllItems();
482
    }
483 54
484 54
    /**
485 54
     * Gets a single Index Queue item by its uid.
486
     *
487 54
     * @param int $itemId Index Queue item uid
488
     * @return Item The request Index Queue item or NULL if no item with $itemId was found
489 54
     */
490
    public function getItem($itemId)
491
    {
492 54
        return $this->queueItemRepository->findItemByUid($itemId);
493
    }
494
495
    /**
496
     * Gets Index Queue items by type and uid.
497
     *
498
     * @param string $itemType item type, usually  the table name
499
     * @param int $itemUid item uid
500
     * @return Item[] An array of items matching $itemType and $itemUid
501
     */
502
    public function getItems($itemType, $itemUid)
503 6
    {
504
        return $this->queueItemRepository->findItemsByItemTypeAndItemUid($itemType, (int)$itemUid);
505 6
    }
506 6
507 6
    /**
508 6
     * Returns all items in the queue.
509 6
     *
510 6
     * @return Item[] An array of items
511
     */
512
    public function getAllItems()
513 6
    {
514
        return $this->queueItemRepository->findAll();
515
    }
516
517
    /**
518
     * Returns the number of items for all queues.
519
     *
520
     * @return int
521
     */
522
    public function getAllItemsCount()
523
    {
524
        return $this->queueItemRepository->count();
525 55
    }
526
527 55
    /**
528 55
     * Extracts the number of pending, indexed and erroneous items from the
529 55
     * Index Queue.
530 55
     *
531 55
     * @param Site $site
532 55
     * @param string $indexingConfigurationName
533
     *
534
     * @return QueueStatistic
535 55
     */
536
    public function getStatisticsBySite(Site $site, $indexingConfigurationName = '')
537
    {
538
        return $this->queueStatisticsRepository->findOneByRootPidAndOptionalIndexingConfigurationName($site->getRootPageId(), $indexingConfigurationName);
539
    }
540
541
    /**
542
     * Gets $limit number of items to index for a particular $site.
543
     *
544
     * @param Site $site TYPO3 site
545
     * @param int $limit Number of items to get from the queue
546
     * @return Item[] Items to index to the given solr server
547
     */
548 3
    public function getItemsToIndex(Site $site, $limit = 50)
549
    {
550 3
        return $this->queueItemRepository->findItemsToIndex($site, $limit);
551 3
    }
552 3
553 3
    /**
554 3
     * Marks an item as failed and causes the indexer to skip the item in the
555 3
     * next run.
556 3
     *
557
     * @param int|Item $item Either the item's Index Queue uid or the complete item
558
     * @param string $errorMessage Error message
559 3
     */
560
    public function markItemAsFailed($item, $errorMessage = '')
561
    {
562
        $this->queueItemRepository->markItemAsFailed($item, $errorMessage);
563
    }
564
565
    /**
566
     * Sets the timestamp of when an item last has been indexed.
567
     *
568 29
     * @param Item $item
569
     */
570 29
    public function updateIndexTimeByItem(Item $item)
571
    {
572
        $this->queueItemRepository->updateIndexTimeByItem($item);
573 29
    }
574
}
575