QueueItemRepository::flushAllErrors()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 3
c 1
b 0
f 0
dl 0
loc 5
ccs 3
cts 3
cp 1
rs 10
cc 1
nc 1
nop 0
crap 1
1
<?php declare(strict_types = 1);
2
namespace ApacheSolrForTypo3\Solr\Domain\Index\Queue;
3
4
/***************************************************************
5
 *  Copyright notice
6
 *
7
 *  (c) 2010-2017 dkd Internet Service GmbH <[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 3 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\Item;
28
use ApacheSolrForTypo3\Solr\Domain\Site\Site;
29
use ApacheSolrForTypo3\Solr\System\Logging\SolrLogManager;
30
use ApacheSolrForTypo3\Solr\System\Records\AbstractRepository;
31
use Doctrine\DBAL\DBALException;
32
use TYPO3\CMS\Core\Database\ConnectionPool;
33
use TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression;
34
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
35
use TYPO3\CMS\Core\Utility\GeneralUtility;
36
37
/**
38
 * Class QueueItemRepository
39
 * Handles all CRUD operations to tx_solr_indexqueue_item table
40
 *
41
 */
42
class QueueItemRepository extends AbstractRepository
43
{
44
    /**
45
     * @var string
46
     */
47
    protected $table = 'tx_solr_indexqueue_item';
48
49
    /**
50
     * @var SolrLogManager
51
     */
52
    protected $logger;
53
54
    /**
55
     * QueueItemRepository constructor.
56
     *
57
     * @param SolrLogManager|null $logManager
58 123
     */
59
    public function __construct(SolrLogManager $logManager = null)
60 123
    {
61 123
        $this->logger = $logManager ?? GeneralUtility::makeInstance(SolrLogManager::class, /** @scrutinizer ignore-type */ __CLASS__);
62
    }
63
64
    /**
65
     * Fetches the last indexed row
66
     *
67
     * @param int $rootPageId The root page uid for which to get the last indexed row
68
     * @return array
69 5
     */
70
    public function findLastIndexedRow(int $rootPageId) : array
71 5
    {
72
        $queryBuilder = $this->getQueryBuilder();
73 5
        $row = $queryBuilder
74 5
            ->select('uid', 'indexed')
75 5
            ->from($this->table)
76 5
            ->where($queryBuilder->expr()->eq('root', $rootPageId))
77 5
            ->andWhere($queryBuilder->expr()->neq('indexed', 0))
78 5
            ->orderBy('indexed', 'DESC')
79 5
            ->setMaxResults(1)
80
            ->execute()->fetchAll();
81 5
82
        return $row;
83
    }
84
85
    /**
86
     * Finds indexing errors for the current site
87
     *
88
     * @param Site $site
89
     * @return array Error items for the current site's Index Queue
90 3
     */
91
    public function findErrorsBySite(Site $site) : array
92 3
    {
93
        $queryBuilder = $this->getQueryBuilder();
94 3
        $errors = $queryBuilder
95 3
            ->select('uid', 'item_type', 'item_uid', 'errors')
96 3
            ->from($this->table)
97 3
            ->andWhere(
98 3
                $queryBuilder->expr()->notLike('errors', $queryBuilder->createNamedParameter('')),
99
                $queryBuilder->expr()->eq('root', $site->getRootPageId())
100 3
            )
101
            ->execute()->fetchAll();
102 3
103
        return $errors;
104
    }
105
106
    /**
107
     * Resets all the errors for all index queue items.
108
     *
109
     * @return int affected rows
110 1
     */
111
    public function flushAllErrors() : int
112 1
    {
113 1
        $queryBuilder = $this->getQueryBuilder();
114 1
        $affectedRows = $this->getPreparedFlushErrorQuery($queryBuilder)->execute();
115
        return $affectedRows;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $affectedRows could return the type Doctrine\DBAL\Driver\Statement which is incompatible with the type-hinted return integer. Consider adding an additional type-check to rule them out.
Loading history...
116
    }
117
118
    /**
119
     * Flushes the errors for a single site.
120
     *
121
     * @param Site $site
122
     * @return int
123 1
     */
124
    public function flushErrorsBySite(Site $site) : int
125 1
    {
126 1
        $queryBuilder = $this->getQueryBuilder();
127 1
        $affectedRows = $this->getPreparedFlushErrorQuery($queryBuilder)
128 1
            ->andWhere(
129
                $queryBuilder->expr()->eq('root', (int)$site->getRootPageId())
130 1
            )
131 1
            ->execute();
132
        return $affectedRows;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $affectedRows could return the type Doctrine\DBAL\Driver\Statement which is incompatible with the type-hinted return integer. Consider adding an additional type-check to rule them out.
Loading history...
133
    }
134
135
    /**
136
     * Flushes the error for a single item.
137
     *
138
     * @param Item $item
139
     * @return int affected rows
140 2
     */
141
    public function flushErrorByItem(Item $item) : int
142 2
    {
143 2
        $queryBuilder = $this->getQueryBuilder();
144 2
        $affectedRows = $this->getPreparedFlushErrorQuery($queryBuilder)
145 2
            ->andWhere(
146
                $queryBuilder->expr()->eq('uid', $item->getIndexQueueUid())
147 2
            )
148 2
            ->execute();
149
        return $affectedRows;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $affectedRows could return the type Doctrine\DBAL\Driver\Statement which is incompatible with the type-hinted return integer. Consider adding an additional type-check to rule them out.
Loading history...
150
    }
151
152
    /**
153
     * Initializes the QueryBuilder with a query the resets the error field for items that have an error.
154
     *
155
     * @return QueryBuilder
156 4
     */
157
    private function getPreparedFlushErrorQuery(QueryBuilder $queryBuilder)
158
    {
159 4
        return $queryBuilder
160 4
            ->update($this->table)
161 4
            ->set('errors', '')
162 4
            ->where(
163
                $queryBuilder->expr()->notLike('errors', $queryBuilder->createNamedParameter(''))
164
            );
165
    }
166
167
    /**
168
     * Updates an existing queue entry by $itemType $itemUid and $rootPageId.
169
     *
170
     * @param string $itemType The item's type, usually a table name.
171
     * @param int $itemUid The item's uid, usually an integer uid, could be a
172
     *      different value for non-database-record types.
173
     * @param string $indexingConfiguration The name of the related indexConfiguration
174
     * @param int $rootPageId The uid of the rootPage
175
     * @param int $changedTime The forced change time that should be used for updating
176
     * @return int affected rows
177 10
     */
178
    public function updateExistingItemByItemTypeAndItemUidAndRootPageId(string $itemType, int $itemUid, int $rootPageId, int $changedTime, string $indexingConfiguration = '') : int
179 10
    {
180
        $queryBuilder = $this->getQueryBuilder();
181 10
        $queryBuilder
182 10
            ->update($this->table)
183 10
            ->set('changed', $changedTime)
184 10
            ->andWhere(
185 10
                $queryBuilder->expr()->eq('item_type', $queryBuilder->createNamedParameter($itemType)),
186 10
                $queryBuilder->expr()->eq('item_uid', $itemUid),
187
                $queryBuilder->expr()->eq('root', $rootPageId)
188
            );
189 10
190 10
        if (!empty($indexingConfiguration)) {
191
            $queryBuilder->set('indexing_configuration', $indexingConfiguration);
192
        }
193 10
194
        return $queryBuilder->execute();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $queryBuilder->execute() could return the type Doctrine\DBAL\Driver\Statement which is incompatible with the type-hinted return integer. Consider adding an additional type-check to rule them out.
Loading history...
195
    }
196
197
    /**
198
     * Adds an item to the index queue.
199
     *
200
     * Not meant for public use.
201
     *
202
     * @param string $itemType The item's type, usually a table name.
203
     * @param int $itemUid The item's uid, usually an integer uid, could be a different value for non-database-record types.
204
     * @param int $rootPageId
205
     * @param int $changedTime
206
     * @param string $indexingConfiguration The item's indexing configuration to use. Optional, overwrites existing / determined configuration.
207
     * @return int the number of inserted rows, which is typically 1
208 52
     */
209
    public function add(string $itemType, int $itemUid, int $rootPageId, int $changedTime, string $indexingConfiguration) : int
210 52
    {
211
        $queryBuilder = $this->getQueryBuilder();
212 52
        return $queryBuilder
0 ignored issues
show
Bug Best Practice introduced by
The expression return $queryBuilder->in...figuration))->execute() could return the type Doctrine\DBAL\Driver\Statement which is incompatible with the type-hinted return integer. Consider adding an additional type-check to rule them out.
Loading history...
213 52
            ->insert($this->table)
