TYPO3 Backend Livesearch - Basics und Beispiele

Diese Beispiele sind für TYPO3 v12, welches noch nicht in der LTS veröffentlicht ist. Auch sind noch nicht alle Features vorhanden und ggf. können sich bis zur endgültigen Veröffentlichung noch Sachen ändern. Der Blogbeitrag wird sich anpassen. Stand: 22.12.2022 - TYPO3 12.1

Inhaltsverzeichnis


Basics / Intro


Die TYPO3 Backend Livesearch wurde in den letzten Jahren aus Entwicklersicht etwas vernachlässigt. Auch für mich als Entwickler und Integrator kann ich sagen, dass man nicht die volle Energie in dieses Feature gesteckt hat. In TYPO3 v12 wird sich das nun ändern. Die Suche bekommt eine visuelle und inhaltliche Überarbeitung. Dadurch haben alle mehr Möglichkeiten die Suche zu steuern.

Für alle unten aufgeführten Beispiele wurde eine Beispielextension auf Github abgelegt: https://github.com/ayacoo/examplesearch Hier werden die Beispiele aus dem Core zusammen geführt und mit weiteren Beispielen angereichert.


Wie sieht die neue Suche aus?


In TYPO3 v12 kann man die Suche nun über CTRL+K öffnen. Es öffnet sich ein Modal und das Ganze sieht so aus

https://review.typo3.org/c/Packages/TYPO3.CMS/+/76118


Suche für deine Extension einrichten


Über eine lange Zeit kann man Tabellen aus seiner Extension schon der TYPO3 Backend Livesearch bekannt machen. Dazu kann man in der ext_localconf.php die Tabelle registrieren:


// Add tables to livesearch (e.g. "#meta:foo")
$GLOBALS['TYPO3_CONF_VARS']['SYS']['livesearch']['meta'] = 'sys_file_metadata';
                                            

Technisch läuft es so ab, dass die Suche sich die searchFields aus dem TCA schnappt und anhand dessen ein Query zusammen baut. Wer "hideTables" auf true hat, dem wird die Tabelle auch nicht in der Suche angezeigt.


Suchtreffer begrenzen / Performance optimieren


