Passed
Push — master ( 14a771...9e67b2 )
by Timo
05:24
created

buildQueryForPropertyDeletion()   B

Complexity

Conditions 7
Paths 2

Size

Total Lines 30
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 7

Importance

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