214 52
            ->values([
215 52
                'root' => $rootPageId,
216 52
                'item_type' => $itemType,
217 52
                'item_uid' => $itemUid,
218 52
                'changed' => $changedTime,
219 52
                'errors' => '',
220
                'indexing_configuration' => $indexingConfiguration
221 52
            ])
222
            ->execute();
223
224
    }
225
226
    /**
227
     * Retrieves the count of items that match certain filters. Each filter is passed as parts of the where claus combined with AND.
228
     *
229
     * @param array $sites
230
     * @param array $indexQueueConfigurationNames
231
     * @param array $itemTypes
232
     * @param array $itemUids
233
     * @param array $uids
234
     * @return int
235 1
     */
236
    public function countItems(array $sites = [], array $indexQueueConfigurationNames = [], array $itemTypes = [], array $itemUids = [], array $uids = []): int
237 1
    {
238 1
        $rootPageIds = Site::getRootPageIdsFromSites($sites);
239 1
        $indexQueueConfigurationList = implode(",", $indexQueueConfigurationNames);
240 1
        $itemTypeList = implode(",", $itemTypes);
241 1
        $itemUids = array_map("intval", $itemUids);
242
        $uids = array_map("intval", $uids);
243 1
244 1
        $queryBuilderForCountingItems = $this->getQueryBuilder();
245 1
        $queryBuilderForCountingItems->count('uid')->from($this->table);
246
        $queryBuilderForCountingItems = $this->addItemWhereClauses($queryBuilderForCountingItems, $rootPageIds, $indexQueueConfigurationList, $itemTypeList, $itemUids, $uids);
247 1
248
        return (int)$queryBuilderForCountingItems->execute()->fetchColumn(0);
249
    }
