Passed
Push — master ( 8e4f2f...ed38f6 )
by Timo
25:49
created

QueueItemRepository::flushErrorByItem()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 1

Importance

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