Feature: #102337 - PSR-14 event for modifying record list export data

See forge#102337

Description

A new PSR-14 event \TYPO3\CMS\Backend\RecordList\Event\BeforeRecordDownloadIsExecutedEvent has been introduced to modify the result of a download / export initiated in the Web > List module.

This replaces the $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['TYPO3\CMS\Recordlist\RecordList\DatabaseRecordList']['customizeCsvHeader'] and $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['TYPO3\CMS\Recordlist\RecordList\DatabaseRecordList']['customizeCsvRow'] , hooks, which have been deprecated.

The event allows body and header sections of the data dump to be modified, so that they can e.g. be used to redact specific data for GDPR compliance, transform / translate specific data, trigger creation of archives or web hooks, log export access and more.

The event offers the following methods:

  • getHeaderRow(): Return the current header row of the dataset.
  • setHeaderRow(): Sets the modified header row of the dataset.
  • getRecords(): Returns the current body rows of the dataset.
  • setRecords(): Sets the modified body rows of the dataset.
  • getRequest(): Returns the PSR request context.
  • getTable(): Returns the name of the database table of the dataset.
  • getFormat(): Returns the format of the download action (CSV/JSON).
  • getFilename(): Returns the name of the download filename (for browser output).
  • getId(): Returns the page UID of the download origin.
  • getModTSconfig(): Returns the active module TSconfig of the download origin.
  • getColumnsToRender(): Returns the list of header columns for the triggered download.
  • isHideTranslations(): Returns whether translations are hidden or not.

Example

The corresponding event listener class:

<?php

declare(strict_types=1);

namespace Vendor\MyPackage\Core\EventListener;

use TYPO3\CMS\Backend\RecordList\Event\BeforeRecordDownloadIsExecutedEvent;
use TYPO3\CMS\Core\Attribute\AsEventListener;

#[AsEventListener(identifier: 'my-package/record-list-download-data')]
final readonly class DataListener
{
    public function __invoke(BeforeRecordDownloadIsExecutedEvent $event): void
    {
        // List of redactable fields.
        $gdprFields = ['title', 'author'];

        $headerRow = $event->getHeaderRow();
        $records = $event->getRecords();

        // Iterate header to mark redacted fields...
        foreach ($headerRow as $headerRowKey => $headerRowValue) {
            if (in_array($headerRowKey, $gdprFields, true)) {
                $headerRow[$headerRowKey] .= ' (REDACTED)';
            }
        }

        // Redact actual content...
        foreach ($records as $uid => $record) {
            foreach ($gdprFields as $gdprField) {
                if (isset($record[$gdprField])) {
                    $records[$uid][$gdprField] = '(REDACTED)';
                }
            }
        }

        $event->setHeaderRow($headerRow);
        $event->setRecords($records);
    }
}
Copied!

Migration

The functionality of both hooks customizeCsvHeader and customizeCsvRow are now handled by the new PSR-14 event.

Migrating customizeCsvHeader

The prior hook parameter/variable fields is now available via $event->getColumnsToRender(). The actual record data (previously $this->recordList, submitted to the hook as its object reference) is accessible via $event->getHeaderRow().

Migrating customizeCsvRow

The prior hook parameters/variables have the following substitutes:

  • databaseRow is now available via $event->getRecords() (see note below).
  • tableName is now available via $event->getTable().
  • pageId is now available via $event->getId().

The actual record data (previously $this->recordList, submitted to the hook as its object reference) is accessible via $event->getRecords().

Please note that the hook was previously executed once per row retrieved from the database. The PSR-14 event however - due to performance reasons - is only executed for the full record list after database retrieval, thus allowing post-processing on the whole dataset.

Impact

Using the PSR-14 event BeforeRecordDownloadIsExecutedEvent it is now possible to modify all of the data available when downloading / exporting a list of records via the Web > List module.