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