[TASK] Use fully qualified name resolution in PHP 5.5
[Packages/TYPO3.CMS.git] / typo3 / sysext / fluid / Classes / View / TemplateView.php
index 64e9841..d846f60 100644 (file)
@@ -1,22 +1,33 @@
 <?php
+namespace TYPO3\CMS\Fluid\View;
+
 /*                                                                        *
- * This script is backported from the FLOW3 package "TYPO3.Fluid".        *
+ * This script is backported from the TYPO3 Flow package "TYPO3.Fluid".   *
  *                                                                        *
  * It is free software; you can redistribute it and/or modify it under    *
  * the terms of the GNU Lesser General Public License, either version 3   *
- *  of the License, or (at your option) any later version.                *
+ * of the License, or (at your option) any later version.                 *
  *                                                                        *
  * The TYPO3 project - inspiring people to share!                         *
  *                                                                        */
+
+use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Extbase\Mvc\Controller\ControllerContext;
+use TYPO3\CMS\Fluid\Compatibility\TemplateParserBuilder;
+use TYPO3\CMS\Fluid\Fluid;
+
 /**
  * The main template view. Should be used as view if you want Fluid Templating
  *
  * @api
  */
-class Tx_Fluid_View_TemplateView extends Tx_Fluid_View_AbstractTemplateView {
+class TemplateView extends AbstractTemplateView {
 
        /**
         * Pattern to be resolved for "@templateRoot" in the other patterns.
+        * Following placeholders are supported:
+        * - "@packageResourcesPath"
         *
         * @var string
         */
@@ -24,6 +35,8 @@ class Tx_Fluid_View_TemplateView extends Tx_Fluid_View_AbstractTemplateView {
 
        /**
         * Pattern to be resolved for "@partialRoot" in the other patterns.
+        * Following placeholders are supported:
+        * - "@packageResourcesPath"
         *
         * @var string
         */
@@ -31,34 +44,43 @@ class Tx_Fluid_View_TemplateView extends Tx_Fluid_View_AbstractTemplateView {
 
        /**
         * Pattern to be resolved for "@layoutRoot" in the other patterns.
+        * Following placeholders are supported:
+        * - "@packageResourcesPath"
         *
         * @var string
         */
        protected $layoutRootPathPattern = '@packageResourcesPath/Private/Layouts';
 
        /**
-        * Path to the template root. If NULL, then $this->templateRootPathPattern will be used.
+        * Path(s) to the template root. If NULL, then $this->templateRootPathPattern will be used.
         *
-        * @var string
+        * @var array
         */
-       protected $templateRootPath = NULL;
+       protected $templateRootPaths = NULL;
 
        /**
-        * Path to the partial root. If NULL, then $this->partialRootPathPattern will be used.
+        * Path(s) to the partial root. If NULL, then $this->partialRootPathPattern will be used.
         *
-        * @var string
+        * @var array
         */
-       protected $partialRootPath = NULL;
+       protected $partialRootPaths = NULL;
 
        /**
-        * Path to the layout root. If NULL, then $this->layoutRootPathPattern will be used.
+        * Path(s) to the layout root. If NULL, then $this->layoutRootPathPattern will be used.
         *
-        * @var string
+        * @var array
         */
-       protected $layoutRootPath = NULL;
+       protected $layoutRootPaths = NULL;
 
        /**
         * File pattern for resolving the template file
+        * Following placeholders are supported:
+        * - "@templateRoot"
+        * - "@partialRoot"
+        * - "@layoutRoot"
+        * - "@subpackage"
+        * - "@action"
+        * - "@format"
         *
         * @var string
         */
@@ -66,6 +88,13 @@ class Tx_Fluid_View_TemplateView extends Tx_Fluid_View_AbstractTemplateView {
 
        /**
         * Directory pattern for global partials. Not part of the public API, should not be changed for now.
+        * Following placeholders are supported:
+        * - "@templateRoot"
+        * - "@partialRoot"
+        * - "@layoutRoot"
+        * - "@subpackage"
+        * - "@partial"
+        * - "@format"
         *
         * @var string
         */
@@ -73,6 +102,13 @@ class Tx_Fluid_View_TemplateView extends Tx_Fluid_View_AbstractTemplateView {
 
        /**
         * File pattern for resolving the layout
+        * Following placeholders are supported:
+        * - "@templateRoot"
+        * - "@partialRoot"
+        * - "@layoutRoot"
+        * - "@subpackage"
+        * - "@layout"
+        * - "@format"
         *
         * @var string
         */
@@ -92,17 +128,22 @@ class Tx_Fluid_View_TemplateView extends Tx_Fluid_View_AbstractTemplateView {
         */
        protected $layoutPathAndFilename = NULL;
 
+       /**
+        * Constructor
+        */
        public function __construct() {
-               $this->injectTemplateParser(Tx_Fluid_Compatibility_TemplateParserBuilder::build());
-               $this->injectObjectManager(t3lib_div::makeInstance('Tx_Extbase_Object_ObjectManager'));
-               $this->setRenderingContext($this->objectManager->create('Tx_Fluid_Core_Rendering_RenderingContextInterface'));
+               $this->templateParser = TemplateParserBuilder::build();
+               $this->objectManager = GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Object\ObjectManager::class);
+               $this->setRenderingContext($this->objectManager->get(\TYPO3\CMS\Fluid\Core\Rendering\RenderingContextInterface::class));
        }
 
+       /**
+        * Init view
+        */
        public function initializeView() {
-
        }
+       // Here, the backporter can insert a constructor method, which is needed for the TYPO3 CMS extension
 
-       // Here, the backporter can insert a constructor method, which is needed for Fluid v4.
        /**
         * Sets the path and name of of the template file. Effectively overrides the
         * dynamic resolving of a template file.
@@ -127,32 +168,120 @@ class Tx_Fluid_View_TemplateView extends Tx_Fluid_View_AbstractTemplateView {
        }
 
        /**
-        * Checks whether a template can be resolved for the current request context.
+        * Set the root path to the templates.
+        * If set, overrides the one determined from $this->templateRootPathPattern
         *
-        * @param Tx_Extbase_MVC_Controller_ControllerContext $controllerContext Controller context which is available inside the view
-        * @return boolean
+        * @param string $templateRootPath Root path to the templates. If set, overrides the one determined from $this->templateRootPathPattern
+        * @return void
         * @api
+        * @see setTemplateRootPaths()
         */
-       public function canRender(Tx_Extbase_MVC_Controller_ControllerContext $controllerContext) {
-               $this->setControllerContext($controllerContext);
-               try {
-                       $this->getTemplateSource();
-                       return TRUE;
-               } catch (Tx_Fluid_View_Exception_InvalidTemplateResourceException $e) {
-                       return FALSE;
+       public function setTemplateRootPath($templateRootPath) {
+               $this->setTemplateRootPaths(array($templateRootPath));
+       }
+
+       /**
+        * Resolves the template root to be used inside other paths.
+        *
+        * @return array Path(s) to template root directory
+        */
+       public function getTemplateRootPaths() {
+               if ($this->templateRootPaths !== NULL) {
+                       return $this->templateRootPaths;
                }
+               /** @var $actionRequest \TYPO3\CMS\Extbase\Mvc\Request */
+               $actionRequest = $this->controllerContext->getRequest();
+               return array(str_replace('@packageResourcesPath', ExtensionManagementUtility::extPath($actionRequest->getControllerExtensionKey()) . 'Resources/', $this->templateRootPathPattern));
        }
 
        /**
-        * Set the root path to the templates.
+        * Set the root path(s) to the templates.
         * If set, overrides the one determined from $this->templateRootPathPattern
         *
-        * @param string $templateRootPath Root path to the templates. If set, overrides the one determined from $this->templateRootPathPattern
+        * @param array $templateRootPaths Root path(s) to the templates. If set, overrides the one determined from $this->templateRootPathPattern
         * @return void
         * @api
         */
-       public function setTemplateRootPath($templateRootPath) {
-               $this->templateRootPath = $templateRootPath;
+       public function setTemplateRootPaths(array $templateRootPaths) {
+               $this->templateRootPaths = $templateRootPaths;
+       }
+
+       /**
+        * Set the root path to the partials.
+        * If set, overrides the one determined from $this->partialRootPathPattern
+        *
+        * @param string $partialRootPath Root path to the partials. If set, overrides the one determined from $this->partialRootPathPattern
+        * @return void
+        * @api
+        * @see setPartialRootPaths()
+        */
+       public function setPartialRootPath($partialRootPath) {
+               $this->setPartialRootPaths(array($partialRootPath));
+       }
+
+       /**
+        * Set the root path(s) to the partials.
+        * If set, overrides the one determined from $this->partialRootPathPattern
+        *
+        * @param array $partialRootPaths Root paths to the partials. If set, overrides the one determined from $this->partialRootPathPattern
+        * @return void
+        * @api
+        */
+       public function setPartialRootPaths(array $partialRootPaths) {
+               $this->partialRootPaths = $partialRootPaths;
+       }
+
+       /**
+        * Resolves the partial root to be used inside other paths.
+        *
+        * @return array Path(s) to partial root directory
+        */
+       protected function getPartialRootPaths() {
+               if ($this->partialRootPaths !== NULL) {
+                       return $this->partialRootPaths;
+               }
+               /** @var $actionRequest \TYPO3\CMS\Extbase\Mvc\Request */
+               $actionRequest = $this->controllerContext->getRequest();
+               return array(str_replace('@packageResourcesPath', ExtensionManagementUtility::extPath($actionRequest->getControllerExtensionKey()) . 'Resources/', $this->partialRootPathPattern));
+       }
+
+       /**
+        * Set the root path to the layouts.
+        * If set, overrides the one determined from $this->layoutRootPathPattern
+        *
+        * @param string $layoutRootPath Root path to the layouts. If set, overrides the one determined from $this->layoutRootPathPattern
+        * @return void
+        * @api
+        * @see setLayoutRootPaths()
+        */
+       public function setLayoutRootPath($layoutRootPath) {
+               $this->setLayoutRootPaths(array($layoutRootPath));
+       }
+
+       /**
+        * Set the root path(s) to the layouts.
+        * If set, overrides the one determined from $this->layoutRootPathPattern
+        *
+        * @param array $layoutRootPaths Root path to the layouts. If set, overrides the one determined from $this->layoutRootPathPattern
+        * @return void
+        * @api
+        */
+       public function setLayoutRootPaths(array $layoutRootPaths) {
+               $this->layoutRootPaths = $layoutRootPaths;
+       }
+
+       /**
+        * Resolves the layout root to be used inside other paths.
+        *
+        * @return string Path(s) to layout root directory
+        */
+       protected function getLayoutRootPaths() {
+               if ($this->layoutRootPaths !== NULL) {
+                       return $this->layoutRootPaths;
+               }
+               /** @var $actionRequest \TYPO3\CMS\Extbase\Mvc\Request */
+               $actionRequest = $this->controllerContext->getRequest();
+               return array(str_replace('@packageResourcesPath', ExtensionManagementUtility::extPath($actionRequest->getControllerExtensionKey()) . 'Resources/', $this->layoutRootPathPattern));
        }
 
        /**
@@ -165,7 +294,9 @@ class Tx_Fluid_View_TemplateView extends Tx_Fluid_View_AbstractTemplateView {
        protected function getTemplateIdentifier($actionName = NULL) {
                $templatePathAndFilename = $this->getTemplatePathAndFilename($actionName);
                if ($actionName === NULL) {
-                       $actionName = $this->controllerContext->getRequest()->getControllerActionName();
+                       /** @var $actionRequest \TYPO3\CMS\Extbase\Mvc\Request */
+                       $actionRequest = $this->controllerContext->getRequest();
+                       $actionName = $actionRequest->getControllerActionName();
                }
                $prefix = 'action_' . $actionName;
                return $this->createIdentifierForFile($templatePathAndFilename, $prefix);
@@ -177,13 +308,13 @@ class Tx_Fluid_View_TemplateView extends Tx_Fluid_View_AbstractTemplateView {
         *
         * @param string $actionName Name of the action. If NULL, will be taken from request.
         * @return string Full path to template
-        * @throws Tx_Fluid_View_Exception_InvalidTemplateResourceException
+        * @throws Exception\InvalidTemplateResourceException
         */
        protected function getTemplateSource($actionName = NULL) {
                $templatePathAndFilename = $this->getTemplatePathAndFilename($actionName);
                $templateSource = file_get_contents($templatePathAndFilename);
                if ($templateSource === FALSE) {
-                       throw new Tx_Fluid_View_Exception_InvalidTemplateResourceException(('"' . $templatePathAndFilename) . '" is not a valid template resource URI.', 1257246929);
+                       throw new Exception\InvalidTemplateResourceException('"' . $templatePathAndFilename . '" is not a valid template resource URI.', 1257246929);
                }
                return $templateSource;
        }
@@ -194,34 +325,27 @@ class Tx_Fluid_View_TemplateView extends Tx_Fluid_View_AbstractTemplateView {
         *
         * @param string $actionName Name of the action. If NULL, will be taken from request.
         * @return string Full path to template
-        * @throws Tx_Fluid_View_Exception_InvalidTemplateResourceException
+        * @throws Exception\InvalidTemplateResourceException
         */
        protected function getTemplatePathAndFilename($actionName = NULL) {
                if ($this->templatePathAndFilename !== NULL) {
                        return $this->templatePathAndFilename;
                }
                if ($actionName === NULL) {
-                       $actionName = $this->controllerContext->getRequest()->getControllerActionName();
+                       /** @var $actionRequest \TYPO3\CMS\Extbase\Mvc\Request */
+                       $actionRequest = $this->controllerContext->getRequest();
+                       $actionName = $actionRequest->getControllerActionName();
                }
                $actionName = ucfirst($actionName);
+
                $paths = $this->expandGenericPathPattern($this->templatePathAndFilenamePattern, FALSE, FALSE);
                foreach ($paths as &$templatePathAndFilename) {
-                       // These tokens are replaced by the Backporter for the graceful fallback in version 4.
-                       $fallbackPath = str_replace('@action', lcfirst($actionName), $templatePathAndFilename);
-                       $templatePathAndFilename = str_replace('@action', $actionName, $templatePathAndFilename);
-                       if (file_exists($templatePathAndFilename)) {
-                               // additional check for deprecated template filename for case insensitive file systems (Windows)
-                               $realFileName = basename(realpath($templatePathAndFilename));
-                               if ($realFileName !== ucfirst($realFileName)) {
-                                       t3lib_div::deprecationLog(((('the template filename "' . t3lib_div::fixWindowsFilePath(realpath($templatePathAndFilename))) . '" is lowercase. This is deprecated since TYPO3 4.4. Please rename the template to "') . basename($templatePathAndFilename)) . '"');
-                               }
+                       $templatePathAndFilename = $this->resolveFileNamePath(str_replace('@action', $actionName, $templatePathAndFilename));
+                       if (is_file($templatePathAndFilename)) {
                                return $templatePathAndFilename;
-                       } elseif (file_exists($fallbackPath)) {
-                               t3lib_div::deprecationLog(((('the template filename "' . $fallbackPath) . '" is lowercase. This is deprecated since TYPO3 4.4. Please rename the template to "') . basename($templatePathAndFilename)) . '"');
-                               return $fallbackPath;
                        }
                }
-               throw new Tx_Fluid_View_Exception_InvalidTemplateResourceException(('Template could not be loaded. I tried "' . implode('", "', $paths)) . '"', 1225709595);
+               throw new Exception\InvalidTemplateResourceException('Template could not be loaded. I tried "' . implode('", "', $paths) . '"', 1225709595);
        }
 
        /**
@@ -245,15 +369,15 @@ class Tx_Fluid_View_TemplateView extends Tx_Fluid_View_AbstractTemplateView {
         * this method returns that path, otherwise a path and filename will be
         * resolved using the layoutPathAndFilenamePattern.
         *
-        * @param string $layoutName Name of the layout to use. If none given, use "Default
+        * @param string $layoutName Name of the layout to use. If none given, use "Default"
         * @return string contents of the layout template
-        * @throws Tx_Fluid_View_Exception_InvalidTemplateResourceException
+        * @throws Exception\InvalidTemplateResourceException
         */
        protected function getLayoutSource($layoutName = 'Default') {
                $layoutPathAndFilename = $this->getLayoutPathAndFilename($layoutName);
                $layoutSource = file_get_contents($layoutPathAndFilename);
                if ($layoutSource === FALSE) {
-                       throw new Tx_Fluid_View_Exception_InvalidTemplateResourceException(('"' . $layoutPathAndFilename) . '" is not a valid template resource URI.', 1257246929);
+                       throw new Exception\InvalidTemplateResourceException('"' . $layoutPathAndFilename . '" is not a valid template resource URI.', 1257246930);
                }
                return $layoutSource;
        }
@@ -266,9 +390,9 @@ class Tx_Fluid_View_TemplateView extends Tx_Fluid_View_AbstractTemplateView {
         * this method returns that path, otherwise a path and filename will be
         * resolved using the layoutPathAndFilenamePattern.
         *
-        * @param string $layoutName Name of the layout to use. If none given, use "Default
+        * @param string $layoutName Name of the layout to use. If none given, use "Default"
         * @return string Path and filename of layout files
-        * @throws Tx_Fluid_View_Exception_InvalidTemplateResourceException
+        * @throws Exception\InvalidTemplateResourceException
         */
        protected function getLayoutPathAndFilename($layoutName = 'Default') {
                if ($this->layoutPathAndFilename !== NULL) {
@@ -277,17 +401,12 @@ class Tx_Fluid_View_TemplateView extends Tx_Fluid_View_AbstractTemplateView {
                $paths = $this->expandGenericPathPattern($this->layoutPathAndFilenamePattern, TRUE, TRUE);
                $layoutName = ucfirst($layoutName);
                foreach ($paths as &$layoutPathAndFilename) {
-                       // These tokens are replaced by the Backporter for the graceful fallback in version 4.
-                       $fallbackPath = str_replace('@layout', lcfirst($layoutName), $layoutPathAndFilename);
-                       $layoutPathAndFilename = str_replace('@layout', $layoutName, $layoutPathAndFilename);
-                       if (file_exists($layoutPathAndFilename)) {
+                       $layoutPathAndFilename = $this->resolveFileNamePath(str_replace('@layout', $layoutName, $layoutPathAndFilename));
+                       if (is_file($layoutPathAndFilename)) {
                                return $layoutPathAndFilename;
-                       } elseif (file_exists($fallbackPath)) {
-                               t3lib_div::deprecationLog(((('the layout filename "' . $fallbackPath) . '" is lowercase. This is deprecated since TYPO3 4.6. Please rename the layout to "') . basename($layoutPathAndFilename)) . '"');
-                               return $fallbackPath;
                        }
                }
-               throw new Tx_Fluid_View_Exception_InvalidTemplateResourceException(('The template files "' . implode('", "', $paths)) . '" could not be loaded.', 1225709595);
+               throw new Exception\InvalidTemplateResourceException('The template files "' . implode('", "', $paths) . '" could not be loaded.', 1225709596);
        }
 
        /**
@@ -308,13 +427,13 @@ class Tx_Fluid_View_TemplateView extends Tx_Fluid_View_AbstractTemplateView {
         *
         * @param string $partialName The name of the partial
         * @return string contents of the partial template
-        * @throws Tx_Fluid_View_Exception_InvalidTemplateResourceException
+        * @throws Exception\InvalidTemplateResourceException
         */
        protected function getPartialSource($partialName) {
                $partialPathAndFilename = $this->getPartialPathAndFilename($partialName);
                $partialSource = file_get_contents($partialPathAndFilename);
                if ($partialSource === FALSE) {
-                       throw new Tx_Fluid_View_Exception_InvalidTemplateResourceException(('"' . $partialPathAndFilename) . '" is not a valid template resource URI.', 1257246929);
+                       throw new Exception\InvalidTemplateResourceException('"' . $partialPathAndFilename . '" is not a valid template resource URI.', 1257246931);
                }
                return $partialSource;
        }
@@ -324,84 +443,45 @@ class Tx_Fluid_View_TemplateView extends Tx_Fluid_View_AbstractTemplateView {
         *
         * @param string $partialName The name of the partial
         * @return string the full path which should be used. The path definitely exists.
-        * @throws Tx_Fluid_View_Exception_InvalidTemplateResourceException
+        * @throws Exception\InvalidTemplateResourceException
         */
        protected function getPartialPathAndFilename($partialName) {
                $paths = $this->expandGenericPathPattern($this->partialPathAndFilenamePattern, TRUE, TRUE);
                foreach ($paths as &$partialPathAndFilename) {
-                       $partialPathAndFilename = str_replace('@partial', $partialName, $partialPathAndFilename);
-                       if (file_exists($partialPathAndFilename)) {
+                       $partialPathAndFilename = $this->resolveFileNamePath(str_replace('@partial', $partialName, $partialPathAndFilename));
+                       if (is_file($partialPathAndFilename)) {
                                return $partialPathAndFilename;
                        }
                }
-               throw new Tx_Fluid_View_Exception_InvalidTemplateResourceException(('The template files "' . implode('", "', $paths)) . '" could not be loaded.', 1225709595);
+               throw new Exception\InvalidTemplateResourceException('The template files "' . implode('", "', $paths) . '" could not be loaded.', 1225709597);
        }
 
        /**
-        * Resolves the template root to be used inside other paths.
-        *
-        * @return string Path to template root directory
-        */
-       protected function getTemplateRootPath() {
-               if ($this->templateRootPath !== NULL) {
-                       return $this->templateRootPath;
-               } else {
-                       return str_replace('@packageResourcesPath', t3lib_extMgm::extPath($this->controllerContext->getRequest()->getControllerExtensionKey()) . 'Resources/', $this->templateRootPathPattern);
-               }
-       }
-
-       /**
-        * Set the root path to the partials.
-        * If set, overrides the one determined from $this->partialRootPathPattern
+        * Checks whether a template can be resolved for the current request context.
         *
-        * @param string $partialRootPath Root path to the partials. If set, overrides the one determined from $this->partialRootPathPattern
-        * @return void
+        * @param ControllerContext $controllerContext Controller context which is available inside the view
+        * @return bool
         * @api
         */
-       public function setPartialRootPath($partialRootPath) {
-               $this->partialRootPath = $partialRootPath;
-       }
-
-       /**
-        * Resolves the partial root to be used inside other paths.
-        *
-        * @return string Path to partial root directory
-        */
-       protected function getPartialRootPath() {
-               if ($this->partialRootPath !== NULL) {
-                       return $this->partialRootPath;
-               } else {
-                       return str_replace('@packageResourcesPath', t3lib_extMgm::extPath($this->controllerContext->getRequest()->getControllerExtensionKey()) . 'Resources/', $this->partialRootPathPattern);
+       public function canRender(ControllerContext $controllerContext) {
+               $this->setControllerContext($controllerContext);
+               try {
+                       $this->getTemplateSource();
+                       return TRUE;
+               } catch (Exception\InvalidTemplateResourceException $e) {
+                       return FALSE;
                }
        }
 
        /**
-        * Set the root path to the layouts.
-        * If set, overrides the one determined from $this->layoutRootPathPattern
-        *
-        * @param string $layoutRootPath Root path to the layouts. If set, overrides the one determined from $this->layoutRootPathPattern
-        * @return void
-        * @api
-        */
-       public function setLayoutRootPath($layoutRootPath) {
-               $this->layoutRootPath = $layoutRootPath;
-       }
-
-       /**
-        * Resolves the layout root to be used inside other paths.
+        * Processes following placeholders inside $pattern:
+        *  - "@templateRoot"
+        *  - "@partialRoot"
+        *  - "@layoutRoot"
+        *  - "@subpackage"
+        *  - "@controller"
+        *  - "@format"
         *
-        * @return string Path to layout root directory
-        */
-       protected function getLayoutRootPath() {
-               if ($this->layoutRootPath !== NULL) {
-                       return $this->layoutRootPath;
-               } else {
-                       return str_replace('@packageResourcesPath', t3lib_extMgm::extPath($this->controllerContext->getRequest()->getControllerExtensionKey()) . 'Resources/', $this->layoutRootPathPattern);
-               }
-       }
-
-       /**
-        * Processes "@templateRoot", "@subpackage", "@controller", and "@format" placeholders inside $pattern.
         * This method is used to generate "fallback chains" for file system locations where a certain Partial can reside.
         *
         * If $bubbleControllerAndSubpackage is FALSE and $formatIsOptional is FALSE, then the resulting array will only have one element
@@ -416,41 +496,85 @@ class Tx_Fluid_View_TemplateView extends Tx_Fluid_View_AbstractTemplateView {
         * as Controller Object Name and the current format is "html"
         *
         * If pattern is "@templateRoot/@subpackage/@controller/@action.@format", then the resulting array is:
-        * - "Resources/Private/Templates/MySubPackage/My/@action.html"
-        * - "Resources/Private/Templates/MySubPackage/@action.html"
-        * - "Resources/Private/Templates/@action.html"
+        *  - "Resources/Private/Templates/MySubPackage/My/@action.html"
+        *  - "Resources/Private/Templates/MySubPackage/@action.html"
+        *  - "Resources/Private/Templates/@action.html"
         *
         * If you set $formatIsOptional to TRUE, then for any of the above arrays, every element will be duplicated  - once with "@format"
         * replaced by the current request format, and once with ."@format" stripped off.
         *
         * @param string $pattern Pattern to be resolved
-        * @param boolean $bubbleControllerAndSubpackage if TRUE, then we successively split off parts from "@controller" and "@subpackage" until both are empty.
-        * @param boolean $formatIsOptional if TRUE, then half of the resulting strings will have ."@format" stripped off, and the other half will have it.
-        * @return array unix style path
+        * @param bool $bubbleControllerAndSubpackage if TRUE, then we successively split off parts from "@controller" and "@subpackage" until both are empty.
+        * @param bool $formatIsOptional if TRUE, then half of the resulting strings will have ."@format" stripped off, and the other half will have it.
+        * @return array unix style paths
         */
        protected function expandGenericPathPattern($pattern, $bubbleControllerAndSubpackage, $formatIsOptional) {
-               $pattern = str_replace('@templateRoot', $this->getTemplateRootPath(), $pattern);
-               $pattern = str_replace('@partialRoot', $this->getPartialRootPath(), $pattern);
-               $pattern = str_replace('@layoutRoot', $this->getLayoutRootPath(), $pattern);
-               $subpackageKey = $this->controllerContext->getRequest()->getControllerSubpackageKey();
-               $controllerName = $this->controllerContext->getRequest()->getControllerName();
-               $subpackageParts = $subpackageKey !== NULL ? explode(Tx_Fluid_Fluid::NAMESPACE_SEPARATOR, $subpackageKey) : array();
-               $results = array();
-               $i = $controllerName === NULL ? 0 : -1;
-               do {
-                       $temporaryPattern = $pattern;
-                       if ($i < 0) {
-                               $temporaryPattern = str_replace('@controller', $controllerName, $temporaryPattern);
+               $paths = array($pattern);
+               $this->expandPatterns($paths, '@templateRoot', $this->getTemplateRootPaths());
+               $this->expandPatterns($paths, '@partialRoot', $this->getPartialRootPaths());
+               $this->expandPatterns($paths, '@layoutRoot', $this->getLayoutRootPaths());
+
+               /** @var $actionRequest \TYPO3\CMS\Extbase\Mvc\Request */
+               $actionRequest = $this->controllerContext->getRequest();
+               $subpackageKey = $actionRequest->getControllerSubpackageKey();
+               $controllerName = $actionRequest->getControllerName();
+               if ($subpackageKey !== NULL) {
+                       if (strpos($subpackageKey, Fluid::NAMESPACE_SEPARATOR) !== FALSE) {
+                               $namespaceSeparator = Fluid::NAMESPACE_SEPARATOR;
                        } else {
-                               $temporaryPattern = str_replace('//', '/', str_replace('@controller', '', $temporaryPattern));
+                               $namespaceSeparator = Fluid::LEGACY_NAMESPACE_SEPARATOR;
+                       }
+                       $subpackageKeyParts = explode($namespaceSeparator, $subpackageKey);
+               } else {
+                       $subpackageKeyParts = array();
+               }
+               if ($bubbleControllerAndSubpackage) {
+                       $numberOfPathsBeforeSubpackageExpansion = count($paths);
+                       $numberOfSubpackageParts = count($subpackageKeyParts);
+                       $subpackageReplacements = array();
+                       for ($i = 0; $i <= $numberOfSubpackageParts; $i++) {
+                               $subpackageReplacements[] = implode('/', ($i < 0 ? $subpackageKeyParts : array_slice($subpackageKeyParts, $i)));
+                       }
+                       $this->expandPatterns($paths, '@subpackage', $subpackageReplacements);
+
+                       for ($i = ($numberOfPathsBeforeSubpackageExpansion - 1) * ($numberOfSubpackageParts + 1); $i >= 0; $i -= ($numberOfSubpackageParts + 1)) {
+                               array_splice($paths, $i, 0, str_replace('@controller', $controllerName, $paths[$i]));
                        }
-                       $temporaryPattern = str_replace('@subpackage', implode('/', $i < 0 ? $subpackageParts : array_slice($subpackageParts, $i)), $temporaryPattern);
-                       $results[] = t3lib_div::fixWindowsFilePath(str_replace('@format', $this->controllerContext->getRequest()->getFormat(), $temporaryPattern));
-                       if ($formatIsOptional) {
-                               $results[] = t3lib_div::fixWindowsFilePath(str_replace('.@format', '', $temporaryPattern));
+                       $this->expandPatterns($paths, '@controller', array(''));
+               } else {
+                       $i = $controllerName === NULL ? 0 : -1;
+                       $this->expandPatterns($paths, '@subpackage', array(implode('/', $i < 0 ? $subpackageKeyParts :
+                               array_slice($subpackageKeyParts, $i))));
+                       $this->expandPatterns($paths, '@controller', array($controllerName));
+               }
+
+               if ($formatIsOptional) {
+                       $this->expandPatterns($paths, '.@format', array('.' . $actionRequest->getFormat(), ''));
+                       $this->expandPatterns($paths, '@format', array($actionRequest->getFormat(), ''));
+               } else {
+                       $this->expandPatterns($paths, '.@format', array('.' . $actionRequest->getFormat()));
+                       $this->expandPatterns($paths, '@format', array($actionRequest->getFormat()));
+               }
+               return array_values(array_unique($paths));
+       }
+
+       /**
+        * Expands the given $patterns by adding an array element for each $replacement
+        * replacing occurrences of $search.
+        *
+        * @param array $patterns
+        * @param string $search
+        * @param array $replacements
+        * @return void
+        */
+       protected function expandPatterns(array &$patterns, $search, array $replacements) {
+               $patternsWithReplacements = array();
+               foreach ($patterns as $pattern) {
+                       foreach ($replacements as $replacement) {
+                               $patternsWithReplacements[] = GeneralUtility::fixWindowsFilePath(str_replace($search, $replacement, $pattern));
                        }
-               } while ($i++ < count($subpackageParts) && $bubbleControllerAndSubpackage);
-               return $results;
+               }
+               $patterns = $patternsWithReplacements;
        }
 
        /**
@@ -463,18 +587,27 @@ class Tx_Fluid_View_TemplateView extends Tx_Fluid_View_AbstractTemplateView {
         * @return string
         */
        protected function createIdentifierForFile($pathAndFilename, $prefix) {
-               $request = $this->controllerContext->getRequest();
-               $extensionName = $request->getControllerExtensionName();
-               $subPackageKey = $request->getControllerSubpackageKey();
+               /** @var $actionRequest \TYPO3\CMS\Extbase\Mvc\Request */
+               $actionRequest = $this->controllerContext->getRequest();
+               $extensionName = $actionRequest->getControllerExtensionName();
+               $subPackageKey = $actionRequest->getControllerSubpackageKey();
                if ($subPackageKey !== NULL) {
                        $extensionName .= '_' . $subPackageKey;
                }
-               $controllerName = $request->getControllerName();
+               $controllerName = $actionRequest->getControllerName();
                $templateModifiedTimestamp = filemtime($pathAndFilename);
-               $templateIdentifier = sprintf('%s_%s_%s_%s', $extensionName, $controllerName, $prefix, sha1(($pathAndFilename . '|') . $templateModifiedTimestamp));
+               $templateIdentifier = sprintf('%s_%s_%s_%s', $extensionName, $controllerName, $prefix, sha1($pathAndFilename . '|' . $templateModifiedTimestamp));
                return $templateIdentifier;
        }
 
+       /**
+        * Wrapper method to make the static call to GeneralUtility mockable in tests
+        *
+        * @param string $pathAndFilename
+        *
+        * @return string absolute pathAndFilename
+        */
+       protected function resolveFileNamePath($pathAndFilename) {
+               return GeneralUtility::getFileAbsFileName($pathAndFilename);
+       }
 }
-
-?>
\ No newline at end of file