Passed
Push — master ( a5d0a9...8581cd )
by Timo
23:17
created

QueueItemRepository::containsIndexedItem()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

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