Completed
Pull Request — master (#446)
by Claus
02:04
created

AbstractConditionViewHelper::renderElseChild()   B

Complexity

Conditions 8
Paths 10

Size

Total Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
nc 10
nop 0
dl 0
loc 25
rs 8.4444
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\NodeFilterInterface;
11
use TYPO3Fluid\Fluid\Core\Parser\SyntaxTree\NodeInterface;
12
use TYPO3Fluid\Fluid\Core\Parser\SyntaxTree\TextNode;
13
use TYPO3Fluid\Fluid\Core\Parser\SyntaxTree\ViewHelperNode;
14
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
15
use TYPO3Fluid\Fluid\ViewHelpers\ElseViewHelper;
16
use TYPO3Fluid\Fluid\ViewHelpers\ThenViewHelper;
17
18
/**
19
 * This view helper is an abstract ViewHelper which implements an if/else condition.
20
 *
21
 * = Usage =
22
 *
23
 * To create a custom Condition ViewHelper, you need to subclass this class, and
24
 * implement your own render() method. Inside there, you should call $this->renderThenChild()
25
 * if the condition evaluated to TRUE, and $this->renderElseChild() if the condition evaluated
26
 * to FALSE.
27
 *
28
 * Every Condition ViewHelper has a "then" and "else" argument, so it can be used like:
29
 * <[aConditionViewHelperName] .... then="condition true" else="condition false" />,
30
 * or as well use the "then" and "else" child nodes.
31
 *
32
 * @see TYPO3Fluid\Fluid\ViewHelpers\IfViewHelper for a more detailed explanation and a simple usage example.
33
 * Make sure to NOT OVERRIDE the constructor.
34
 *
35
 * @api
36
 */
37
abstract class AbstractConditionViewHelper extends AbstractViewHelper implements NodeFilterInterface
38
{
39
40
    /**
41
     * @var boolean
42
     */
43
    protected $escapeOutput = false;
44
45
    /**
46
     * Initializes the "then" and "else" arguments
47
     */
48
    public function initializeArguments()
49
    {
50
        $this->registerArgument('then', 'mixed', 'Value to be returned if the condition if met.', false);
51
        $this->registerArgument('else', 'mixed', 'Value to be returned if the condition if not met.', false);
52
    }
53
54
    /**
55
     * Renders <f:then> child if $condition is true, otherwise renders <f:else> child.
56
     * Method which only gets called if the template is not compiled. For static calling,
57
     * the then/else nodes are converted to closures and condition evaluation closures.
58
     *
59
     * @return string the rendered string
60
     * @api
61
     */
62
    public function render()
63
    {
64
        if (static::verdict($this->arguments, $this->renderingContext)) {
65
            return $this->renderThenChild();
66
        }
67
        return $this->renderElseChild();
68
    }
69
70
    /**
71
     * @param array $arguments
72
     * @param \Closure $renderChildrenClosure
73
     * @param RenderingContextInterface $renderingContext
74
     * @return mixed
75
     */
76
    public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext)
77
    {
78
        if (static::verdict($arguments, $renderingContext)) {
79
            if (isset($arguments['then'])) {
80
                return $arguments['then'];
81
            }
82
            if (isset($arguments['__thenClosure'])) {
83
                return $arguments['__thenClosure']();
84
            }
85
        } elseif (!empty($arguments['__elseClosures'])) {
86
            $elseIfClosures = isset($arguments['__elseifClosures']) ? $arguments['__elseifClosures'] : [];
87
            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...
88
        } elseif (array_key_exists('else', $arguments)) {
89
            return $arguments['else'];
90
        }
91
        return '';
92
    }
93
94
    /**
95
     * Static method which can be overridden by subclasses. If a subclass
96
     * requires a different (or faster) decision then this method is the one
97
     * to override and implement.
98
     *
99
     * @param array $arguments
100
     * @param RenderingContextInterface $renderingContext
101
     * @return bool
102
     */
103
    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...
104
    {
105
        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...
106
    }
107
108
    /**
109
     * Static method which can be overridden by subclasses. If a subclass
110
     * requires a different (or faster) decision then this method is the one
111
     * to override and implement.
112
     *
113
     * Note: method signature does not type-hint that an array is desired,
114
     * and as such, *appears* to accept any input type. There is no type hint
115
     * here for legacy reasons - the signature is kept compatible with third
116
     * party packages which depending on PHP version would error out if this
117
     * signature was not compatible with that of existing and in-production
118
     * subclasses that will be using this base class in the future. Let this
119
     * be a warning if someone considers changing this method signature!
120
     *
121
     * @deprecated Deprecated in favor of ClassName::verdict($arguments, renderingContext), will no longer be called in 3.0
122
     * @param array|NULL $arguments
123
     * @return boolean
124
     * @api
125
     */
126
    protected static function evaluateCondition($arguments = null)
127
    {
128
        return isset($arguments['condition']) && (bool)($arguments['condition']);
129
    }
130
131
    /**
132
     * @param array $closures
133
     * @param array $conditionClosures
134
     * @param RenderingContextInterface $renderingContext
135
     * @return string
136
     */
137
    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...
138
    {
139
        foreach ($closures as $elseNodeIndex => $elseNodeClosure) {
140
            if (!isset($conditionClosures[$elseNodeIndex])) {
141
                return $elseNodeClosure();
142
            } else {
143
                if ($conditionClosures[$elseNodeIndex]()) {
144
                    return $elseNodeClosure();
145
                }
146
            }
147
        }
148
        return '';
149
    }
150
151
    /**
152
     * Returns value of "then" attribute.
153
     * If then attribute is not set, iterates through child nodes and renders ThenViewHelper.
154
     * If then attribute is not set and no ThenViewHelper and no ElseViewHelper is found, all child nodes are rendered
155
     *
156
     * @return mixed rendered ThenViewHelper or contents of <f:if> if no ThenViewHelper was found
157
     * @api
158
     */
159
    protected function renderThenChild()
160
    {
161
        if ($this->hasArgument('then')) {
162
            return $this->arguments['then'];
163
        }
164
165
        $elseViewHelperEncountered = false;
166
        foreach ($this->viewHelperNode->getChildNodes() as $childNode) {
167
            if ($childNode instanceof ViewHelperNode
168
                && substr($childNode->getViewHelperClassName(), -14) === 'ThenViewHelper') {
169
                $data = $childNode->evaluate($this->renderingContext);
170
                return $data;
171
            }
172
            if ($childNode instanceof ViewHelperNode
173
                && substr($childNode->getViewHelperClassName(), -14) === 'ElseViewHelper') {
174
                $elseViewHelperEncountered = true;
175
            }
176
        }
177
178
        if ($elseViewHelperEncountered) {
179
            return '';
180
        } else {
181
            return $this->renderChildren();
182
        }
183
    }
184
185
    /**
186
     * Returns value of "else" attribute.
187
     * If else attribute is not set, iterates through child nodes and renders ElseViewHelper.
188
     * If else attribute is not set and no ElseViewHelper is found, an empty string will be returned.
189
     *
190
     * @return string rendered ElseViewHelper or an empty string if no ThenViewHelper was found
191
     * @api
192
     */
193
    protected function renderElseChild()
194
    {
195
196
        if ($this->hasArgument('else')) {
197
            return $this->arguments['else'];
198
        }
199
200
        /** @var ViewHelperNode|NULL $elseNode */
201
        $elseNode = null;
202
        foreach ($this->viewHelperNode->getChildNodes() as $childNode) {
203
            if ($childNode instanceof ViewHelperNode
204
                && substr($childNode->getViewHelperClassName(), -14) === 'ElseViewHelper') {
205
                $arguments = $childNode->getArguments();
206
                if (isset($arguments['if'])) {
207
                    if ($arguments['if']->evaluate($this->renderingContext)) {
208
                        return $childNode->evaluate($this->renderingContext);
209
                    }
210
                } else {
211
                    $elseNode = $childNode;
212
                }
213
            }
214
        }
215
216
        return $elseNode instanceof ViewHelperNode ? $elseNode->evaluate($this->renderingContext) : '';
217
    }
218
219
    /**
220
     * Condition ViewHelpers disallow child nodes that consist purely of whitespace,
221
     * thus avoiding whitespace in output inside f:if structures but not inside any
222
     * f:then or f:else nodes.
223
     *
224
     * @param NodeInterface $node
225
     * @return bool
226
     */
227 View Code Duplication
    public function allowsChildNodeType(NodeInterface $node): bool
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
228
    {
229
        if ($node instanceof TextNode && trim($node->getText()) === '') {
230
            return false;
231
        }
232
        return true;
233
    }
234
235
    /**
236
     * The compiled ViewHelper adds two new ViewHelper arguments: __thenClosure and __elseClosure.
237
     * These contain closures which are be executed to render the then(), respectively else() case.
238
     *
239
     * @param string $argumentsName
240
     * @param string $closureName
241
     * @param string $initializationPhpCode
242
     * @param ViewHelperNode $node
243
     * @param TemplateCompiler $compiler
244
     * @return string
245
     */
246
    public function compile($argumentsName, $closureName, &$initializationPhpCode, ViewHelperNode $node, TemplateCompiler $compiler)
247
    {
248
        $thenViewHelperEncountered = $elseViewHelperEncountered = false;
249
        foreach ($node->getChildNodes() as $childNode) {
250
            if ($childNode instanceof ViewHelperNode) {
251
                $viewHelperClassName = $childNode->getViewHelperClassName();
252
                if (substr($viewHelperClassName, -14) === 'ThenViewHelper') {
253
                    $thenViewHelperEncountered = true;
254
                    $childNodesAsClosure = $compiler->wrapChildNodesInClosure($childNode);
255
                    $initializationPhpCode .= sprintf('%s[\'__thenClosure\'] = %s;', $argumentsName, $childNodesAsClosure) . chr(10);
256
                } elseif (substr($viewHelperClassName, -14) === 'ElseViewHelper') {
257
                    $elseViewHelperEncountered = true;
258
                    $childNodesAsClosure = $compiler->wrapChildNodesInClosure($childNode);
259
                    $initializationPhpCode .= sprintf('%s[\'__elseClosures\'][] = %s;', $argumentsName, $childNodesAsClosure) . chr(10);
260
                    $arguments = $childNode->getArguments();
261
                    if (isset($arguments['if'])) {
262
                        // The "else" has an argument, indicating it has a secondary (elseif) condition.
263
                        // Compile a closure which will evaluate the condition.
264
                        $elseIfConditionAsClosure = $compiler->wrapViewHelperNodeArgumentEvaluationInClosure($childNode, 'if');
265
                        $initializationPhpCode .= sprintf('%s[\'__elseifClosures\'][] = %s;', $argumentsName, $elseIfConditionAsClosure) . chr(10);
266
                    }
267
                }
268
            }
269
        }
270
        if (!$thenViewHelperEncountered && !$elseViewHelperEncountered && !isset($node->getArguments()['then'])) {
271
            $initializationPhpCode .= sprintf('%s[\'__thenClosure\'] = %s;', $argumentsName, $closureName) . chr(10);
272
        }
273
        return parent::compile($argumentsName, $closureName, $initializationPhpCode, $node, $compiler);
274
    }
275
}
276