250
251
    /**
252
     * Gets the most recent changed time of a page's content elements
253
     *
254
     * @param int $pageUid
255
     * @return int|null Timestamp of the most recent content element change or null if nothing is found.
256 35
     */
257
    public function getPageItemChangedTimeByPageUid(int $pageUid)
258 35
    {
259 35
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tt_content');
260
        $queryBuilder->getRestrictions()->removeAll();
261 35
        $pageContentLastChangedTime = $queryBuilder
262 35
            ->add('select', $queryBuilder->expr()->max('tstamp', 'changed_time'))
263 35
            ->from('tt_content')
264 35
            ->where(
265
                $queryBuilder->expr()->eq('pid', $pageUid)
266 35
            )
267
            ->execute()->fetch();
268 35
269
        return $pageContentLastChangedTime['changed_time'];
270
    }
271
272
    /**
273
     * Gets the most recent changed time for an item taking into account
274
     * localized records.
275
     *
276
     * @param string $itemType The item's type, usually a table name.
277
     * @param int $itemUid The item's uid
278
     * @return int Timestamp of the most recent content element change
279 58
     */
280
    public function getLocalizableItemChangedTime(string $itemType, int $itemUid) : int
281 58
    {
282
        $localizedChangedTime = 0;
283 58
284 35
        if (isset($GLOBALS['TCA'][$itemType]['ctrl']['transOrigPointerField'])) {
285
            // table is localizable
286
            $translationOriginalPointerField = $GLOBALS['TCA'][$itemType]['ctrl']['transOrigPointerField'];
287 58
288
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($itemType);
289 58
            $queryBuilder->getRestrictions()->removeAll();
290
            $localizedChangedTime = $queryBuilder
291 58
                ->add('select', $queryBuilder->expr()->max('tstamp', 'changed_time'))
292 58
                ->from($itemType)
293
                ->orWhere(
294 58
                    $queryBuilder->expr()->eq('uid', $itemUid),
295 58
                    $queryBuilder->expr()->eq($translationOriginalPointerField, $itemUid)
296 58
                )->execute()->fetchColumn(0);
297 58
        }
298 58
299 58
        return (int)$localizedChangedTime;
300
    }
301
302 58
    /**
303
     * Returns prepared QueryBuilder for contains* methods in this repository
304
     *
305
     * @param string $itemType
306
     * @param int $itemUid
307
     * @return QueryBuilder
308
     */
309
    protected function getQueryBuilderForContainsMethods(string $itemType, int $itemUid) : QueryBuilder
310
    {
311
        $queryBuilder = $this->getQueryBuilder();
312 64
        return $queryBuilder->count('uid')->from($this->table)
313
            ->andWhere(
314 64
                $queryBuilder->expr()->eq('item_type', $queryBuilder->createNamedParameter($itemType)),
315 64
                $queryBuilder->expr()->eq('item_uid', $itemUid)
316 64
            );
317 64
    }
318 64
319
    /**
320
     * Checks whether the Index Queue contains a specific item.
321
     *
322
     * @param string $itemType The item's type, usually a table name.
323
     * @param int $itemUid The item's uid
324
     * @return bool TRUE if the item is found in the queue, FALSE otherwise
325
     */
326
    public function containsItem(string $itemType, int $itemUid) : bool
327
    {
328
        return (bool)$this->getQueryBuilderForContainsMethods($itemType, $itemUid)->execute()->fetchColumn(0);
329 6
    }
330
331 6
    /**
332
     * Checks whether the Index Queue contains a specific item.
333
     *
334
     * @param string $itemType The item's type, usually a table name.
335
     * @param int $itemUid The item's uid
336
     * @param integer $rootPageId
337
     * @return bool TRUE if the item is found in the queue, FALSE otherwise
338
     */
339
    public function containsItemWithRootPageId(string $itemType, int $itemUid, int $rootPageId) : bool
340
    {
341
        $queryBuilder = $this->getQueryBuilderForContainsMethods($itemType, $itemUid);
342 59
        return (bool)$queryBuilder
343
            ->andWhere($queryBuilder->expr()->eq('root', $rootPageId))
344 59
            ->execute()->fetchColumn(0);
345
    }
346 59
347 59
    /**
348
     * Checks whether the Index Queue contains a specific item that has been
349
     * marked as indexed.
350
     *
351
     * @param string $itemType The item's type, usually a table name.
352
     * @param int $itemUid The item's uid
353
     * @return bool TRUE if the item is found in the queue and marked as
354
     *      indexed, FALSE otherwise
355
     */
356
    public function containsIndexedItem(string $itemType, int $itemUid) : bool
357
    {
358
        $queryBuilder = $this->getQueryBuilderForContainsMethods($itemType, $itemUid);
359 2
        return (bool)$queryBuilder
360
            ->andWhere($queryBuilder->expr()->gt('indexed', 0))
361 2
            ->execute()->fetchColumn(0);
362
    }
