Completed
Branch master (b22b71)
by Timo
13:13 queued 21s
created

Queue::markItemAsFailed()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 21
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

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