Completed
Push — master ( 5d7fca...978afd )
by Timo
42:26
created

QueueItemRepository::getPreparedFlushErrorQuery()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

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