363 2
364 2
    /**
365
     * Removes an item from the Index Queue.
366
     *
367
     * @param string $itemType The type of the item to remove, usually a table name.
368
     * @param int $itemUid The uid of the item to remove
369
     */
370
    public function deleteItem(string $itemType, int $itemUid = null)
371
    {
372
        $itemUids = empty($itemUid) ? [] : [$itemUid];
373 33
        $this->deleteItems([], [], [$itemType], $itemUids);
374
    }
375 33
376 33
    /**
377 33
     * Removes all items of a certain type from the Index Queue.
378
     *
379
     * @param string $itemType The type of items to remove, usually a table name.
380
     */
381
    public function deleteItemsByType(string $itemType)
382
    {
383
        $this->deleteItem($itemType);
384 1
    }
385
386 1
    /**
387 1
     * Removes all items of a certain site from the Index Queue. Accepts an
388
     * optional parameter to limit the deleted items by indexing configuration.
389
     *
390
     * @param Site $site The site to remove items for.
391
     * @param string $indexingConfigurationName Name of a specific indexing configuration
392
     * @throws \Exception
393
     */
394
    public function deleteItemsBySite(Site $site, string $indexingConfigurationName = '')
395
    {
396
        $indexingConfigurationNames = empty($indexingConfigurationName) ? [] : [$indexingConfigurationName];
397 6
        $this->deleteItems([$site], $indexingConfigurationNames);
398
    }
399 6
400 6
    /**
401 6
     * Removes items in the index queue filtered by the passed arguments.
402
     *
403
     * @param array $sites
404
     * @param array $indexQueueConfigurationNames
405
     * @param array $itemTypes
406
     * @param array $itemUids
407
     * @param array $uids
408
     * @throws \Exception
409
     */
410
    public function deleteItems(array $sites = [], array $indexQueueConfigurationNames = [], array $itemTypes = [], array $itemUids = [], array $uids = [])
411
    {
412
        $rootPageIds = Site::getRootPageIdsFromSites($sites);
413 39
        $indexQueueConfigurationList = implode(",", $indexQueueConfigurationNames);
414
        $itemTypeList = implode(",", $itemTypes);
415 39
        $itemUids = array_map("intval", $itemUids);
416 39
        $uids = array_map("intval", $uids);
417 39
418 39
        $queryBuilderForDeletingItems = $this->getQueryBuilder();
419 39
        $queryBuilderForDeletingItems->delete($this->table);
420
        $queryBuilderForDeletingItems = $this->addItemWhereClauses($queryBuilderForDeletingItems, $rootPageIds, $indexQueueConfigurationList, $itemTypeList, $itemUids, $uids);
421 39
422 39
        $queryBuilderForDeletingProperties = $this->buildQueryForPropertyDeletion($queryBuilderForDeletingItems, $rootPageIds, $indexQueueConfigurationList, $itemTypeList, $itemUids, $uids);
423 39
424
        $queryBuilderForDeletingItems->getConnection()->beginTransaction();
425 39
        try {
426
            $queryBuilderForDeletingItems->execute();
427 39
            $queryBuilderForDeletingProperties->execute();
428
429 39
            $queryBuilderForDeletingItems->getConnection()->commit();
430 39
        } catch (\Exception $e) {
431
            $queryBuilderForDeletingItems->getConnection()->rollback();
432 39
            throw $e;
433
        }
434
    }
435
436
    /**
437 39
     * Initializes the query builder to delete items in the index queue filtered by the passed arguments.
438
     *
439
     * @param array $rootPageIds filter on a set of rootPageUids.
440
     * @param string $indexQueueConfigurationList
441
     * @param string $itemTypeList
442
     * @param array $itemUids filter on a set of item uids
443
     * @param array $uids filter on a set of queue item uids
444
     * @return QueryBuilder
445
     */
446
    private function addItemWhereClauses(QueryBuilder $queryBuilderForDeletingItems, array $rootPageIds, string $indexQueueConfigurationList, string $itemTypeList, array $itemUids, array $uids): QueryBuilder
447
    {
448
449 41
        if (!empty($rootPageIds)) {
450
            $queryBuilderForDeletingItems->andWhere($queryBuilderForDeletingItems->expr()->in('root', $rootPageIds));
451
        };
452 41
453 6
        if (!empty($indexQueueConfigurationList)) {
454
            $queryBuilderForDeletingItems->andWhere($queryBuilderForDeletingItems->expr()->in('indexing_configuration', $queryBuilderForDeletingItems->createNamedParameter($indexQueueConfigurationList)));
455
        }
456 41
457 8
        if (!empty($itemTypeList)) {
458
            $queryBuilderForDeletingItems->andWhere($queryBuilderForDeletingItems->expr()->in('item_type', $queryBuilderForDeletingItems->createNamedParameter($itemTypeList)));
459
        }
460 41
461 33
        if (!empty($itemUids)) {
462
            $queryBuilderForDeletingItems->andWhere($queryBuilderForDeletingItems->expr()->in('item_uid', $itemUids));
463
        }
464 41
465 32
        if (!empty($uids)) {
466
            $queryBuilderForDeletingItems->andWhere($queryBuilderForDeletingItems->expr()->in('uid', $uids));
467
        }
468 41
469 1
        return $queryBuilderForDeletingItems;
470
    }
