Template-Pfade via Flexform setzen

Früher war alles so einfach… Für Plugins konnte einfach in einem Flexform-Feld eine Template-Datei angegeben werden, und schwupps war die via TypoScript gesetzte Datei für dieses eine Content-Element überschrieben. Das wohl bekannteste Beispiel hierfür ist die Extension News (tt_news).

Die Zeiten haben sich geändert. Mit Extbase/Fluid ist Vieles flexibler und einfacher geworden: wir können Dank Partials HTML-Schnipsel wiederverwenden, können Dank der Overrides Kaskaden für Template-Pfade in TypoScript festlegen – aber wie kann gelöst werden, dass ein einzelnes Content-Element ein anderes Template bekommen soll?

Szenario:
wir suchen nach einer Lösung, um auf einer beliebigen Seite, auf der dasselbe Plugin mehrmals verwendet wird, einem Content-Element ein anderes Template zu geben.

Schritt 1: Flexform

Zuerst brauchen wir im Flexform ein entsprechendes Feld. Um später im Controller einfach darauf zugreifen zu können, die Namen mit settings. prefixen. Ich gehe von folgenden Bezeichnern aus: settings.layoutRootPath, settings.templateRootPath, settings.partialRootPath.

Zur Vereinfachung wird stellvertretend nur der templateRootPath weiter betrachtet. Die beiden anderen können analog behandelt werden.

Schritt 2: Methode im Controller überschreiben

Die Auswahl der möglichen Template-Pfade erfolgt in der Klasse \TYPO3\CMS\Fluid\View\TemplateView mittels der Methode getTemplatePathAndFilename()

/**
 * Resolve the template path and filename for the given action. If $actionName
 * is NULL, looks into the current request.
 *
 * @param string $actionName Name of the action. If NULL, will be taken from request.
 * @return string Full path to template
 * @throws Exception\InvalidTemplateResourceException
 */
protected function getTemplatePathAndFilename($actionName = null)
{
    if ($this->templatePathAndFilename !== null) {
        return $this->resolveFileNamePath($this->templatePathAndFilename);
    }
    if ($actionName === null) {
        /** @var $actionRequest \TYPO3\CMS\Extbase\Mvc\Request */
        $actionRequest = $this->controllerContext->getRequest();
        $actionName = $actionRequest->getControllerActionName();
    }

    $paths = $this->expandGenericPathPattern($this->templatePathAndFilenamePattern, false, false);
    $possibleFileNames = $this->buildListOfTemplateCandidates($actionName, $paths, '@action');
    foreach ($possibleFileNames as $templatePathAndFilename) {
        if ($this->testFileExistence($templatePathAndFilename)) {
            return $templatePathAndFilename;
        }
    }
    throw new Exception\InvalidTemplateResourceException('Template could not be loaded. I tried "' . implode('", "', $possibleFileNames) . '"', 1225709595);
}

Verfolgt man etwas weiter, woher die möglichen Pfade denn kommen, landet man im ActionController (\TYPO3\CMS\Extbase\Mvc\Controller\ActionController), den die meisten Extbase-Controller erweitern, und dort konkret in der Methode setViewConfiguration():

/**
 * @param ViewInterface $view
 *
 * @return void
 */
protected function setViewConfiguration(ViewInterface $view)
{
    // Template Path Override
    $extbaseFrameworkConfiguration = $this->configurationManager->getConfiguration(
        ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK
    );

    // set TemplateRootPaths
    $viewFunctionName = 'setTemplateRootPaths';
    if (method_exists($view, $viewFunctionName)) {
        $setting = 'templateRootPaths';
        $parameter = $this->getViewProperty($extbaseFrameworkConfiguration, $setting);
        // no need to bother if there is nothing to set
        if ($parameter) {
            $view->$viewFunctionName($parameter);
        }
    }

    // ...
}

Nachdem wird jetzt die passende Stelle gefunden haben, ist es ein Leichtes, hier einen zusätzlichen Pfad einzufügen. Hierzu überschreiben wir die Methode einfach in unserem eigenen Controller:

/**
 * @param ViewInterface $view
 *
 * @return void
 */
protected function setViewConfiguration(ViewInterface $view)
{
    // Template Path Override
    $extbaseFrameworkConfiguration = $this->configurationManager->getConfiguration(
        ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK
    );

    // set TemplateRootPaths
    $viewFunctionName = 'setTemplateRootPaths';
    if (method_exists($view, $viewFunctionName)) {
        $deprecatedSetting = 'templateRootPath';
        $setting = 'templateRootPaths';
        $parameter = $this->getViewProperty($extbaseFrameworkConfiguration, $setting, $deprecatedSetting);
        // Merge templateRootPath from flexform (if set)
        if ($this->settings['templateRootPath']) {
            array_unshift($parameter, $this->getResolvedRootPathFromFlexform('templateRootPath'));
        }
        // no need to bother if there is nothing to set
        if ($parameter) {
            $view->$viewFunctionName($parameter);
        }
    }

    // ...
}

In aktuellen TYPO3-Version steht in settings[‚templateRootPath‘] jedoch nicht mehr der Pfad direkt drin, sondern „nur noch“ ein FAL-Identifier (bis v7.6) bzw. ein Link-Parameter (v8). Um möglichst viele TYPO3-Versionen mit richtigem Template-Pfad versorgen zu können, habe ich daher die Umwandlung in eine separate Methode ausgelagert:

/**
 * @param string $pathName
 * @return string
 */
protected function getResolvedRootPathFromFlexform($pathName)
{
    $rootPath = $this->settings[$pathName];
    // old-styled paths, relative to site-root (e.g fileadmin/MyExt/Templates)
    if (strpos($rootPath, ':') === false) {
        return $rootPath;
    }
    // TYPO3 v8
    if (strpos($rootPath, '//')) {
        $linkService = GeneralUtility::makeInstance(\TYPO3\CMS\Core\LinkHandling\LinkService::class);
        $data = $linkService->resolve($rootPath);
        $rootPath = $data['folder']->getPublicUrl();
        return $rootPath;
    }
    $resourceFactory = \TYPO3\CMS\Core\Resource\ResourceFactory::getInstance();
    $parts = explode(':', $rootPath);
    if (count($parts) === 3) {
        $folderCombinedIdentifier = $parts[1] . ':' . $parts[2];
        $rootPath = $resourceFactory->getFolderObjectFromCombinedIdentifier($folderCombinedIdentifier)->getPublicUrl();
    }
    return $rootPath;
}

Fertig!

Soweit, so gut. Die Lösung ist noch in der Test-Phase. Sie läuft in TYPO3 6.2, 7.6 und 8.7.

 

Zu kompliziert? Gibt’s einen einfacheren Weg? Wie hast Du ggf. ein solches Szenario bewältigt?
Freue mich über jedes Feedback dazu.

Hinterlasse einen Kommentar.

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.