Extension Settings in Plugin überschreiben

In TYPO3 gibt es zwei Möglichkeiten settings oder allgemein Einstellungen für Plugins zu konfigurieren. Entweder man konfiguriert für die komplette Extension oder für einzelne Plugins. Da ich das nicht besser beschreiben kann, versuche ich es an einem Beispiel.

Angenommen, die Extension heißt my_extension und hat zwei Plugins pi1 und pi2. Die Konfiguration allgemein kann unter plugin.tx_myextension abgelegt werden und für die einzelnen Plugins unter plugin.tx_myextension_pi1 und plugin.tx_myextension_pi2. Die „allgemeine“ Konfiguration wird dabei an die Plugins vererbt.

plugin.tx_myextension.settings {
  var1 = Variable 1
}
 
plugin.tx_myextension_pi1.settings {
  var2 = Variable 2
}
 
plugin.tx_myextension_pi2.settings {
  var3 = Variable 3
}

Im ersten Plugin pi1 stehen damit in den Settings die Variablen var1 und var2 zur Verfügung, im Plugin pi2 die Variablen var1 und var3.

Was ist aber, wenn eine Variable entfernt werden soll? In diesem Fall funktioniert der > Operator leider nicht, da die Settings allgemein und Plugin als Array gemergt werden. Die Lösung ist das Wörtchen „__UNSET“, das ein entfernen des Schlüssels zur Folge hat.

plugin.tx_myextension_pi1.settings {
  var1 = __UNSET
}

Fehlermeldung nach TYPO3 12 Update: The rendering context of ViewHelper f:uri.page is missing a valid request object

Wie es der Titel schon sagt, nach einem Update auf TYPO3 12 hat der Mailversand nicht mehr funktioniert, Fehlermeldung

the rendering context of ViewHelper f:uri.page is missing a valid request object

Dank Websuche bin ich schnell auf diesen Eintrag bei stackoverflow gestoßen.

In meinem Fall war es geringfügig anders, ich nutze FluidEmail, dort kann es aber genauso verwendet werden:

/** @var FluidEmail $email */
$email = GeneralUtility::makeInstance(FluidEmail::class);
$email->setRequest($this->request);
$email->subject($subject);
$email->setTemplate($templateName);
$email->assignMultiple($variables);

Modul Template für Redakteure freigeben

Für ein Projekt, das ich übernommen habe, brauchen die Redakteure der Seite Zugriff auf TypoScript. Ich hätte es definitiv anders implementiert (Datensätze, Modul, etc.), mein Vorgänger hatte aber den Weg gewählt. Wenn also ein Benutzer Anpassungen am TypoScript vornehmen wollte, brauche er zwangsweise die Admin-Rechte. Seit TYPO3 10 gibt es noch die Abgrenzung zum System Maintainer, aber sobald ein Benutzer Admin-Rechte besitzt, können Sie nicht weiter eingeschränkt werden. „Mit großer Macht kommt große Verantwortung“ (Onkel Ben aus Spiderman) und ich traue den Redakteure nicht über den Weg mit Admin-Rechten. Also habe ich nach der Möglichkeit gesucht, das Modul und die Tabellen für die Redakteure freizugeben, damit sie wieder normale Redakteure sein können.

In TYPO3 11 hat das folgende gut funktioniert. Wahrscheinlich auch unter TYPO3 10.

1. Zugriffsrechte für Modul anpassen in my_extension/ext_tables.php
$GLOBALS['TBE_MODULES']['_configuration']['web_ts']['access'] = 'user,group';

2. Tabellen für Benutzer freigeben (dann tauchen sie in der Liste der erlaubten Tabellen auf) in my_extension/Configuration/TCA/Overrides/sys_template.php
$GLOBALS['TCA']['sys_template']['ctrl']['adminOnly'] = 0;

3. Benutzergruppe oder Benutzer bearbeiten und dort das Modul „Template“ und die Tabelle „sys_template“ erlauben.

In TYPO3 12 hat sich die Registrierung der Module geändert. Abgesehen davon gibt es nun mehrere Template-Module. Es ist natürlich auch ein Vorteil, denn die Redakteure brauchen sie ja nicht alle.

1. Event-Handler für die Modul-Konfiguration registrieren
Backend Module configuration

2. Event Listener implementieren

namespace Vendor\MyExtension\Backend\EventListener;
 
use TYPO3\CMS\Backend\Module\BeforeModuleCreationEvent;
 
final class ModuleEventListener
{
    public function __invoke(BeforeModuleCreationEvent $event): void
    {
        // Change module icon of page module
        $modules = ['web_ts', 'web_typoscript_recordsoverview', 'web_typoscript_infomodify'];
        if (in_array($event->getIdentifier(), $modules)) {
            $event->setConfigurationValue('access', 'user');
        }
    }
}

Es reicht an dieser Stelle nicht, nur web_ts aufzulisten. Die verfügbaren Module sind die Keys in vendor/typo3/cms-tstemplate/Configuration/Backend/Modules.php

3. Benutzergruppe oder Benutzer bearbeiten und dort die Module „Template“, gewünschte Submodule und die Tabelle „sys_template“ erlauben.

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();

E-Mail Versand defekt nach Plesk Update

Nach einem Plesk Update bei einem meiner Kunden funktionierte auf einmal der Mailversand nicht mehr. Selbst das Senden einer Test-E-Mail aus dem Backend lieferte folgende Fehlermeldung:

Core: Exception handler (WEB): Uncaught TYPO3 Exception: #451: Expected response code "250/251/252" but got code "451", with message "451 qmail-spp failure: plugins/chkrcptto: can't execute (#4.3.0)". | Symfony\Component\Mailer\Exception\TransportException thrown in file [...]vendor/symfony/mailer/Transport/Smtp/SmtpTransport.php

Man kann den Fehler lösen, wenn man den sendmail-Befehlt in den Settings explzit angibt, z.B. /usr/sbin/sendmail -t

Wenn unter $GLOBALS['MAIL']['transport_sendmail_command'] nicht angegeben ist, dann wird als Fallback /usr/sbin/sendmail -bs verwendet. Die Option -bs bedeutet folgendes: „Stand-alone SMTP server mode. Read SMTP commands from standard input, and write responses to standard output. In stand-alone SMTP server mode, mail relaying and other access controls are disabled by default. To enable them, run the process as the mail_owner user.“ (Quelle)

Meta-Felder Fallbacks

Aus dem Stehgreif weiß ich so was natürlich nicht. In der offiziellen Doku ist eine Anleitung am Beispiel von og:image beschrieben, hier ist der Schnipsel für og:title.

page = PAGE
page {
    meta {
        og:title.stdWrap.cObject = TEXT
        og:title.stdWrap.cObject {
            if.isFals.field = og_title
            field = title
        }
    }
}

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.