471
472 41
    /**
473
     * Initializes a query builder to delete the indexing properties of an item by the passed conditions.
474
     *
475
     * @param QueryBuilder $queryBuilderForDeletingItems
476
     * @param array $rootPageIds
477
     * @param string $indexQueueConfigurationList
478
     * @param string $itemTypeList
479
     * @param array $itemUids
480
     * @param array $uids
481
     * @return QueryBuilder
482
     */
483
    private function buildQueryForPropertyDeletion(QueryBuilder $queryBuilderForDeletingItems, array $rootPageIds, string $indexQueueConfigurationList, string $itemTypeList, array $itemUids, array $uids): QueryBuilder
484
    {
485
        $queryBuilderForSelectingProperties = $queryBuilderForDeletingItems->getConnection()->createQueryBuilder();
486 39
        $queryBuilderForSelectingProperties->select('items.uid')->from('tx_solr_indexqueue_indexing_property', 'properties')->innerJoin(
487
            'properties',
488 39
            $this->table,
489 39
            'items',
490 39
            (string)$queryBuilderForSelectingProperties->expr()->andX(
491 39
                $queryBuilderForSelectingProperties->expr()->eq('items.uid', $queryBuilderForSelectingProperties->quoteIdentifier('properties.item_id')),
492 39
                empty($rootPageIds) ? '' : $queryBuilderForSelectingProperties->expr()->in('items.root', $rootPageIds),
493 39
                empty($indexQueueConfigurationList) ? '' : $queryBuilderForSelectingProperties->expr()->in('items.indexing_configuration', $queryBuilderForSelectingProperties->createNamedParameter($indexQueueConfigurationList)),
494 39
                empty($itemTypeList) ? '' : $queryBuilderForSelectingProperties->expr()->in('items.item_type', $queryBuilderForSelectingProperties->createNamedParameter($itemTypeList)),
495 39
                empty($itemUids) ? '' : $queryBuilderForSelectingProperties->expr()->in('items.item_uid', $itemUids),
496 39
                empty($uids) ? '' : $queryBuilderForSelectingProperties->expr()->in('items.uid', $uids)
497 39
            )
498 39
        );
499 39
        $propertyEntriesToDelete = implode(',', array_column($queryBuilderForSelectingProperties->execute()->fetchAll(), 'uid'));
500
501
        $queryBuilderForDeletingProperties = $queryBuilderForDeletingItems->getConnection()->createQueryBuilder();
502 39
503
        // make sure executing the propety deletion query doesn't fail if there are no properties to delete
504
        if (empty($propertyEntriesToDelete)) {
505
            $propertyEntriesToDelete = '0';
506
        }
507
508
        $queryBuilderForDeletingProperties->delete('tx_solr_indexqueue_indexing_property')->where(
509
            $queryBuilderForDeletingProperties->expr()->in('item_id', $propertyEntriesToDelete)
510 1
        );
511
512 1
        return $queryBuilderForDeletingProperties;
513
    }
514
515
    /**
516
     * Removes all items from the Index Queue.
517
     *
518
     * @return int The number of affected rows. For a truncate this is unreliable as theres no meaningful information.
519
     */
520
    public function deleteAllItems()
521 26
    {
522
        return $this->getQueryBuilder()->getConnection()->truncate($this->table);
523 26
    }
524
525 26
    /**
526 26
     * Gets a single Index Queue item by its uid.
527 26
     *
528 26
     * @param int $uid Index Queue item uid
529
     * @return Item|null The request Index Queue item or NULL if no item with $itemId was found
530 26
     */
531 3
    public function findItemByUid(int $uid)
532
    {
533
        $queryBuilder = $this->getQueryBuilder();
534
        $indexQueueItemRecord = $queryBuilder
535 23
            ->select('*')
536 23
            ->from($this->table)
537
            ->where($queryBuilder->expr()->eq('uid', $uid))
538
            ->execute()->fetch();
539
540
        if (!isset($indexQueueItemRecord['uid'])) {
541
            return null;
542
        }
543
544
        /** @var Item $item*/
545
        $item = GeneralUtility::makeInstance(Item::class, /** @scrutinizer ignore-type */ $indexQueueItemRecord);
546 34
        return $item;
547
    }
548 34
549 34
    /**
550 34
     * Gets Index Queue items by type and uid.
551 34
     *
552
     * @param string $itemType item type, usually  the table name
553 34
     * @param int $itemUid item uid
554
     * @return Item[] An array of items matching $itemType and $itemUid
555
     */
556
    public function findItemsByItemTypeAndItemUid(string $itemType, int $itemUid) : array
557
    {
558
        $queryBuilder = $this->getQueryBuilder();
559
        $compositeExpression = $queryBuilder->expr()->andX(
560
            $queryBuilder->expr()->eq('item_type', $queryBuilder->getConnection()->quote($itemType, \PDO::PARAM_STR)),
561
            $queryBuilder->expr()->eq('item_uid', $itemUid)
562
        );
563
        return $this->getItemsByCompositeExpression($compositeExpression, $queryBuilder);
564 34
    }
