Completed
Push — master ( 3d2504...b6b629 )
by Timo
13:22
created

Queue::getAllItemsCount()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

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