Completed
Pull Request — master (#393)
by Claus
06:03
created

AbstractConditionViewHelper::render()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 0
dl 0
loc 7
rs 10
c 0
b 0
f 0
1
<?php
2
namespace TYPO3Fluid\Fluid\Core\ViewHelper;
3
4
/*
5
 * This file belongs to the package "TYPO3 Fluid".
6
 * See LICENSE.txt that was shipped with this package.
7
 */
8
9
use TYPO3Fluid\Fluid\Core\Compiler\TemplateCompiler;
10
use TYPO3Fluid\Fluid\Core\Parser\SyntaxTree\ViewHelperNode;
11
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
12
13
/**
14
 * This view helper is an abstract ViewHelper which implements an if/else condition.
15
 *
16
 * = Usage =
17
 *
18
 * To create a custom Condition ViewHelper, you need to subclass this class, and
19
 * implement your own render() method. Inside there, you should call $this->renderThenChild()
20
 * if the condition evaluated to TRUE, and $this->renderElseChild() if the condition evaluated
21
 * to FALSE.
22
 *
23
 * Every Condition ViewHelper has a "then" and "else" argument, so it can be used like:
24
 * <[aConditionViewHelperName] .... then="condition true" else="condition false" />,
25
 * or as well use the "then" and "else" child nodes.
26
 *
27
 * @see TYPO3Fluid\Fluid\ViewHelpers\IfViewHelper for a more detailed explanation and a simple usage example.
28
 * Make sure to NOT OVERRIDE the constructor.
29
 *
30
 * @api
31
 */
32
abstract class AbstractConditionViewHelper extends AbstractViewHelper
33
{
34
35
    /**
36
     * @var boolean
37
     */
38
    protected $escapeOutput = false;
39
40
    /**
41
     * Initializes the "then" and "else" arguments
42
     */
43
    public function initializeArguments()
44
    {
45
        $this->registerArgument('then', 'mixed', 'Value to be returned if the condition if met.', false);
46
        $this->registerArgument('else', 'mixed', 'Value to be returned if the condition if not met.', false);
47
        $this->registerArgument('condition', 'boolean', 'Condition expression conforming to Fluid boolean rules', false, false);
48
    }
49
50
    /**
51
     * Renders <f:then> child if $condition is true, otherwise renders <f:else> child.
52
     * Method which only gets called if the template is not compiled. For static calling,
53
     * the then/else nodes are converted to closures and condition evaluation closures.
54
     *
55
     * @return string the rendered string
56
     * @api
57
     */
58
    public function render()
59
    {
60
        if (static::verdict($this->arguments, $this->renderingContext)) {
61
            return $this->renderThenChild();
62
        }
63
        return $this->renderElseChild();
64
    }
65
66
    /**
67
     * @param array $arguments
68
     * @param \Closure $renderChildrenClosure
69
     * @param RenderingContextInterface $renderingContext
70
     * @return mixed
71
     */
72
    public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext)
73
    {
74
        if (static::verdict($arguments, $renderingContext)) {
75
            if (isset($arguments['then'])) {
76
                return $arguments['then'];
77
            }
78
            if (isset($arguments['__thenClosure'])) {
79
                return $arguments['__thenClosure']();
80
            }
81
        } elseif (!empty($arguments['__elseClosures'])) {
82
            $elseIfClosures = isset($arguments['__elseifClosures']) ? $arguments['__elseifClosures'] : [];
83
            return static::evaluateElseClosures($arguments['__elseClosures'], $elseIfClosures, $renderingContext);
0 ignored issues
show
Bug introduced by
Since evaluateElseClosures() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of evaluateElseClosures() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
84
        } elseif (array_key_exists('else', $arguments)) {
85
            return $arguments['else'];
86
        }
87
        return '';
88
    }
89
90
    /**
91
     * Static method which can be overridden by subclasses. If a subclass
92
     * requires a different (or faster) decision then this method is the one
93
     * to override and implement.
94
     *
95
     * @param array $arguments
96
     * @param RenderingContextInterface $renderingContext
97
     * @return bool
98
     */
99
    public static function verdict(array $arguments, RenderingContextInterface $renderingContext)
0 ignored issues
show
Unused Code introduced by
The parameter $renderingContext is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
100
    {
101
        return static::evaluateCondition($arguments);
0 ignored issues
show
Deprecated Code introduced by
The method TYPO3Fluid\Fluid\Core\Vi...er::evaluateCondition() has been deprecated with message: Deprecated in favor of ClassName::verdict($arguments, renderingContext), will no longer be called in 3.0

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
102
    }
103
104
    /**
105
     * Static method which can be overridden by subclasses. If a subclass
106
     * requires a different (or faster) decision then this method is the one
107
     * to override and implement.
108
     *
109
     * Note: method signature does not type-hint that an array is desired,
110
     * and as such, *appears* to accept any input type. There is no type hint
111
     * here for legacy reasons - the signature is kept compatible with third
112
     * party packages which depending on PHP version would error out if this
113
     * signature was not compatible with that of existing and in-production
114
     * subclasses that will be using this base class in the future. Let this
115
     * be a warning if someone considers changing this method signature!
116
     *
117
     * @deprecated Deprecated in favor of ClassName::verdict($arguments, renderingContext), will no longer be called in 3.0
118
     * @param array|NULL $arguments
119
     * @return boolean
120
     * @api
121
     */
122
    protected static function evaluateCondition($arguments = null)
123
    {
124
        return (boolean) $arguments['condition'];
125
    }
126
127
    /**
128
     * @param array $closures
129
     * @param array $conditionClosures
130
     * @param RenderingContextInterface $renderingContext
131
     * @return string
132
     */
133
    private static function evaluateElseClosures(array $closures, array $conditionClosures, RenderingContextInterface $renderingContext)
0 ignored issues
show
Unused Code introduced by
The parameter $renderingContext is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
134
    {
135
        foreach ($closures as $elseNodeIndex => $elseNodeClosure) {
136
            if (!isset($conditionClosures[$elseNodeIndex])) {
137
                return $elseNodeClosure();
138
            } else {
139
                if ($conditionClosures[$elseNodeIndex]()) {
140
                    return $elseNodeClosure();
141
                }
142
            }
143
        }
144
        return '';
145
    }
146
147
    /**
148
     * Returns value of "then" attribute.
149
     * If then attribute is not set, iterates through child nodes and renders ThenViewHelper.
150
     * If then attribute is not set and no ThenViewHelper and no ElseViewHelper is found, all child nodes are rendered
151
     *
152
     * @return mixed rendered ThenViewHelper or contents of <f:if> if no ThenViewHelper was found
153
     * @api
154
     */
155
    protected function renderThenChild()
156
    {
157
        if ($this->hasArgument('then')) {
158
            return $this->arguments['then'];
159
        }
160
161
        $elseViewHelperEncountered = false;
162
        foreach ($this->viewHelperNode->getChildNodes() as $childNode) {
163
            if ($childNode instanceof ViewHelperNode
164
                && substr($childNode->getViewHelperClassName(), -14) === 'ThenViewHelper') {
165
                $data = $childNode->evaluate($this->renderingContext);
166
                return $data;
167
            }
168
            if ($childNode instanceof ViewHelperNode
169
                && substr($childNode->getViewHelperClassName(), -14) === 'ElseViewHelper') {
170
                $elseViewHelperEncountered = true;
171
            }
172
        }
173
174
        if ($elseViewHelperEncountered) {
175
            return '';
176
        } else {
177
            return $this->renderChildren();
178
        }
179
    }
180
181
    /**
182
     * Returns value of "else" attribute.
183
     * If else attribute is not set, iterates through child nodes and renders ElseViewHelper.
184
     * If else attribute is not set and no ElseViewHelper is found, an empty string will be returned.
185
     *
186
     * @return string rendered ElseViewHelper or an empty string if no ThenViewHelper was found
187
     * @api
188
     */
189
    protected function renderElseChild()
190
    {
191
192
        if ($this->hasArgument('else')) {
193
            return $this->arguments['else'];
194
        }
195
196
        /** @var ViewHelperNode|NULL $elseNode */
197
        $elseNode = null;
198
        foreach ($this->viewHelperNode->getChildNodes() as $childNode) {
199
            if ($childNode instanceof ViewHelperNode
200
                && substr($childNode->getViewHelperClassName(), -14) === 'ElseViewHelper') {
201
                $arguments = $childNode->getArguments();
202
                if (isset($arguments['if'])) {
203
                    if ($arguments['if']->evaluate($this->renderingContext)) {
204
                        return $childNode->evaluate($this->renderingContext);
205
                    }
206
                } else {
207
                    $elseNode = $childNode;
208
                }
209
            }
210
        }
211
212
        return $elseNode instanceof ViewHelperNode ? $elseNode->evaluate($this->renderingContext) : '';
213
    }
214
215
    /**
216
     * The compiled ViewHelper adds two new ViewHelper arguments: __thenClosure and __elseClosure.
217
     * These contain closures which are be executed to render the then(), respectively else() case.
218
     *
219
     * @param string $argumentsName
220
     * @param string $closureName
221
     * @param string $initializationPhpCode
222
     * @param ViewHelperNode $node
223
     * @param TemplateCompiler $compiler
224
     * @return string
225
     */
226
    public function compile($argumentsName, $closureName, &$initializationPhpCode, ViewHelperNode $node, TemplateCompiler $compiler)
227
    {
228
        $thenViewHelperEncountered = $elseViewHelperEncountered = false;
229
        foreach ($node->getChildNodes() as $childNode) {
230
            if ($childNode instanceof ViewHelperNode) {
231
                $viewHelperClassName = $childNode->getViewHelperClassName();
232
                if (substr($viewHelperClassName, -14) === 'ThenViewHelper') {
233
                    $thenViewHelperEncountered = true;
234
                    $childNodesAsClosure = $compiler->wrapChildNodesInClosure($childNode);
235
                    $initializationPhpCode .= sprintf('%s[\'__thenClosure\'] = %s;', $argumentsName, $childNodesAsClosure) . chr(10);
236
                } elseif (substr($viewHelperClassName, -14) === 'ElseViewHelper') {
237
                    $elseViewHelperEncountered = true;
238
                    $childNodesAsClosure = $compiler->wrapChildNodesInClosure($childNode);
239
                    $initializationPhpCode .= sprintf('%s[\'__elseClosures\'][] = %s;', $argumentsName, $childNodesAsClosure) . chr(10);
240
                    $arguments = $childNode->getArguments();
241
                    if (isset($arguments['if'])) {
242
                        // The "else" has an argument, indicating it has a secondary (elseif) condition.
243
                        // Compile a closure which will evaluate the condition.
244
                        $elseIfConditionAsClosure = $compiler->wrapViewHelperNodeArgumentEvaluationInClosure($childNode, 'if');
245
                        $initializationPhpCode .= sprintf('%s[\'__elseifClosures\'][] = %s;', $argumentsName, $elseIfConditionAsClosure) . chr(10);
246
                    }
247
                }
248
            }
249
        }
250
        if (!$thenViewHelperEncountered && !$elseViewHelperEncountered && !isset($node->getArguments()['then'])) {
251
            $initializationPhpCode .= sprintf('%s[\'__thenClosure\'] = %s;', $argumentsName, $closureName) . chr(10);
252
        }
253
        return parent::compile($argumentsName, $closureName, $initializationPhpCode, $node, $compiler);
254
    }
255
}
256