903 lines
35 KiB
PHP
903 lines
35 KiB
PHP
<?php declare(strict_types = 1);
|
|
namespace WapplerSystems\Meilisearch\Domain\Index\Queue;
|
|
|
|
/***************************************************************
|
|
* Copyright notice
|
|
*
|
|
* (c) 2010-2017 dkd Internet Service GmbH <meilisearch-support@dkd.de>
|
|
* All rights reserved
|
|
*
|
|
* This script is part of the TYPO3 project. The TYPO3 project is
|
|
* free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* The GNU General Public License can be found at
|
|
* http://www.gnu.org/copyleft/gpl.html.
|
|
*
|
|
* This script is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* This copyright notice MUST APPEAR in all copies of the script!
|
|
***************************************************************/
|
|
|
|
use WapplerSystems\Meilisearch\IndexQueue\Item;
|
|
use WapplerSystems\Meilisearch\Domain\Site\Site;
|
|
use WapplerSystems\Meilisearch\System\Logging\MeilisearchLogManager;
|
|
use WapplerSystems\Meilisearch\System\Records\AbstractRepository;
|
|
use Doctrine\DBAL\DBALException;
|
|
use TYPO3\CMS\Core\Database\ConnectionPool;
|
|
use TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression;
|
|
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
|
|
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
|
|
|
/**
|
|
* Class QueueItemRepository
|
|
* Handles all CRUD operations to tx_meilisearch_indexqueue_item table
|
|
*
|
|
*/
|
|
class QueueItemRepository extends AbstractRepository
|
|
{
|
|
/**
|
|
* @var string
|
|
*/
|
|
protected $table = 'tx_meilisearch_indexqueue_item';
|
|
|
|
/**
|
|
* @var MeilisearchLogManager
|
|
*/
|
|
protected $logger;
|
|
|
|
/**
|
|
* QueueItemRepository constructor.
|
|
*
|
|
* @param MeilisearchLogManager|null $logManager
|
|
*/
|
|
public function __construct(MeilisearchLogManager $logManager = null)
|
|
{
|
|
$this->logger = $logManager ?? GeneralUtility::makeInstance(MeilisearchLogManager::class, /** @scrutinizer ignore-type */ __CLASS__);
|
|
}
|
|
|
|
/**
|
|
* Fetches the last indexed row
|
|
*
|
|
* @param int $rootPageId The root page uid for which to get the last indexed row
|
|
* @return array
|
|
*/
|
|
public function findLastIndexedRow(int $rootPageId) : array
|
|
{
|
|
$queryBuilder = $this->getQueryBuilder();
|
|
$row = $queryBuilder
|
|
->select('uid', 'indexed')
|
|
->from($this->table)
|
|
->where($queryBuilder->expr()->eq('root', $rootPageId))
|
|
->andWhere($queryBuilder->expr()->neq('indexed', 0))
|
|
->orderBy('indexed', 'DESC')
|
|
->setMaxResults(1)
|
|
->execute()->fetchAll();
|
|
|
|
return $row;
|
|
}
|
|
|
|
/**
|
|
* Finds indexing errors for the current site
|
|
*
|
|
* @param Site $site
|
|
* @return array Error items for the current site's Index Queue
|
|
*/
|
|
public function findErrorsBySite(Site $site) : array
|
|
{
|
|
$queryBuilder = $this->getQueryBuilder();
|
|
$errors = $queryBuilder
|
|
->select('uid', 'item_type', 'item_uid', 'errors')
|
|
->from($this->table)
|
|
->andWhere(
|
|
$queryBuilder->expr()->notLike('errors', $queryBuilder->createNamedParameter('')),
|
|
$queryBuilder->expr()->eq('root', $site->getRootPageId())
|
|
)
|
|
->execute()->fetchAll();
|
|
|
|
return $errors;
|
|
}
|
|
|
|
/**
|
|
* Resets all the errors for all index queue items.
|
|
*
|
|
* @return int affected rows
|
|
*/
|
|
public function flushAllErrors() : int
|
|
{
|
|
$queryBuilder = $this->getQueryBuilder();
|
|
$affectedRows = $this->getPreparedFlushErrorQuery($queryBuilder)->execute();
|
|
return $affectedRows;
|
|
}
|
|
|
|
/**
|
|
* Flushes the errors for a single site.
|
|
*
|
|
* @param Site $site
|
|
* @return int
|
|
*/
|
|
public function flushErrorsBySite(Site $site) : int
|
|
{
|
|
$queryBuilder = $this->getQueryBuilder();
|
|
$affectedRows = $this->getPreparedFlushErrorQuery($queryBuilder)
|
|
->andWhere(
|
|
$queryBuilder->expr()->eq('root', (int)$site->getRootPageId())
|
|
)
|
|
->execute();
|
|
return $affectedRows;
|
|
}
|
|
|
|
/**
|
|
* Flushes the error for a single item.
|
|
*
|
|
* @param Item $item
|
|
* @return int affected rows
|
|
*/
|
|
public function flushErrorByItem(Item $item) : int
|
|
{
|
|
$queryBuilder = $this->getQueryBuilder();
|
|
$affectedRows = $this->getPreparedFlushErrorQuery($queryBuilder)
|
|
->andWhere(
|
|
$queryBuilder->expr()->eq('uid', $item->getIndexQueueUid())
|
|
)
|
|
->execute();
|
|
return $affectedRows;
|
|
}
|
|
|
|
/**
|
|
* Initializes the QueryBuilder with a query the resets the error field for items that have an error.
|
|
*
|
|
* @return QueryBuilder
|
|
*/
|
|
private function getPreparedFlushErrorQuery(QueryBuilder $queryBuilder)
|
|
{
|
|
return $queryBuilder
|
|
->update($this->table)
|
|
->set('errors', '')
|
|
->where(
|
|
$queryBuilder->expr()->notLike('errors', $queryBuilder->createNamedParameter(''))
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Updates an existing queue entry by $itemType $itemUid and $rootPageId.
|
|
*
|
|
* @param string $itemType The item's type, usually a table name.
|
|
* @param int $itemUid The item's uid, usually an integer uid, could be a
|
|
* different value for non-database-record types.
|
|
* @param string $indexingConfiguration The name of the related indexConfiguration
|
|
* @param int $rootPageId The uid of the rootPage
|
|
* @param int $changedTime The forced change time that should be used for updating
|
|
* @return int affected rows
|
|
*/
|
|
public function updateExistingItemByItemTypeAndItemUidAndRootPageId(string $itemType, int $itemUid, int $rootPageId, int $changedTime, string $indexingConfiguration = '') : int
|
|
{
|
|
$queryBuilder = $this->getQueryBuilder();
|
|
$queryBuilder
|
|
->update($this->table)
|
|
->set('changed', $changedTime)
|
|
->andWhere(
|
|
$queryBuilder->expr()->eq('item_type', $queryBuilder->createNamedParameter($itemType)),
|
|
$queryBuilder->expr()->eq('item_uid', $itemUid),
|
|
$queryBuilder->expr()->eq('root', $rootPageId)
|
|
);
|
|
|
|
if (!empty($indexingConfiguration)) {
|
|
$queryBuilder->set('indexing_configuration', $indexingConfiguration);
|
|
}
|
|
|
|
return $queryBuilder->execute();
|
|
}
|
|
|
|
/**
|
|
* Adds an item to the index queue.
|
|
*
|
|
* Not meant for public use.
|
|
*
|
|
* @param string $itemType The item's type, usually a table name.
|
|
* @param int $itemUid The item's uid, usually an integer uid, could be a different value for non-database-record types.
|
|
* @param int $rootPageId
|
|
* @param int $changedTime
|
|
* @param string $indexingConfiguration The item's indexing configuration to use. Optional, overwrites existing / determined configuration.
|
|
* @return int the number of inserted rows, which is typically 1
|
|
*/
|
|
public function add(string $itemType, int $itemUid, int $rootPageId, int $changedTime, string $indexingConfiguration) : int
|
|
{
|
|
$queryBuilder = $this->getQueryBuilder();
|
|
return $queryBuilder
|
|
->insert($this->table)
|
|
->values([
|
|
'root' => $rootPageId,
|
|
'item_type' => $itemType,
|
|
'item_uid' => $itemUid,
|
|
'changed' => $changedTime,
|
|
'errors' => '',
|
|
'indexing_configuration' => $indexingConfiguration
|
|
])
|
|
->execute();
|
|
|
|
}
|
|
|
|
/**
|
|
* Retrieves the count of items that match certain filters. Each filter is passed as parts of the where claus combined with AND.
|
|
*
|
|
* @param array $sites
|
|
* @param array $indexQueueConfigurationNames
|
|
* @param array $itemTypes
|
|
* @param array $itemUids
|
|
* @param array $uids
|
|
* @return int
|
|
*/
|
|
public function countItems(array $sites = [], array $indexQueueConfigurationNames = [], array $itemTypes = [], array $itemUids = [], array $uids = []): int
|
|
{
|
|
$rootPageIds = Site::getRootPageIdsFromSites($sites);
|
|
$indexQueueConfigurationList = implode(",", $indexQueueConfigurationNames);
|
|
$itemTypeList = implode(",", $itemTypes);
|
|
$itemUids = array_map("intval", $itemUids);
|
|
$uids = array_map("intval", $uids);
|
|
|
|
$queryBuilderForCountingItems = $this->getQueryBuilder();
|
|
$queryBuilderForCountingItems->count('uid')->from($this->table);
|
|
$queryBuilderForCountingItems = $this->addItemWhereClauses($queryBuilderForCountingItems, $rootPageIds, $indexQueueConfigurationList, $itemTypeList, $itemUids, $uids);
|
|
|
|
return (int)$queryBuilderForCountingItems->execute()->fetchColumn(0);
|
|
}
|
|
|
|
/**
|
|
* Gets the most recent changed time of a page's content elements
|
|
*
|
|
* @param int $pageUid
|
|
* @return int|null Timestamp of the most recent content element change or null if nothing is found.
|
|
*/
|
|
public function getPageItemChangedTimeByPageUid(int $pageUid)
|
|
{
|
|
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tt_content');
|
|
$queryBuilder->getRestrictions()->removeAll();
|
|
$pageContentLastChangedTime = $queryBuilder
|
|
->add('select', $queryBuilder->expr()->max('tstamp', 'changed_time'))
|
|
->from('tt_content')
|
|
->where(
|
|
$queryBuilder->expr()->eq('pid', $pageUid)
|
|
)
|
|
->execute()->fetch();
|
|
|
|
return $pageContentLastChangedTime['changed_time'];
|
|
}
|
|
|
|
/**
|
|
* Gets the most recent changed time for an item taking into account
|
|
* localized records.
|
|
*
|
|
* @param string $itemType The item's type, usually a table name.
|
|
* @param int $itemUid The item's uid
|
|
* @return int Timestamp of the most recent content element change
|
|
*/
|
|
public function getLocalizableItemChangedTime(string $itemType, int $itemUid) : int
|
|
{
|
|
$localizedChangedTime = 0;
|
|
|
|
if (isset($GLOBALS['TCA'][$itemType]['ctrl']['transOrigPointerField'])) {
|
|
// table is localizable
|
|
$translationOriginalPointerField = $GLOBALS['TCA'][$itemType]['ctrl']['transOrigPointerField'];
|
|
|
|
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($itemType);
|
|
$queryBuilder->getRestrictions()->removeAll();
|
|
$localizedChangedTime = $queryBuilder
|
|
->add('select', $queryBuilder->expr()->max('tstamp', 'changed_time'))
|
|
->from($itemType)
|
|
->orWhere(
|
|
$queryBuilder->expr()->eq('uid', $itemUid),
|
|
$queryBuilder->expr()->eq($translationOriginalPointerField, $itemUid)
|
|
)->execute()->fetchColumn(0);
|
|
}
|
|
|
|
return (int)$localizedChangedTime;
|
|
}
|
|
|
|
/**
|
|
* Returns prepared QueryBuilder for contains* methods in this repository
|
|
*
|
|
* @param string $itemType
|
|
* @param int $itemUid
|
|
* @return QueryBuilder
|
|
*/
|
|
protected function getQueryBuilderForContainsMethods(string $itemType, int $itemUid) : QueryBuilder
|
|
{
|
|
$queryBuilder = $this->getQueryBuilder();
|
|
return $queryBuilder->count('uid')->from($this->table)
|
|
->andWhere(
|
|
$queryBuilder->expr()->eq('item_type', $queryBuilder->createNamedParameter($itemType)),
|
|
$queryBuilder->expr()->eq('item_uid', $itemUid)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Checks whether the Index Queue contains a specific item.
|
|
*
|
|
* @param string $itemType The item's type, usually a table name.
|
|
* @param int $itemUid The item's uid
|
|
* @return bool TRUE if the item is found in the queue, FALSE otherwise
|
|
*/
|
|
public function containsItem(string $itemType, int $itemUid) : bool
|
|
{
|
|
return (bool)$this->getQueryBuilderForContainsMethods($itemType, $itemUid)->execute()->fetchColumn(0);
|
|
}
|
|
|
|
/**
|
|
* Checks whether the Index Queue contains a specific item.
|
|
*
|
|
* @param string $itemType The item's type, usually a table name.
|
|
* @param int $itemUid The item's uid
|
|
* @param integer $rootPageId
|
|
* @return bool TRUE if the item is found in the queue, FALSE otherwise
|
|
*/
|
|
public function containsItemWithRootPageId(string $itemType, int $itemUid, int $rootPageId) : bool
|
|
{
|
|
$queryBuilder = $this->getQueryBuilderForContainsMethods($itemType, $itemUid);
|
|
return (bool)$queryBuilder
|
|
->andWhere($queryBuilder->expr()->eq('root', $rootPageId))
|
|
->execute()->fetchColumn(0);
|
|
}
|
|
|
|
/**
|
|
* Checks whether the Index Queue contains a specific item that has been
|
|
* marked as indexed.
|
|
*
|
|
* @param string $itemType The item's type, usually a table name.
|
|
* @param int $itemUid The item's uid
|
|
* @return bool TRUE if the item is found in the queue and marked as
|
|
* indexed, FALSE otherwise
|
|
*/
|
|
public function containsIndexedItem(string $itemType, int $itemUid) : bool
|
|
{
|
|
$queryBuilder = $this->getQueryBuilderForContainsMethods($itemType, $itemUid);
|
|
return (bool)$queryBuilder
|
|
->andWhere($queryBuilder->expr()->gt('indexed', 0))
|
|
->execute()->fetchColumn(0);
|
|
}
|
|
|
|
/**
|
|
* Removes an item from the Index Queue.
|
|
*
|
|
* @param string $itemType The type of the item to remove, usually a table name.
|
|
* @param int $itemUid The uid of the item to remove
|
|
*/
|
|
public function deleteItem(string $itemType, int $itemUid = null)
|
|
{
|
|
$itemUids = empty($itemUid) ? [] : [$itemUid];
|
|
$this->deleteItems([], [], [$itemType], $itemUids);
|
|
}
|
|
|
|
/**
|
|
* Removes all items of a certain type from the Index Queue.
|
|
*
|
|
* @param string $itemType The type of items to remove, usually a table name.
|
|
*/
|
|
public function deleteItemsByType(string $itemType)
|
|
{
|
|
$this->deleteItem($itemType);
|
|
}
|
|
|
|
/**
|
|
* Removes all items of a certain site from the Index Queue. Accepts an
|
|
* optional parameter to limit the deleted items by indexing configuration.
|
|
*
|
|
* @param Site $site The site to remove items for.
|
|
* @param string $indexingConfigurationName Name of a specific indexing configuration
|
|
* @throws \Exception
|
|
*/
|
|
public function deleteItemsBySite(Site $site, string $indexingConfigurationName = '')
|
|
{
|
|
$indexingConfigurationNames = empty($indexingConfigurationName) ? [] : [$indexingConfigurationName];
|
|
$this->deleteItems([$site], $indexingConfigurationNames);
|
|
}
|
|
|
|
/**
|
|
* Removes items in the index queue filtered by the passed arguments.
|
|
*
|
|
* @param array $sites
|
|
* @param array $indexQueueConfigurationNames
|
|
* @param array $itemTypes
|
|
* @param array $itemUids
|
|
* @param array $uids
|
|
* @throws \Exception
|
|
*/
|
|
public function deleteItems(array $sites = [], array $indexQueueConfigurationNames = [], array $itemTypes = [], array $itemUids = [], array $uids = [])
|
|
{
|
|
$rootPageIds = Site::getRootPageIdsFromSites($sites);
|
|
$indexQueueConfigurationList = implode(",", $indexQueueConfigurationNames);
|
|
$itemTypeList = implode(",", $itemTypes);
|
|
$itemUids = array_map("intval", $itemUids);
|
|
$uids = array_map("intval", $uids);
|
|
|
|
$queryBuilderForDeletingItems = $this->getQueryBuilder();
|
|
$queryBuilderForDeletingItems->delete($this->table);
|
|
$queryBuilderForDeletingItems = $this->addItemWhereClauses($queryBuilderForDeletingItems, $rootPageIds, $indexQueueConfigurationList, $itemTypeList, $itemUids, $uids);
|
|
|
|
$queryBuilderForDeletingProperties = $this->buildQueryForPropertyDeletion($queryBuilderForDeletingItems, $rootPageIds, $indexQueueConfigurationList, $itemTypeList, $itemUids, $uids);
|
|
|
|
$queryBuilderForDeletingItems->getConnection()->beginTransaction();
|
|
try {
|
|
$queryBuilderForDeletingItems->execute();
|
|
$queryBuilderForDeletingProperties->execute();
|
|
|
|
$queryBuilderForDeletingItems->getConnection()->commit();
|
|
} catch (\Exception $e) {
|
|
$queryBuilderForDeletingItems->getConnection()->rollback();
|
|
throw $e;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initializes the query builder to delete items in the index queue filtered by the passed arguments.
|
|
*
|
|
* @param array $rootPageIds filter on a set of rootPageUids.
|
|
* @param string $indexQueueConfigurationList
|
|
* @param string $itemTypeList
|
|
* @param array $itemUids filter on a set of item uids
|
|
* @param array $uids filter on a set of queue item uids
|
|
* @return QueryBuilder
|
|
*/
|
|
private function addItemWhereClauses(QueryBuilder $queryBuilderForDeletingItems, array $rootPageIds, string $indexQueueConfigurationList, string $itemTypeList, array $itemUids, array $uids): QueryBuilder
|
|
{
|
|
|
|
if (!empty($rootPageIds)) {
|
|
$queryBuilderForDeletingItems->andWhere($queryBuilderForDeletingItems->expr()->in('root', $rootPageIds));
|
|
};
|
|
|
|
if (!empty($indexQueueConfigurationList)) {
|
|
$queryBuilderForDeletingItems->andWhere($queryBuilderForDeletingItems->expr()->in('indexing_configuration', $queryBuilderForDeletingItems->createNamedParameter($indexQueueConfigurationList)));
|
|
}
|
|
|
|
if (!empty($itemTypeList)) {
|
|
$queryBuilderForDeletingItems->andWhere($queryBuilderForDeletingItems->expr()->in('item_type', $queryBuilderForDeletingItems->createNamedParameter($itemTypeList)));
|
|
}
|
|
|
|
if (!empty($itemUids)) {
|
|
$queryBuilderForDeletingItems->andWhere($queryBuilderForDeletingItems->expr()->in('item_uid', $itemUids));
|
|
}
|
|
|
|
if (!empty($uids)) {
|
|
$queryBuilderForDeletingItems->andWhere($queryBuilderForDeletingItems->expr()->in('uid', $uids));
|
|
}
|
|
|
|
return $queryBuilderForDeletingItems;
|
|
}
|
|
|
|
/**
|
|
* Initializes a query builder to delete the indexing properties of an item by the passed conditions.
|
|
*
|
|
* @param QueryBuilder $queryBuilderForDeletingItems
|
|
* @param array $rootPageIds
|
|
* @param string $indexQueueConfigurationList
|
|
* @param string $itemTypeList
|
|
* @param array $itemUids
|
|
* @param array $uids
|
|
* @return QueryBuilder
|
|
*/
|
|
private function buildQueryForPropertyDeletion(QueryBuilder $queryBuilderForDeletingItems, array $rootPageIds, string $indexQueueConfigurationList, string $itemTypeList, array $itemUids, array $uids): QueryBuilder
|
|
{
|
|
$queryBuilderForSelectingProperties = $queryBuilderForDeletingItems->getConnection()->createQueryBuilder();
|
|
$queryBuilderForSelectingProperties->select('items.uid')->from('tx_meilisearch_indexqueue_indexing_property', 'properties')->innerJoin(
|
|
'properties',
|
|
$this->table,
|
|
'items',
|
|
(string)$queryBuilderForSelectingProperties->expr()->andX(
|
|
$queryBuilderForSelectingProperties->expr()->eq('items.uid', $queryBuilderForSelectingProperties->quoteIdentifier('properties.item_id')),
|
|
empty($rootPageIds) ? '' : $queryBuilderForSelectingProperties->expr()->in('items.root', $rootPageIds),
|
|
empty($indexQueueConfigurationList) ? '' : $queryBuilderForSelectingProperties->expr()->in('items.indexing_configuration', $queryBuilderForSelectingProperties->createNamedParameter($indexQueueConfigurationList)),
|
|
empty($itemTypeList) ? '' : $queryBuilderForSelectingProperties->expr()->in('items.item_type', $queryBuilderForSelectingProperties->createNamedParameter($itemTypeList)),
|
|
empty($itemUids) ? '' : $queryBuilderForSelectingProperties->expr()->in('items.item_uid', $itemUids),
|
|
empty($uids) ? '' : $queryBuilderForSelectingProperties->expr()->in('items.uid', $uids)
|
|
)
|
|
);
|
|
$propertyEntriesToDelete = implode(',', array_column($queryBuilderForSelectingProperties->execute()->fetchAll(), 'uid'));
|
|
|
|
$queryBuilderForDeletingProperties = $queryBuilderForDeletingItems->getConnection()->createQueryBuilder();
|
|
|
|
// make sure executing the propety deletion query doesn't fail if there are no properties to delete
|
|
if (empty($propertyEntriesToDelete)) {
|
|
$propertyEntriesToDelete = '0';
|
|
}
|
|
|
|
$queryBuilderForDeletingProperties->delete('tx_meilisearch_indexqueue_indexing_property')->where(
|
|
$queryBuilderForDeletingProperties->expr()->in('item_id', $propertyEntriesToDelete)
|
|
);
|
|
|
|
return $queryBuilderForDeletingProperties;
|
|
}
|
|
|
|
/**
|
|
* Removes all items from the Index Queue.
|
|
*
|
|
* @return int The number of affected rows. For a truncate this is unreliable as theres no meaningful information.
|
|
*/
|
|
public function deleteAllItems()
|
|
{
|
|
return $this->getQueryBuilder()->getConnection()->truncate($this->table);
|
|
}
|
|
|
|
/**
|
|
* Gets a single Index Queue item by its uid.
|
|
*
|
|
* @param int $uid Index Queue item uid
|
|
* @return Item|null The request Index Queue item or NULL if no item with $itemId was found
|
|
*/
|
|
public function findItemByUid(int $uid)
|
|
{
|
|
$queryBuilder = $this->getQueryBuilder();
|
|
$indexQueueItemRecord = $queryBuilder
|
|
->select('*')
|
|
->from($this->table)
|
|
->where($queryBuilder->expr()->eq('uid', $uid))
|
|
->execute()->fetch();
|
|
|
|
if (!isset($indexQueueItemRecord['uid'])) {
|
|
return null;
|
|
}
|
|
|
|
/** @var Item $item*/
|
|
$item = GeneralUtility::makeInstance(Item::class, /** @scrutinizer ignore-type */ $indexQueueItemRecord);
|
|
return $item;
|
|
}
|
|
|
|
/**
|
|
* Gets Index Queue items by type and uid.
|
|
*
|
|
* @param string $itemType item type, usually the table name
|
|
* @param int $itemUid item uid
|
|
* @return Item[] An array of items matching $itemType and $itemUid
|
|
*/
|
|
public function findItemsByItemTypeAndItemUid(string $itemType, int $itemUid) : array
|
|
{
|
|
$queryBuilder = $this->getQueryBuilder();
|
|
$compositeExpression = $queryBuilder->expr()->andX(
|
|
$queryBuilder->expr()->eq('item_type', $queryBuilder->getConnection()->quote($itemType, \PDO::PARAM_STR)),
|
|
$queryBuilder->expr()->eq('item_uid', $itemUid)
|
|
);
|
|
return $this->getItemsByCompositeExpression($compositeExpression, $queryBuilder);
|
|
}
|
|
|
|
/**
|
|
* Returns a collection of items by CompositeExpression.
|
|
* D
|
|
*
|
|
* @param CompositeExpression|null $expression Optional expression to filter records.
|
|
* @param QueryBuilder|null $queryBuilder QueryBuilder to use
|
|
* @return array
|
|
*/
|
|
protected function getItemsByCompositeExpression(CompositeExpression $expression = null, QueryBuilder $queryBuilder = null) : array
|
|
{
|
|
if (!$queryBuilder instanceof QueryBuilder) {
|
|
$queryBuilder = $this->getQueryBuilder();
|
|
}
|
|
|
|
$queryBuilder->select('*')->from($this->table);
|
|
if (isset($expression)) {
|
|
$queryBuilder->where($expression);
|
|
}
|
|
|
|
$indexQueueItemRecords = $queryBuilder->execute()->fetchAll();
|
|
return $this->getIndexQueueItemObjectsFromRecords($indexQueueItemRecords);
|
|
}
|
|
|
|
/**
|
|
* Returns all items in the queue.
|
|
*
|
|
* @return Item[] all Items from Queue without restrictions
|
|
*/
|
|
public function findAll() : array
|
|
{
|
|
$queryBuilder = $this->getQueryBuilder();
|
|
$allRecords = $queryBuilder
|
|
->select('*')
|
|
->from($this->table)
|
|
->execute()->fetchAll();
|
|
return $this->getIndexQueueItemObjectsFromRecords($allRecords);
|
|
}
|
|
|
|
/**
|
|
* Gets $limit number of items to index for a particular $site.
|
|
*
|
|
* @param Site $site TYPO3 site
|
|
* @param int $limit Number of items to get from the queue
|
|
* @return Item[] Items to index to the given meilisearch server
|
|
*/
|
|
public function findItemsToIndex(Site $site, int $limit = 50) : array
|
|
{
|
|
$queryBuilder = $this->getQueryBuilder();
|
|
// determine which items to index with this run
|
|
$indexQueueItemRecords = $queryBuilder
|
|
->select('*')
|
|
->from($this->table)
|
|
->andWhere(
|
|
$queryBuilder->expr()->eq('root', $site->getRootPageId()),
|
|
$queryBuilder->expr()->gt('changed', 'indexed'),
|
|
$queryBuilder->expr()->lte('changed', time()),
|
|
$queryBuilder->expr()->eq('errors', $queryBuilder->createNamedParameter(''))
|
|
)
|
|
->orderBy('indexing_priority', 'DESC')
|
|
->addOrderBy('changed', 'DESC')
|
|
->addOrderBy('uid', 'DESC')
|
|
->setMaxResults($limit)
|
|
->execute()->fetchAll();
|
|
|
|
return $this->getIndexQueueItemObjectsFromRecords($indexQueueItemRecords);
|
|
}
|
|
|
|
/**
|
|
* Retrieves the count of items that match certain filters. Each filter is passed as parts of the where claus combined with AND.
|
|
*
|
|
* @param array $sites
|
|
* @param array $indexQueueConfigurationNames
|
|
* @param array $itemTypes
|
|
* @param array $itemUids
|
|
* @param array $uids
|
|
* @param int $start
|
|
* @param int $limit
|
|
* @return array
|
|
*/
|
|
public function findItems(array $sites = [], array $indexQueueConfigurationNames = [], array $itemTypes = [], array $itemUids = [], array $uids = [], $start = 0, $limit = 50): array
|
|
{
|
|
$rootPageIds = Site::getRootPageIdsFromSites($sites);
|
|
$indexQueueConfigurationList = implode(",", $indexQueueConfigurationNames);
|
|
$itemTypeList = implode(",", $itemTypes);
|
|
$itemUids = array_map("intval", $itemUids);
|
|
$uids = array_map("intval", $uids);
|
|
$itemQueryBuilder = $this->getQueryBuilder()->select('*')->from($this->table);
|
|
$itemQueryBuilder = $this->addItemWhereClauses($itemQueryBuilder, $rootPageIds, $indexQueueConfigurationList, $itemTypeList, $itemUids, $uids);
|
|
$itemRecords = $itemQueryBuilder->setFirstResult($start)->setMaxResults($limit)->execute()->fetchAll();
|
|
return $this->getIndexQueueItemObjectsFromRecords($itemRecords);
|
|
}
|
|
|
|
/**
|
|
* Creates an array of WapplerSystems\Meilisearch\IndexQueue\Item objects from an array of
|
|
* index queue records.
|
|
*
|
|
* @param array $indexQueueItemRecords Array of plain index queue records
|
|
* @return array Array of WapplerSystems\Meilisearch\IndexQueue\Item objects
|
|
*/
|
|
protected function getIndexQueueItemObjectsFromRecords(array $indexQueueItemRecords) : array
|
|
{
|
|
$tableRecords = $this->getAllQueueItemRecordsByUidsGroupedByTable($indexQueueItemRecords);
|
|
return $this->getQueueItemObjectsByRecords($indexQueueItemRecords, $tableRecords);
|
|
}
|
|
|
|
/**
|
|
* Returns the records for suitable item type.
|
|
*
|
|
* @param array $indexQueueItemRecords
|
|
* @return array
|
|
*/
|
|
protected function getAllQueueItemRecordsByUidsGroupedByTable(array $indexQueueItemRecords) : array
|
|
{
|
|
$tableUids = [];
|
|
$tableRecords = [];
|
|
// grouping records by table
|
|
foreach ($indexQueueItemRecords as $indexQueueItemRecord) {
|
|
$tableUids[$indexQueueItemRecord['item_type']][] = $indexQueueItemRecord['item_uid'];
|
|
}
|
|
|
|
// fetching records by table, saves us a lot of single queries
|
|
foreach ($tableUids as $table => $uids) {
|
|
$uidList = implode(',', $uids);
|
|
|
|
$queryBuilderForRecordTable = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
|
|
$queryBuilderForRecordTable->getRestrictions()->removeAll();
|
|
$resultsFromRecordTable = $queryBuilderForRecordTable
|
|
->select('*')
|
|
->from($table)
|
|
->where($queryBuilderForRecordTable->expr()->in('uid', $uidList))
|
|
->execute();
|
|
$records = [];
|
|
while ($record = $resultsFromRecordTable->fetch()) {
|
|
$records[$record['uid']] = $record;
|
|
}
|
|
|
|
$tableRecords[$table] = $records;
|
|
$this->hookPostProcessFetchRecordsForIndexQueueItem($table, $uids, $tableRecords);
|
|
}
|
|
|
|
return $tableRecords;
|
|
}
|
|
|
|
/**
|
|
* Calls defined in postProcessFetchRecordsForIndexQueueItem hook method.
|
|
*
|
|
* @param string $table
|
|
* @param array $uids
|
|
* @param array $tableRecords
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function hookPostProcessFetchRecordsForIndexQueueItem(string $table, array $uids, array &$tableRecords)
|
|
{
|
|
if (!is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['meilisearch']['postProcessFetchRecordsForIndexQueueItem'])) {
|
|
return;
|
|
}
|
|
$params = ['table' => $table, 'uids' => $uids, 'tableRecords' => &$tableRecords];
|
|
foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['meilisearch']['postProcessFetchRecordsForIndexQueueItem'] as $reference) {
|
|
GeneralUtility::callUserFunction($reference, $params, $this);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Instantiates a list of Item objects from database records.
|
|
*
|
|
* @param array $indexQueueItemRecords records from database
|
|
* @param array $tableRecords
|
|
* @return array
|
|
*/
|
|
protected function getQueueItemObjectsByRecords(array $indexQueueItemRecords, array $tableRecords) : array
|
|
{
|
|
$indexQueueItems = [];
|
|
foreach ($indexQueueItemRecords as $indexQueueItemRecord) {
|
|
if (isset($tableRecords[$indexQueueItemRecord['item_type']][$indexQueueItemRecord['item_uid']])) {
|
|
$indexQueueItems[] = GeneralUtility::makeInstance(
|
|
Item::class,
|
|
/** @scrutinizer ignore-type */ $indexQueueItemRecord,
|
|
/** @scrutinizer ignore-type */ $tableRecords[$indexQueueItemRecord['item_type']][$indexQueueItemRecord['item_uid']]
|
|
);
|
|
} else {
|
|
$this->logger->log(
|
|
MeilisearchLogManager::ERROR,
|
|
'Record missing for Index Queue item. Item removed.',
|
|
[
|
|
$indexQueueItemRecord
|
|
]
|
|
);
|
|
$this->deleteItem($indexQueueItemRecord['item_type'],
|
|
$indexQueueItemRecord['item_uid']);
|
|
}
|
|
}
|
|
|
|
return $indexQueueItems;
|
|
}
|
|
|
|
/**
|
|
* Marks an item as failed and causes the indexer to skip the item in the
|
|
* next run.
|
|
*
|
|
* @param int|Item $item Either the item's Index Queue uid or the complete item
|
|
* @param string $errorMessage Error message
|
|
* @return int affected rows
|
|
*/
|
|
public function markItemAsFailed($item, string $errorMessage = ''): int
|
|
{
|
|
$itemUid = ($item instanceof Item) ? $item->getIndexQueueUid() : (int)$item;
|
|
$errorMessage = empty($errorMessage) ? '1' : $errorMessage;
|
|
|
|
$queryBuilder = $this->getQueryBuilder();
|
|
return (int)$queryBuilder
|
|
->update($this->table)
|
|
->set('errors', $errorMessage)
|
|
->where($queryBuilder->expr()->eq('uid', $itemUid))
|
|
->execute();
|
|
}
|
|
|
|
/**
|
|
* Sets the timestamp of when an item last has been indexed.
|
|
*
|
|
* @param Item $item
|
|
* @return int affected rows
|
|
*/
|
|
public function updateIndexTimeByItem(Item $item) : int
|
|
{
|
|
$queryBuilder = $this->getQueryBuilder();
|
|
return (int)$queryBuilder
|
|
->update($this->table)
|
|
->set('indexed', time())
|
|
->where($queryBuilder->expr()->eq('uid', $item->getIndexQueueUid()))
|
|
->execute();
|
|
}
|
|
|
|
/**
|
|
* Sets the change timestamp of an item.
|
|
*
|
|
* @param Item $item
|
|
* @param int $changedTime
|
|
* @return int affected rows
|
|
*/
|
|
public function updateChangedTimeByItem(Item $item, int $changedTime) : int
|
|
{
|
|
$queryBuilder = $this->getQueryBuilder();
|
|
return (int)$queryBuilder
|
|
->update($this->table)
|
|
->set('changed', $changedTime)
|
|
->where($queryBuilder->expr()->eq('uid', $item->getIndexQueueUid()))
|
|
->execute();
|
|
}
|
|
|
|
/**
|
|
* Initializes Queue by given sql
|
|
*
|
|
* Note: Do not use platform specific functions!
|
|
*
|
|
* @param string $sqlStatement Native SQL statement
|
|
* @return int The number of affected rows.
|
|
* @internal
|
|
* @throws DBALException
|
|
*/
|
|
public function initializeByNativeSQLStatement(string $sqlStatement) : int
|
|
{
|
|
return $this->getQueryBuilder()->getConnection()->exec($sqlStatement);
|
|
}
|
|
|
|
/**
|
|
* Retrieves an array of pageIds from mountPoints that allready have a queue entry.
|
|
*
|
|
* @param string $identifier identifier of the mount point
|
|
* @return array pageIds from mountPoints that allready have a queue entry
|
|
*/
|
|
public function findPageIdsOfExistingMountPagesByMountIdentifier(string $identifier) : array
|
|
{
|
|
$queryBuilder = $this->getQueryBuilder();
|
|
$resultSet = $queryBuilder
|
|
->select('item_uid')
|
|
->add('select', $queryBuilder->expr()->count('*', 'queueItemCount'), true)
|
|
->from($this->table)
|
|
->where(
|
|
$queryBuilder->expr()->eq('item_type', $queryBuilder->createNamedParameter('pages')),
|
|
$queryBuilder->expr()->eq('pages_mountidentifier', $queryBuilder->createNamedParameter($identifier))
|
|
)
|
|
->groupBy('item_uid')
|
|
->execute();
|
|
|
|
$mountedPagesIdsWithQueueItems = [];
|
|
while ($record = $resultSet->fetch()) {
|
|
if ($record['queueItemCount'] > 0) {
|
|
$mountedPagesIdsWithQueueItems[] = $record['item_uid'];
|
|
}
|
|
}
|
|
|
|
return $mountedPagesIdsWithQueueItems;
|
|
}
|
|
|
|
/**
|
|
* Retrieves an array of items for mount destinations mathed by root page ID, Mount Identifier and a list of mounted page IDs.
|
|
*
|
|
* @param int $rootPid
|
|
* @param string $identifier identifier of the mount point
|
|
* @param array $mountedPids An array of mounted page IDs
|
|
* @return array
|
|
*/
|
|
public function findAllIndexQueueItemsByRootPidAndMountIdentifierAndMountedPids(int $rootPid, string $identifier, array $mountedPids) : array
|
|
{
|
|
$queryBuilder = $this->getQueryBuilder();
|
|
return $queryBuilder
|
|
->select('*')
|
|
->from($this->table)
|
|
->where(
|
|
$queryBuilder->expr()->eq('root', $queryBuilder->createNamedParameter($rootPid, \PDO::PARAM_INT)),
|
|
$queryBuilder->expr()->eq('item_type', $queryBuilder->createNamedParameter('pages')),
|
|
$queryBuilder->expr()->in('item_uid', $mountedPids),
|
|
$queryBuilder->expr()->eq('has_indexing_properties', $queryBuilder->createNamedParameter(1, \PDO::PARAM_INT)),
|
|
$queryBuilder->expr()->eq('pages_mountidentifier', $queryBuilder->createNamedParameter($identifier))
|
|
)
|
|
->execute()->fetchAll();
|
|
}
|
|
|
|
/**
|
|
* Updates has_indexing_properties field for given Item
|
|
*
|
|
* @param int $itemUid
|
|
* @param bool $hasIndexingPropertiesFlag
|
|
* @return int number of affected rows, 1 on success
|
|
*/
|
|
public function updateHasIndexingPropertiesFlagByItemUid(int $itemUid, bool $hasIndexingPropertiesFlag): int
|
|
{
|
|
$queryBuilder = $this->getQueryBuilder();
|
|
|
|
return $queryBuilder
|
|
->update($this->table)
|
|
->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($itemUid, \PDO::PARAM_INT)))
|
|
->set('has_indexing_properties', $queryBuilder->createNamedParameter($hasIndexingPropertiesFlag, \PDO::PARAM_INT), false)
|
|
->execute();
|
|
}
|
|
}
|