Skip to content

Commit

Permalink
[TASK] Refine LiveSearch search demand
Browse files Browse the repository at this point in the history
The search demand implementation used by the LiveSearch is refined to be
more flexible. Instead of a hard-coded list of properties, the
constructor now receives a list of `DemandProperty` instances that
accept everything that is backed by the `DemandPropertyName` enum,
allowing to pass only the required properties.

The `SearchDemand` class is immutable by design, it's API must not
allow modification by 3rd parties. However, there is an internal
`MutableSearchDemand` acting as a helper that allows to add / override
properties. A `SearchDemand` object may be constructed from a request
object, where it's payload must contain properties from the
aforementioned `DemandPropertyName` enum.

Resolves: #99941
Releases: main
Change-Id: I880855943a67237fa765cd3da94333e8aaa3a2a2
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/77842
Reviewed-by: Andreas Fernandez <a.fernandez@scripting-base.de>
Tested-by: core-ci <typo3@b13.com>
Tested-by: Georg Ringer <georg.ringer@gmail.com>
Reviewed-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Reviewed-by: Georg Ringer <georg.ringer@gmail.com>
Tested-by: Andreas Fernandez <a.fernandez@scripting-base.de>
Tested-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
  • Loading branch information
andreaskienast committed Feb 14, 2023
1 parent 26ca687 commit 0a9b218
Show file tree
Hide file tree
Showing 14 changed files with 390 additions and 68 deletions.
4 changes: 2 additions & 2 deletions Build/Sources/TypeScript/backend/toolbar/live-search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,8 @@ class LiveSearch {
searchResultContainer.loading = true;

const response = await new AjaxRequest(TYPO3.settings.ajaxUrls.livesearch).post({
q: this.searchTerm,
options: this.searchOptions,
query: this.searchTerm,
...this.searchOptions,
});

resultSet = await response.raw().json();
Expand Down
16 changes: 8 additions & 8 deletions typo3/sysext/backend/Classes/Controller/LiveSearchController.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Backend\Attribute\Controller;
use TYPO3\CMS\Backend\Search\Event\ModifyResultItemInLiveSearchEvent;
use TYPO3\CMS\Backend\Search\LiveSearch\SearchDemand;
use TYPO3\CMS\Backend\Search\LiveSearch\SearchDemand\DemandPropertyName;
use TYPO3\CMS\Backend\Search\LiveSearch\SearchDemand\MutableSearchDemand;
use TYPO3\CMS\Backend\Search\LiveSearch\SearchProviderRegistry;
use TYPO3\CMS\Backend\View\BackendViewFactory;
use TYPO3\CMS\Core\Http\JsonResponse;
Expand All @@ -49,12 +50,11 @@ public function __construct(
*/
public function searchAction(ServerRequestInterface $request): ResponseInterface
{
if (!isset($request->getParsedBody()['q'])) {
return new Response('', 400, [], 'Missing argument "q"');
if (!isset($request->getParsedBody()['query'])) {
return new Response('', 400, [], 'Missing argument "query"');
}

$queryString = trim($request->getParsedBody()['q']);
$searchProviders = $request->getParsedBody()['options']['searchProviders'] ?? [];
$mutableSearchDemand = MutableSearchDemand::fromRequest($request);
$searchResults = [];
$remainingItems = self::LIMIT;

Expand All @@ -63,12 +63,12 @@ public function searchAction(ServerRequestInterface $request): ResponseInterface
break;
}

if ($searchProviders !== [] && !in_array(get_class($provider), $searchProviders, true)) {
if ($mutableSearchDemand->getSearchProviders() !== [] && !in_array(get_class($provider), $mutableSearchDemand->getSearchProviders(), true)) {
continue;
}

$searchDemand = new SearchDemand($queryString, $remainingItems, 0);
$providerResult = $provider->find($searchDemand);
$mutableSearchDemand->setProperty(DemandPropertyName::limit, $remainingItems);
$providerResult = $provider->find($mutableSearchDemand->freeze());
foreach ($providerResult as $key => $resultItem) {
$modifyRecordEvent = $this->eventDispatcher->dispatch(new ModifyResultItemInLiveSearchEvent($resultItem));
$providerResult[$key] = $modifyRecordEvent->getResultItem();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@

namespace TYPO3\CMS\Backend\Search\Event;

use TYPO3\CMS\Backend\Search\LiveSearch\SearchDemand;
use TYPO3\CMS\Backend\Search\LiveSearch\SearchDemand\SearchDemand;

/**
* PSR-14 event to modify the incoming input about which tables should be searched for within
* the live search results. This allows adding additional DB tables to be excluded / ignored, to
* further limit the search result on certain page IDs or to modify the search query altogether.
* further limit the search result on certain page IDs or to modify the search query altogether.
*/
final class BeforeSearchInDatabaseRecordProviderEvent
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use TYPO3\CMS\Backend\Routing\UriBuilder;
use TYPO3\CMS\Backend\Search\Event\BeforeSearchInDatabaseRecordProviderEvent;
use TYPO3\CMS\Backend\Search\Event\ModifyQueryForLiveSearchEvent;
use TYPO3\CMS\Backend\Search\LiveSearch\SearchDemand\SearchDemand;
use TYPO3\CMS\Backend\Tree\Repository\PageTreeRepository;
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
Expand Down Expand Up @@ -85,9 +86,11 @@ public function find(SearchDemand $searchDemand): array
);
$this->pageIdList = $event->getSearchPageIds();
$searchDemand = $event->getSearchDemand();
$query = $searchDemand->getQuery();
$remainingItems = $searchDemand->getLimit();

if ($this->queryParser->isValidPageJump($searchDemand->getQuery())) {
$commandQuery = $this->queryParser->getCommandForPageJump($searchDemand->getQuery());
if ($this->queryParser->isValidPageJump($query)) {
$commandQuery = $this->queryParser->getCommandForPageJump($query);
$extractedQueryString = $this->queryParser->getSearchQueryValue($commandQuery);
$tableName = $this->queryParser->getTableNameFromCommand($commandQuery);

Expand All @@ -105,7 +108,7 @@ public function find(SearchDemand $searchDemand): array
continue;
}

$tableResult = $this->findByTable($searchDemand->getQuery(), $tableName, $remainingItems);
$tableResult = $this->findByTable($query, $tableName, $remainingItems);
$remainingItems -= count($tableResult);

$result[] = $tableResult;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use TYPO3\CMS\Backend\Routing\PreviewUriBuilder;
use TYPO3\CMS\Backend\Routing\UriBuilder;
use TYPO3\CMS\Backend\Search\Event\ModifyQueryForLiveSearchEvent;
use TYPO3\CMS\Backend\Search\LiveSearch\SearchDemand\SearchDemand;
use TYPO3\CMS\Backend\Tree\Repository\PageTreeRepository;
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
Expand Down Expand Up @@ -81,19 +82,21 @@ public function find(SearchDemand $searchDemand): array

$result = [];

if ($this->queryParser->isValidPageJump($searchDemand->getQuery())) {
$commandQuery = $this->queryParser->getCommandForPageJump($searchDemand->getQuery());
$query = $searchDemand->getQuery();
$remainingItems = $searchDemand->getLimit();
if ($this->queryParser->isValidPageJump($query)) {
$commandQuery = $this->queryParser->getCommandForPageJump($query);
$extractedQueryString = $this->queryParser->getSearchQueryValue($commandQuery);
$tableName = $this->queryParser->getTableNameFromCommand($commandQuery);

if ($tableName !== 'pages') {
return [];
}

return $this->findByTable($extractedQueryString, $searchDemand->getLimit());
return $this->findByTable($extractedQueryString, $remainingItems);
}

$tableResult = $this->findByTable($searchDemand->getQuery(), $searchDemand->getLimit());
$tableResult = $this->findByTable($query, $remainingItems);

$result[] = $tableResult;

Expand Down
48 changes: 0 additions & 48 deletions typo3/sysext/backend/Classes/Search/LiveSearch/SearchDemand.php

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?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\SearchDemand;

final class DemandProperty
{
public function __construct(
private readonly DemandPropertyName $name,
private readonly mixed $value,
) {
}

public function getName(): DemandPropertyName
{
return $this->name;
}

public function getValue(): mixed
{
return $this->value;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?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\SearchDemand;

enum DemandPropertyName
{
case query;
case limit;
case offset;
case searchProviders;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?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\SearchDemand;

/**
* @internal for internal use only, no public API
*/
final class MutableSearchDemand extends SearchDemand
{
public function setProperty(DemandPropertyName $name, mixed $value): self
{
$this->demandProperties[$name->name] = new DemandProperty($name, $value);

return $this;
}

public function freeze(): SearchDemand
{
return new SearchDemand($this->demandProperties);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<?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\SearchDemand;

use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Backend\Search\LiveSearch\SearchProviderInterface;

/**
* Holds necessary data to query data from a search provider
*
* @internal may change in further iterations, do not rely on it
*/
class SearchDemand
{
public const DEFAULT_LIMIT = 50;

/**
* @var DemandProperty[] $demandProperties
*/
protected array $demandProperties = [];

/**
* @param DemandProperty[] $demandProperties
*/
final public function __construct(array $demandProperties = [])
{
$this->demandProperties = array_reduce($demandProperties, static function (array $result, DemandProperty $item) {
$result[$item->getName()->name] = $item;
return $result;
}, []);
}

public function getProperty(DemandPropertyName $demandPropertyName): ?DemandProperty
{
return $this->demandProperties[$demandPropertyName->name] ?? null;
}

/**
* @return DemandProperty[]
*/
public function getProperties(): array
{
return $this->demandProperties;
}

public function getQuery(): string
{
return $this->getProperty(DemandPropertyName::query)?->getValue() ?? '';
}

public function getLimit(): int
{
return (int)($this->getProperty(DemandPropertyName::limit)?->getValue() ?? self::DEFAULT_LIMIT);
}

public function getOffset(): int
{
return (int)($this->getProperty(DemandPropertyName::offset)?->getValue() ?? 0);
}

/**
* @return class-string<SearchProviderInterface>[]
*/
public function getSearchProviders(): array
{
return $this->getProperty(DemandPropertyName::searchProviders)?->getValue() ?? [];
}

public static function fromRequest(ServerRequestInterface $request): static
{
$demandProperties = [];
foreach (DemandPropertyName::cases() as $demandProperty) {
$demandPropertyName = $demandProperty->name;
$valueFromRequest = $request->getParsedBody()[$demandPropertyName] ?? $request->getQueryParams()[$demandPropertyName] ?? null;
if ($valueFromRequest !== null) {
$demandProperties[] = new DemandProperty($demandProperty, $valueFromRequest);
}
}

return new static($demandProperties);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

namespace TYPO3\CMS\Backend\Search\LiveSearch;

use TYPO3\CMS\Backend\Search\LiveSearch\SearchDemand\SearchDemand;

/**
* Interface to declare a search provider used for the backend search
*
Expand Down

0 comments on commit 0a9b218

Please sign in to comment.