565
566 34
    /**
567
     * Returns a collection of items by CompositeExpression.
568
     * D
569
     *
570 34
     * @param CompositeExpression|null $expression Optional expression to filter records.
571 34
     * @param QueryBuilder|null $queryBuilder QueryBuilder to use
572 34
     * @return array
573
     */
574
    protected function getItemsByCompositeExpression(CompositeExpression $expression = null, QueryBuilder $queryBuilder = null) : array
575 34
    {
576 34
        if (!$queryBuilder instanceof QueryBuilder) {
577
            $queryBuilder = $this->getQueryBuilder();
578
        }
579
580
        $queryBuilder->select('*')->from($this->table);
581
        if (isset($expression)) {
582
            $queryBuilder->where($expression);
583
        }
584
585
        $indexQueueItemRecords = $queryBuilder->execute()->fetchAll();
586
        return $this->getIndexQueueItemObjectsFromRecords($indexQueueItemRecords);
587
    }
588
589
    /**
590
     * Returns all items in the queue.
591
     *
592
     * @return Item[] all Items from Queue without restrictions
593
     */
594
    public function findAll() : array
595
    {
596
        $queryBuilder = $this->getQueryBuilder();
597
        $allRecords = $queryBuilder
598
            ->select('*')
599
            ->from($this->table)
600
            ->execute()->fetchAll();
601 6
        return $this->getIndexQueueItemObjectsFromRecords($allRecords);
602
    }
603 6
604
    /**
605
     * Gets $limit number of items to index for a particular $site.
606 6
     *
607 6
     * @param Site $site TYPO3 site
608 6
     * @param int $limit Number of items to get from the queue
609 6
     * @return Item[] Items to index to the given solr server
610 6
     */
611 6
    public function findItemsToIndex(Site $site, int $limit = 50) : array
612 6
    {
613
        $queryBuilder = $this->getQueryBuilder();
614 6
        // determine which items to index with this run
615 6
        $indexQueueItemRecords = $queryBuilder
616 6
            ->select('*')
617 6
            ->from($this->table)
618 6
            ->andWhere(
619
                $queryBuilder->expr()->eq('root', $site->getRootPageId()),
620 6
                $queryBuilder->expr()->gt('changed', 'indexed'),
621
                $queryBuilder->expr()->lte('changed', time()),
622
                $queryBuilder->expr()->eq('errors', $queryBuilder->createNamedParameter(''))
623
            )
624
            ->orderBy('indexing_priority', 'DESC')
625
            ->addOrderBy('changed', 'DESC')
626
            ->addOrderBy('uid', 'DESC')
627
            ->setMaxResults($limit)
628
            ->execute()->fetchAll();
629
630
        return $this->getIndexQueueItemObjectsFromRecords($indexQueueItemRecords);
631
    }
632
633
    /**
634
     * Retrieves the count of items that match certain filters. Each filter is passed as parts of the where claus combined with AND.
635 1
     *
636
     * @param array $sites
637 1
     * @param array $indexQueueConfigurationNames
638 1
     * @param array $itemTypes
639 1
     * @param array $itemUids
640 1
     * @param array $uids
641 1
     * @param int $start
642 1
     * @param int $limit
643 1
     * @return array
644 1
     */
645 1
    public function findItems(array $sites = [], array $indexQueueConfigurationNames = [], array $itemTypes = [], array $itemUids = [], array $uids = [], $start = 0, $limit = 50): array
646
    {
647
        $rootPageIds = Site::getRootPageIdsFromSites($sites);
648
        $indexQueueConfigurationList = implode(",", $indexQueueConfigurationNames);
649
        $itemTypeList = implode(",", $itemTypes);
650
        $itemUids = array_map("intval", $itemUids);
651
        $uids = array_map("intval", $uids);
652
        $itemQueryBuilder = $this->getQueryBuilder()->select('*')->from($this->table);
653
        $itemQueryBuilder = $this->addItemWhereClauses($itemQueryBuilder, $rootPageIds, $indexQueueConfigurationList, $itemTypeList, $itemUids, $uids);
654
        $itemRecords = $itemQueryBuilder->setFirstResult($start)->setMaxResults($limit)->execute()->fetchAll();
655 39
        return $this->getIndexQueueItemObjectsFromRecords($itemRecords);
656
    }
657 39
658 39
    /**
659
     * Creates an array of ApacheSolrForTypo3\Solr\IndexQueue\Item objects from an array of
660
     * index queue records.
661
     *
662
     * @param array $indexQueueItemRecords Array of plain index queue records
663
     * @return array Array of ApacheSolrForTypo3\Solr\IndexQueue\Item objects
664
     */
665
    protected function getIndexQueueItemObjectsFromRecords(array $indexQueueItemRecords) : array
666
    {
667 39
        $tableRecords = $this->getAllQueueItemRecordsByUidsGroupedByTable($indexQueueItemRecords);
668
        return $this->getQueueItemObjectsByRecords($indexQueueItemRecords, $tableRecords);
669 39
    }
