Completed
Pull Request — master (#270)
by Marc
02:19
created

ViewHelperNode::callArgumentInterceptors()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 8
nc 3
nop 1
dl 0
loc 14
rs 9.2
c 0
b 0
f 0
1
<?php
2
3
namespace TYPO3Fluid\Fluid\Core\Parser\SyntaxTree;
4
5
/*
6
 * This file belongs to the package "TYPO3 Fluid".
7
 * See LICENSE.txt that was shipped with this package.
8
 */
9
10
use TYPO3Fluid\Fluid\Core\Parser\Exception;
11
use TYPO3Fluid\Fluid\Core\Parser\InterceptorInterface;
12
use TYPO3Fluid\Fluid\Core\Parser\ParsingState;
13
use TYPO3Fluid\Fluid\Core\Parser\TemplateParser;
14
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
15
use TYPO3Fluid\Fluid\Core\ViewHelper\ArgumentDefinition;
16
use TYPO3Fluid\Fluid\Core\ViewHelper\ViewHelperInterface;
17
18
/**
19
 * Node which will call a ViewHelper associated with this node.
20
 */
21
class ViewHelperNode extends AbstractNode
22
{
23
24
    /**
25
     * @var string
26
     */
27
    protected $viewHelperClassName;
28
29
    /**
30
     * @var NodeInterface[]
31
     */
32
    protected $arguments = [];
33
34
    /**
35
     * @var ViewHelperInterface
36
     */
37
    protected $uninitializedViewHelper = null;
38
39
    /**
40
     * @var ArgumentDefinition[]
41
     */
42
    protected $argumentDefinitions = [];
43
44
    /**
45
     * @var string
46
     */
47
    protected $pointerTemplateCode = null;
48
49
    /**
50
     * @var ParsingState
51
     */
52
    protected $parsingState;
53
54
    /**
55
     * Constructor.
56
     *
57
     * @param RenderingContextInterface $renderingContext a RenderingContext, provided by invoker
58
     * @param string $namespace the namespace identifier of the ViewHelper.
59
     * @param string $identifier the name of the ViewHelper to render, inside the namespace provided.
60
     * @param NodeInterface[] $arguments Arguments of view helper - each value is a RootNode.
61
     * @param ParsingState $state
62
     */
63
    public function __construct(
64
        RenderingContextInterface $renderingContext,
65
        $namespace,
66
        $identifier,
67
        array $arguments,
68
        ParsingState $state
69
    ) {
70
        $this->parsingState = $state;
71
        $resolver = $renderingContext->getViewHelperResolver();
72
        $this->arguments = $arguments;
73
        $this->viewHelperClassName = $resolver->resolveViewHelperClassName($namespace, $identifier);
74
        $this->uninitializedViewHelper = $resolver->createViewHelperInstanceFromClassName($this->viewHelperClassName);
75
        $this->uninitializedViewHelper->setViewHelperNode($this);
76
        // Note: RenderingContext required here though replaced later. See https://github.com/TYPO3Fluid/Fluid/pull/93
77
        $this->uninitializedViewHelper->setRenderingContext($renderingContext);
78
        $this->argumentDefinitions = $resolver->getArgumentDefinitionsForViewHelper($this->uninitializedViewHelper);
79
        $this->rewriteBooleanNodesInArgumentsObjectTree($this->argumentDefinitions, $this->arguments);
80
        $this->validateArguments($this->argumentDefinitions, $this->arguments);
81
    }
82
83
    /**
84
     * @return ArgumentDefinition[]
85
     */
86
    public function getArgumentDefinitions()
87
    {
88
        return $this->argumentDefinitions;
89
    }
90
91
    /**
92
     * Returns the attached (but still uninitialized) ViewHelper for this ViewHelperNode.
93
     * We need this method because sometimes Interceptors need to ask some information from the ViewHelper.
94
     *
95
     * @return ViewHelperInterface
96
     */
97
    public function getUninitializedViewHelper()
98
    {
99
        return $this->uninitializedViewHelper;
100
    }
101
102
    /**
103
     * Get class name of view helper
104
     *
105
     * @return string Class Name of associated view helper
106
     */
107
    public function getViewHelperClassName()
108
    {
109
        return $this->viewHelperClassName;
110
    }
111
112
    /**
113
     * INTERNAL - only needed for compiling templates
114
     *
115
     * @return NodeInterface[]
116
     */
117
    public function getArguments()
118
    {
119
        return $this->arguments;
120
    }
121
122
    /**
123
     * INTERNAL - only needed for compiling templates
124
     *
125
     * @param string $argumentName
126
     * @return ArgumentDefinition
127
     */
128
    public function getArgumentDefinition($argumentName)
129
    {
130
        return $this->argumentDefinitions[$argumentName];
131
    }
132
133
    /**
134
     * @param NodeInterface $childNode
135
     * @return void
136
     */
137
    public function addChildNode(NodeInterface $childNode)
138
    {
139
        parent::addChildNode($childNode);
140
        $this->uninitializedViewHelper->setChildNodes($this->childNodes);
141
    }
142
143
    /**
144
     * @param string $pointerTemplateCode
145
     * @return void
146
     */
147
    public function setPointerTemplateCode($pointerTemplateCode)
148
    {
149
        $this->pointerTemplateCode = $pointerTemplateCode;
150
    }
151
152
    /**
153
     * Call the view helper associated with this object.
154
     *
155
     * First, it evaluates the arguments of the view helper.
156
     *
157
     * If the view helper implements \TYPO3Fluid\Fluid\Core\ViewHelper\ChildNodeAccessInterface,
158
     * it calls setChildNodes(array childNodes) on the view helper.
159
     *
160
     * Afterwards, checks that the view helper did not leave a variable lying around.
161
     *
162
     * @param RenderingContextInterface $renderingContext
163
     * @return string evaluated node after the view helper has been called.
164
     */
165
    public function evaluate(RenderingContextInterface $renderingContext)
166
    {
167
        return $renderingContext->getViewHelperInvoker()->invoke($this->uninitializedViewHelper, $this->arguments,
168
            $renderingContext);
169
    }
170
171
    /**
172
     * Wraps the argument tree, if a node is boolean, into a Boolean syntax tree node
173
     *
174
     * @param ArgumentDefinition[] $argumentDefinitions the argument definitions, key is the argument name, value is the ArgumentDefinition object
175
     * @param NodeInterface[] $argumentsObjectTree the arguments syntax tree, key is the argument name, value is an AbstractNode
176
     * @return void
177
     */
178
    protected function rewriteBooleanNodesInArgumentsObjectTree($argumentDefinitions, &$argumentsObjectTree)
179
    {
180
        /** @var $argumentDefinition ArgumentDefinition */
181
        foreach ($argumentDefinitions as $argumentName => $argumentDefinition) {
182
            if (($argumentDefinition->getType() === 'boolean' || $argumentDefinition->getType() === 'bool')
183
                && isset($argumentsObjectTree[$argumentName])
184
            ) {
185
                $argumentsObjectTree[$argumentName] = new BooleanNode($argumentsObjectTree[$argumentName]);
186
            }
187
        }
188
    }
189
190
    /**
191
     * Call interceptors for viewHelper arguments
192
     *
193
     * @param TemplateParser $templateParser
194
     * @return void
195
     */
196
    public function callArgumentInterceptors(TemplateParser $templateParser)
197
    {
198
        foreach ($this->argumentDefinitions as $argumentName => $argumentDefinition) {
199
            /** @var ArgumentDefinition $argumentDefinition */
200
            if (isset($this->arguments[$argumentName]) && $this->arguments[$argumentName] instanceof NodeInterface) {
201
                $this->arguments[$argumentName]->setEscapeOutput($argumentDefinition->isEscaped());
202
                $templateParser->callInterceptor(
203
                    $this->arguments[$argumentName],
204
                    InterceptorInterface::INTERCEPT_VIEWHELPER_ARGUMENT,
205
                    $this->parsingState
206
                );
207
            }
208
        }
209
    }
210
211
    /**
212
     * @param ArgumentDefinition[] $argumentDefinitions
213
     * @param NodeInterface[] $argumentsObjectTree
214
     * @throws Exception
215
     */
216
    protected function validateArguments(array $argumentDefinitions, array $argumentsObjectTree)
217
    {
218
        $additionalArguments = [];
219
        foreach ($argumentsObjectTree as $argumentName => $value) {
220
            if (!array_key_exists($argumentName, $argumentDefinitions)) {
221
                $additionalArguments[$argumentName] = $value;
222
            }
223
        }
224
        foreach ($argumentDefinitions as $argumentDefinition) {
225
            if ($argumentDefinition->isRequired() && $argumentDefinition->getDefaultValue() === null) {
226
                $name = $argumentDefinition->getName();
227
                if (!array_key_exists($name, $argumentsObjectTree)) {
228
                    throw new Exception(sprintf('Required argument %s for ViewHelper %s was not provided', $name,
229
                        $this->viewHelperClassName));
230
                }
231
            }
232
        }
233
        $this->abortIfRequiredArgumentsAreMissing($argumentDefinitions, $argumentsObjectTree);
234
        $this->uninitializedViewHelper->validateAdditionalArguments($additionalArguments);
235
    }
236
237
    /**
238
     * Throw an exception if required arguments are missing
239
     *
240
     * @param ArgumentDefinition[] $expectedArguments Array of all expected arguments
241
     * @param NodeInterface[] $actualArguments Actual arguments
242
     * @throws Exception
243
     */
244
    protected function abortIfRequiredArgumentsAreMissing($expectedArguments, $actualArguments)
245
    {
246
        $actualArgumentNames = array_keys($actualArguments);
247
        foreach ($expectedArguments as $expectedArgument) {
248
            if ($expectedArgument->isRequired() && !in_array($expectedArgument->getName(), $actualArgumentNames)) {
249
                throw new Exception('Required argument "' . $expectedArgument->getName() . '" was not supplied.',
250
                    1237823699);
251
            }
252
        }
253
    }
254
}
255