[BUGFIX] Alternative implementations for view helpers do not work 57/24057/8
authorMarc Bastian Heinrichs <typo3@mbh-software.de>
Sat, 15 Feb 2014 10:29:55 +0000 (11:29 +0100)
committerOliver Hader <oliver.hader@typo3.org>
Fri, 23 May 2014 08:12:08 +0000 (10:12 +0200)
Extbase allows to register alternative implementations for
objects. However that does not work for view helpers using
a closing tag. The resolved (alternative) object is compared
to the name of the original view helper and throws an
exception like:

  #1224485398: Templating tags not properly nested. Expected:
  "AlternativeViewHelper"; Actual: "OriginalViewHelper"

A simple solution is to save the class name of the object returned
from the object manager in a runtime cache and check this when
resolving a view helper name. A nice side effect is, that a same
view helper name must not be calculated over and over again.

Fixes: #52272
Releases: 6.0, 6.1, 6.2
Change-Id: Ie49e5e83c779b4748dc2059f8fbc85552ce4b406
Reviewed-on: https://review.typo3.org/24057
Reviewed-by: Markus Klein
Tested-by: Markus Klein
Reviewed-by: Oliver Hader
Tested-by: Oliver Hader
typo3/sysext/fluid/Classes/Core/Parser/TemplateParser.php

index c3e358c..3344cc0 100644 (file)
@@ -268,6 +268,11 @@ class TemplateParser {
        protected $settings;
 
        /**
+        * @var array
+        */
+       protected $viewHelperNameToImplementationClassNameRuntimeCache = array();
+
+       /**
         * Constructor. Preprocesses the $SCAN_PATTERN_NAMESPACEDECLARATION by
         * inserting the correct namespace separator.
         */
@@ -483,6 +488,7 @@ class TemplateParser {
                        throw new \TYPO3\CMS\Fluid\Core\Parser\Exception('Namespace could not be resolved. This exception should never be thrown!', 1224254792);
                }
                $viewHelper = $this->objectManager->get($this->resolveViewHelperName($namespaceIdentifier, $methodIdentifier));
+               $this->viewHelperNameToImplementationClassNameRuntimeCache[$namespaceIdentifier][$methodIdentifier] = get_class($viewHelper);
 
                // The following three checks are only done *in an uncached template*, and not needed anymore in the cached version
                $expectedViewHelperArguments = $viewHelper->prepareArguments();
@@ -570,16 +576,23 @@ class TemplateParser {
         * @return string The fully qualified class name of the viewhelper
         */
        protected function resolveViewHelperName($namespaceIdentifier, $methodIdentifier) {
-               $explodedViewHelperName = explode('.', $methodIdentifier);
-               $namespaceSeparator = strpos($this->namespaces[$namespaceIdentifier], \TYPO3\CMS\Fluid\Fluid::NAMESPACE_SEPARATOR) !== FALSE ? \TYPO3\CMS\Fluid\Fluid::NAMESPACE_SEPARATOR : \TYPO3\CMS\Fluid\Fluid::LEGACY_NAMESPACE_SEPARATOR;
-               if (count($explodedViewHelperName) > 1) {
-                       $className = implode($namespaceSeparator, array_map('ucfirst', $explodedViewHelperName));
+               if (isset($this->viewHelperNameToImplementationClassNameRuntimeCache[$namespaceIdentifier][$methodIdentifier])) {
+                       $name = $this->viewHelperNameToImplementationClassNameRuntimeCache[$namespaceIdentifier][$methodIdentifier];
                } else {
-                       $className = ucfirst($explodedViewHelperName[0]);
+                       $explodedViewHelperName = explode('.', $methodIdentifier);
+                       $namespaceSeparator = strpos($this->namespaces[$namespaceIdentifier], \TYPO3\CMS\Fluid\Fluid::NAMESPACE_SEPARATOR) !== FALSE ? \TYPO3\CMS\Fluid\Fluid::NAMESPACE_SEPARATOR : \TYPO3\CMS\Fluid\Fluid::LEGACY_NAMESPACE_SEPARATOR;
+                       if (count($explodedViewHelperName) > 1) {
+                               $className = implode($namespaceSeparator, array_map('ucfirst', $explodedViewHelperName));
+                       } else {
+                               $className = ucfirst($explodedViewHelperName[0]);
+                       }
+                       $className .= 'ViewHelper';
+                       $name = $this->namespaces[$namespaceIdentifier] . $namespaceSeparator . $className;
+                       $name = \TYPO3\CMS\Core\Core\ClassLoader::getClassNameForAlias($name);
+                       // The name isn't cached in viewHelperNameToImplementationClassNameRuntimeCache here because the
+                       // class could be overloaded by extbase object manager. Thus the cache is filled in
+                       // initializeViewHelperAndAddItToStack after getting the real object from the object manager.
                }
-               $className .= 'ViewHelper';
-               $name = $this->namespaces[$namespaceIdentifier] . $namespaceSeparator . $className;
-               $name = \TYPO3\CMS\Core\Core\ClassLoader::getClassNameForAlias($name);
                return $name;
        }