Display search metrics from TYPO3 extension ke_search in Matomo

Finally you have built a search function into your TYPO3 website with the extension ke_search. Users start searching. Wouldn't it be nice if you could see the search phrases used? Since version 3.6 the extensions ships with a dashboard widget which displays the search phrases over the last seven days. But that might not be enough:

  • The owner of a website may want to monitor the search terms and their trend over time.
  • Interesting are search terms for which there are no results. The website can be optimised for these phrases.
  • An administrator would like to know the query time for the search over time in order to identify possible bottlenecks at an early stage.

Since I am using Matomo for tracking page views (and some other topics) of a website, it would be useful to have these metrics in this analysis tool as well. All three aspects mentioned above can be solved with Matomo — and you get a few more features on top. The tricky part is to get these metrics from the extension to Matomo.

What's possible with Matomo

First the easy part: In Matomo, you need to enable the site search feature, configure the query parameter that contains the search phrases, and from now on, the analytics tool will track them.

Matomo also offers a report for search phrases without a result. This is possible with a query parameter that is added to the current URL and evaluated by Matomo. But for that a custom URL for Matomo has to be set before the trackPageView call which can be difficult depending how the Matomo snippet is embedded.

The ke_search extension also provides the query time of a result set, i.e. how long it takes to retrieve the results. This can be stored in Matomo using a custom dimension.

Let's summarise: All three requests are possible in Matomo, but it can be a bit of work to implement: We need to put the search phrase, the number of results and a custom dimension into a tracking request. This is possible with the JavaScript call trackSiteSearch, which is used instead of the standard call trackPageView:

_paq.push(["trackSiteSearch",
    // Search keyword searched for
    "Banana",
    // Search category selected in your search engine. If you do not need this, set to false
    "Organic Food",
    // Number of results on the Search results page. Zero indicates a 'No Result Search Keyword'.
    // Set to false if you don't know
    0
]);

What is not mentioned in the documentation: There is a 4th argument available: customData. If you provide a JSON with key/value pairs, this data will be added as query parameters to the resulting tracking call. This way we can also add the custom dimension for the query time (and some more if necessary).

This way, we don't have to configure the query parameters in Matomo, because we do everything ourselves. Only the search site feature for the website has to be activated in Matomo.

Implementing the tracking call in TYPO3

I am the maintainer of the Matomo Integration extension for TYPO3. This extension provides some PSR-14 events to hook into the default Matomo JavaScript tracking code. The following implementation is based on this extension, but the examples can also be used for a user function or page renderer hook.

One way to pass the search metrics from the ke_search plugin to a PSR-14 event provided by the Matomo Integration extension is to use a transient cache. This cache stores the specified data in memory and is only valid in the same request — perfect for our use case.

Note: The following code examples are written for use in TYPO3 v11 and with PHP 8.1. However, they can be easily adapted for TYPO3 v10 and older PHP versions.

1. Register the transient cache

First we have to register the transient cache:

use TYPO3\CMS\Core\Cache\Backend\TransientMemoryBackend;
use TYPO3\CMS\Core\Cache\Frontend\VariableFrontend;

$GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['myext_sitesearchtracking'] = [
    'backend' => TransientMemoryBackend::class,
    'frontend' => VariableFrontend::class,
]; 
ext_localconf.php

As we want to use dependency injection to provide the cache, we define our transient cache as a service:

services:
  cache.myext_sitesearchtracking:
    class: TYPO3\CMS\Core\Cache\Frontend\FrontendInterface
    factory: ['@TYPO3\CMS\Core\Cache\CacheManager', 'getCache']
    arguments: ['myext_sitesearchtracking']
Configuration/Services.yaml

2. Store the search metrics in the transient cache

To store the metrics in the transient cache, the "modifyResultList" hook of ke_search can be used. This hook provides all the necessary values:

<?php
declare(strict_types=1);

namespace MyVendor\MyExtension\Hooks\Search;

use Tpwd\KeSearch\Plugins\ResultlistPlugin;
use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;

