Passed
Push — master ( eb39ca...45d20b )
by Rafael
41:52
created

Classes/Domain/Index/Queue/QueueItemRepository.php (6 issues)

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
     */
59 123
    public function __construct(SolrLogManager $logManager = null)
60
    {
61 123
        $this->logger = $logManager ?? GeneralUtility::makeInstance(SolrLogManager::class, /** @scrutinizer ignore-type */ __CLASS__);
62 123
    }
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
     */
70 5
    public function findLastIndexedRow(int $rootPageId) : array
71
    {
72 5
        $queryBuilder = $this->getQueryBuilder();
73
        $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 5
            ->execute()->fetchAll();
81
82 5
        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 3
    public function findErrorsBySite(Site $site) : array
92
    {
93 3
        $queryBuilder = $this->getQueryBuilder();
94
        $errors = $queryBuilder
95 3
            ->select('uid', 'item_type', 'item_uid', 'errors')
96 3
            ->from($this->table)
97 3
            ->andWhere(
98 3
                $queryBuilder->expr()->notLike('errors', $queryBuilder->createNamedParameter('')),
99 3
                $queryBuilder->expr()->eq('root', $site->getRootPageId())
100
            )
101 3
            ->execute()->fetchAll();
102
103 3
        return $errors;
104
    }
105
106
    /**
107
     * Resets all the errors for all index queue items.
108
     *
109
     * @return int affected rows
110
     */
111 1
    public function flushAllErrors() : int
112
    {
113 1
        $queryBuilder = $this->getQueryBuilder();
114 1
        $affectedRows = $this->getPreparedFlushErrorQuery($queryBuilder)->execute();
115 1
        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 1
    public function flushErrorsBySite(Site $site) : int
125
    {
126 1
        $queryBuilder = $this->getQueryBuilder();
127 1
        $affectedRows = $this->getPreparedFlushErrorQuery($queryBuilder)
128 1
            ->andWhere(
129 1
                $queryBuilder->expr()->eq('root', (int)$site->getRootPageId())
130
            )
131 1
            ->execute();
132 1
        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
135
    /**
136
     * Flushes the error for a single item.
137
     *
138
     * @param Item $item
139
     * @return int affected rows
140
     */
141 2
    public function flushErrorByItem(Item $item) : int
142
    {
143 2
        $queryBuilder = $this->getQueryBuilder();
144 2
        $affectedRows = $this->getPreparedFlushErrorQuery($queryBuilder)
145 2
            ->andWhere(
146 2
                $queryBuilder->expr()->eq('uid', $item->getIndexQueueUid())
147
            )
148 2
            ->execute();
149 2
        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
    }
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 4
    private function getPreparedFlushErrorQuery(QueryBuilder $queryBuilder)
158
    {
159
        return $queryBuilder
160 4
            ->update($this->table)
161 4
            ->set('errors', '')
162 4
            ->where(
163 4
                $queryBuilder->expr()->notLike('errors', $queryBuilder->createNamedParameter(''))
164
            );
165
    }
166
167
    /**
168
     * Updates an existing queue entry by $itemType $itemUid and $rootPageId.
169
     *
170
     * @param string $itemType The item's type, usually a table name.
171
     * @param int $itemUid The item's uid, usually an integer uid, could be a
172
     *      different value for non-database-record types.
173
     * @param string $indexingConfiguration The name of the related indexConfiguration
174
     * @param int $rootPageId The uid of the rootPage
175
     * @param int $changedTime The forced change time that should be used for updating
176
     * @return int affected rows
177
     */
178 13
    public function updateExistingItemByItemTypeAndItemUidAndRootPageId(string $itemType, int $itemUid, int $rootPageId, int $changedTime, string $indexingConfiguration = '') : int
179
    {
180 13
        $queryBuilder = $this->getQueryBuilder();
181
        $queryBuilder
182 13
            ->update($this->table)
183 13
            ->set('changed', $changedTime)
184 13
            ->andWhere(
185 13
                $queryBuilder->expr()->eq('item_type', $queryBuilder->createNamedParameter($itemType)),
186 13
                $queryBuilder->expr()->eq('item_uid', $itemUid),
187 13
                $queryBuilder->expr()->eq('root', $rootPageId)
188
            );
189
190 13
        if (!empty($indexingConfiguration)) {
191 13
            $queryBuilder->set('indexing_configuration', $indexingConfiguration);
192
        }
193
194 13
        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
    }
196
197
    /**
198
     * Adds an item to the index queue.
199
     *
200
     * 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 56
    public function add(string $itemType, int $itemUid, int $rootPageId, int $changedTime, string $indexingConfiguration) : int
210
    {
211 56
        $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 56
            ->insert($this->table)
214 56
            ->values([
215 56
                'root' => $rootPageId,
216 56
                'item_type' => $itemType,
217 56
                'item_uid' => $itemUid,
218 56
                'changed' => $changedTime,
219 56
                'errors' => '',
220 56
                'indexing_configuration' => $indexingConfiguration
221
            ])
222 56
            ->execute();
223
224
    }
225
226
    /**
227
     * Retrieves the count of items that match certain filters. Each filter is passed as parts of the where claus combined with AND.
228
     *
229
     * @param array $sites
230
     * @param array $indexQueueConfigurationNames
231
     * @param array $itemTypes
232
     * @param array $itemUids
233
     * @param array $uids
234
     * @return int
235
     */
236 1
    public function countItems(array $sites = [], array $indexQueueConfigurationNames = [], array $itemTypes = [], array $itemUids = [], array $uids = []): int
237
    {
238 1
        $rootPageIds = Site::getRootPageIdsFromSites($sites);
239 1
        $indexQueueConfigurationList = implode(",", $indexQueueConfigurationNames);
240 1
        $itemTypeList = implode(",", $itemTypes);
241 1
        $itemUids = array_map("intval", $itemUids);
242 1
        $uids = array_map("intval", $uids);
243
244 1
        $queryBuilderForCountingItems = $this->getQueryBuilder();
245 1
        $queryBuilderForCountingItems->count('uid')->from($this->table);
246 1
        $queryBuilderForCountingItems = $this->addItemWhereClauses($queryBuilderForCountingItems, $rootPageIds, $indexQueueConfigurationList, $itemTypeList, $itemUids, $uids);
247
248 1
        return (int)$queryBuilderForCountingItems->execute()->fetchColumn(0);
249
    }
250
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 38
    public function getPageItemChangedTimeByPageUid(int $pageUid)
258
    {
259 38
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tt_content');
260 38
        $queryBuilder->getRestrictions()->removeAll();
261
        $pageContentLastChangedTime = $queryBuilder
262 38
            ->add('select', $queryBuilder->expr()->max('tstamp', 'changed_time'))
263 38
            ->from('tt_content')
264 38
            ->where(
265 38
                $queryBuilder->expr()->eq('pid', $pageUid)
266
            )
267 38
            ->execute()->fetch();
268
269 38
        return $pageContentLastChangedTime['changed_time'];
270
    }
271
272
    /**
273
     * Gets the most recent changed time for an item taking into account
274
     * localized records.
275
     *
276
     * @param string $itemType The item's type, usually a table name.
277
     * @param int $itemUid The item's uid
278
     * @return int Timestamp of the most recent content element change
279
     */
280 61
    public function getLocalizableItemChangedTime(string $itemType, int $itemUid) : int
281
    {
282 61
        $localizedChangedTime = 0;
283
284 61
        if (isset($GLOBALS['TCA'][$itemType]['ctrl']['transOrigPointerField'])) {
285
            // table is localizable
286 61
            $translationOriginalPointerField = $GLOBALS['TCA'][$itemType]['ctrl']['transOrigPointerField'];
287
288 61
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($itemType);
289 61
            $queryBuilder->getRestrictions()->removeAll();
290
            $localizedChangedTime = $queryBuilder
291 61
                ->add('select', $queryBuilder->expr()->max('tstamp', 'changed_time'))
292 61
                ->from($itemType)
293 61
                ->orWhere(
294 61
                    $queryBuilder->expr()->eq('uid', $itemUid),
295 61
                    $queryBuilder->expr()->eq($translationOriginalPointerField, $itemUid)
296 61
                )->execute()->fetchColumn(0);
297
        }
298
299 61
        return (int)$localizedChangedTime;
300
    }
301
302
    /**
303
     * Returns prepared QueryBuilder for contains* methods in this repository
304
     *
305
     * @param string $itemType
306
     * @param int $itemUid
307
     * @return QueryBuilder
308
     */
309 66
    protected function getQueryBuilderForContainsMethods(string $itemType, int $itemUid) : QueryBuilder
310
    {
311 66
        $queryBuilder = $this->getQueryBuilder();
312 66
        return $queryBuilder->count('uid')->from($this->table)
313 66
            ->andWhere(
314 66
                $queryBuilder->expr()->eq('item_type', $queryBuilder->createNamedParameter($itemType)),
315 66
                $queryBuilder->expr()->eq('item_uid', $itemUid)
316
            );
317
    }
318
319
    /**
320
     * 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 7
    public function containsItem(string $itemType, int $itemUid) : bool
327
    {
328 7
        return (bool)$this->getQueryBuilderForContainsMethods($itemType, $itemUid)->execute()->fetchColumn(0);
329
    }
330
331
    /**
332
     * Checks whether the Index Queue contains a specific item.
333
     *
334
     * @param string $itemType The item's type, usually a table name.
335
     * @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
     */
339 61
    public function containsItemWithRootPageId(string $itemType, int $itemUid, int $rootPageId) : bool
340
    {
341 61
        $queryBuilder = $this->getQueryBuilderForContainsMethods($itemType, $itemUid);
342
        return (bool)$queryBuilder
343 61
            ->andWhere($queryBuilder->expr()->eq('root', $rootPageId))
344 61
            ->execute()->fetchColumn(0);
345
    }
346
347
    /**
348
     * Checks whether the Index Queue contains a specific item that has been
349
     * marked as indexed.
350
     *
351
     * @param string $itemType The item's type, usually a table name.
352
     * @param int $itemUid The item's uid
353
     * @return bool TRUE if the item is found in the queue and marked as
354
     *      indexed, FALSE otherwise
355
     */
356 2
    public function containsIndexedItem(string $itemType, int $itemUid) : bool
357
    {
358 2
        $queryBuilder = $this->getQueryBuilderForContainsMethods($itemType, $itemUid);
359
        return (bool)$queryBuilder
360 2
            ->andWhere($queryBuilder->expr()->gt('indexed', 0))
361 2
            ->execute()->fetchColumn(0);
362
    }
363
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 35
    public function deleteItem(string $itemType, int $itemUid = null)
371
    {
372 35
        $itemUids = empty($itemUid) ? [] : [$itemUid];
373 35
        $this->deleteItems([], [], [$itemType], $itemUids);
374 35
    }
375
376
    /**
377
     * Removes all items of a certain type from the Index Queue.
378
     *
379
     * @param string $itemType The type of items to remove, usually a table name.
380
     */
381 1
    public function deleteItemsByType(string $itemType)
382
    {
383 1
        $this->deleteItem($itemType);
384 1
    }
385
386
    /**
387
     * Removes all items of a certain site from the Index Queue. Accepts an
388
     * optional parameter to limit the deleted items by indexing configuration.
389
     *
390
     * @param Site $site The site to remove items for.
391
     * @param string $indexingConfigurationName Name of a specific indexing configuration
392
     * @throws \Exception
393
     */
394 7
    public function deleteItemsBySite(Site $site, string $indexingConfigurationName = '')
395
    {
396 7
        $indexingConfigurationNames = empty($indexingConfigurationName) ? [] : [$indexingConfigurationName];
397 7
        $this->deleteItems([$site], $indexingConfigurationNames);
398 7
    }
399
400
    /**
401
     * Removes items in the index queue filtered by the passed arguments.
402
     *
403
     * @param array $sites
404
     * @param array $indexQueueConfigurationNames
405
     * @param array $itemTypes
406
     * @param array $itemUids
407
     * @param array $uids
408
     * @throws \Exception
409
     */
410 42
    public function deleteItems(array $sites = [], array $indexQueueConfigurationNames = [], array $itemTypes = [], array $itemUids = [], array $uids = [])
411
    {
412 42
        $rootPageIds = Site::getRootPageIdsFromSites($sites);
413 42
        $indexQueueConfigurationList = implode(",", $indexQueueConfigurationNames);
414 42
        $itemTypeList = implode(",", $itemTypes);
415 42
        $itemUids = array_map("intval", $itemUids);
416 42
        $uids = array_map("intval", $uids);
417
418 42
        $queryBuilderForDeletingItems = $this->getQueryBuilder();
419 42
        $queryBuilderForDeletingItems->delete($this->table);
420 42
        $queryBuilderForDeletingItems = $this->addItemWhereClauses($queryBuilderForDeletingItems, $rootPageIds, $indexQueueConfigurationList, $itemTypeList, $itemUids, $uids);
421
422 42
        $queryBuilderForDeletingProperties = $this->buildQueryForPropertyDeletion($queryBuilderForDeletingItems, $rootPageIds, $indexQueueConfigurationList, $itemTypeList, $itemUids, $uids);
423
424 42
        $queryBuilderForDeletingItems->getConnection()->beginTransaction();
425
        try {
426 42
            $queryBuilderForDeletingItems->execute();
427 42
            $queryBuilderForDeletingProperties->execute();
428
429 42
            $queryBuilderForDeletingItems->getConnection()->commit();
430
        } catch (\Exception $e) {
431
            $queryBuilderForDeletingItems->getConnection()->rollback();
432
            throw $e;
433
        }
434 42
    }
435
436
    /**
437
     * 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
     * @param string $itemTypeList
442
     * @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 44
    private function addItemWhereClauses(QueryBuilder $queryBuilderForDeletingItems, array $rootPageIds, string $indexQueueConfigurationList, string $itemTypeList, array $itemUids, array $uids): QueryBuilder
447
    {
448
449 44
        if (!empty($rootPageIds)) {
450 7
            $queryBuilderForDeletingItems->andWhere($queryBuilderForDeletingItems->expr()->in('root', $rootPageIds));
451
        };
452
453 44
        if (!empty($indexQueueConfigurationList)) {
454 9
            $queryBuilderForDeletingItems->andWhere($queryBuilderForDeletingItems->expr()->in('indexing_configuration', $queryBuilderForDeletingItems->createNamedParameter($indexQueueConfigurationList)));
455
        }
456
457 44
        if (!empty($itemTypeList)) {
458 35
            $queryBuilderForDeletingItems->andWhere($queryBuilderForDeletingItems->expr()->in('item_type', $queryBuilderForDeletingItems->createNamedParameter($itemTypeList)));
459
        }
460
461 44
        if (!empty($itemUids)) {
462 34
            $queryBuilderForDeletingItems->andWhere($queryBuilderForDeletingItems->expr()->in('item_uid', $itemUids));
463
        }
464
465 44
        if (!empty($uids)) {
466 1
            $queryBuilderForDeletingItems->andWhere($queryBuilderForDeletingItems->expr()->in('uid', $uids));
467
        }
468
469 44
        return $queryBuilderForDeletingItems;
470
    }
471
472
    /**
473
     * Initializes a query builder to delete the indexing properties of an item by the passed conditions.
474
     *
475
     * @param QueryBuilder $queryBuilderForDeletingItems
476
     * @param array $rootPageIds
477
     * @param string $indexQueueConfigurationList
478
     * @param string $itemTypeList
479
     * @param array $itemUids
480
     * @param array $uids
481
     * @return QueryBuilder
482
     */
483 42
    private function buildQueryForPropertyDeletion(QueryBuilder $queryBuilderForDeletingItems, array $rootPageIds, string $indexQueueConfigurationList, string $itemTypeList, array $itemUids, array $uids): QueryBuilder
484
    {
485 42
        $queryBuilderForSelectingProperties = $queryBuilderForDeletingItems->getConnection()->createQueryBuilder();
486 42
        $queryBuilderForSelectingProperties->select('items.uid')->from('tx_solr_indexqueue_indexing_property', 'properties')->innerJoin(
487 42
            'properties',
488 42
            $this->table,
489 42
            'items',
490 42
            (string)$queryBuilderForSelectingProperties->expr()->andX(
491 42
                $queryBuilderForSelectingProperties->expr()->eq('items.uid', $queryBuilderForSelectingProperties->quoteIdentifier('properties.item_id')),
492 42
                empty($rootPageIds) ? '' : $queryBuilderForSelectingProperties->expr()->in('items.root', $rootPageIds),
493 42
                empty($indexQueueConfigurationList) ? '' : $queryBuilderForSelectingProperties->expr()->in('items.indexing_configuration', $queryBuilderForSelectingProperties->createNamedParameter($indexQueueConfigurationList)),
494 42
                empty($itemTypeList) ? '' : $queryBuilderForSelectingProperties->expr()->in('items.item_type', $queryBuilderForSelectingProperties->createNamedParameter($itemTypeList)),
495 42
                empty($itemUids) ? '' : $queryBuilderForSelectingProperties->expr()->in('items.item_uid', $itemUids),
496 42
                empty($uids) ? '' : $queryBuilderForSelectingProperties->expr()->in('items.uid', $uids)
497
            )
498
        );
499 42
        $propertyEntriesToDelete = implode(',', array_column($queryBuilderForSelectingProperties->execute()->fetchAll(), 'uid'));
500
501 42
        $queryBuilderForDeletingProperties = $queryBuilderForDeletingItems->getConnection()->createQueryBuilder();
502
503
        // make sure executing the propety deletion query doesn't fail if there are no properties to delete
504 42
        if (empty($propertyEntriesToDelete)) {
505 41
            $propertyEntriesToDelete = '0';
506
        }
507
508 42
        $queryBuilderForDeletingProperties->delete('tx_solr_indexqueue_indexing_property')->where(
509 42
            $queryBuilderForDeletingProperties->expr()->in('item_id', $propertyEntriesToDelete)
510
        );
511
512 42
        return $queryBuilderForDeletingProperties;
513
    }
514
515
    /**
516
     * Removes all items from the Index Queue.
517
     *
518
     * @return int The number of affected rows. For a truncate this is unreliable as theres no meaningful information.
519
     */
520 1
    public function deleteAllItems()
521
    {
522 1
        return $this->getQueryBuilder()->getConnection()->truncate($this->table);
523
    }
524
525
    /**
526
     * 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 28
    public function findItemByUid(int $uid)
532
    {
533 28
        $queryBuilder = $this->getQueryBuilder();
534
        $indexQueueItemRecord = $queryBuilder
535 28
            ->select('*')
536 28
            ->from($this->table)
537 28
            ->where($queryBuilder->expr()->eq('uid', $uid))
538 28
            ->execute()->fetch();
539
540 28
        if (!isset($indexQueueItemRecord['uid'])) {
541 3
            return null;
542
        }
543
544
        /** @var Item $item*/
545 25
        $item = GeneralUtility::makeInstance(Item::class, /** @scrutinizer ignore-type */ $indexQueueItemRecord);
546 25
        return $item;
547
    }
548
549
    /**
550
     * Gets Index Queue items by type and uid.
551
     *
552
     * @param string $itemType item type, usually  the table name
553
     * @param int $itemUid item uid
554
     * @return Item[] An array of items matching $itemType and $itemUid
555
     */
556 38
    public function findItemsByItemTypeAndItemUid(string $itemType, int $itemUid) : array
557
    {
558 38
        $queryBuilder = $this->getQueryBuilder();
559 38
        $compositeExpression = $queryBuilder->expr()->andX(
560 38
            $queryBuilder->expr()->eq('item_type', $queryBuilder->getConnection()->quote($itemType, \PDO::PARAM_STR)),
561 38
            $queryBuilder->expr()->eq('item_uid', $itemUid)
562
        );
563 38
        return $this->getItemsByCompositeExpression($compositeExpression, $queryBuilder);
564
    }
565
566
    /**
567
     * Returns a collection of items by CompositeExpression.
568
     * D
569
     *
570
     * @param CompositeExpression|null $expression Optional expression to filter records.
571
     * @param QueryBuilder|null $queryBuilder QueryBuilder to use
572
     * @return array
573
     */
574 38
    protected function getItemsByCompositeExpression(CompositeExpression $expression = null, QueryBuilder $queryBuilder = null) : array
575
    {
576 38
        if (!$queryBuilder instanceof QueryBuilder) {
577
            $queryBuilder = $this->getQueryBuilder();
578
        }
579
580 38
        $queryBuilder->select('*')->from($this->table);
581 38
        if (isset($expression)) {
582 38
            $queryBuilder->where($expression);
583
        }
584
585 38
        $indexQueueItemRecords = $queryBuilder->execute()->fetchAll();
586 38
        return $this->getIndexQueueItemObjectsFromRecords($indexQueueItemRecords);
587
    }
588
589
    /**
590
     * Returns all items in the queue.
591
     *
592
     * @return Item[] all Items from Queue without restrictions
593
     */
594 1
    public function findAll() : array
595
    {
596 1
        $queryBuilder = $this->getQueryBuilder();
597
        $allRecords = $queryBuilder
598 1
            ->select('*')
599 1
            ->from($this->table)
600 1
            ->execute()->fetchAll();
601 1
        return $this->getIndexQueueItemObjectsFromRecords($allRecords);
602
    }
603
604
    /**
605
     * Gets $limit number of items to index for a particular $site.
606
     *
607
     * @param Site $site TYPO3 site
608
     * @param int $limit Number of items to get from the queue
609
     * @return Item[] Items to index to the given solr server
610
     */
611 3
    public function findItemsToIndex(Site $site, int $limit = 50) : array
612
    {
613 3
        $queryBuilder = $this->getQueryBuilder();
614
        // determine which items to index with this run
615
        $indexQueueItemRecords = $queryBuilder
616 3
            ->select('*')
617 3
            ->from($this->table)
618 3
            ->andWhere(
619 3
                $queryBuilder->expr()->eq('root', $site->getRootPageId()),
620 3
                $queryBuilder->expr()->gt('changed', 'indexed'),
621 3
                $queryBuilder->expr()->lte('changed', time()),
622 3
                $queryBuilder->expr()->eq('errors', $queryBuilder->createNamedParameter(''))
623
            )
624 3
            ->orderBy('indexing_priority', 'DESC')
625 3
            ->addOrderBy('changed', 'DESC')
626 3
            ->addOrderBy('uid', 'DESC')
627 3
            ->setMaxResults($limit)
628 3
            ->execute()->fetchAll();
629
630 3
        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
     * @param int $start
642
     * @param int $limit
643
     * @return array
644
     */
645 1
    public function findItems(array $sites = [], array $indexQueueConfigurationNames = [], array $itemTypes = [], array $itemUids = [], array $uids = [], $start = 0, $limit = 50): array
646
    {
647 1
        $rootPageIds = Site::getRootPageIdsFromSites($sites);
648 1
        $indexQueueConfigurationList = implode(",", $indexQueueConfigurationNames);
649 1
        $itemTypeList = implode(",", $itemTypes);
650 1
        $itemUids = array_map("intval", $itemUids);
651 1
        $uids = array_map("intval", $uids);
652 1
        $itemQueryBuilder = $this->getQueryBuilder()->select('*')->from($this->table);
653 1
        $itemQueryBuilder = $this->addItemWhereClauses($itemQueryBuilder, $rootPageIds, $indexQueueConfigurationList, $itemTypeList, $itemUids, $uids);
654 1
        $itemRecords = $itemQueryBuilder->setFirstResult($start)->setMaxResults($limit)->execute()->fetchAll();
655 1
        return $this->getIndexQueueItemObjectsFromRecords($itemRecords);
656
    }
657
658
    /**
659
     * 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 40
    protected function getIndexQueueItemObjectsFromRecords(array $indexQueueItemRecords) : array
666
    {
667 40
        $tableRecords = $this->getAllQueueItemRecordsByUidsGroupedByTable($indexQueueItemRecords);
668 40
        return $this->getQueueItemObjectsByRecords($indexQueueItemRecords, $tableRecords);
669
    }
670
671
    /**
672
     * Returns the records for suitable item type.
673
     *
674
     * @param array $indexQueueItemRecords
675
     * @return array
676
     */
677 40
    protected function getAllQueueItemRecordsByUidsGroupedByTable(array $indexQueueItemRecords) : array
678
    {
679 40
        $tableUids = [];
680 40
        $tableRecords = [];
681
        // grouping records by table
682 40
        foreach ($indexQueueItemRecords as $indexQueueItemRecord) {
683 40
            $tableUids[$indexQueueItemRecord['item_type']][] = $indexQueueItemRecord['item_uid'];
684
        }
685
686
        // fetching records by table, saves us a lot of single queries
687 40
        foreach ($tableUids as $table => $uids) {
688 40
            $uidList = implode(',', $uids);
689
690 40
            $queryBuilderForRecordTable = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
691 40
            $queryBuilderForRecordTable->getRestrictions()->removeAll();
692
            $resultsFromRecordTable = $queryBuilderForRecordTable
693 40
                ->select('*')
694 40
                ->from($table)
695 40
                ->where($queryBuilderForRecordTable->expr()->in('uid', $uidList))
696 40
                ->execute();
697 40
            $records = [];
698 40
            while ($record = $resultsFromRecordTable->fetch()) {
699 39
                $records[$record['uid']] = $record;
700
            }
701
702 40
            $tableRecords[$table] = $records;
703 40
            $this->hookPostProcessFetchRecordsForIndexQueueItem($table, $uids, $tableRecords);
704
        }
705
706 40
        return $tableRecords;
707
    }
708
709
    /**
710
     * Calls defined in postProcessFetchRecordsForIndexQueueItem hook method.
711
     *
712
     * @param string $table
713
     * @param array $uids
714
     * @param array $tableRecords
715
     *
716
     * @return void
717
     */
718 40
    protected function hookPostProcessFetchRecordsForIndexQueueItem(string $table, array $uids, array &$tableRecords)
719
    {
720 40
        if (!is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['postProcessFetchRecordsForIndexQueueItem'])) {
721 40
            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
     *
732
     * @param array $indexQueueItemRecords records from database
733
     * @param array $tableRecords
734
     * @return array
735
     */
736 40
    protected function getQueueItemObjectsByRecords(array $indexQueueItemRecords, array $tableRecords) : array
737
    {
738 40
        $indexQueueItems = [];
739 40
        foreach ($indexQueueItemRecords as $indexQueueItemRecord) {
740 40
            if (isset($tableRecords[$indexQueueItemRecord['item_type']][$indexQueueItemRecord['item_uid']])) {
741 39
                $indexQueueItems[] = GeneralUtility::makeInstance(
742 39
                    Item::class,
743 39
                    /** @scrutinizer ignore-type */ $indexQueueItemRecord,
744 39
                    /** @scrutinizer ignore-type */ $tableRecords[$indexQueueItemRecord['item_type']][$indexQueueItemRecord['item_uid']]
745
                );
746
            } else {
747 1
                $this->logger->log(
748 1
                    SolrLogManager::ERROR,
749 1
                    'Record missing for Index Queue item. Item removed.',
750
                    [
751 1
                        $indexQueueItemRecord
752
                    ]
753
                );
754 1
                $this->deleteItem($indexQueueItemRecord['item_type'],
755 1
                    $indexQueueItemRecord['item_uid']);
756
            }
757
        }
758
759 40
        return $indexQueueItems;
760
    }
761
762
    /**
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 6
    public function markItemAsFailed($item, string $errorMessage = ''): int
771
    {
772 6
        $itemUid = ($item instanceof Item) ? $item->getIndexQueueUid() : (int)$item;
773 6
        $errorMessage = empty($errorMessage) ? '1' : $errorMessage;
774
775 6
        $queryBuilder = $this->getQueryBuilder();
776
        return (int)$queryBuilder
777 6
            ->update($this->table)
778 6
            ->set('errors', $errorMessage)
779 6
            ->where($queryBuilder->expr()->eq('uid', $itemUid))
780 6
            ->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 2
    public function updateIndexTimeByItem(Item $item) : int
790
    {
791 2
        $queryBuilder = $this->getQueryBuilder();
792
        return (int)$queryBuilder
793 2
            ->update($this->table)
794 2
            ->set('indexed', time())
795 2
            ->where($queryBuilder->expr()->eq('uid', $item->getIndexQueueUid()))
796 2
            ->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 12
    public function initializeByNativeSQLStatement(string $sqlStatement) : int
827
    {
828 12
        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 7
    public function findPageIdsOfExistingMountPagesByMountIdentifier(string $identifier) : array
838
    {
839 7
        $queryBuilder = $this->getQueryBuilder();
840
        $resultSet = $queryBuilder
841 7
            ->select('item_uid')
842 7
            ->add('select', $queryBuilder->expr()->count('*', 'queueItemCount'), true)
843 7
            ->from($this->table)
844 7
            ->where(
845 7
                $queryBuilder->expr()->eq('item_type', $queryBuilder->createNamedParameter('pages')),
846 7
                $queryBuilder->expr()->eq('pages_mountidentifier', $queryBuilder->createNamedParameter($identifier))
847
            )
848 7
            ->groupBy('item_uid')
849 7
            ->execute();
850
851 7
        $mountedPagesIdsWithQueueItems = [];
852 7
        while ($record = $resultSet->fetch()) {
853
            if ($record['queueItemCount'] > 0) {
854
                $mountedPagesIdsWithQueueItems[] = $record['item_uid'];
855
            }
856
        }
857
858 7
        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 7
    public function findAllIndexQueueItemsByRootPidAndMountIdentifierAndMountedPids(int $rootPid, string $identifier, array $mountedPids) : array
870
    {
871 7
        $queryBuilder = $this->getQueryBuilder();
872
        return $queryBuilder
873 7
            ->select('*')
874 7
            ->from($this->table)
875 7
            ->where(
876 7
                $queryBuilder->expr()->eq('root', $queryBuilder->createNamedParameter($rootPid, \PDO::PARAM_INT)),
877 7
                $queryBuilder->expr()->eq('item_type', $queryBuilder->createNamedParameter('pages')),
878 7
                $queryBuilder->expr()->in('item_uid', $mountedPids),
879 7
                $queryBuilder->expr()->eq('has_indexing_properties', $queryBuilder->createNamedParameter(1, \PDO::PARAM_INT)),
880 7
                $queryBuilder->expr()->eq('pages_mountidentifier', $queryBuilder->createNamedParameter($identifier))
881
            )
882 7
            ->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 9
    public function updateHasIndexingPropertiesFlagByItemUid(int $itemUid, bool $hasIndexingPropertiesFlag): int
893
    {
894 9
        $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 9
            ->update($this->table)
898 9
            ->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($itemUid, \PDO::PARAM_INT)))
899 9
            ->set('has_indexing_properties', $queryBuilder->createNamedParameter($hasIndexingPropertiesFlag, \PDO::PARAM_INT), false)
900 9
            ->execute();
901
    }
902
}
903