Completed
Branch master (a6ebf8)
by Timo
03:27
created

QueueItemRepository::deleteItems()   A

Complexity

Conditions 2
Paths 4

Size

Total Lines 23
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 2.0219

Importance

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