Completed
Push — master ( 034cdd...f94280 )
by Timo
43s
created

Queue::getItemCount()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

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