670 39
671
    /**
672 39
     * Returns the records for suitable item type.
673 39
     *
674
     * @param array $indexQueueItemRecords
675
     * @return array
676
     */
677 39
    protected function getAllQueueItemRecordsByUidsGroupedByTable(array $indexQueueItemRecords) : array
678 39
    {
679
        $tableUids = [];
680 39
        $tableRecords = [];
681 39
        // grouping records by table
682
        foreach ($indexQueueItemRecords as $indexQueueItemRecord) {
683 39
            $tableUids[$indexQueueItemRecord['item_type']][] = $indexQueueItemRecord['item_uid'];
684 39
        }
685 39
686 39
        // fetching records by table, saves us a lot of single queries
687 39
        foreach ($tableUids as $table => $uids) {
688 39
            $uidList = implode(',', $uids);
689 38
690
            $queryBuilderForRecordTable = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
691
            $queryBuilderForRecordTable->getRestrictions()->removeAll();
692 39
            $resultsFromRecordTable = $queryBuilderForRecordTable
693 39
                ->select('*')
694
                ->from($table)
695
                ->where($queryBuilderForRecordTable->expr()->in('uid', $uidList))
696 39
                ->execute();
697
            $records = [];
698
            while ($record = $resultsFromRecordTable->fetch()) {
699
                $records[$record['uid']] = $record;
700
            }
701
702
            $tableRecords[$table] = $records;
703
            $this->hookPostProcessFetchRecordsForIndexQueueItem($table, $uids, $tableRecords);
704
        }
705
706
        return $tableRecords;
707
    }
708 39
709
    /**
710 39
     * Calls defined in postProcessFetchRecordsForIndexQueueItem hook method.
711 39
     *
712
     * @param string $table
713
     * @param array $uids
714
     * @param array $tableRecords
715
     *
716
     * @return void
717
     */
718
    protected function hookPostProcessFetchRecordsForIndexQueueItem(string $table, array $uids, array &$tableRecords)
719
    {
720
        if (!is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['postProcessFetchRecordsForIndexQueueItem'])) {
721
            return;
722
        }
723
        $params = ['table' => $table, 'uids' => $uids, 'tableRecords' => &$tableRecords];
724
        foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['postProcessFetchRecordsForIndexQueueItem'] as $reference) {
725
            GeneralUtility::callUserFunction($reference, $params, $this);
726 39
        }
727
    }
728 39
729 39
    /**
730 39
     * Instantiates a list of Item objects from database records.
731 38
     *
732 38
     * @param array $indexQueueItemRecords records from database
733 38
     * @param array $tableRecords
734 38
     * @return array
735
     */
736
    protected function getQueueItemObjectsByRecords(array $indexQueueItemRecords, array $tableRecords) : array
737 1
    {
738 1
        $indexQueueItems = [];
739 1
        foreach ($indexQueueItemRecords as $indexQueueItemRecord) {
740
            if (isset($tableRecords[$indexQueueItemRecord['item_type']][$indexQueueItemRecord['item_uid']])) {
741 1
                $indexQueueItems[] = GeneralUtility::makeInstance(
742
                    Item::class,
743
                    /** @scrutinizer ignore-type */ $indexQueueItemRecord,
744 1
                    /** @scrutinizer ignore-type */ $tableRecords[$indexQueueItemRecord['item_type']][$indexQueueItemRecord['item_uid']]
745 39
                );
746
            } else {
747
                $this->logger->log(
748
                    SolrLogManager::ERROR,
749 39
                    'Record missing for Index Queue item. Item removed.',
750
                    [
751
                        $indexQueueItemRecord
752
                    ]
753
                );
754
                $this->deleteItem($indexQueueItemRecord['item_type'],
755
                    $indexQueueItemRecord['item_uid']);
756
            }
757
        }
758
759
        return $indexQueueItems;
760 6
    }
761
762 6
    /**
763 6
     * Marks an item as failed and causes the indexer to skip the item in the
764
     * next run.
765 6
     *
766
     * @param int|Item $item Either the item's Index Queue uid or the complete item
767 6
     * @param string $errorMessage Error message
768 6
     * @return int affected rows
769 6
     */
770 6
    public function markItemAsFailed($item, string $errorMessage = ''): int
771
    {
772
        $itemUid = ($item instanceof Item) ? $item->getIndexQueueUid() : (int)$item;
773
        $errorMessage = empty($errorMessage) ? '1' : $errorMessage;
774
775
        $queryBuilder = $this->getQueryBuilder();
776
        return (int)$queryBuilder
777
            ->update($this->table)
778
            ->set('errors', $errorMessage)
779 5
            ->where($queryBuilder->expr()->eq('uid', $itemUid))
780
            ->execute();
781 5
    }
782
783 5
    /**
784 5
     * Sets the timestamp of when an item last has been indexed.
785 5
     *
786 5
     * @param Item $item
787
     * @return int affected rows
788
     */
789
    public function updateIndexTimeByItem(Item $item) : int
