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.