diff --git a/examples/Resources/Private/Singles/Variables.html b/examples/Resources/Private/Singles/Variables.html index fc56707d7..fa99ab292 100644 --- a/examples/Resources/Private/Singles/Variables.html +++ b/examples/Resources/Private/Singles/Variables.html @@ -40,6 +40,12 @@ } }"/> + + + First argument + Second argument + + @@ -49,3 +55,8 @@ Received $array.xyz.foobar with value {array.xyz.foobar} Received $myVariable with value {myVariable} + + +Input variable "arg1" was: {arg1} +Input variable "arg2" was: {arg2} + diff --git a/src/Core/ViewHelper/ViewHelperVariableContainer.php b/src/Core/ViewHelper/ViewHelperVariableContainer.php index 5e60dc973..d197e31f4 100644 --- a/src/Core/ViewHelper/ViewHelperVariableContainer.php +++ b/src/Core/ViewHelper/ViewHelperVariableContainer.php @@ -6,6 +6,7 @@ * See LICENSE.txt that was shipped with this package. */ +use TYPO3Fluid\Fluid\Core\Variables\VariableProviderInterface; use TYPO3Fluid\Fluid\View\ViewInterface; /** @@ -29,6 +30,53 @@ class ViewHelperVariableContainer */ protected $view; + /** + * Push a new delegate variable container to a stack. Done before you start + * sub-rendering with a ViewHelper that requires a special container for + * variables which is capable of being used in multidepth stacks without + * overriding the last instance or last set of variables. + * + * @param string $viewHelperClassName + * @param VariableProviderInterface $variableProvider + */ + public function pushDelegateVariableContainer($viewHelperClassName, VariableProviderInterface $variableProvider) + { + if (!isset($this->objects[$viewHelperClassName]['delegateVariableProviderStack'])) { + $this->objects[$viewHelperClassName]['delegateVariableProviderStack'] = []; + } + $this->objects[$viewHelperClassName]['delegateVariableProviderStack'][] = $variableProvider; + } + + /** + * Get the topmost delegate variable container that was previously pushed + * onto the stack by pushDelegateVariableContainer(). + * + * @param string $viewHelperClassName + * @return VariableProviderInterface|null + */ + public function getTopmostDelegateVariableContainer($viewHelperClassName) + { + if (!isset($this->objects[$viewHelperClassName]['delegateVariableProviderStack'])) { + return null; + } + return end($this->objects[$viewHelperClassName]['delegateVariableProviderStack']); + } + + /** + * Return and remove the topmost delegate variable container. Done after + * completion of a sub-rendering that uses a delegate variable provider. + * + * @param string $viewHelperClassName + * @return VariableProviderInterface|null + */ + public function popDelegateVariableContainer($viewHelperClassName) + { + if (isset($this->objects[$viewHelperClassName]['delegateVariableProviderStack'])) { + return array_pop($this->objects[$viewHelperClassName]['delegateVariableProviderStack']); + } + return null; + } + /** * Add a variable to the Variable Container. Make sure that $viewHelperName is ALWAYS set * to your fully qualified ViewHelper Class Name diff --git a/src/ViewHelpers/RenderViewHelper.php b/src/ViewHelpers/RenderViewHelper.php index ebcab45f4..e94bfd11b 100644 --- a/src/ViewHelpers/RenderViewHelper.php +++ b/src/ViewHelpers/RenderViewHelper.php @@ -121,7 +121,21 @@ public static function renderStatic(array $arguments, \Closure $renderChildrenCl $delegate = $arguments['delegate']; /** @var RenderableInterface $renderable */ $renderable = $arguments['renderable']; + + + // Prepare a delegate variable provider that will be possible to extract after rendering the child closure. + // Any variable defined therein gets used as argument and overrides any argument of the same name. + // Note: not using late static binding here is a conscious decision: if late static binding had been used + // then f:variable would not be able to reference this ViewHelper class' stack variable correctly. + $viewHelperVariableContainer = $renderingContext->getViewHelperVariableContainer(); + $collector = $renderingContext->getVariableProvider()->getScopeCopy($variables); + + $viewHelperVariableContainer->pushDelegateVariableContainer(self::class, $collector); + $tagContent = $renderChildrenClosure(); + + $variables = $viewHelperVariableContainer->popDelegateVariableContainer(self::class)->getAll(); + if ($arguments['contentAs']) { $variables[$arguments['contentAs']] = $tagContent; } diff --git a/src/ViewHelpers/VariableViewHelper.php b/src/ViewHelpers/VariableViewHelper.php index 5f0debc41..429aa75a7 100644 --- a/src/ViewHelpers/VariableViewHelper.php +++ b/src/ViewHelpers/VariableViewHelper.php @@ -57,6 +57,9 @@ public static function renderStatic( ) { $value = $renderChildrenClosure(); $renderingContext->getVariableProvider()->add($arguments['name'], $value); + if ($delegateVariableProvider = $renderingContext->getViewHelperVariableContainer()->getTopmostDelegateVariableContainer(RenderViewHelper::class)) { + $delegateVariableProvider->add($arguments['name'], $value); + } } } diff --git a/tests/Functional/ExamplesTest.php b/tests/Functional/ExamplesTest.php index e0659f1c2..b3a9b8d54 100644 --- a/tests/Functional/ExamplesTest.php +++ b/tests/Functional/ExamplesTest.php @@ -222,7 +222,9 @@ public function getExampleScriptTestValues() 'Received $array.printf with formatted string Formatted string, value: formatted', 'Received $array.baz with value 42', 'Received $array.xyz.foobar with value Escaped sub-string', - 'Received $myVariable with value Nice string' + 'Received $myVariable with value Nice string', + 'Input variable "arg1" was: First argument', + 'Input variable "arg2" was: Second argument', ] ], 'example_variableprovider.php' => [