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.