diff --git a/src/Core/ViewHelper/ViewHelperResolver.php b/src/Core/ViewHelper/ViewHelperResolver.php
index 0ac858a89..85cefe200 100644
--- a/src/Core/ViewHelper/ViewHelperResolver.php
+++ b/src/Core/ViewHelper/ViewHelperResolver.php
@@ -173,6 +173,10 @@ public function setNamespaces(array $namespaces)
*/
public function isNamespaceValid($namespaceIdentifier)
{
+ if (strpos($namespaceIdentifier, '.')) {
+ return true;
+ }
+
if (!array_key_exists($namespaceIdentifier, $this->namespaces)) {
return false;
}
@@ -245,18 +249,8 @@ public function isNamespaceIgnored($namespaceIdentifier)
public function resolveViewHelperClassName($namespaceIdentifier, $methodIdentifier)
{
if (!isset($this->resolvedViewHelperClassNames[$namespaceIdentifier][$methodIdentifier])) {
- $resolvedViewHelperClassName = $this->resolveViewHelperName($namespaceIdentifier, $methodIdentifier);
- $actualViewHelperClassName = implode('\\', array_map('ucfirst', explode('.', $resolvedViewHelperClassName)));
- if (false === class_exists($actualViewHelperClassName) || $actualViewHelperClassName === false) {
- throw new ParserException(sprintf(
- 'The ViewHelper "<%s:%s>" could not be resolved.' . chr(10) .
- 'Based on your spelling, the system would load the class "%s", however this class does not exist.',
- $namespaceIdentifier,
- $methodIdentifier,
- $resolvedViewHelperClassName
- ), 1407060572);
- }
- $this->resolvedViewHelperClassNames[$namespaceIdentifier][$methodIdentifier] = $actualViewHelperClassName;
+ $this->resolvedViewHelperClassNames[$namespaceIdentifier][$methodIdentifier] =
+ $this->resolveViewHelperName($namespaceIdentifier, $methodIdentifier);
}
return $this->resolvedViewHelperClassNames[$namespaceIdentifier][$methodIdentifier];
}
@@ -320,13 +314,42 @@ protected function resolveViewHelperName($namespaceIdentifier, $methodIdentifier
} else {
$className = ucfirst($explodedViewHelperName[0]);
}
- $className .= 'ViewHelper';
+ $classNames = [
+ $className . 'ViewHelper',
+ $className
+ ];
+
+ if (array_key_exists($namespaceIdentifier, $this->namespaces)) {
+ $namespaces = (array) $this->namespaces[$namespaceIdentifier];
+ } else {
+ $namespacePrefix = $this->namespaces[$namespaceIdentifier] = str_replace('.', '\\', $namespaceIdentifier);
+ $namespaces = [
+ $namespacePrefix . '\\ViewHelpers',
+ $namespacePrefix
+ ];
+ }
- $namespaces = (array) $this->namespaces[$namespaceIdentifier];
+ $checked = [];
+ foreach (array_reverse($namespaces) as $namespace) {
+ $namespace = rtrim($namespace, '\\');
+ foreach ($classNames as $className) {
+ $name = $namespace . '\\' . $className;
+ if (class_exists($name) && is_a($name, ViewHelperInterface::class, true)) {
+ return $name;
+ }
+ $checked[] = $name;
+ }
+ }
- do {
- $name = rtrim(array_pop($namespaces), '\\') . '\\' . $className;
- } while (!class_exists($name) && count($namespaces));
+ throw new ParserException(
+ sprintf(
+ 'The ViewHelper "<%s:%s>" could not be resolved. Fluid checked for "%s" but none of those classes exist.',
+ $namespaceIdentifier,
+ $methodIdentifier,
+ implode(', ', $checked)
+ ),
+ 1407060572
+ );
return $name;
}
diff --git a/tests/Functional/ExamplesTest.php b/tests/Functional/ExamplesTest.php
index 82b508c7b..33af29f9e 100644
--- a/tests/Functional/ExamplesTest.php
+++ b/tests/Functional/ExamplesTest.php
@@ -251,7 +251,7 @@ public function getExampleScriptTestValues()
'Section rendering error: Section "DoesNotExist" does not exist. Section rendering is mandatory; "optional" is false.',
'ViewHelper error: Undeclared arguments passed to ViewHelper TYPO3Fluid\Fluid\ViewHelpers\IfViewHelper: notregistered. Valid arguments are: then, else, condition - Offending code: ',
'Parser error: The ViewHelper "" could not be resolved.',
- 'Based on your spelling, the system would load the class "TYPO3Fluid\Fluid\ViewHelpers\InvalidViewHelper", however this class does not exist. Offending code: ',
+ 'Fluid checked for "TYPO3Fluid\Fluid\ViewHelpers\InvalidViewHelper, TYPO3Fluid\Fluid\ViewHelpers\Invalid" but none of those classes exist. Offending code: ',
'Invalid expression: Invalid target conversion type "invalidtype" specified in casting expression "{foobar as invalidtype}".',
]
]
diff --git a/tests/Unit/Core/ViewHelper/ViewHelperResolverTest.php b/tests/Unit/Core/ViewHelper/ViewHelperResolverTest.php
index 48ca8aa06..176502d87 100644
--- a/tests/Unit/Core/ViewHelper/ViewHelperResolverTest.php
+++ b/tests/Unit/Core/ViewHelper/ViewHelperResolverTest.php
@@ -92,6 +92,16 @@ public function testResolveViewHelperClassNameSupportsMultipleNamespaces()
$this->assertEquals('TYPO3Fluid\\Fluid\\ViewHelpers\\RenderViewHelper', $result);
}
+ /**
+ * @test
+ */
+ public function testResolveViewHelperClassNameSupportsDirectDottedNamespace()
+ {
+ $resolver = $this->getAccessibleMock(ViewHelperResolver::class, ['dummy']);
+ $result = $resolver->_call('resolveViewHelperName', 'TYPO3Fluid.Fluid', 'render');
+ $this->assertEquals('TYPO3Fluid\\Fluid\\ViewHelpers\\RenderViewHelper', $result);
+ }
+
/**
* @test
*/
@@ -103,6 +113,16 @@ public function testResolveViewHelperClassNameTrimsBackslashSuffixFromNamespace(
$this->assertEquals('TYPO3Fluid\\Fluid\\ViewHelpers\\RenderViewHelper', $result);
}
+ /**
+ * @test
+ */
+ public function testResolveViewHelperClassNameSupportsVendorAndPackageNameAsNamespace()
+ {
+ $resolver = $this->getAccessibleMock(ViewHelperResolver::class, ['dummy']);
+ $result = $resolver->_call('resolveViewHelperName', 'TYPO3Fluid.Fluid', 'render');
+ $this->assertEquals('TYPO3Fluid\\Fluid\\ViewHelpers\\RenderViewHelper', $result);
+ }
+
/**
* @test
*/
@@ -255,6 +275,7 @@ public function getIsNamespaceValidTestValues()
[['foo' => ['test']], 'foo', true],
[['foo' => ['test']], 'foobar', false],
[['foo*' => null], 'foo', false],
+ [[], 'Vendor.Namespace', true],
];
}