Beiträge der Kategorie TYPO3 v11

LimitToPages in Site Config dynamisch setzen

Im Moment implementiere ich eine Extension, bei der es unter anderem digitale Produkte gibt. Die Produkte können jeweils von einem Typ sein – z.B. Kurs oder Tutorial. Der Kunde hat sich für die Produkte sprechende URLs gewünscht, die den Produkttyp enthalten – z.B. /kurs/produkt-1 oder /tutorial/produkt-2. Da schien es mir sinnvoll, pro Produkt-Typ eine separate Detailseite anzugelen, deren Slug entsprechend zu setzen und in der Site Config hübsche URLs zu konfigurieren.

Nun gibt es folgendes Problem: die Produkt-Typen stehen in der Datenbank und können erweitert werden. Die Site Config hingegen ist ja dateibasiert und wird versioniert. Bei der Konfiguration der hübschen URLs kann mit limitToPages angegeben werden, für welche Seiten die URLs angewendet werden. Mal absehen davon, dass die IDs der Detailseiten sich lokal, auf Stage und Live durchaus unterscheiden können, wäre es nicht schön, wenn limitToPages dynamisch aus der Datenbank ermittelt werden könnte.

Seit TYPO3 10.3. gibt es die Möglichkeit, in der Site Config Placeholder zu verwenden und diese mit eingenen Prozessoren zu verarbeiten. In der Dokumentation findet sich ein Beispiel: YAML API.

Und so sieht man Implementierung aus. In der Site Configuration config.yaml gebe ich die Platzhalter an:

limitToPages: '%detailPid(product)%'

In LocalConfiguration.php wird der Processor registriert:

<?php
[...]
'yamlLoader' => [
    'placeholderProcessors' => [
        \Example\MyExtension\Configuration\Processor\DetailPagePlaceholderProcessor::class => [
            'after' => [
                \TYPO3\CMS\Core\Configuration\Processor\Placeholder\ValueFromReferenceArrayProcessor::class
            ]
        ]
    ]
],
[...]

Und das ist der Processor:

<?php
declare(strict_types=1);
 
namespace Example\MyExtension\Configuration\Processor;
 
use TYPO3\CMS\Core\Configuration\Processor\Placeholder\PlaceholderProcessorInterface;
use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
use TYPO3\CMS\Core\Utility\GeneralUtility;
 
class DetailPagePlaceholderProcessor implements PlaceholderProcessorInterface
{
    public function canProcess(string $placeholder, array $referenceArray): bool
    {
        return strpos($placeholder, '%detailPid(') !== false;
    }
 
    public function process(string $value, array $referenceArray)
    {
        $detailPages = $this->getDetailPages();
 
        // Throw this exception if the placeholder can't be substituted
        if (empty($detailPages)) {
            throw new \UnexpectedValueException('No detail page found', 1692345790);
        }
        return $detailPages;
    }
 
    private function getDetailPages()
    {
        $table = 'tx_myextension_domain_model_type';
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
        $queryBuilder
            ->getRestrictions()
            ->removeAll()
            ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
 
        $result = $queryBuilder
            ->select('*')
            ->from($table)
            ->where(
                $queryBuilder->expr()->gt('detail_pid',
                    $queryBuilder->createNamedParameter(0, Connection::PARAM_INT))
            )
            ->execute()
            ->fetchAllAssociative();
        $detailPid = [];
        foreach ($result as $row) {
            $detailPid[] = $row['detail_pid'];
        }
        return array_unique($detailPid);
    }
}

Ich will nicht ausschließen, dass das Beispiel nicht funktioniert, denn das ist ein angepasster Demo-Code, der keine tatsächlichen Kundennamen oder Extension-Namen enthält.

Tabellenname in Repository ermitteln

Das ist ein Update zu meinem Beitrag von 2012. Mit diesem Coder kann im Repository der Tabellen-Name des zugehörigen Models ermittelt werden. Falls mal mit QueryBuilder eine Abfrage gebastelt werden muss. So funktioniert es nun in TYPO3 11:

use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper;
[...]
$dataMapper = GeneralUtility::makeInstance(DataMapper::class);
$tableName = $dataMapper->getDataMap($this->objectType)->getTableName();

Warning im TYPO3 Backend

Nach einem Update auf TYPO3 11 und PHP 8.1 hatte ich im Backend folgende Warnung:


Core: Error handler (BE): PHP Warning: Undefined array key type in /var/www/html/vendor/symfony/expression-language/Node/GetAttrNode.php line 97

Ich hatte schon die Vermutung, dass es mit TypoScript-Conditions zu tun hat. Dieser Blog-Artikel auf typo3-probleme.de hat mich auf die richtige Anpassung gebracht.

So muss die Condition angepasst werden, damit es keine Warnung mehr gibt:

[request.getQueryParams()['type'] == 1452982642]
[traverse(request.getQueryParams(), 'type') == 1452982642]

Exports aus TYPO3 Backend

Für einen Kunden habe ich Module entwickelt, in denen die dargestellten Daten als CSV exportiert werden könnne. Ursprünglich für TYPO3 8 entwickelt, wurden die Module in den letzten Jahren mehrfach aktualisiert, im Moment für TYPO3 11.

Ein Export in einem Backend-Modul kann wie folgt implementiert werden:

public function exportContactListAction(): ResponseInterface
    {
        $records = $this->recordRepository->findAll();
 
        $filename = 'records_list' . '_' . date('Y-m-d') . '.csv';
        $export = $this->createExport($records);
 
        $response = $this->responseFactory->createResponse()
            ->withHeader('Content-Type', 'application/octet-stream')
            ->withHeader('Content-Length', (string)strlen($export))
            ->withHeader('Content-Disposition', 'attachment; filename=' . PathUtility::basename($filename));
        $response->getBody()->write($export);
 
        return $response;
    }

In der Funktion createExport kann ein beliebiger Content generiert werden. Als praktisch hat sich für mich folgende Funktion erwiesen:

CsvUtility::csvValues($dataRow, ';') . "\n";

Man packt die zu exportierenden Daten in ein Array und macht ein Csv daraus.