790
    {
791
        $queryBuilder = $this->getQueryBuilder();
792
        return (int)$queryBuilder
793
            ->update($this->table)
794
            ->set('indexed', time())
795
            ->where($queryBuilder->expr()->eq('uid', $item->getIndexQueueUid()))
796
            ->execute();
797
    }
798
799
    /**
800
     * Sets the change timestamp of an item.
801
     *
802
     * @param Item $item
803
     * @param int $changedTime
804
     * @return int affected rows
805
     */
806
    public function updateChangedTimeByItem(Item $item, int $changedTime) : int
807
    {
808
        $queryBuilder = $this->getQueryBuilder();
809
        return (int)$queryBuilder
810
            ->update($this->table)
811
            ->set('changed', $changedTime)
812
            ->where($queryBuilder->expr()->eq('uid', $item->getIndexQueueUid()))
813
            ->execute();
814
    }
815
816 12
    /**
817
     * Initializes Queue by given sql
818 12
     *
819
     * Note: Do not use platform specific functions!
820
     *
821
     * @param string $sqlStatement Native SQL statement
822
     * @return int The number of affected rows.
823
     * @internal
824
     * @throws DBALException
825
     */
826
    public function initializeByNativeSQLStatement(string $sqlStatement) : int
827 7
    {
828
        return $this->getQueryBuilder()->getConnection()->exec($sqlStatement);
829 7
    }
830
831 7
    /**
832 7
     * Retrieves an array of pageIds from mountPoints that allready have a queue entry.
833 7
     *
834 7
     * @param string $identifier identifier of the mount point
835 7
     * @return array pageIds from mountPoints that allready have a queue entry
836 7
     */
837
    public function findPageIdsOfExistingMountPagesByMountIdentifier(string $identifier) : array
838 7
    {
839 7
        $queryBuilder = $this->getQueryBuilder();
840
        $resultSet = $queryBuilder
841 7
            ->select('item_uid')
842 7
            ->add('select', $queryBuilder->expr()->count('*', 'queueItemCount'), true)
843
            ->from($this->table)
844
            ->where(
845
                $queryBuilder->expr()->eq('item_type', $queryBuilder->createNamedParameter('pages')),
846
                $queryBuilder->expr()->eq('pages_mountidentifier', $queryBuilder->createNamedParameter($identifier))
847
            )
848 7
            ->groupBy('item_uid')
849
            ->execute();
850
851
        $mountedPagesIdsWithQueueItems = [];
852
        while ($record = $resultSet->fetch()) {
853
            if ($record['queueItemCount'] > 0) {
854
                $mountedPagesIdsWithQueueItems[] = $record['item_uid'];
855
            }
856
        }
857
858
        return $mountedPagesIdsWithQueueItems;
859 7
    }
860
861 7
    /**
862
     * Retrieves an array of items for mount destinations mathed by root page ID, Mount Identifier and a list of mounted page IDs.
863 7
     *
864 7
     * @param int $rootPid
865 7
     * @param string $identifier identifier of the mount point
866 7
     * @param array $mountedPids An array of mounted page IDs
867 7
     * @return array
868 7
     */
869 7
    public function findAllIndexQueueItemsByRootPidAndMountIdentifierAndMountedPids(int $rootPid, string $identifier, array $mountedPids) : array
870 7
    {
871
        $queryBuilder = $this->getQueryBuilder();
872 7
        return $queryBuilder
873
            ->select('*')
874
            ->from($this->table)
875
            ->where(
876
                $queryBuilder->expr()->eq('root', $queryBuilder->createNamedParameter($rootPid, \PDO::PARAM_INT)),
877
                $queryBuilder->expr()->eq('item_type', $queryBuilder->createNamedParameter('pages')),
878
                $queryBuilder->expr()->in('item_uid', $mountedPids),
879
                $queryBuilder->expr()->eq('has_indexing_properties', $queryBuilder->createNamedParameter(1, \PDO::PARAM_INT)),
880
                $queryBuilder->expr()->eq('pages_mountidentifier', $queryBuilder->createNamedParameter($identifier))
881
            )
882 8
            ->execute()->fetchAll();
883
    }
884 8
885
    /**
886
     * Updates has_indexing_properties field for given Item
887 8
     *
888 8
     * @param int $itemUid
889 8
     * @param bool $hasIndexingPropertiesFlag
890 8
     * @return int number of affected rows, 1 on success
891
     */
892
    public function updateHasIndexingPropertiesFlagByItemUid(int $itemUid, bool $hasIndexingPropertiesFlag): int
893
    {
894
        $queryBuilder = $this->getQueryBuilder();
895
896
        return $queryBuilder
0 ignored issues
show
Bug Best Practice introduced by
The expression return $queryBuilder->up...INT), false)->execute() could return the type Doctrine\DBAL\Driver\Statement which is incompatible with the type-hinted return integer. Consider adding an additional type-check to rule them out.
Loading history...
897
            ->update($this->table)
898
            ->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($itemUid, \PDO::PARAM_INT)))
899
            ->set('has_indexing_properties', $queryBuilder->createNamedParameter($hasIndexingPropertiesFlag, \PDO::PARAM_INT), false)
900
            ->execute();
901
    }
902
}
903