Passed
Push — master ( 4bd1a9...6d6a68 )
by Timo
22:16
created

QueueItemRepository::findErrorsBySite()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 13
ccs 0
cts 9
cp 0
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 9
nc 1
nop 1
crap 2
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 113
    public function __construct(SolrLogManager $logManager = null)
59
    {
60 113
        $this->logger = isset($logManager) ? $logManager : GeneralUtility::makeInstance(SolrLogManager::class, __CLASS__);
0 ignored issues
show
Bug introduced by
__CLASS__ of type string is incompatible with the type array<integer,mixed> expected by parameter $constructorArguments of TYPO3\CMS\Core\Utility\G...Utility::makeInstance(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

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