140 lines
5.6 KiB
PHP
140 lines
5.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/*
|
|
* This file is part of the TYPO3 CMS project.
|
|
*
|
|
* It is free software; you can redistribute it and/or modify it under
|
|
* the terms of the GNU General Public License, either version 2
|
|
* of the License, or any later version.
|
|
*
|
|
* For the full copyright and license information, please read the
|
|
* LICENSE.txt file that was distributed with this source code.
|
|
*
|
|
* The TYPO3 project - inspiring people to share!
|
|
*/
|
|
|
|
namespace TYPO3\CMS\Backend\Search\LiveSearch;
|
|
|
|
use Psr\EventDispatcher\EventDispatcherInterface;
|
|
use TYPO3\CMS\Backend\Search\Event\ModifyResultItemInLiveSearchEvent;
|
|
use TYPO3\CMS\Backend\Search\LiveSearch\SearchDemand\DemandPropertyName;
|
|
use TYPO3\CMS\Backend\Search\LiveSearch\SearchDemand\MutableSearchDemand;
|
|
use TYPO3\CMS\Backend\Search\LiveSearch\SearchDemand\SearchDemand;
|
|
use TYPO3\CMS\Core\Pagination\ArrayPaginator;
|
|
|
|
/**
|
|
* Repository class to ease using the search API.
|
|
*
|
|
* @internal
|
|
*/
|
|
final readonly class SearchRepository
|
|
{
|
|
public function __construct(
|
|
private EventDispatcherInterface $eventDispatcher,
|
|
private SearchProviderRegistry $searchProviderRegistry,
|
|
) {}
|
|
|
|
/**
|
|
* Returns a list of available search providers including a flag whether they are currently active.
|
|
*
|
|
* @param SearchDemand $searchDemand
|
|
* @return array<class-string, array{instance: SearchProviderInterface, isActive: bool}>
|
|
*/
|
|
public function getSearchProviderState(SearchDemand $searchDemand): array
|
|
{
|
|
$searchProviders = [];
|
|
foreach ($this->searchProviderRegistry->getProviders() as $searchProviderClassName => $searchProvider) {
|
|
$searchProviders[$searchProviderClassName] = [
|
|
'instance' => $searchProvider,
|
|
'isActive' => in_array($searchProviderClassName, $searchDemand->getSearchProviders(), true),
|
|
];
|
|
}
|
|
|
|
return $searchProviders;
|
|
}
|
|
|
|
/**
|
|
* @return SearchProviderInterface[]
|
|
*/
|
|
public function getViableSearchProviders(SearchDemand $searchDemand): array
|
|
{
|
|
return array_filter(
|
|
$this->searchProviderRegistry->getProviders(),
|
|
static fn(SearchProviderInterface $provider): bool => $searchDemand->getSearchProviders() === [] || in_array(get_class($provider), $searchDemand->getSearchProviders(), true)
|
|
);
|
|
}
|
|
|
|
public function find(SearchDemand $searchDemand): ArrayPaginator
|
|
{
|
|
$searchResults = [];
|
|
$totalCount = 0;
|
|
$mutableSearchDemand = MutableSearchDemand::fromSearchDemand($searchDemand);
|
|
$offset = $searchDemand->getOffset();
|
|
$remainingItems = $searchDemand->getLimit();
|
|
|
|
foreach ($this->getViableSearchProviders($searchDemand) as $provider) {
|
|
// Initialize remaining-items and offset for current iteration
|
|
$mutableSearchDemand
|
|
->setProperty(DemandPropertyName::limit, $remainingItems)
|
|
->setProperty(DemandPropertyName::offset, $offset);
|
|
|
|
$count = $provider->count($mutableSearchDemand->freeze());
|
|
// Total count is relevant outside of this loop:
|
|
// Paginator calculates number of pages and fills up its result set with stub-entries.
|
|
$totalCount += $count;
|
|
|
|
if ($count < $offset) {
|
|
// The number of potential results is smaller than the offset, do not query results
|
|
$offset -= $count;
|
|
continue;
|
|
}
|
|
|
|
if ($remainingItems < 1) {
|
|
continue;
|
|
}
|
|
|
|
$providerResult = $provider->find($mutableSearchDemand->freeze());
|
|
if ($providerResult !== []) {
|
|
foreach ($providerResult as $key => $resultItem) {
|
|
$modifyRecordEvent = $this->eventDispatcher->dispatch(new ModifyResultItemInLiveSearchEvent($resultItem));
|
|
$providerResult[$key] = $modifyRecordEvent->getResultItem();
|
|
}
|
|
$remainingItems -= count($providerResult);
|
|
// We have got a first usable result from the current SearchProvider here. The offset is thereby fulfilled.
|
|
// All follow-up SearchProviders must run with offset=0 to start with their first item:
|
|
// * to generate a correct item-count and
|
|
// * to provide more items if there are still open remaining-items.
|
|
$offset = 0;
|
|
|
|
$searchResults[] = $providerResult;
|
|
}
|
|
}
|
|
unset($mutableSearchDemand);
|
|
|
|
$flattenedSearchResults = array_merge([], ...$searchResults);
|
|
$resultCount = count($flattenedSearchResults);
|
|
|
|
$currentPage = (int)floor(($searchDemand->getOffset() + $searchDemand->getLimit()) / $searchDemand->getLimit());
|
|
if (ceil($totalCount / $searchDemand->getLimit()) < $currentPage) {
|
|
// Requested page does not match with the overall amount of items, reset to first page
|
|
$currentPage = 1;
|
|
}
|
|
|
|
if ($resultCount > 0) {
|
|
// The paginator expects a full result set to be able to calculate its pagination. This will have negative
|
|
// performance consequences, therefore we only consider the current result set and create stubs for the "gaps".
|
|
$paginatorItems = array_merge(
|
|
array_fill(0, $searchDemand->getOffset(), null),
|
|
$flattenedSearchResults,
|
|
array_fill(0, $totalCount - $searchDemand->getOffset() - $resultCount, null)
|
|
);
|
|
} else {
|
|
$paginatorItems = [];
|
|
}
|
|
|
|
return new ArrayPaginator($paginatorItems, $currentPage, SearchDemand::DEFAULT_LIMIT);
|
|
}
|
|
}
|