Es gibt seit TYPO3 12.0 ein neues ModifyQueryForLiveSearchEvent, um den Query der Suche beeinflussen zu können (https://review.typo3.org/c/Packages/TYPO3.CMS/+/68528) Dazu müssen wir unseren EventListener in der Services.yaml registrieren


Ayacoo\ExampleSearch\EventListener\ModifyQueryForLiveSearchEventListener:
  tags:
    - name: event.listener
      identifier: 'ayacoo/example-search/modify-query-for-live-search-event-listener'
      event: 'TYPO3\CMS\Backend\Search\Event\ModifyQueryForLiveSearchEvent'
                                            

Im EventListener können wir nun die Query beeinflussen, z.B. Suchtreffer für bestimmte Tabellen begrenzen oder die Sortierung entfernen


namespace Ayacoo\ExampleSearch\EventListener;

use TYPO3\CMS\Backend\Search\Event\ModifyQueryForLiveSearchEvent;

final class ModifyQueryForLiveSearchEventListener
{
    public function __invoke(ModifyQueryForLiveSearchEvent $event): void
    {
        // Get the current instance
        $queryBuilder = $event->getQueryBuilder();

        // Change limit depending on the table
        if ($event->getTableName() === 'be_dashboards') {
            $queryBuilder->setMaxResults(1);
        }

        // Reset the orderBy part
        $queryBuilder->resetQueryPart('orderBy');
    }
}
                                                

Suchtreffer erweitern


Das ModifyResultItemInLiveSearchEvent ist noch in Arbeit, aber wird zeitnah sicher im Core landen. Mit diesem Event könnt ihr einzelne Suchtreffer erweitern. In dem Core Beispiel wird z.B. die tt_content Tabelle, um den Punkt History erweitert. Eurer Phantasie sind keine Grenzen gesetzt. Wie gehabt müssen wir erst den EventListener in der Services.yaml registrieren.


Ayacoo\ExampleSearch\EventListener\AddLiveSearchResultActionsListener:
  tags:
    - name: 'event.listener'
      identifier: 'ayacoo/example-search/add-live-search-result-actions-listener'
      event: 'TYPO3\CMS\Backend\Search\Event\ModifyResultItemInLiveSearchEvent'
                                            

namespace Ayacoo\ExampleSearch\EventListener;

use ...

final class AddLiveSearchResultActionsListener
{
    protected LanguageService $languageService;

    public function __construct(
        protected readonly IconFactory $iconFactory,
        protected readonly LanguageServiceFactory $languageServiceFactory,
        protected readonly UriBuilder $uriBuilder
    ) {
        $this->languageService = $this->languageServiceFactory->createFromUserPreferences($GLOBALS['BE_USER']);
    }

    public function __invoke(ModifyResultItemInLiveSearchEvent $event): void
    {
        $resultItem = $event->getResultItem();
        if ($resultItem->getProviderClassName() !== DatabaseRecordProvider::class) {
            return;
        }

        if (($resultItem->getExtraData()['table'] ?? null) === 'tt_content') {
            /**
             * WARNING: THIS EXAMPLE OMITS ANY ACCESS CHECK FOR SIMPLICITY REASONS - DO NOT USE AS-IS
             */
            $showHistoryAction = (new ResultItemAction('view_history'))
                ->setLabel($this->languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:history'))
                ->setIcon($this->iconFactory->getIcon('actions-document-history-open', Icon::SIZE_SMALL))
                ->setUrl((string)$this->uriBuilder->buildUriFromRoute('record_history', [
                    'element' => $resultItem->getExtraData()['table'] . ':' . $resultItem->getExtraData()['uid']
                ]));
            $resultItem->addAction($showHistoryAction);
        }
    }
}
                                                

Vorher

Nachher


Tabellen in der Suche verstecken


Ab TYPO3 v12 können wir Tabellen auch verstecken. Das könnte man zwar auch via TCA mit hideTables, aber das passt nicht in allen Fällen. Nehmen wir dieses konstruierte Beispiel: Ich möchte gerne Dashboards in TYPO3 bearbeiten können, aber sie sollen nicht in der Suche auftauchen.

Dafür gibt es natürlich auch ein PSR-14 Event: BeforeSearchInDatabaseRecordProviderEvent
Mit dem Event könnte man sicher auch noch die Performance etwas optimieren und weitere Tabellen ausschließen.


Ayacoo\ExampleSearch\EventListener\BeforeSearchInDatabaseRecordProviderEventListener:
  tags:
    - name: 'event.listener'
      identifier: 'ayacoo/example-search/before-search-in-database-record-provider-event-listener'
      event: 'TYPO3\CMS\Backend\Search\Event\BeforeSearchInDatabaseRecordProviderEvent'
                                            

namespace Ayacoo\ExampleSearch\EventListener;

use TYPO3\CMS\Backend\Search\Event\BeforeSearchInDatabaseRecordProviderEvent;

final class BeforeSearchInDatabaseRecordProviderEventListener
{
    public function __invoke(BeforeSearchInDatabaseRecordProviderEvent $event): void
    {
        $event->ignoreTable('be_dashboards');
    }
}
                                                

Einen eigenen SearchProvider anbinden - Grundlagen


Wir können nun auch in TYPO3 v12 eigene SearchProvider anbinden. Ein konkretes Beispiel dazu weiter unten. Um einen eigenen SearchProvider anbinden zu können, müssen wir diesen in der Services.yaml registrieren:


Ayacoo\ExampleSearch\LiveSearch\DictionaryProvider:
  tags:
    - name: 'livesearch.provider'
      priority: 70
                                            

Mit der Priorität können wir die Daten noch etwas beeinflussen, falls sich Sachen überschreiben.
Der eigene SearchProvider benötigt das SearchProviderInterface. Schauen wir uns erstmal das Grundgerüst an.


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

class DictionaryProvider implements SearchProviderInterface
{
    public function find(SearchDemand $searchDemand): array
    {
    }

    public function getFilterLabel(): string
    {
        return 'Dictionary records';
    }
}

                                            

In der find Methode findet die eigentliche Logik der Suche statt und wir übergeben am Ende in Array an ResultItems zurück. Die Methode getFilterLabel wird für den Filter oben links im Modal genutzt, um die Ergebnisse einschränken zu können.

Erzeugen wir uns nun mal eine Kategorie, die einen Eintrag zu Google mit einer URL enthält.


public function find(SearchDemand $searchDemand): array
{
    $actions = [];
    $resultItems = [];

    // Hier definieren wir uns einen Suchtreffer
    $resultItem = new ResultItem(self::class);

    // Hier werden die Basicwerte gesetzt inkl. Icon welches man in der Configuration/Icons.php registrieren kann
    $resultItem->setItemTitle('Google');
    $resultItem->setTypeLabel('My Dictionary');
    $resultItem->setIcon($this->iconFactory->getIcon('mimetypes-example-search-icon', Icon::SIZE_SMALL));

    // Beispielaction - mehrere Actions sind natürlich möglich
    $action = new ResultItemAction('open_website');
    $action->setLabel('Open website');
    $action->setIcon($this->iconFactory->getIcon('actions-open', Icon::SIZE_SMALL));
    $action->setUrl('https://www.google.de');
    $actions[] = $action;
    $resultItem->setActions(...$actions);

    // Suchtreffer in die Liste aufnehmen
    $resultItems[] = $resultItem;

    return [...$resultItems];
}
                                            

Hooray! Wir haben es geschafft und einen eigenen Provider registriert.


Einen SearchProvider für solr anbinden


Die Backend Livesearch ist eine Volltextsuche und somit was Filter angeht begrenzt. Es soll einen schnellen Überblick verschaffen und genau das passiert auch. Wenn wir nun mehr Suchlogik nutzen wollen, könnten wir z.B. solr anbinden.

Fiktives Szenario: Ich habe seit 2010 News in meiner Datenbank und parallel auch in solr indexiert. Ich möchte nun gerne meine News mit dem Titel "Weihnachten" finden. aber auf die Jahre 2010 bis 2015 begrenzen. Hier würde die aktuelle TYPO3 Backend Livesearch an die Grenze kommen. Natürlich könnte man das Query über ein Event beeinflussen und mit etwas Parsing kommt man auch zum Ziel. Aber warum nicht die Suchmöglichkeiten von solr nutzen? Die Performance bei sehr vielen Treffern wäre auch nicht zu vernachlässigen.

 

In der find Methode wird ein SearchDemand übergeben, dieser Demand kann z.B. solr Query Logik entgegennehmen:

title:*Weihnachten* + year:[2010 to 2015]
Dieses Query kann man dann selbst parsen oder, noch besser, direkt an solr schicken und wir bekommen von solr ein JSON Result zurück. Das muss dann noch in ein Array mit ResultItems() umgewandelt werden und dann könnten wir tatsächlich komplexere Suchlogik in die TYPO3 Backend Livesearch einbauen.


public function find(SearchDemand $searchDemand): array
{
    $query = $searchDemand->getQuery();

    // fiktiver Code
    $solrConnection = new SolrConnection();
    $solrResults = $solrConnection->execute($query);

    // Ergebnisse parsen
    $resultItems = $this->parseSolrResults($solrResults);
    return [...$resultItems];
}
                                            

Das könnte gerade für größere Seiten spannend sein.

Header Foto von Tobias Aeppli von Pexels: https://www.pexels.com/de-de/foto/person-die-kompass-im-wald-halt-1125272/