updateHasIndexingPropertiesFlagByItemUid()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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