[BUGFIX] Use late static binding for compilable viewhelpers
[Packages/TYPO3.CMS.git] / typo3 / sysext / fluid / Classes / Core / ViewHelper / AbstractConditionViewHelper.php
1 <?php
2 namespace TYPO3\CMS\Fluid\Core\ViewHelper;
3
4 /* *
5 * This script is backported from the TYPO3 Flow package "TYPO3.Fluid". *
6 * *
7 * It is free software; you can redistribute it and/or modify it under *
8 * the terms of the GNU Lesser General Public License, either version 3 *
9 * of the License, or (at your option) any later version. *
10 * *
11 * The TYPO3 project - inspiring people to share! *
12 * */
13
14 use TYPO3\CMS\Fluid\ViewHelpers\ThenViewHelper;
15 use TYPO3\CMS\Fluid\ViewHelpers\ElseViewHelper;
16 use TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\ViewHelperNode;
17
18 /**
19 * This view helper is an abstract ViewHelper which implements an if/else condition.
20 * @see TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\ViewHelperNode::convertArgumentValue() to find see how boolean arguments are evaluated
21 *
22 * = Usage =
23 *
24 * To create a custom Condition ViewHelper, you need to subclass this class, and
25 * implement your own render() method. Inside there, you should call $this->renderThenChild()
26 * if the condition evaluated to TRUE, and $this->renderElseChild() if the condition evaluated
27 * to FALSE.
28 *
29 * Every Condition ViewHelper has a "then" and "else" argument, so it can be used like:
30 * <[aConditionViewHelperName] .... then="condition true" else="condition false" />,
31 * or as well use the "then" and "else" child nodes.
32 *
33 * @see TYPO3\CMS\Fluid\ViewHelpers\IfViewHelper for a more detailed explanation and a simple usage example.
34 * Make sure to NOT OVERRIDE the constructor.
35 *
36 * @api
37 */
38 abstract class AbstractConditionViewHelper extends \TYPO3\CMS\Fluid\Core\ViewHelper\AbstractViewHelper implements \TYPO3\CMS\Fluid\Core\ViewHelper\Facets\ChildNodeAccessInterface, \TYPO3\CMS\Fluid\Core\ViewHelper\Facets\CompilableInterface {
39
40 /**
41 * An array containing child nodes
42 *
43 * @var array<\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\AbstractNode>
44 */
45 private $childNodes = array();
46
47 /**
48 * Setter for ChildNodes - as defined in ChildNodeAccessInterface
49 *
50 * @param array $childNodes Child nodes of this syntax tree node
51 * @return void
52 */
53 public function setChildNodes(array $childNodes) {
54 $this->childNodes = $childNodes;
55 }
56
57 /**
58 * Initializes the "then" and "else" arguments
59 */
60 public function __construct() {
61 $this->registerArgument('then', 'mixed', 'Value to be returned if the condition if met.', FALSE);
62 $this->registerArgument('else', 'mixed', 'Value to be returned if the condition if not met.', FALSE);
63 }
64
65 /**
66 * renders <f:then> child if $condition is true, otherwise renders <f:else> child.
67 *
68 * @return string the rendered string
69 * @api
70 */
71 public function render() {
72 if (static::evaluateCondition($this->arguments)) {
73 return $this->renderThenChild();
74 } else {
75 return $this->renderElseChild();
76 }
77 }
78
79 /**
80 * Returns value of "then" attribute.
81 * If then attribute is not set, iterates through child nodes and renders ThenViewHelper.
82 * If then attribute is not set and no ThenViewHelper and no ElseViewHelper is found, all child nodes are rendered
83 *
84 * @return string rendered ThenViewHelper or contents of <f:if> if no ThenViewHelper was found
85 * @api
86 */
87 protected function renderThenChild() {
88 $hasEvaluated = TRUE;
89 $result = static::renderStaticThenChild($this->arguments, $hasEvaluated);
90 if ($hasEvaluated) {
91 return $result;
92 }
93
94 $elseViewHelperEncountered = FALSE;
95 foreach ($this->childNodes as $childNode) {
96 if ($childNode instanceof ViewHelperNode
97 && $childNode->getViewHelperClassName() === ThenViewHelper::class) {
98 $data = $childNode->evaluate($this->renderingContext);
99 return $data;
100 }
101 if ($childNode instanceof ViewHelperNode
102 && $childNode->getViewHelperClassName() === ElseViewHelper::class) {
103 $elseViewHelperEncountered = TRUE;
104 }
105 }
106
107 if ($elseViewHelperEncountered) {
108 return '';
109 } else {
110 return $this->renderChildren();
111 }
112 }
113
114 /**
115 * Statically evalute "then" children.
116 * The "$hasEvaluated" argument is there to distinguish the case that "then" returned NULL or was not evaluated.
117 *
118 * @param array $arguments ViewHelper arguments
119 * @param bool $hasEvaluated Can be used to check if the "then" child was actually evaluated by this method.
120 * @return string
121 */
122 protected static function renderStaticThenChild($arguments, &$hasEvaluated) {
123 if (isset($arguments['then'])) {
124 return $arguments['then'];
125 }
126 if (isset($arguments['__thenClosure'])) {
127 $thenClosure = $arguments['__thenClosure'];
128 return $thenClosure();
129 } elseif (isset($arguments['__elseClosure'])) {
130 return '';
131 }
132
133 $hasEvaluated = FALSE;
134 }
135
136 /**
137 * Returns value of "else" attribute.
138 * If else attribute is not set, iterates through child nodes and renders ElseViewHelper.
139 * If else attribute is not set and no ElseViewHelper is found, an empty string will be returned.
140 *
141 * @return string rendered ElseViewHelper or an empty string if no ThenViewHelper was found
142 * @api
143 */
144 protected function renderElseChild() {
145 $hasEvaluated = TRUE;
146 $result = static::renderStaticElseChild($this->arguments, $hasEvaluated);
147 if ($hasEvaluated) {
148 return $result;
149 }
150
151 foreach ($this->childNodes as $childNode) {
152 if ($childNode instanceof ViewHelperNode
153 && $childNode->getViewHelperClassName() === ElseViewHelper::class) {
154 return $childNode->evaluate($this->renderingContext);
155 }
156 }
157
158 return '';
159 }
160
161
162 /**
163 * Statically evalute "else" children.
164 * The "$hasEvaluated" argument is there to distinguish the case that "else" returned NULL or was not evaluated.
165 *
166 * @param array $arguments ViewHelper arguments
167 * @param bool $hasEvaluated Can be used to check if the "else" child was actually evaluated by this method.
168 * @return string
169 */
170 protected static function renderStaticElseChild($arguments, &$hasEvaluated) {
171 if (isset($arguments['else'])) {
172 return $arguments['else'];
173 }
174 if (isset($arguments['__elseClosure'])) {
175 $elseClosure = $arguments['__elseClosure'];
176 return $elseClosure();
177 }
178
179 $hasEvaluated = FALSE;
180 }
181
182 /**
183 * The compiled ViewHelper adds two new ViewHelper arguments: __thenClosure and __elseClosure.
184 * These contain closures which are be executed to render the then(), respectively else() case.
185 *
186 * @param string $argumentsVariableName
187 * @param string $renderChildrenClosureVariableName
188 * @param string $initializationPhpCode
189 * @param \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\AbstractNode $syntaxTreeNode
190 * @param \TYPO3\CMS\Fluid\Core\Compiler\TemplateCompiler $templateCompiler
191 * @return string
192 * @internal
193 */
194 public function compile($argumentsVariableName, $renderChildrenClosureVariableName, &$initializationPhpCode, \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\AbstractNode $syntaxTreeNode, \TYPO3\CMS\Fluid\Core\Compiler\TemplateCompiler $templateCompiler) {
195 foreach ($syntaxTreeNode->getChildNodes() as $childNode) {
196 if ($childNode instanceof ViewHelperNode
197 && $childNode->getViewHelperClassName() === ThenViewHelper::class) {
198
199 $childNodesAsClosure = $templateCompiler->wrapChildNodesInClosure($childNode);
200 $initializationPhpCode .= sprintf('%s[\'__thenClosure\'] = %s;', $argumentsVariableName, $childNodesAsClosure) . LF;
201 }
202 if ($childNode instanceof ViewHelperNode
203 && $childNode->getViewHelperClassName() === ElseViewHelper::class) {
204
205 $childNodesAsClosure = $templateCompiler->wrapChildNodesInClosure($childNode);
206 $initializationPhpCode .= sprintf('%s[\'__elseClosure\'] = %s;', $argumentsVariableName, $childNodesAsClosure) . LF;
207 }
208 }
209
210 return sprintf('%s::renderStatic(%s, %s, $renderingContext)',
211 get_class($this), $argumentsVariableName, $renderChildrenClosureVariableName);
212 }
213
214 /**
215 * Default implementation for CompilableInterface. See CompilableInterface
216 * for a detailed description of this method.
217 *
218 * @param array $arguments
219 * @param \Closure $renderChildrenClosure
220 * @param \TYPO3\CMS\Fluid\Core\Rendering\RenderingContextInterface $renderingContext
221 * @return mixed
222 * @see \TYPO3\CMS\Fluid\Core\ViewHelper\Facets\CompilableInterface
223 */
224 static public function renderStatic(array $arguments, \Closure $renderChildrenClosure, \TYPO3\CMS\Fluid\Core\Rendering\RenderingContextInterface $renderingContext) {
225 $hasEvaluated = TRUE;
226 if (static::evaluateCondition($arguments)) {
227 $result = static::renderStaticThenChild($arguments, $hasEvaluated);
228 if ($hasEvaluated) {
229 return $result;
230 }
231
232 return $renderChildrenClosure();
233 } else {
234 $result = static::renderStaticElseChild($arguments, $hasEvaluated);
235 if ($hasEvaluated) {
236 return $result;
237 }
238 }
239
240 return '';
241 }
242
243 /**
244 * This method decides if the condition is TRUE or FALSE. It can be overriden in extending viewhelpers to adjust functionality.
245 *
246 * @param array $arguments ViewHelper arguments to evaluate the condition for this ViewHelper, allows for flexiblity in overriding this method.
247 * @return bool
248 */
249 static protected function evaluateCondition($arguments = NULL) {
250 return (isset($arguments['condition']) && $arguments['condition']);
251 }
252
253 }