From 9ac9c72a6fb64b3db12fb8b230d1e1b49e92620b Mon Sep 17 00:00:00 2001 From: Simon Praetorius Date: Fri, 1 Aug 2025 15:31:58 +0200 Subject: [PATCH 01/64] [TASK] Avoid testing of mocked abstract classes (#1144) --- .../ViewHelper/AbstractViewHelperTest.php | 42 ++++++++++++++++--- 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/tests/Unit/Core/ViewHelper/AbstractViewHelperTest.php b/tests/Unit/Core/ViewHelper/AbstractViewHelperTest.php index 0f570857b..7d8e13826 100644 --- a/tests/Unit/Core/ViewHelper/AbstractViewHelperTest.php +++ b/tests/Unit/Core/ViewHelper/AbstractViewHelperTest.php @@ -49,7 +49,12 @@ public static function getFirstElementOfNonEmptyTestValues(): array #[IgnoreDeprecations] public function getFirstElementOfNonEmptyReturnsExpectedValue(mixed $input, ?string $expected): void { - $subject = $this->getMockBuilder(AbstractViewHelper::class)->onlyMethods([])->getMock(); + $subject = new class () extends AbstractViewHelper { + public function render(): string + { + return ''; + } + }; $method = new \ReflectionMethod($subject, 'getFirstElementOfNonEmpty'); self::assertEquals($expected, $method->invoke($subject, $input)); } @@ -57,7 +62,12 @@ public function getFirstElementOfNonEmptyReturnsExpectedValue(mixed $input, ?str #[Test] public function registeringTheSameArgumentNameAgainOverridesArgument(): void { - $subject = $this->getMockBuilder(AbstractViewHelper::class)->onlyMethods([])->getMock(); + $subject = new class () extends AbstractViewHelper { + public function render(): string + { + return ''; + } + }; $method = new \ReflectionMethod($subject, 'registerArgument'); $method->invoke($subject, 'someName', 'string', 'desc', true); $method->invoke($subject, 'someName', 'integer', 'changed desc', true); @@ -75,7 +85,12 @@ public function setRenderingContextShouldSetInnerVariables(): void $renderingContext = new RenderingContext(); $renderingContext->setVariableProvider($templateVariableContainer); $renderingContext->setViewHelperVariableContainer($viewHelperVariableContainer); - $subject = $this->getMockBuilder(AbstractViewHelper::class)->onlyMethods(['prepareArguments'])->getMock(); + $subject = new class () extends AbstractViewHelper { + public function render(): string + { + return ''; + } + }; $subject->setRenderingContext($renderingContext); $property = new \ReflectionProperty($subject, 'templateVariableContainer'); self::assertSame($templateVariableContainer, $property->getValue($subject)); @@ -86,7 +101,12 @@ public function setRenderingContextShouldSetInnerVariables(): void #[Test] public function renderChildrenCallsRenderChildrenClosureIfSet(): void { - $subject = $this->getMockBuilder(AbstractViewHelper::class)->onlyMethods([])->getMock(); + $subject = new class () extends AbstractViewHelper { + public function render(): string + { + return ''; + } + }; $subject->setRenderChildrenClosure(function () { return 'foobar'; }); @@ -98,7 +118,12 @@ public function renderChildrenCallsRenderChildrenClosureIfSet(): void public function validateAdditionalArgumentsThrowsExceptionIfNotEmpty(): void { $this->expectException(Exception::class); - $subject = $this->getMockBuilder(AbstractViewHelper::class)->onlyMethods([])->getMock(); + $subject = new class () extends AbstractViewHelper { + public function render(): string + { + return ''; + } + }; $subject->setRenderingContext(new RenderingContext()); $subject->validateAdditionalArguments(['foo' => 'bar']); } @@ -109,7 +134,12 @@ public function testCompileReturnsAndAssignsExpectedPhpCode(): void $context = new RenderingContext(); $node = new ViewHelperNode($context, 'f', 'comment', []); $init = ''; - $subject = $this->getMockBuilder(AbstractViewHelper::class)->onlyMethods([])->getMock(); + $subject = new class () extends AbstractViewHelper { + public function render(): string + { + return ''; + } + }; $result = $subject->compile('foobar', 'baz', $init, $node, new TemplateCompiler()); self::assertEmpty($init); self::assertEquals('$renderingContext->getViewHelperInvoker()->invoke(' . get_class($subject) . '::class, foobar, $renderingContext, baz)', $result); From 54f0898cceaac58270441c9adf4997d8747153ee Mon Sep 17 00:00:00 2001 From: Simon Praetorius Date: Fri, 1 Aug 2025 16:33:12 +0200 Subject: [PATCH 02/64] [DOCS] Improve ViewHelper example code (#1147) (#1148) --- Documentation/Extending/ViewHelpers.rst | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/Documentation/Extending/ViewHelpers.rst b/Documentation/Extending/ViewHelpers.rst index 667d5aa96..e5f5fbeef 100644 --- a/Documentation/Extending/ViewHelpers.rst +++ b/Documentation/Extending/ViewHelpers.rst @@ -43,12 +43,10 @@ To enable this usage we must then create a ViewHelper class: * This ViewHelper takes two arrays and returns * the `array_combine`d result. */ - class CombineViewHelper extends AbstractViewHelper { - - /** - * @return void - */ - public function initializeArguments() { + class CombineViewHelper extends AbstractViewHelper + { + public function initializeArguments(): void + { $this->registerArgument('values', 'array', 'Values to use in array_combine'); $this->registerArgument('keys', 'array', 'Keys to use in array_combine', true); } @@ -57,10 +55,9 @@ To enable this usage we must then create a ViewHelper class: * Combines two arrays using one for keys and * the other for values. If values are not provided * in argument it can be provided as tag content. - * - * @return array */ - public function render() { + public function render(): array + { $values = $this->arguments['values']; $keys = $this->arguments['keys']; if ($values === null) { From b63ecd4b9fe43ed071545dbbdd84a75b639c0188 Mon Sep 17 00:00:00 2001 From: Simon Praetorius Date: Sat, 2 Aug 2025 17:50:14 +0200 Subject: [PATCH 03/64] [TASK] Clarify code comments related to escaping (#1151) (#1152) A code search both for "HtmlspecialcharsViewHelper" and "htmlspecialchars" confirmed that the ViewHelper is no longer used internally for escaping. Instead, the `EscapingNode` executes `htmlspecialchars()` directly on the string, both in uncached and cached context. The `@todo` in `HtmlspecialcharsViewHelper` is removed without changing the return type though, because this would require a breaking change to the ViewHelper's internal behavior. --- src/Core/Parser/Interceptor/Escape.php | 5 ++--- src/Core/ViewHelper/AbstractViewHelper.php | 2 +- src/ViewHelpers/Format/HtmlspecialcharsViewHelper.php | 1 - 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Core/Parser/Interceptor/Escape.php b/src/Core/Parser/Interceptor/Escape.php index 3c7d6f7ee..5ca0bc8c3 100644 --- a/src/Core/Parser/Interceptor/Escape.php +++ b/src/Core/Parser/Interceptor/Escape.php @@ -18,7 +18,7 @@ use TYPO3Fluid\Fluid\Core\Parser\SyntaxTree\ViewHelperNode; /** - * An interceptor adding the "Htmlspecialchars" viewhelper to the suitable places. + * An interceptor adding EscapingNodes to the suitable places, which execute htmlspecialchars(). */ class Escape implements InterceptorInterface { @@ -38,8 +38,7 @@ class Escape implements InterceptorInterface protected array $viewHelperNodesWhichDisableTheInterceptor = []; /** - * Adds a ViewHelper node using the Format\HtmlspecialcharsViewHelper to the given node. - * If "escapingInterceptorEnabled" in the ViewHelper is false, will disable itself inside the ViewHelpers body. + * Adds a special EscapingNode to the given node if escaping for the node is necessary. * * @param int $interceptorPosition One of the INTERCEPT_* constants for the current interception point * @param ParsingState $parsingState the current parsing state. Not needed in this interceptor. diff --git a/src/Core/ViewHelper/AbstractViewHelper.php b/src/Core/ViewHelper/AbstractViewHelper.php index 411c3d2c9..039342ee5 100644 --- a/src/Core/ViewHelper/AbstractViewHelper.php +++ b/src/Core/ViewHelper/AbstractViewHelper.php @@ -95,7 +95,7 @@ abstract class AbstractViewHelper implements ViewHelperInterface * Specifies whether the escaping interceptors should be disabled or enabled for the result of renderChildren() calls within this ViewHelper * @see isChildrenEscapingEnabled() * - * Note: If this is null, the value of $this->escapingInterceptorEnabled is considered for backwards compatibility. + * Note: If this is null, the value will be determined based on $escapeOutput. * * @var bool * @api diff --git a/src/ViewHelpers/Format/HtmlspecialcharsViewHelper.php b/src/ViewHelpers/Format/HtmlspecialcharsViewHelper.php index a6feaeaf1..1202bd85e 100644 --- a/src/ViewHelpers/Format/HtmlspecialcharsViewHelper.php +++ b/src/ViewHelpers/Format/HtmlspecialcharsViewHelper.php @@ -71,7 +71,6 @@ public function initializeArguments() * @return mixed the altered string. If a non-string is provided, the value is returned unchanged * @see http://www.php.net/manual/function.htmlspecialchars.php * @api - * @todo change return type to string. This needs further investigation because the ViewHelper is used internally by Fluid */ public function render() { From 652bea79db12dfb77f1aac3e8b3640478bf2cb4f Mon Sep 17 00:00:00 2001 From: Matthias Vogel Date: Sun, 3 Aug 2025 15:16:24 +0200 Subject: [PATCH 04/64] [BUGFIX] allow usage of interfaces in StrictArgumentProcessor (#1155) --- .../ViewHelper/StrictArgumentProcessor.php | 3 ++ .../LenientArgumentProcessorTest.php | 2 ++ .../StrictArgumentProcessorTest.php | 31 +++++++++++++++++++ 3 files changed, 36 insertions(+) diff --git a/src/Core/ViewHelper/StrictArgumentProcessor.php b/src/Core/ViewHelper/StrictArgumentProcessor.php index 97a409d7c..f46a9e4ed 100644 --- a/src/Core/ViewHelper/StrictArgumentProcessor.php +++ b/src/Core/ViewHelper/StrictArgumentProcessor.php @@ -104,6 +104,9 @@ private function isValidType(string $type, mixed $value): bool if (class_exists($type) && $value instanceof $type) { return true; } + if (interface_exists($type) && $value instanceof $type) { + return true; + } return false; } diff --git a/tests/Unit/Core/ViewHelper/LenientArgumentProcessorTest.php b/tests/Unit/Core/ViewHelper/LenientArgumentProcessorTest.php index b76833c6d..4e4622ab0 100644 --- a/tests/Unit/Core/ViewHelper/LenientArgumentProcessorTest.php +++ b/tests/Unit/Core/ViewHelper/LenientArgumentProcessorTest.php @@ -132,6 +132,8 @@ public static function isValidAndProcessDataProvider(): array ['test', 'DateTime', false], [null, 'DateTime', true], // @todo this can lead to PHP warnings + [new \Datetime('now'), 'DateTimeInterface', true], + ['test', 'object', false], [null, 'object', false], diff --git a/tests/Unit/Core/ViewHelper/StrictArgumentProcessorTest.php b/tests/Unit/Core/ViewHelper/StrictArgumentProcessorTest.php index a792aeb84..a297f2d32 100644 --- a/tests/Unit/Core/ViewHelper/StrictArgumentProcessorTest.php +++ b/tests/Unit/Core/ViewHelper/StrictArgumentProcessorTest.php @@ -18,6 +18,7 @@ use TYPO3Fluid\Fluid\Core\ViewHelper\ArgumentDefinition; use TYPO3Fluid\Fluid\Core\ViewHelper\StrictArgumentProcessor; use TYPO3Fluid\Fluid\Tests\Functional\Fixtures\Various\ArrayAccessExample; +use TYPO3Fluid\Fluid\Tests\Functional\Fixtures\Various\BackedEnumExample; use TYPO3Fluid\Fluid\Tests\Functional\Fixtures\Various\UserWithToString; final class StrictArgumentProcessorTest extends TestCase @@ -497,6 +498,36 @@ public function count(): int 'expectedProcessedValue' => null, 'expectedProcessedValidity' => false, ]; + // Interfaces + yield [ + 'type' => 'DateTimeInterface', + 'value' => $dateTime, + 'expectedValidity' => true, + 'expectedProcessedValue' => $dateTime, + 'expectedProcessedValidity' => true, + ]; + yield [ + 'type' => 'DateTimeInterface', + 'value' => $stdClass, + 'expectedValidity' => false, + 'expectedProcessedValue' => $stdClass, + 'expectedProcessedValidity' => false, + ]; + // Enums + yield [ + 'type' => BackedEnumExample::class, + 'value' => BackedEnumExample::BAR, + 'expectedValidity' => true, + 'expectedProcessedValue' => BackedEnumExample::BAR, + 'expectedProcessedValidity' => true, + ]; + yield [ + 'type' => BackedEnumExample::class, + 'value' => $stdClass, + 'expectedValidity' => false, + 'expectedProcessedValue' => $stdClass, + 'expectedProcessedValidity' => false, + ]; // // Iterable From c937d83e1f61f16c8daf418686417a1121353eeb Mon Sep 17 00:00:00 2001 From: Simon Praetorius Date: Fri, 25 Jul 2025 11:43:54 +0200 Subject: [PATCH 05/64] [DOCS] Re-enable cross reference to TYPO3 doc for new ViewHelpers Resolves: #1108 --- Documentation/ViewHelpers/Fluid/Flatten.rst | 8 ++++---- Documentation/ViewHelpers/Fluid/Fragment.rst | 8 ++++---- Documentation/ViewHelpers/Fluid/Shuffle.rst | 8 ++++---- Documentation/ViewHelpers/Fluid/Slot.rst | 8 ++++---- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Documentation/ViewHelpers/Fluid/Flatten.rst b/Documentation/ViewHelpers/Fluid/Flatten.rst index f3da585f0..18e075136 100644 --- a/Documentation/ViewHelpers/Fluid/Flatten.rst +++ b/Documentation/ViewHelpers/Fluid/Flatten.rst @@ -12,10 +12,10 @@ Flatten ViewHelper `` ================================ -.. .. note:: -.. This reference is part of the documentation of Fluid Standalone. -.. If you are working with Fluid in TYPO3 CMS, please refer to -.. :doc:`TYPO3's ViewHelper reference ` instead. +.. note:: + This reference is part of the documentation of Fluid Standalone. + If you are working with Fluid in TYPO3 CMS, please refer to + :doc:`TYPO3's ViewHelper reference ` instead. .. typo3:viewhelper:: flatten :source: ../Fluid.json diff --git a/Documentation/ViewHelpers/Fluid/Fragment.rst b/Documentation/ViewHelpers/Fluid/Fragment.rst index 718f97744..630e13b56 100644 --- a/Documentation/ViewHelpers/Fluid/Fragment.rst +++ b/Documentation/ViewHelpers/Fluid/Fragment.rst @@ -12,10 +12,10 @@ Fragment ViewHelper `` ================================== -.. .. note:: -.. This reference is part of the documentation of Fluid Standalone. -.. If you are working with Fluid in TYPO3 CMS, please refer to -.. :doc:`TYPO3's ViewHelper reference ` instead. +.. note:: + This reference is part of the documentation of Fluid Standalone. + If you are working with Fluid in TYPO3 CMS, please refer to + :doc:`TYPO3's ViewHelper reference ` instead. .. typo3:viewhelper:: fragment :source: ../Fluid.json diff --git a/Documentation/ViewHelpers/Fluid/Shuffle.rst b/Documentation/ViewHelpers/Fluid/Shuffle.rst index d2cc9558a..d35b2dbcf 100644 --- a/Documentation/ViewHelpers/Fluid/Shuffle.rst +++ b/Documentation/ViewHelpers/Fluid/Shuffle.rst @@ -12,10 +12,10 @@ Shuffle ViewHelper `` ================================ -.. .. note:: -.. This reference is part of the documentation of Fluid Standalone. -.. If you are working with Fluid in TYPO3 CMS, please refer to -.. :doc:`TYPO3's ViewHelper reference ` instead. +.. note:: + This reference is part of the documentation of Fluid Standalone. + If you are working with Fluid in TYPO3 CMS, please refer to + :doc:`TYPO3's ViewHelper reference ` instead. .. typo3:viewhelper:: shuffle :source: ../Fluid.json diff --git a/Documentation/ViewHelpers/Fluid/Slot.rst b/Documentation/ViewHelpers/Fluid/Slot.rst index 0f98e7b66..a90d3bfec 100644 --- a/Documentation/ViewHelpers/Fluid/Slot.rst +++ b/Documentation/ViewHelpers/Fluid/Slot.rst @@ -12,10 +12,10 @@ Slot ViewHelper `` ========================== -.. .. note:: -.. This reference is part of the documentation of Fluid Standalone. -.. If you are working with Fluid in TYPO3 CMS, please refer to -.. :doc:`TYPO3's ViewHelper reference ` instead. +.. note:: + This reference is part of the documentation of Fluid Standalone. + If you are working with Fluid in TYPO3 CMS, please refer to + :doc:`TYPO3's ViewHelper reference ` instead. .. typo3:viewhelper:: slot :source: ../Fluid.json From 6a5b44e2cd863e77fa47289170913441ef993537 Mon Sep 17 00:00:00 2001 From: Joost <81250358+jramke@users.noreply.github.com> Date: Mon, 4 Aug 2025 11:14:41 +0200 Subject: [PATCH 06/64] [TASK] Treat f:argument with default value as optional (#1150) If `` is used with the `default` argument, it is now assumed that the argument is optional without specifying `optional="{true}"`. Resolves: #1149 --- src/ViewHelpers/ArgumentViewHelper.php | 8 ++++++-- .../Layouts/LayoutWithArgumentDefinitions.html | 1 + .../Partials/PartialWithArgumentDefinitions.html | 1 + .../TemplateWithArgumentDefinitions.html | 1 + .../ViewHelpers/ArgumentViewHelperTest.php | 16 ++++++++-------- 5 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/ViewHelpers/ArgumentViewHelper.php b/src/ViewHelpers/ArgumentViewHelper.php index ac6bdc203..c763153bf 100644 --- a/src/ViewHelpers/ArgumentViewHelper.php +++ b/src/ViewHelpers/ArgumentViewHelper.php @@ -128,14 +128,18 @@ public static function nodeInitializedEvent(ViewHelperNode $node, array $argumen ), 1744908509); } + // Automatically make the argument definition optional if it has a default value + $hasDefaultValue = array_key_exists('default', $evaluatedArguments); + $optional = ($evaluatedArguments['optional'] ?? false) || $hasDefaultValue; + // Create argument definition to be interpreted later during rendering // This will also be written to the cache by the TemplateCompiler $argumentDefinitions[$argumentName] = new ArgumentDefinition( $argumentName, (string)$evaluatedArguments['type'], array_key_exists('description', $evaluatedArguments) ? (string)$evaluatedArguments['description'] : '', - array_key_exists('optional', $evaluatedArguments) ? !$evaluatedArguments['optional'] : true, - array_key_exists('default', $evaluatedArguments) ? $evaluatedArguments['default'] : null, + !$optional, + $hasDefaultValue ? $evaluatedArguments['default'] : null, ); $parsingState->setArgumentDefinitions($argumentDefinitions); } diff --git a/tests/Functional/Fixtures/Layouts/LayoutWithArgumentDefinitions.html b/tests/Functional/Fixtures/Layouts/LayoutWithArgumentDefinitions.html index 8755c7bd3..690712fd4 100644 --- a/tests/Functional/Fixtures/Layouts/LayoutWithArgumentDefinitions.html +++ b/tests/Functional/Fixtures/Layouts/LayoutWithArgumentDefinitions.html @@ -1,5 +1,6 @@ + {_all -> f:format.json() -> f:format.raw()} diff --git a/tests/Functional/Fixtures/Partials/PartialWithArgumentDefinitions.html b/tests/Functional/Fixtures/Partials/PartialWithArgumentDefinitions.html index 8755c7bd3..690712fd4 100644 --- a/tests/Functional/Fixtures/Partials/PartialWithArgumentDefinitions.html +++ b/tests/Functional/Fixtures/Partials/PartialWithArgumentDefinitions.html @@ -1,5 +1,6 @@ + {_all -> f:format.json() -> f:format.raw()} diff --git a/tests/Functional/Fixtures/Templates/TemplateWithArgumentDefinitions.html b/tests/Functional/Fixtures/Templates/TemplateWithArgumentDefinitions.html index 8755c7bd3..690712fd4 100644 --- a/tests/Functional/Fixtures/Templates/TemplateWithArgumentDefinitions.html +++ b/tests/Functional/Fixtures/Templates/TemplateWithArgumentDefinitions.html @@ -1,5 +1,6 @@ + {_all -> f:format.json() -> f:format.raw()} diff --git a/tests/Functional/ViewHelpers/ArgumentViewHelperTest.php b/tests/Functional/ViewHelpers/ArgumentViewHelperTest.php index 96a7d0393..eadc3d401 100644 --- a/tests/Functional/ViewHelpers/ArgumentViewHelperTest.php +++ b/tests/Functional/ViewHelpers/ArgumentViewHelperTest.php @@ -20,24 +20,24 @@ public static function templateWithArgumentDefinitionsDataProvider(): iterable { return [ 'all parameters provided with correct types' => [ - ['title' => 'My title', 'tags' => ['tag1', 'tag2'], 'user' => 'me'], - ['title' => 'My title', 'tags' => ['tag1', 'tag2'], 'user' => 'me'], + ['title' => 'My title', 'tags' => ['tag1', 'tag2'], 'user' => 'me', 'autoOptional' => 'custom-value'], + ['title' => 'My title', 'tags' => ['tag1', 'tag2'], 'user' => 'me', 'autoOptional' => 'custom-value'], ], 'all parameters provided with type conversion' => [ - ['title' => 123, 'tags' => ['tag1', 'tag2'], 'user' => 1.23], - ['title' => '123', 'tags' => ['tag1', 'tag2'], 'user' => '1.23'], + ['title' => 123, 'tags' => ['tag1', 'tag2'], 'user' => 1.23, 'autoOptional' => 456], + ['title' => '123', 'tags' => ['tag1', 'tag2'], 'user' => '1.23', 'autoOptional' => '456'], ], 'fallback to default value' => [ ['title' => 'My title', 'tags' => ['tag1', 'tag2']], - ['title' => 'My title', 'tags' => ['tag1', 'tag2'], 'user' => 'admin'], + ['title' => 'My title', 'tags' => ['tag1', 'tag2'], 'user' => 'admin', 'autoOptional' => 'default-value'], ], 'optional parameter not provided' => [ ['title' => 'My title'], - ['title' => 'My title', 'tags' => null, 'user' => 'admin'], + ['title' => 'My title', 'tags' => null, 'user' => 'admin', 'autoOptional' => 'default-value'], ], 'additional parameter provided' => [ ['title' => 'My title', 'additional' => 'foo'], - ['title' => 'My title', 'additional' => 'foo', 'tags' => null, 'user' => 'admin'], + ['title' => 'My title', 'additional' => 'foo', 'tags' => null, 'user' => 'admin', 'autoOptional' => 'default-value'], ], ]; } @@ -197,7 +197,7 @@ public static function templateArgumentsAreIgnoredWithLayoutDataProvider(): iter public function templateArgumentsAreIgnoredWithLayout(string $templateSource): void { $layoutRootPath = __DIR__ . '/../Fixtures/Layouts/'; - $variables = ['title' => 'My title', 'tags' => ['tag1', 'tag2'], 'user' => 'me']; + $variables = ['title' => 'My title', 'tags' => ['tag1', 'tag2'], 'user' => 'me', 'autoOptional' => 'custom-value']; $view = new TemplateView(); $view->assignMultiple($variables); $view->getRenderingContext()->setCache(self::$cache); From c551ff159ef2d35b70ee23cf3057423427bca62a Mon Sep 17 00:00:00 2001 From: Matthias Vogel Date: Fri, 1 Aug 2025 13:20:35 +0200 Subject: [PATCH 07/64] [BUGFIX] empty array is now valid as typed array (#1139) A typed array, e.g.: string[] doesn't allow an empty array [] as value until now. reset() returns false instead of null, that results in the array being invalid. --- src/Core/ViewHelper/StrictArgumentProcessor.php | 7 +++---- .../Unit/Core/ViewHelper/LenientArgumentProcessorTest.php | 1 + tests/Unit/Core/ViewHelper/StrictArgumentProcessorTest.php | 7 +++++++ 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/Core/ViewHelper/StrictArgumentProcessor.php b/src/Core/ViewHelper/StrictArgumentProcessor.php index f46a9e4ed..ab71be17e 100644 --- a/src/Core/ViewHelper/StrictArgumentProcessor.php +++ b/src/Core/ViewHelper/StrictArgumentProcessor.php @@ -93,7 +93,7 @@ private function isValidType(string $type, mixed $value): bool return false; } if (str_ends_with($type, '[]')) { - $firstElement = $this->getFirstElementOfNonEmpty($value); + $firstElement = $this->getFirstElement($value); if ($firstElement === null) { return true; } @@ -112,11 +112,10 @@ private function isValidType(string $type, mixed $value): bool /** * Return the first element of the given array, ArrayAccess or Traversable - * that is not empty */ - private function getFirstElementOfNonEmpty(mixed $value): mixed + private function getFirstElement(mixed $value): mixed { - if (is_array($value)) { + if (is_array($value) && $value !== []) { return reset($value); } if ($value instanceof Traversable) { diff --git a/tests/Unit/Core/ViewHelper/LenientArgumentProcessorTest.php b/tests/Unit/Core/ViewHelper/LenientArgumentProcessorTest.php index 4e4622ab0..d7fa50b3e 100644 --- a/tests/Unit/Core/ViewHelper/LenientArgumentProcessorTest.php +++ b/tests/Unit/Core/ViewHelper/LenientArgumentProcessorTest.php @@ -141,6 +141,7 @@ public static function isValidAndProcessDataProvider(): array [[1, 2, 3], 'array', true], [new \ArrayObject(), 'array', true], + [[], 'string[]', true], [['foo', 'bar'], 'string[]', true], [new \IteratorIterator(new \ArrayIterator(['foo', 'bar'])), 'string[]', true], [['foo', 1], 'string[]', true], diff --git a/tests/Unit/Core/ViewHelper/StrictArgumentProcessorTest.php b/tests/Unit/Core/ViewHelper/StrictArgumentProcessorTest.php index a297f2d32..91636df54 100644 --- a/tests/Unit/Core/ViewHelper/StrictArgumentProcessorTest.php +++ b/tests/Unit/Core/ViewHelper/StrictArgumentProcessorTest.php @@ -684,6 +684,13 @@ public function count(): int 'expectedProcessedValue' => null, 'expectedProcessedValidity' => false, ]; + yield [ + 'type' => 'string[]', + 'value' => [], + 'expectedValidity' => true, + 'expectedProcessedValue' => [], + 'expectedProcessedValidity' => true, + ]; yield [ 'type' => 'string[]', 'value' => ['foo', 'bar'], From e47a75457604fada7b53852609c6b5d5203552d5 Mon Sep 17 00:00:00 2001 From: Simon Praetorius Date: Mon, 11 Aug 2025 13:10:53 +0200 Subject: [PATCH 08/64] [TASK] Avoid mocking of nodes in tests (#1143) Backport of test adjustments in Fluid v5. --- .../Core/Compiler/TemplateCompilerTest.php | 33 +++++++++++ .../Interceptor/EscapeInterceptorTest.php | 41 +++++++++++++ .../Core/Compiler/TemplateCompilerTest.php | 16 ----- .../Core/Parser/Interceptor/EscapeTest.php | 59 ------------------- tests/Unit/Core/Parser/ParsingStateTest.php | 12 ---- .../Parser/SyntaxTree/BooleanNodeTest.php | 29 +++------ .../Core/Parser/SyntaxTree/RootNodeTest.php | 26 -------- 7 files changed, 82 insertions(+), 134 deletions(-) create mode 100644 tests/Functional/Core/Compiler/TemplateCompilerTest.php delete mode 100644 tests/Unit/Core/Parser/Interceptor/EscapeTest.php delete mode 100644 tests/Unit/Core/Parser/SyntaxTree/RootNodeTest.php diff --git a/tests/Functional/Core/Compiler/TemplateCompilerTest.php b/tests/Functional/Core/Compiler/TemplateCompilerTest.php new file mode 100644 index 000000000..b6a94ebf9 --- /dev/null +++ b/tests/Functional/Core/Compiler/TemplateCompilerTest.php @@ -0,0 +1,33 @@ + new TextNode('foo')]); + $expected = 'function() use ($renderingContext) {' . chr(10); + $expected .= chr(10); + $expected .= 'return \'foo\';' . chr(10); + $expected .= '}'; + $subject = new TemplateCompiler(); + self::assertEquals($expected, $subject->wrapViewHelperNodeArgumentEvaluationInClosure($viewHelperNode, 'value')); + } +} diff --git a/tests/Functional/Core/Parser/Interceptor/EscapeInterceptorTest.php b/tests/Functional/Core/Parser/Interceptor/EscapeInterceptorTest.php index e44701fac..670e67cf6 100644 --- a/tests/Functional/Core/Parser/Interceptor/EscapeInterceptorTest.php +++ b/tests/Functional/Core/Parser/Interceptor/EscapeInterceptorTest.php @@ -12,6 +12,13 @@ use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Test; use TYPO3Fluid\Fluid\Core\Parser\Exception; +use TYPO3Fluid\Fluid\Core\Parser\Interceptor\Escape; +use TYPO3Fluid\Fluid\Core\Parser\InterceptorInterface; +use TYPO3Fluid\Fluid\Core\Parser\ParsingState; +use TYPO3Fluid\Fluid\Core\Parser\SyntaxTree\EscapingNode; +use TYPO3Fluid\Fluid\Core\Parser\SyntaxTree\ObjectAccessorNode; +use TYPO3Fluid\Fluid\Core\Parser\SyntaxTree\ViewHelperNode; +use TYPO3Fluid\Fluid\Core\Rendering\RenderingContext; use TYPO3Fluid\Fluid\Tests\Functional\AbstractFunctionalTestCase; use TYPO3Fluid\Fluid\View\TemplateView; @@ -156,4 +163,38 @@ public function disablingEscapingTwiceInTemplateThrowsParsingException(): void $view->getRenderingContext()->getViewHelperResolver()->addNamespace('test', 'TYPO3Fluid\\Fluid\\Tests\\Functional\\Fixtures\\ViewHelpers'); $view->render(); } + + #[Test] + public function processDoesNotDisableEscapingInterceptorByDefault(): void + { + $renderingContext = new RenderingContext(); + $renderingContext->getViewHelperResolver()->addNamespace('test', 'TYPO3Fluid\Fluid\Tests\Functional\Fixtures\ViewHelpers'); + $viewHelperNode = new ViewHelperNode($renderingContext, 'test', 'escapeChildrenEnabledAndEscapeOutputDisabled', []); + $subject = new Escape(); + $property = new \ReflectionProperty($subject, 'childrenEscapingEnabled'); + self::assertTrue($property->getValue($subject)); + $subject->process($viewHelperNode, InterceptorInterface::INTERCEPT_OPENING_VIEWHELPER, new ParsingState()); + self::assertTrue($property->getValue($subject)); + } + + #[Test] + public function processDisablesEscapingInterceptorIfViewHelperDisablesIt(): void + { + $renderingContext = new RenderingContext(); + $renderingContext->getViewHelperResolver()->addNamespace('test', 'TYPO3Fluid\Fluid\Tests\Functional\Fixtures\ViewHelpers'); + $viewHelperNode = new ViewHelperNode($renderingContext, 'test', 'escapeChildrenDisabledAndEscapeOutputDisabled', []); + $subject = new Escape(); + $property = new \ReflectionProperty($subject, 'childrenEscapingEnabled'); + self::assertTrue($property->getValue($subject)); + $subject->process($viewHelperNode, InterceptorInterface::INTERCEPT_OPENING_VIEWHELPER, new ParsingState()); + self::assertFalse($property->getValue($subject)); + } + + #[Test] + public function processWrapsCurrentViewHelperInEscapeNode(): void + { + $node = new ObjectAccessorNode('foo'); + $subject = new Escape(); + self::assertInstanceOf(EscapingNode::class, $subject->process($node, InterceptorInterface::INTERCEPT_OBJECTACCESSOR, new ParsingState())); + } } diff --git a/tests/Unit/Core/Compiler/TemplateCompilerTest.php b/tests/Unit/Core/Compiler/TemplateCompilerTest.php index e3fed01b3..534ff31f8 100644 --- a/tests/Unit/Core/Compiler/TemplateCompilerTest.php +++ b/tests/Unit/Core/Compiler/TemplateCompilerTest.php @@ -15,8 +15,6 @@ use TYPO3Fluid\Fluid\Core\Compiler\StopCompilingException; use TYPO3Fluid\Fluid\Core\Compiler\TemplateCompiler; use TYPO3Fluid\Fluid\Core\Parser\ParsingState; -use TYPO3Fluid\Fluid\Core\Parser\SyntaxTree\TextNode; -use TYPO3Fluid\Fluid\Core\Parser\SyntaxTree\ViewHelperNode; use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface; final class TemplateCompilerTest extends TestCase @@ -63,20 +61,6 @@ public function hasReturnsTrueWithCache(): void self::assertTrue($subject->has('test')); } - #[Test] - public function wrapViewHelperNodeArgumentEvaluationInClosureCreatesExpectedString(): void - { - $arguments = ['value' => new TextNode('sometext')]; - $viewHelperNodeMock = $this->createMock(ViewHelperNode::class); - $viewHelperNodeMock->expects(self::once())->method('getArguments')->willReturn($arguments); - $expected = 'function() use ($renderingContext) {' . chr(10); - $expected .= chr(10); - $expected .= 'return \'sometext\';' . chr(10); - $expected .= '}'; - $subject = new TemplateCompiler(); - self::assertEquals($expected, $subject->wrapViewHelperNodeArgumentEvaluationInClosure($viewHelperNodeMock, 'value')); - } - #[Test] public function storeReturnsNullIfDisabled(): void { diff --git a/tests/Unit/Core/Parser/Interceptor/EscapeTest.php b/tests/Unit/Core/Parser/Interceptor/EscapeTest.php deleted file mode 100644 index 5cdb10b9b..000000000 --- a/tests/Unit/Core/Parser/Interceptor/EscapeTest.php +++ /dev/null @@ -1,59 +0,0 @@ -createMock(AbstractViewHelper::class); - $viewHelperMock->expects(self::once())->method('isChildrenEscapingEnabled')->willReturn(true); - $viewHelperNodeMock = $this->createMock(ViewHelperNode::class); - $viewHelperNodeMock->expects(self::once())->method('getUninitializedViewHelper')->willReturn($viewHelperMock); - $subject = new Escape(); - $property = new \ReflectionProperty($subject, 'childrenEscapingEnabled'); - self::assertTrue($property->getValue($subject)); - $subject->process($viewHelperNodeMock, InterceptorInterface::INTERCEPT_OPENING_VIEWHELPER, new ParsingState()); - self::assertTrue($property->getValue($subject)); - } - - #[Test] - public function processDisablesEscapingInterceptorIfViewHelperDisablesIt(): void - { - $viewHelperMock = $this->createMock(AbstractViewHelper::class); - $viewHelperMock->expects(self::once())->method('isChildrenEscapingEnabled')->willReturn(false); - $viewHelperNodeMock = $this->createMock(ViewHelperNode::class); - $viewHelperNodeMock->expects(self::once())->method('getUninitializedViewHelper')->willReturn($viewHelperMock); - $subject = new Escape(); - $property = new \ReflectionProperty($subject, 'childrenEscapingEnabled'); - self::assertTrue($property->getValue($subject)); - $subject->process($viewHelperNodeMock, InterceptorInterface::INTERCEPT_OPENING_VIEWHELPER, new ParsingState()); - self::assertFalse($property->getValue($subject)); - } - - #[Test] - public function processWrapsCurrentViewHelperInEscapeNode(): void - { - $mockNode = $this->createMock(ObjectAccessorNode::class); - $subject = new Escape(); - self::assertInstanceOf(EscapingNode::class, $subject->process($mockNode, InterceptorInterface::INTERCEPT_OBJECTACCESSOR, new ParsingState())); - } -} diff --git a/tests/Unit/Core/Parser/ParsingStateTest.php b/tests/Unit/Core/Parser/ParsingStateTest.php index 8f71b95c1..9303d0b93 100644 --- a/tests/Unit/Core/Parser/ParsingStateTest.php +++ b/tests/Unit/Core/Parser/ParsingStateTest.php @@ -50,18 +50,6 @@ public function pushAndGetFromStackWorks(): void self::assertSame($rootNode, $subject->popNodeFromStack()); } - #[Test] - public function renderCallsTheRightMethodsOnTheRootNode(): void - { - $subject = new ParsingState(); - $renderingContext = new RenderingContext(); - $rootNode = $this->createMock(RootNode::class); - $rootNode->expects(self::once())->method('evaluate')->with($renderingContext)->willReturn('T3DD09 Rock!'); - $subject->setRootNode($rootNode); - $renderedValue = $subject->render($renderingContext); - self::assertSame('T3DD09 Rock!', $renderedValue); - } - public static function getLayoutNameDataProvider(): iterable { return [ diff --git a/tests/Unit/Core/Parser/SyntaxTree/BooleanNodeTest.php b/tests/Unit/Core/Parser/SyntaxTree/BooleanNodeTest.php index 8fb8984c2..9ab88d49f 100644 --- a/tests/Unit/Core/Parser/SyntaxTree/BooleanNodeTest.php +++ b/tests/Unit/Core/Parser/SyntaxTree/BooleanNodeTest.php @@ -484,20 +484,13 @@ public function equalsReturnsFalseIfComparingStringZeroWithZero(): void public function objectsAreComparedStrictly(): void { $renderingContext = new RenderingContext(); - $object1 = new \stdClass(); - $object2 = new \stdClass(); + $renderingContext->getVariableProvider()->add('object1', new \stdClass()); + $renderingContext->getVariableProvider()->add('object2', new \stdClass()); $rootNode = new RootNode(); - - $object1Node = $this->createMock(ObjectAccessorNode::class); - $object1Node->expects(self::any())->method('evaluate')->willReturn($object1); - - $object2Node = $this->createMock(ObjectAccessorNode::class); - $object2Node->expects(self::any())->method('evaluate')->willReturn($object2); - - $rootNode->addChildNode($object1Node); + $rootNode->addChildNode(new ObjectAccessorNode('object1')); $rootNode->addChildNode(new TextNode('==')); - $rootNode->addChildNode($object2Node); + $rootNode->addChildNode(new ObjectAccessorNode('object2')); $booleanNode = new BooleanNode($rootNode); self::assertFalse($booleanNode->evaluate($renderingContext)); @@ -507,20 +500,14 @@ public function objectsAreComparedStrictly(): void public function objectsAreComparedStrictlyInUnequalComparison(): void { $renderingContext = new RenderingContext(); - $object1 = new \stdClass(); - $object2 = new \stdClass(); + $renderingContext->getVariableProvider()->add('object1', new \stdClass()); + $renderingContext->getVariableProvider()->add('object2', new \stdClass()); $rootNode = new RootNode(); - $object1Node = $this->createMock(ObjectAccessorNode::class); - $object1Node->expects(self::any())->method('evaluate')->willReturn($object1); - - $object2Node = $this->createMock(ObjectAccessorNode::class); - $object2Node->expects(self::any())->method('evaluate')->willReturn($object2); - - $rootNode->addChildNode($object1Node); + $rootNode->addChildNode(new ObjectAccessorNode('object1')); $rootNode->addChildNode(new TextNode('!=')); - $rootNode->addChildNode($object2Node); + $rootNode->addChildNode(new ObjectAccessorNode('object2')); $booleanNode = new BooleanNode($rootNode); self::assertTrue($booleanNode->evaluate($renderingContext)); diff --git a/tests/Unit/Core/Parser/SyntaxTree/RootNodeTest.php b/tests/Unit/Core/Parser/SyntaxTree/RootNodeTest.php deleted file mode 100644 index 4226701a0..000000000 --- a/tests/Unit/Core/Parser/SyntaxTree/RootNodeTest.php +++ /dev/null @@ -1,26 +0,0 @@ -getMockBuilder(RootNode::class)->onlyMethods(['evaluateChildNodes'])->getMock(); - $subject->expects(self::once())->method('evaluateChildNodes'); - $subject->evaluate(new RenderingContext()); - } -} From 49a435fde78c7bdf04fbc2d7571e50ebe5094e55 Mon Sep 17 00:00:00 2001 From: Ulrich Mathes Date: Mon, 11 Aug 2025 12:43:09 +0200 Subject: [PATCH 09/64] [FEATURE] RangeViewHelper to return a range of integers as array (#1122) (#1163) * [FEATURE] RangeViewHelper to return a range of integers as array (#1122) The RangeViewHelper returns a sequence of integers. The sequence is increasing if start is less than equal to end. Otherwise, the sequence is decreasing. step indicates by how much is the produced sequence progressed between values of the sequence. ```xml ``` --- Documentation/ViewHelpers/Fluid.json | 2 +- Documentation/ViewHelpers/Fluid/Range.rst | 21 +++ src/ViewHelpers/RangeViewHelper.php | 108 +++++++++++++++ .../ViewHelpers/RangeViewHelperTest.php | 129 ++++++++++++++++++ 4 files changed, 259 insertions(+), 1 deletion(-) create mode 100644 Documentation/ViewHelpers/Fluid/Range.rst create mode 100644 src/ViewHelpers/RangeViewHelper.php create mode 100644 tests/Functional/ViewHelpers/RangeViewHelperTest.php diff --git a/Documentation/ViewHelpers/Fluid.json b/Documentation/ViewHelpers/Fluid.json index ce2a147ac..a7ca9f268 100644 --- a/Documentation/ViewHelpers/Fluid.json +++ b/Documentation/ViewHelpers/Fluid.json @@ -1 +1 @@ -{"viewHelpers":{"alias":{"className":"TYPO3Fluid\\Fluid\\ViewHelpers\\AliasViewHelper","namespace":"TYPO3Fluid\\Fluid\\ViewHelpers","name":"AliasViewHelper","tagName":"alias","documentation":"Declares new variables which are aliases of other variables.\nTakes a \"map\"-Parameter which is an associative array which defines the shorthand mapping.\n\nThe variables are only declared inside the ``...<\/f:alias>`` tag. After the\nclosing tag, all declared variables are removed again.\n\nUsing this ViewHelper can be a sign of weak architecture. If you end up\nusing it extensively you might want to fine-tune your \"view model\" (the\ndata you assign to the view).\n\nExamples\n========\n\nSingle alias\n------------\n\n::\n\n {x}<\/f:alias>\n\nOutput::\n\n foo\n\nMultiple mappings\n-----------------\n\n::\n\n \n {x.name} or {y}\n <\/f:alias>\n\nOutput::\n\n [name] or [name]\n\nDepending on ``{foo.bar.baz}``.","xmlNamespace":"http:\/\/typo3.org\/ns\/TYPO3Fluid\/Fluid\/ViewHelpers","docTags":{"@api":""},"argumentDefinitions":{"map":{"name":"map","type":"array","description":"Array that specifies which variables should be mapped to which alias","required":true,"defaultValue":null,"escape":null}},"allowsArbitraryArguments":false,"nameWithoutSuffix":"Alias","namespaceWithoutSuffix":"TYPO3Fluid\\Fluid","uri":"Fluid\/Alias"},"argument":{"className":"TYPO3Fluid\\Fluid\\ViewHelpers\\ArgumentViewHelper","namespace":"TYPO3Fluid\\Fluid\\ViewHelpers","name":"ArgumentViewHelper","tagName":"argument","documentation":"``f:argument`` allows to define requirements and type constraints to variables that\nare provided to templates and partials. This can be very helpful to document how\na template or partial is supposed to be used and which input variables are required.\n\nThese requirements are enforced during rendering of the template or partial:\nIf an argument is defined with this ViewHelper which isn't marked as ``optional``,\nan exception will be thrown if that variable isn't present during rendering.\nIf a variable doesn't match the specified type and can't be converted automatically,\nan exception will be thrown as well.\n\nNote that ``f:argument`` ViewHelpers must be used at the root level of the\ntemplate, and can't be nested into other ViewHelpers. Also, the usage of variables\nin any of its arguments is not possible (e. g. you can't define an argument name\nby using a variable).\n\nExample\n========\n\nFor the following partial:\n\n.. code-block:: xml\n\n \n \n \n\n Title: {title}
\n \n Tags: {tags -> f:join(separator: ', ')}
\n <\/f:if>\n User: {user}\n\nThe following render calls will be successful:\n\n.. code-block:: xml\n\n \n \n \n \n \n \n\nThe following render calls will result in an exception:\n\n.. code-block:: xml\n\n \n \n \n ","xmlNamespace":"http:\/\/typo3.org\/ns\/TYPO3Fluid\/Fluid\/ViewHelpers","docTags":{"@api":""},"argumentDefinitions":{"name":{"name":"name","type":"string","description":"name of the template argument","required":true,"defaultValue":null,"escape":null},"type":{"name":"type","type":"string","description":"type of the template argument","required":true,"defaultValue":null,"escape":null},"description":{"name":"description","type":"string","description":"description of the template argument","required":false,"defaultValue":null,"escape":null},"optional":{"name":"optional","type":"boolean","description":"true if the defined argument should be optional","required":false,"defaultValue":false,"escape":null},"default":{"name":"default","type":"mixed","description":"default value for optional argument","required":false,"defaultValue":null,"escape":null}},"allowsArbitraryArguments":false,"nameWithoutSuffix":"Argument","namespaceWithoutSuffix":"TYPO3Fluid\\Fluid","uri":"Fluid\/Argument"},"cache.disable":{"className":"TYPO3Fluid\\Fluid\\ViewHelpers\\Cache\\DisableViewHelper","namespace":"TYPO3Fluid\\Fluid\\ViewHelpers","name":"Cache\\DisableViewHelper","tagName":"cache.disable","documentation":"ViewHelper to disable template compiling\n\nInserting this ViewHelper at any point in the template,\nincluding inside conditions which do not get rendered,\nwill forcibly disable the caching\/compiling of the full\ntemplate file to a PHP class.\n\nUse this if for whatever reason your platform is unable\nto create or load PHP classes (for example on read-only\nfile systems or when using an incompatible default cache\nbackend).\n\nPasses through anything you place inside the ViewHelper,\nso can safely be used as container tag, as self-closing\nor with inline syntax - all with the same result.\n\nExamples\n========\n\nSelf-closing\n------------\n\n::\n\n \n\nInline mode\n-----------\n\n::\n\n {f:cache.disable()}\n\n\nContainer tag\n-------------\n\n::\n\n \n Some output or Fluid code\n <\/f:cache.disable>\n\nAdditional output is also not compilable because of the ViewHelper","xmlNamespace":"http:\/\/typo3.org\/ns\/TYPO3Fluid\/Fluid\/ViewHelpers","docTags":{"@api":""},"argumentDefinitions":[],"allowsArbitraryArguments":false,"nameWithoutSuffix":"Cache\\Disable","namespaceWithoutSuffix":"TYPO3Fluid\\Fluid","uri":"Fluid\/Cache\/Disable"},"cache.static":{"className":"TYPO3Fluid\\Fluid\\ViewHelpers\\Cache\\StaticViewHelper","namespace":"TYPO3Fluid\\Fluid\\ViewHelpers","name":"Cache\\StaticViewHelper","tagName":"cache.static","documentation":"ViewHelper to force compiling to a static string\n\nUsed around chunks of template code where you want the\noutput of said template code to be compiled to a static\nstring (rather than a collection of compiled nodes, as\nis the usual behavior).\n\nThe effect is that none of the child ViewHelpers or nodes\nused inside this tag will be evaluated when rendering the\ntemplate once it is compiled. It will essentially replace\nall logic inside the tag with a plain string output.\n\nWorks by turning the ``compile`` method into a method that\nrenders the child nodes and returns the resulting content\ndirectly as a string variable.\n\nYou can use this with great effect to further optimise the\nperformance of your templates: in use cases where chunks of\ntemplate code depend on static variables (like thoese in\n``{settings}`` for example) and those variables never change,\nand the template uses no other dynamic variables, forcing\nthe template to compile that chunk to a static string can\nsave a lot of operations when rendering the compiled template.\n\nNB: NOT TO BE USED FOR CACHING ANYTHING OTHER THAN STRING-\nCOMPATIBLE OUTPUT!\n\nUSE WITH CARE! WILL PRESERVE EVERYTHING RENDERED, INCLUDING\nPOTENTIALLY SENSITIVE DATA CONTAINED IN OUTPUT!\n\nExamples\n========\n\nUsage and effect\n----------------\n\n::\n\n Is always evaluated also when compiled<\/f:if>\n \n \n Will only be evaluated once and this output will be\n cached as a static string with no logic attached.\n The compiled template will not contain neither the\n condition ViewHelperNodes or the variable accessor\n that are used inside this node.\n <\/f:if>\n <\/f:cache.static>\n\nThis is also evaluated when compiled (static node is closed)::\n\n Also evaluated; is outside static node<\/f:if>","xmlNamespace":"http:\/\/typo3.org\/ns\/TYPO3Fluid\/Fluid\/ViewHelpers","docTags":{"@api":""},"argumentDefinitions":[],"allowsArbitraryArguments":false,"nameWithoutSuffix":"Cache\\Static","namespaceWithoutSuffix":"TYPO3Fluid\\Fluid","uri":"Fluid\/Cache\/Static"},"cache.warmup":{"className":"TYPO3Fluid\\Fluid\\ViewHelpers\\Cache\\WarmupViewHelper","namespace":"TYPO3Fluid\\Fluid\\ViewHelpers","name":"Cache\\WarmupViewHelper","tagName":"cache.warmup","documentation":"ViewHelper to insert variables which only apply during\ncache warmup and only apply if no other variables are\nspecified for the warmup process.\n\nIf a chunk of template code is impossible to compile\nwithout additional variables, for example when rendering\nsections or partials using dynamic names, you can use this\nViewHelper around that chunk and specify a set of variables\nwhich will be assigned only while compiling the template\nand only when this is done as part of cache warmup. The\ntemplate chunk can then be compiled using those default\nvariables.\n\nThis does not imply that only those variable values will\nbe used by the compiled template. It only means that\nDEFAULT values of vital variables will be present during\ncompiling.\n\nIf you find yourself completely unable to properly warm up\na specific template file even with use of this ViewHelper,\nthen you can consider using\n``f:cache.disable`` ViewHelper\nto prevent the template compiler from even attempting to\ncompile it.\n\nUSE WITH CARE! SOME EDGE CASES OF FOR EXAMPLE VIEWHELPERS\nWHICH REQUIRE SPECIAL VARIABLE TYPES MAY NOT BE SUPPORTED\nHERE DUE TO THE RUDIMENTARY NATURE OF VARIABLES YOU DEFINE.\n\nExamples\n========\n\nUsage and effect\n----------------\n\n::\n\n \n Template code depending on {foo} variable which is not\n assigned when warming up Fluid's caches. {foo} is only\n assigned if the variable does not already exist and the\n assignment only happens if Fluid is in warmup mode.\n <\/f:cache.warmup>","xmlNamespace":"http:\/\/typo3.org\/ns\/TYPO3Fluid\/Fluid\/ViewHelpers","docTags":{"@api":""},"argumentDefinitions":{"variables":{"name":"variables","type":"array","description":"Array of variables to assign ONLY when compiling. See main class documentation.","required":false,"defaultValue":[],"escape":null}},"allowsArbitraryArguments":false,"nameWithoutSuffix":"Cache\\Warmup","namespaceWithoutSuffix":"TYPO3Fluid\\Fluid","uri":"Fluid\/Cache\/Warmup"},"case":{"className":"TYPO3Fluid\\Fluid\\ViewHelpers\\CaseViewHelper","namespace":"TYPO3Fluid\\Fluid\\ViewHelpers","name":"CaseViewHelper","tagName":"case","documentation":"Case ViewHelper that is only usable within the ``f:switch`` ViewHelper.","xmlNamespace":"http:\/\/typo3.org\/ns\/TYPO3Fluid\/Fluid\/ViewHelpers","docTags":{"@see":"\\TYPO3Fluid\\Fluid\\ViewHelpers\\SwitchViewHelper","@api":""},"argumentDefinitions":{"value":{"name":"value","type":"mixed","description":"Value to match in this case","required":true,"defaultValue":null,"escape":null}},"allowsArbitraryArguments":false,"nameWithoutSuffix":"Case","namespaceWithoutSuffix":"TYPO3Fluid\\Fluid","uri":"Fluid\/Case"},"comment":{"className":"TYPO3Fluid\\Fluid\\ViewHelpers\\CommentViewHelper","namespace":"TYPO3Fluid\\Fluid\\ViewHelpers","name":"CommentViewHelper","tagName":"comment","documentation":"This ViewHelper prevents rendering of any content inside the tag.\n\nContents of the comment will **not be parsed** thus it can be used to\ncomment out invalid Fluid syntax or non-existent ViewHelpers during\ndevelopment.\n\nUsing this ViewHelper won't have a notable effect on performance,\nespecially once the template is parsed. However, it can lead to reduced\nreadability. You can use layouts and partials to split a large template\ninto smaller parts. Using self-descriptive names for the partials can\nmake comments redundant.\n\nExamples\n========\n\nCommenting out fluid code\n-------------------------\n\n::\n\n Before\n \n This is completely hidden.\n This does not get rendered<\/f:debug>\n <\/f:comment>\n After\n\nOutput::\n\n Before\n After","xmlNamespace":"http:\/\/typo3.org\/ns\/TYPO3Fluid\/Fluid\/ViewHelpers","docTags":{"@api":""},"argumentDefinitions":[],"allowsArbitraryArguments":false,"nameWithoutSuffix":"Comment","namespaceWithoutSuffix":"TYPO3Fluid\\Fluid","uri":"Fluid\/Comment"},"constant":{"className":"TYPO3Fluid\\Fluid\\ViewHelpers\\ConstantViewHelper","namespace":"TYPO3Fluid\\Fluid\\ViewHelpers","name":"ConstantViewHelper","tagName":"constant","documentation":"Wrapper for PHPs :php:`constant` function.\nSee https:\/\/www.php.net\/manual\/function.constant.php.\n\nExamples\n========\n\nGet built-in PHP constant\n-------------------------\n\n::\n\n {f:constant(name: 'PHP_INT_MAX')}\n\nOutput::\n\n 9223372036854775807\n (Depending on CPU architecture).\n\nGet class constant\n------------------\n\n::\n\n {f:constant(name: '\\Vendor\\Package\\Class::CONSTANT')}\n\nGet enum case\n-------------\n\n::\n\n {f:constant(name: '\\Vendor\\Package\\Enum::CASE')}","xmlNamespace":"http:\/\/typo3.org\/ns\/TYPO3Fluid\/Fluid\/ViewHelpers","docTags":[],"argumentDefinitions":{"name":{"name":"name","type":"string","description":"String representation of a PHP constant or enum","required":false,"defaultValue":null,"escape":null}},"allowsArbitraryArguments":false,"nameWithoutSuffix":"Constant","namespaceWithoutSuffix":"TYPO3Fluid\\Fluid","uri":"Fluid\/Constant"},"count":{"className":"TYPO3Fluid\\Fluid\\ViewHelpers\\CountViewHelper","namespace":"TYPO3Fluid\\Fluid\\ViewHelpers","name":"CountViewHelper","tagName":"count","documentation":"This ViewHelper counts elements of the specified array or countable object.\n\nExamples\n========\n\nCount array elements\n--------------------\n\n::\n\n \n\nOutput::\n\n 4\n\ninline notation\n---------------\n\n::\n\n {objects -> f:count()}\n\nOutput::\n\n 10 (depending on the number of items in ``{objects}``)","xmlNamespace":"http:\/\/typo3.org\/ns\/TYPO3Fluid\/Fluid\/ViewHelpers","docTags":{"@api":""},"argumentDefinitions":{"subject":{"name":"subject","type":"array","description":"Countable subject, array or \\Countable","required":false,"defaultValue":null,"escape":null}},"allowsArbitraryArguments":false,"nameWithoutSuffix":"Count","namespaceWithoutSuffix":"TYPO3Fluid\\Fluid","uri":"Fluid\/Count"},"cycle":{"className":"TYPO3Fluid\\Fluid\\ViewHelpers\\CycleViewHelper","namespace":"TYPO3Fluid\\Fluid\\ViewHelpers","name":"CycleViewHelper","tagName":"cycle","documentation":"This ViewHelper cycles through the specified values.\nThis can be often used to specify CSS classes for example.\n\nTo achieve the \"zebra class\" effect in a loop you can also use the\n\"iteration\" argument of the **for** ViewHelper.\n\nExamples\n========\n\nThese examples could also be achieved using the \"iteration\" argument\nof the ForViewHelper.\n\nSimple\n------\n\n::\n\n \n \n {cycle}\n <\/f:cycle>\n <\/f:for>\n\nOutput::\n\n foobarbazfoo\n\nAlternating CSS class\n---------------------\n\n::\n\n
    \n \n \n
  • {foo}<\/li>\n <\/f:cycle>\n <\/f:for>\n <\/ul>\n\nOutput::\n\n
      \n
    • 1<\/li>\n
    • 2<\/li>\n
    • 3<\/li>\n
    • 4<\/li>\n <\/ul>","xmlNamespace":"http:\/\/typo3.org\/ns\/TYPO3Fluid\/Fluid\/ViewHelpers","docTags":{"@api":""},"argumentDefinitions":{"values":{"name":"values","type":"array","description":"The array or object implementing \\ArrayAccess (for example \\SplObjectStorage) to iterated over","required":false,"defaultValue":null,"escape":null},"as":{"name":"as","type":"string","description":"The name of the iteration variable","required":true,"defaultValue":null,"escape":null}},"allowsArbitraryArguments":false,"nameWithoutSuffix":"Cycle","namespaceWithoutSuffix":"TYPO3Fluid\\Fluid","uri":"Fluid\/Cycle"},"debug":{"className":"TYPO3Fluid\\Fluid\\ViewHelpers\\DebugViewHelper","namespace":"TYPO3Fluid\\Fluid\\ViewHelpers","name":"DebugViewHelper","tagName":"debug","documentation":"This ViewHelper is only meant to be used during development.\n\nExamples\n========\n\nInline notation and custom title\n--------------------------------\n\n::\n\n {object -> f:debug(title: 'Custom title')}\n\nOutput::\n\n all properties of {object} nicely highlighted (with custom title)\n\nOnly output the type\n--------------------\n\n::\n\n {object -> f:debug(typeOnly: true)}\n\nOutput::\n\n the type or class name of {object}","xmlNamespace":"http:\/\/typo3.org\/ns\/TYPO3Fluid\/Fluid\/ViewHelpers","docTags":{"@api":""},"argumentDefinitions":{"typeOnly":{"name":"typeOnly","type":"boolean","description":"If true, debugs only the type of variables","required":false,"defaultValue":false,"escape":null},"levels":{"name":"levels","type":"integer","description":"Levels to render when rendering nested objects\/arrays","required":false,"defaultValue":5,"escape":null},"html":{"name":"html","type":"boolean","description":"Render HTML. If false, output is indented plaintext","required":false,"defaultValue":false,"escape":null}},"allowsArbitraryArguments":false,"nameWithoutSuffix":"Debug","namespaceWithoutSuffix":"TYPO3Fluid\\Fluid","uri":"Fluid\/Debug"},"defaultCase":{"className":"TYPO3Fluid\\Fluid\\ViewHelpers\\DefaultCaseViewHelper","namespace":"TYPO3Fluid\\Fluid\\ViewHelpers","name":"DefaultCaseViewHelper","tagName":"defaultCase","documentation":"A ViewHelper which specifies the \"default\" case when used within the ``f:switch`` ViewHelper.","xmlNamespace":"http:\/\/typo3.org\/ns\/TYPO3Fluid\/Fluid\/ViewHelpers","docTags":{"@see":"\\TYPO3Fluid\\Fluid\\ViewHelpers\\SwitchViewHelper","@api":""},"argumentDefinitions":[],"allowsArbitraryArguments":false,"nameWithoutSuffix":"DefaultCase","namespaceWithoutSuffix":"TYPO3Fluid\\Fluid","uri":"Fluid\/DefaultCase"},"else":{"className":"TYPO3Fluid\\Fluid\\ViewHelpers\\ElseViewHelper","namespace":"TYPO3Fluid\\Fluid\\ViewHelpers","name":"ElseViewHelper","tagName":"else","documentation":"Else-Branch of a condition. Only has an effect inside of ``f:if``.\nSee the ``f:if`` ViewHelper for documentation.\n\nExamples\n========\n\nOutput content if condition is not met\n--------------------------------------\n\n::\n\n \n \n condition was not true\n <\/f:else>\n <\/f:if>\n\nOutput::\n\n Everything inside the \"else\" tag is displayed if the condition evaluates to false.\n Otherwise, nothing is outputted in this example.","xmlNamespace":"http:\/\/typo3.org\/ns\/TYPO3Fluid\/Fluid\/ViewHelpers","docTags":{"@see":"TYPO3Fluid\\Fluid\\ViewHelpers\\IfViewHelper","@api":""},"argumentDefinitions":{"if":{"name":"if","type":"boolean","description":"Condition expression conforming to Fluid boolean rules","required":false,"defaultValue":null,"escape":null}},"allowsArbitraryArguments":false,"nameWithoutSuffix":"Else","namespaceWithoutSuffix":"TYPO3Fluid\\Fluid","uri":"Fluid\/Else"},"first":{"className":"TYPO3Fluid\\Fluid\\ViewHelpers\\FirstViewHelper","namespace":"TYPO3Fluid\\Fluid\\ViewHelpers","name":"FirstViewHelper","tagName":"first","documentation":"The FirstViewHelper returns the first item of an array.\n\nExample\n========\n::\n\n \n\n.. code-block:: text\n\n first","xmlNamespace":"http:\/\/typo3.org\/ns\/TYPO3Fluid\/Fluid\/ViewHelpers","docTags":[],"argumentDefinitions":{"value":{"name":"value","type":"array","description":"","required":false,"defaultValue":null,"escape":null}},"allowsArbitraryArguments":false,"nameWithoutSuffix":"First","namespaceWithoutSuffix":"TYPO3Fluid\\Fluid","uri":"Fluid\/First"},"flatten":{"className":"TYPO3Fluid\\Fluid\\ViewHelpers\\FlattenViewHelper","namespace":"TYPO3Fluid\\Fluid\\ViewHelpers","name":"FlattenViewHelper","tagName":"flatten","documentation":"The FlattenViewHelper flattens a multi-dimensional array into a\nsingle-dimensional array.\n\n\nExample\n========\n\n::\n\n \n\n.. code-block:: text\n\n {0: '1', 1: '2', 2: '3', 3: '4'}","xmlNamespace":"http:\/\/typo3.org\/ns\/TYPO3Fluid\/Fluid\/ViewHelpers","docTags":[],"argumentDefinitions":{"value":{"name":"value","type":"array","description":"An array","required":false,"defaultValue":null,"escape":null}},"allowsArbitraryArguments":false,"nameWithoutSuffix":"Flatten","namespaceWithoutSuffix":"TYPO3Fluid\\Fluid","uri":"Fluid\/Flatten"},"for":{"className":"TYPO3Fluid\\Fluid\\ViewHelpers\\ForViewHelper","namespace":"TYPO3Fluid\\Fluid\\ViewHelpers","name":"ForViewHelper","tagName":"for","documentation":"Loop ViewHelper which can be used to iterate over arrays.\nImplements what a basic PHP ``foreach()`` does.\n\nExamples\n========\n\nSimple Loop\n-----------\n\n::\n\n {foo}<\/f:for>\n\nOutput::\n\n 1234\n\nOutput array key\n----------------\n\n::\n\n
        \n \n
      • {label}: {fruit}<\/li>\n <\/f:for>\n <\/ul>\n\nOutput::\n\n
          \n
        • fruit1: apple<\/li>\n
        • fruit2: pear<\/li>\n
        • fruit3: banana<\/li>\n
        • fruit4: cherry<\/li>\n <\/ul>\n\nIteration information\n---------------------\n\n::\n\n
            \n \n
          • Index: {fooIterator.index} Cycle: {fooIterator.cycle} Total: {fooIterator.total}{f:if(condition: fooIterator.isEven, then: ' Even')}{f:if(condition: fooIterator.isOdd, then: ' Odd')}{f:if(condition: fooIterator.isFirst, then: ' First')}{f:if(condition: fooIterator.isLast, then: ' Last')}<\/li>\n <\/f:for>\n <\/ul>\n\nOutput::\n\n
              \n
            • Index: 0 Cycle: 1 Total: 4 Odd First<\/li>\n
            • Index: 1 Cycle: 2 Total: 4 Even<\/li>\n
            • Index: 2 Cycle: 3 Total: 4 Odd<\/li>\n
            • Index: 3 Cycle: 4 Total: 4 Even Last<\/li>\n <\/ul>","xmlNamespace":"http:\/\/typo3.org\/ns\/TYPO3Fluid\/Fluid\/ViewHelpers","docTags":{"@api":""},"argumentDefinitions":{"each":{"name":"each","type":"array","description":"The array or \\SplObjectStorage to iterated over","required":true,"defaultValue":null,"escape":null},"as":{"name":"as","type":"string","description":"The name of the iteration variable","required":true,"defaultValue":null,"escape":null},"key":{"name":"key","type":"string","description":"Variable to assign array key to","required":false,"defaultValue":null,"escape":null},"reverse":{"name":"reverse","type":"boolean","description":"If true, iterates in reverse","required":false,"defaultValue":false,"escape":null},"iteration":{"name":"iteration","type":"string","description":"The name of the variable to store iteration information (index, cycle, total, isFirst, isLast, isEven, isOdd)","required":false,"defaultValue":null,"escape":null}},"allowsArbitraryArguments":false,"nameWithoutSuffix":"For","namespaceWithoutSuffix":"TYPO3Fluid\\Fluid","uri":"Fluid\/For"},"format.case":{"className":"TYPO3Fluid\\Fluid\\ViewHelpers\\Format\\CaseViewHelper","namespace":"TYPO3Fluid\\Fluid\\ViewHelpers","name":"Format\\CaseViewHelper","tagName":"format.case","documentation":"Modifies the case of an input string to upper- or lowercase or capitalization.\nThe default transformation will be uppercase as in `mb_convert_case`_.\n\nPossible modes are:\n\n``lower``\n Transforms the input string to lowercase\n Example: \"Hello World\" -> \"hello world\"\n\n``upper``\n Transforms the input string to uppercase\n Example: \"Hello World\" -> \"HELLO WORLD\"\n\n``capital``\n Transforms the first character of the input string to uppercase\n Example: \"hello world\" -> \"Hello world\"\n\n``uncapital``\n Transforms the input string to its first letter lower-cased\n Example: \"Hello World\" -> \"hello World\"\n\n``capitalWords``\n Transforms the input string to capitalize each word\n Example: \"hello world\" -> \"Hello World\"\n\nNote that the behavior will be the same as in the appropriate PHP function `mb_convert_case`_;\nespecially regarding locale and multibyte behavior.\n\n.. _mb_convert_case: https:\/\/www.php.net\/manual\/function.mb-convert-case.php\n\nExamples\n========\n\nDefault\n-------\n\n::\n\n Some Text with miXed case<\/f:format.case>\n\nOutput::\n\n SOME TEXT WITH MIXED CASE\n\nExample with given mode\n-----------------------\n\n::\n\n someString<\/f:format.case>\n\nOutput::\n\n SomeString","xmlNamespace":"http:\/\/typo3.org\/ns\/TYPO3Fluid\/Fluid\/ViewHelpers","docTags":[],"argumentDefinitions":{"value":{"name":"value","type":"string","description":"The input value. If not given, the evaluated child nodes will be used.","required":false,"defaultValue":null,"escape":null},"mode":{"name":"mode","type":"string","description":"The case to apply, must be one of this' CASE_* constants. Defaults to uppercase application.","required":false,"defaultValue":"upper","escape":null}},"allowsArbitraryArguments":false,"nameWithoutSuffix":"Format\\Case","namespaceWithoutSuffix":"TYPO3Fluid\\Fluid","uri":"Fluid\/Format\/Case"},"format.cdata":{"className":"TYPO3Fluid\\Fluid\\ViewHelpers\\Format\\CdataViewHelper","namespace":"TYPO3Fluid\\Fluid\\ViewHelpers","name":"Format\\CdataViewHelper","tagName":"format.cdata","documentation":"Outputs an argument\/value without any escaping and wraps it with CDATA tags.\n\nPAY SPECIAL ATTENTION TO SECURITY HERE (especially Cross Site Scripting),\nas the output is NOT SANITIZED!\n\nExamples\n========\n\nChild nodes\n-----------\n\n::\n\n {string}<\/f:format.cdata>\n\nOutput::\n\n \n\nValue attribute\n---------------\n\n::\n\n \n\nOutput::\n\n \n\nInline notation\n---------------\n\n::\n\n {string -> f:format.cdata()}\n\nOutput::\n\n ","xmlNamespace":"http:\/\/typo3.org\/ns\/TYPO3Fluid\/Fluid\/ViewHelpers","docTags":{"@api":""},"argumentDefinitions":{"value":{"name":"value","type":"mixed","description":"The value to output","required":false,"defaultValue":null,"escape":null}},"allowsArbitraryArguments":false,"nameWithoutSuffix":"Format\\Cdata","namespaceWithoutSuffix":"TYPO3Fluid\\Fluid","uri":"Fluid\/Format\/Cdata"},"format.htmlspecialchars":{"className":"TYPO3Fluid\\Fluid\\ViewHelpers\\Format\\HtmlspecialcharsViewHelper","namespace":"TYPO3Fluid\\Fluid\\ViewHelpers","name":"Format\\HtmlspecialcharsViewHelper","tagName":"format.htmlspecialchars","documentation":"Applies PHP ``htmlspecialchars()`` escaping to a value.\n\nSee http:\/\/www.php.net\/manual\/function.htmlspecialchars.php\n\nExamples\n========\n\nDefault notation\n----------------\n\n::\n\n {text}<\/f:format.htmlspecialchars>\n\nOutput::\n\n Text with & \" ' < > * replaced by HTML entities (htmlspecialchars applied).\n\nInline notation\n---------------\n\n::\n\n {text -> f:format.htmlspecialchars(encoding: 'ISO-8859-1')}\n\nOutput::\n\n Text with & \" ' < > * replaced by HTML entities (htmlspecialchars applied).","xmlNamespace":"http:\/\/typo3.org\/ns\/TYPO3Fluid\/Fluid\/ViewHelpers","docTags":{"@api":""},"argumentDefinitions":{"value":{"name":"value","type":"string","description":"Value to format","required":false,"defaultValue":null,"escape":null},"keepQuotes":{"name":"keepQuotes","type":"boolean","description":"If true quotes will not be replaced (ENT_NOQUOTES)","required":false,"defaultValue":false,"escape":null},"encoding":{"name":"encoding","type":"string","description":"Encoding","required":false,"defaultValue":"UTF-8","escape":null},"doubleEncode":{"name":"doubleEncode","type":"boolean","description":"If false, html entities will not be encoded","required":false,"defaultValue":true,"escape":null}},"allowsArbitraryArguments":false,"nameWithoutSuffix":"Format\\Htmlspecialchars","namespaceWithoutSuffix":"TYPO3Fluid\\Fluid","uri":"Fluid\/Format\/Htmlspecialchars"},"format.json":{"className":"TYPO3Fluid\\Fluid\\ViewHelpers\\Format\\JsonViewHelper","namespace":"TYPO3Fluid\\Fluid\\ViewHelpers","name":"Format\\JsonViewHelper","tagName":"format.json","documentation":"Wrapper for PHPs :php:`json_encode` function.\nSee https:\/\/www.php.net\/manual\/function.json-encode.php.\n\nExamples\n========\n\nEncoding a view variable\n------------------------\n\n::\n\n {someArray -> f:format.json()}\n\n``[\"array\",\"values\"]``\nDepending on the value of ``{someArray}``.\n\nAssociative array\n-----------------\n\n::\n\n {f:format.json(value: {foo: 'bar', bar: 'baz'})}\n\n``{\"foo\":\"bar\",\"bar\":\"baz\"}``\n\nNon associative array with forced object\n----------------------------------------\n\n::\n\n {f:format.json(value: {0: 'bar', 1: 'baz'}, forceObject: true)}\n\n``{\"0\":\"bar\",\"1\":\"baz\"}``","xmlNamespace":"http:\/\/typo3.org\/ns\/TYPO3Fluid\/Fluid\/ViewHelpers","docTags":[],"argumentDefinitions":{"value":{"name":"value","type":"mixed","description":"The incoming data to convert, or null if VH children should be used","required":false,"defaultValue":null,"escape":null},"forceObject":{"name":"forceObject","type":"bool","description":"Outputs an JSON object rather than an array","required":false,"defaultValue":false,"escape":null}},"allowsArbitraryArguments":false,"nameWithoutSuffix":"Format\\Json","namespaceWithoutSuffix":"TYPO3Fluid\\Fluid","uri":"Fluid\/Format\/Json"},"format.nl2br":{"className":"TYPO3Fluid\\Fluid\\ViewHelpers\\Format\\Nl2brViewHelper","namespace":"TYPO3Fluid\\Fluid\\ViewHelpers","name":"Format\\Nl2brViewHelper","tagName":"format.nl2br","documentation":"Wrapper for PHPs :php:`nl2br` function.\nSee https:\/\/www.php.net\/manual\/function.nl2br.php.\n\nExamples\n========\n\nDefault\n-------\n\n::\n\n {text_with_linebreaks}<\/f:format.nl2br>\n\nText with line breaks replaced by ``
              ``\n\nInline notation\n---------------\n\n::\n\n {text_with_linebreaks -> f:format.nl2br()}\n\nText with line breaks replaced by ``
              ``","xmlNamespace":"http:\/\/typo3.org\/ns\/TYPO3Fluid\/Fluid\/ViewHelpers","docTags":[],"argumentDefinitions":{"value":{"name":"value","type":"string","description":"string to format","required":false,"defaultValue":null,"escape":null}},"allowsArbitraryArguments":false,"nameWithoutSuffix":"Format\\Nl2br","namespaceWithoutSuffix":"TYPO3Fluid\\Fluid","uri":"Fluid\/Format\/Nl2br"},"format.number":{"className":"TYPO3Fluid\\Fluid\\ViewHelpers\\Format\\NumberViewHelper","namespace":"TYPO3Fluid\\Fluid\\ViewHelpers","name":"Format\\NumberViewHelper","tagName":"format.number","documentation":"Formats a number with custom precision, decimal point and grouped thousands.\nSee https:\/\/www.php.net\/manual\/function.number-format.php.\n\nExamples\n========\n\nDefaults\n--------\n\n::\n\n 423423.234<\/f:format.number>\n\n``423,423.20``\n\nWith all parameters\n-------------------\n\n::\n\n \n 423423.234\n <\/f:format.number>\n\n``423.423,2``","xmlNamespace":"http:\/\/typo3.org\/ns\/TYPO3Fluid\/Fluid\/ViewHelpers","docTags":[],"argumentDefinitions":{"decimals":{"name":"decimals","type":"int","description":"The number of digits after the decimal point","required":false,"defaultValue":2,"escape":null},"decimalSeparator":{"name":"decimalSeparator","type":"string","description":"The decimal point character","required":false,"defaultValue":".","escape":null},"thousandsSeparator":{"name":"thousandsSeparator","type":"string","description":"The character for grouping the thousand digits","required":false,"defaultValue":",","escape":null}},"allowsArbitraryArguments":false,"nameWithoutSuffix":"Format\\Number","namespaceWithoutSuffix":"TYPO3Fluid\\Fluid","uri":"Fluid\/Format\/Number"},"format.printf":{"className":"TYPO3Fluid\\Fluid\\ViewHelpers\\Format\\PrintfViewHelper","namespace":"TYPO3Fluid\\Fluid\\ViewHelpers","name":"Format\\PrintfViewHelper","tagName":"format.printf","documentation":"A ViewHelper for formatting values with printf. Either supply an array for\nthe arguments or a single value.\n\nSee http:\/\/www.php.net\/manual\/en\/function.sprintf.php\n\nExamples\n========\n\nScientific notation\n-------------------\n\n::\n\n %.3e<\/f:format.printf>\n\nOutput::\n\n 3.625e+8\n\nArgument swapping\n-----------------\n\n::\n\n %2$s is great, TYPO%1$d too. Yes, TYPO%1$d is great and so is %2$s!<\/f:format.printf>\n\nOutput::\n\n Kasper is great, TYPO3 too. Yes, TYPO3 is great and so is Kasper!\n\nSingle argument\n---------------\n\n::\n\n We love %s<\/f:format.printf>\n\n\nOutput::\n\n We love TYPO3\n\nInline notation\n---------------\n\n::\n\n {someText -> f:format.printf(arguments: {1: 'TYPO3'})}\n\n\nOutput::\n\n We love TYPO3","xmlNamespace":"http:\/\/typo3.org\/ns\/TYPO3Fluid\/Fluid\/ViewHelpers","docTags":{"@api":""},"argumentDefinitions":{"value":{"name":"value","type":"string","description":"String to format","required":false,"defaultValue":null,"escape":null},"arguments":{"name":"arguments","type":"array","description":"The arguments for vsprintf","required":false,"defaultValue":[],"escape":null}},"allowsArbitraryArguments":false,"nameWithoutSuffix":"Format\\Printf","namespaceWithoutSuffix":"TYPO3Fluid\\Fluid","uri":"Fluid\/Format\/Printf"},"format.raw":{"className":"TYPO3Fluid\\Fluid\\ViewHelpers\\Format\\RawViewHelper","namespace":"TYPO3Fluid\\Fluid\\ViewHelpers","name":"Format\\RawViewHelper","tagName":"format.raw","documentation":"Outputs an argument\/value without any escaping. Is normally used to output\nan ObjectAccessor which should not be escaped, but output as-is.\n\nPAY SPECIAL ATTENTION TO SECURITY HERE (especially Cross Site Scripting),\nas the output is NOT SANITIZED!\n\nExamples\n========\n\nChild nodes\n-----------\n\n::\n\n {string}<\/f:format.raw>\n\nOutput::\n\n (Content of ``{string}`` without any conversion\/escaping)\n\nValue attribute\n---------------\n\n::\n\n \n\nOutput::\n\n (Content of ``{string}`` without any conversion\/escaping)\n\nInline notation\n---------------\n\n::\n\n {string -> f:format.raw()}\n\nOutput::\n\n (Content of ``{string}`` without any conversion\/escaping)","xmlNamespace":"http:\/\/typo3.org\/ns\/TYPO3Fluid\/Fluid\/ViewHelpers","docTags":{"@api":""},"argumentDefinitions":{"value":{"name":"value","type":"mixed","description":"The value to output","required":false,"defaultValue":null,"escape":false}},"allowsArbitraryArguments":false,"nameWithoutSuffix":"Format\\Raw","namespaceWithoutSuffix":"TYPO3Fluid\\Fluid","uri":"Fluid\/Format\/Raw"},"format.stripTags":{"className":"TYPO3Fluid\\Fluid\\ViewHelpers\\Format\\StripTagsViewHelper","namespace":"TYPO3Fluid\\Fluid\\ViewHelpers","name":"Format\\StripTagsViewHelper","tagName":"format.stripTags","documentation":"Removes tags from the given string (applying PHPs :php:`strip_tags()` function)\nSee https:\/\/www.php.net\/manual\/function.strip-tags.php.\n\nExamples\n========\n\nDefault notation\n----------------\n\n::\n\n Some Text with Tags<\/b> and an Ümlaut.<\/f:format.stripTags>\n\nSome Text with Tags and an Ümlaut. :php:`strip_tags()` applied.\n\n.. note::\n Encoded entities are not decoded.\n\nDefault notation with allowedTags\n---------------------------------\n\n::\n\n