final class StoreSearchMetricsInCache
{
    public function __construct(
        private readonly FrontendInterface $cache
    ) {
    }

    public function modifyResultList(array $variables, ResultlistPlugin $plugin): void
    {
        $searchWord = $plugin->piVars['sword'] ?? '';
        if ($searchWord === '') {
            return;
        }

        $queryTime = $variables['queryTime'] ?? false;
        $numberOfResults = $variables['numberofresults'] ?? false;

        $this->cache->set('searchWord', $searchWord);
        $this->cache->set('queryTime', $queryTime);
        $this->cache->set('numberOfResults', $numberOfResults);
    }
}
Classes/Hooks/Search/StoreSearchMetricsInCache.php

In the modifyResultList() method, we do not modify the results, but store the metrics in the transient cache.

The cache is injected into the constructor. We need to define the $cache argument for the constructor in Services.yaml as it is not autowirable. Additionally, the hook class must be declared public:

services:
  MyVendor\MyExtension\Hooks\Search\StoreSearchMetricsInCache:
	public: true
    arguments:
      $cache: '@cache.myext_sitesearchtracking'
Configuration/Services.yaml

We also need to register our new hook within ke_search:

use MyVendor\MyExtension\Hooks\Search\StoreSearchMetricsInCache;

$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['ke_search']['modifyResultList'][] = StoreSearchMetricsInCache::class;
ext_localconf.php

2. Retrieve the metrics from the cache

The Matomo integration extension provides a PSR-14 event to track the site search: TrackSiteSearchEvent. Let's create an event listener:

<?php
declare(strict_types=1);

namespace MyVendor\MyExtension\EventListener\Search;

use Brotkrueml\MatomoIntegration\Event\TrackSiteSearchEvent;
use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;

final class TrackSiteSearch
{
	// The custom dimension with ID 3 was previously defined in
    // Matomo with scope "action"
    private const CUSTOM_DIMENSION_ID = 3;

    public function __construct(
        private readonly FrontendInterface $cache
    ) {
    }

    public function __invoke(TrackSiteSearchEvent $event): void
    {
        $searchWord = $this->cache->get('searchWord');
        if ($searchWord === false) {
            return;
        }

        $event->setKeyword($searchWord);

        $numberOfResults = $this->cache->get('numberOfResults');
        if ($numberOfResults !== false) {
            $event->setSearchCount($numberOfResults);
        }

        $queryTime = $this->cache->get('queryTime');
        if ($queryTime !== false) {
            $event->addCustomDimension(self::CUSTOM_DIMENSION_ID, $this->getQueryTimeRange((int)$queryTime));
        }
    }

    private function getQueryTimeRange(int $queryTime): string
    {
        return match (true) {
            $queryTime < 50 => '< 50 ms',
            $queryTime < 100 => '50-99 ms',
            $queryTime < 250 => '100-249 ms',
            $queryTime < 500 => '250-499 ms',
            $queryTime < 1000 => '500-999 ms',
            $queryTime < 2000 => '1000-1999 ms',
            $queryTime < 5000 => '2000-4999 ms',
            default => '≥ 5000 ms'
        };
    }
}
Classes/EventListener/Search/TrackSiteSearch.php

Since the code is pretty self-explanatory, one more note about the custom dimension: As Matomo displays each individual dimension value separately, we have to aggregate them beforehand via the getQueryTimeRange() method to get analysable data.

The last thing we need to do is to register our event listener:

services:
  MyVendor\MyExt\EventListener\Search\TrackSiteSearch:
	tags:
      - name: event.listener
        identifier: 'myext/track-site-search'
    arguments:
      $cache: '@cache.myext_sitesearchtracking'
Configuration/Services.yaml

Site search analysis examples in Matomo

After processing all the steps above we are rewarded with an analysis of our site search metrics in Matomo:

Site search keywords

Example for site search keywords

Site search keywords with no results

Example for site search keywords with no results

Site search query time

Example for site search query time

Take a look at the Matomo Widgets extension, which provides dashboard widgets for Matomo in TYPO3 — also for displaying search phrases and custom dimensions.