[FEATURE] Support literal strings in boolean arguments
[Packages/TYPO3.CMS.git] / typo3 / sysext / fluid / Classes / Core / Compiler / TemplateCompiler.php
1 <?php
2 namespace TYPO3\CMS\Fluid\Core\Compiler;
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 class TemplateCompiler implements \TYPO3\CMS\Core\SingletonInterface {
14
15 const SHOULD_GENERATE_VIEWHELPER_INVOCATION = '##should_gen_viewhelper##';
16
17 /**
18 * @var \TYPO3\CMS\Core\Cache\Frontend\PhpFrontend
19 */
20 protected $templateCache;
21
22 /**
23 * @var integer
24 */
25 protected $variableCounter = 0;
26
27 /**
28 * @var array
29 */
30 protected $syntaxTreeInstanceCache = array();
31
32 /**
33 * @param \TYPO3\CMS\Core\Cache\Frontend\PhpFrontend $templateCache
34 * @return void
35 */
36 public function setTemplateCache(\TYPO3\CMS\Core\Cache\Frontend\PhpFrontend $templateCache) {
37 $this->templateCache = $templateCache;
38 }
39
40 /**
41 * @param string $identifier
42 * @return boolean
43 */
44 public function has($identifier) {
45 $identifier = $this->sanitizeIdentifier($identifier);
46 return $this->templateCache->has($identifier);
47 }
48
49 /**
50 * @param string $identifier
51 * @return \TYPO3\CMS\Fluid\Core\Parser\ParsedTemplateInterface
52 */
53 public function get($identifier) {
54 $identifier = $this->sanitizeIdentifier($identifier);
55 if (!isset($this->syntaxTreeInstanceCache[$identifier])) {
56 $this->templateCache->requireOnce($identifier);
57 $templateClassName = 'FluidCache_' . $identifier;
58 $this->syntaxTreeInstanceCache[$identifier] = new $templateClassName();
59 }
60 return $this->syntaxTreeInstanceCache[$identifier];
61 }
62
63 /**
64 * @param string $identifier
65 * @param \TYPO3\CMS\Fluid\Core\Parser\ParsingState $parsingState
66 * @return void
67 */
68 public function store($identifier, \TYPO3\CMS\Fluid\Core\Parser\ParsingState $parsingState) {
69 $identifier = $this->sanitizeIdentifier($identifier);
70 $this->variableCounter = 0;
71 $generatedRenderFunctions = '';
72 if ($parsingState->getVariableContainer()->exists('sections')) {
73 $sections = $parsingState->getVariableContainer()->get('sections');
74 // TODO: refactor to $parsedTemplate->getSections()
75 foreach ($sections as $sectionName => $sectionRootNode) {
76 $generatedRenderFunctions .= $this->generateCodeForSection($this->convertListOfSubNodes($sectionRootNode), 'section_' . sha1($sectionName), 'section ' . $sectionName);
77 }
78 }
79 $generatedRenderFunctions .= $this->generateCodeForSection($this->convertListOfSubNodes($parsingState->getRootNode()), 'render', 'Main Render function');
80 $convertedLayoutNameNode = $parsingState->hasLayout() ? $this->convert($parsingState->getLayoutNameNode()) : array('initialization' => '', 'execution' => 'NULL');
81 $classDefinition = 'class FluidCache_' . $identifier . ' extends TYPO3\\CMS\\Fluid\\Core\\Compiler\\AbstractCompiledTemplate';
82 $templateCode = '%s {
83
84 public function getVariableContainer() {
85 // TODO
86 return new TYPO3\\CMS\\Fluid\\Core\\ViewHelper\\TemplateVariableContainer();
87 }
88 public function getLayoutName(TYPO3\\CMS\\Fluid\\Core\\Rendering\\RenderingContextInterface $renderingContext) {
89 %s
90 return %s;
91 }
92 public function hasLayout() {
93 return %s;
94 }
95
96 %s
97
98 }';
99 $templateCode = sprintf($templateCode, $classDefinition, $convertedLayoutNameNode['initialization'], $convertedLayoutNameNode['execution'], $parsingState->hasLayout() ? 'TRUE' : 'FALSE', $generatedRenderFunctions);
100 $this->templateCache->set($identifier, $templateCode);
101 }
102
103 /**
104 * Replaces special characters by underscores
105 *
106 * @see http://www.php.net/manual/en/language.variables.basics.php
107 * @param string $identifier
108 * @return string the sanitized identifier
109 */
110 protected function sanitizeIdentifier($identifier) {
111 return preg_replace('([^a-zA-Z0-9_\\x7f-\\xff])', '_', $identifier);
112 }
113
114 protected function generateCodeForSection($converted, $expectedFunctionName, $comment) {
115 $templateCode = '/**
116 * %s
117 */
118 public function %s(TYPO3\\CMS\\Fluid\\Core\\Rendering\\RenderingContextInterface $renderingContext) {
119 $self = $this;
120 %s
121 return %s;
122 }';
123 return sprintf($templateCode, $comment, $expectedFunctionName, $converted['initialization'], $converted['execution']);
124 }
125
126 /**
127 * Returns an array with two elements:
128 * - initialization: contains PHP code which is inserted *before* the actual rendering call. Must be valid, i.e. end with semi-colon.
129 * - execution: contains *a single PHP instruction* which needs to return the rendered output of the given element. Should NOT end with semi-colon.
130 *
131 * @param \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\AbstractNode $node
132 * @return array two-element array, see above
133 */
134 protected function convert(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\AbstractNode $node) {
135 if ($node instanceof \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\TextNode) {
136 return $this->convertTextNode($node);
137 } elseif ($node instanceof \TYPO3\Fluid\Core\Parser\SyntaxTree\NumericNode) {
138 return $this->convertNumericNode($node);
139 } elseif ($node instanceof \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\ViewHelperNode) {
140 return $this->convertViewHelperNode($node);
141 } elseif ($node instanceof \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\ObjectAccessorNode) {
142 return $this->convertObjectAccessorNode($node);
143 } elseif ($node instanceof \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\ArrayNode) {
144 return $this->convertArrayNode($node);
145 } elseif ($node instanceof \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\RootNode) {
146 return $this->convertListOfSubNodes($node);
147 } elseif ($node instanceof \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\BooleanNode) {
148 return $this->convertBooleanNode($node);
149 } else {
150 throw new \Exception('TODO: TYPE XY NOT FOUND');
151 }
152 }
153
154 /**
155 * @param \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\TextNode $node
156 * @return array
157 * @see convert()
158 */
159 protected function convertTextNode(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\TextNode $node) {
160 return array(
161 'initialization' => '',
162 'execution' => '\'' . $this->escapeTextForUseInSingleQuotes($node->getText()) . '\''
163 );
164 }
165
166 /**
167 * @param \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\NumericNode $node
168 * @return array
169 * @see convert()
170 */
171 protected function convertNumericNode(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\NumericNode $node) {
172 return array(
173 'initialization' => '',
174 'execution' => $node->getValue()
175 );
176 }
177
178 /**
179 * Convert a single ViewHelperNode into its cached representation. If the ViewHelper implements the "Compilable" facet,
180 * the ViewHelper itself is asked for its cached PHP code representation. If not, a ViewHelper is built and then invoked.
181 *
182 * @param \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\ViewHelperNode $node
183 * @return array
184 * @see convert()
185 */
186 protected function convertViewHelperNode(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\ViewHelperNode $node) {
187 $initializationPhpCode = '// Rendering ViewHelper ' . $node->getViewHelperClassName() . chr(10);
188 // Build up $arguments array
189 $argumentsVariableName = $this->variableName('arguments');
190 $initializationPhpCode .= sprintf('%s = array();', $argumentsVariableName) . chr(10);
191 $alreadyBuiltArguments = array();
192 foreach ($node->getArguments() as $argumentName => $argumentValue) {
193 $converted = $this->convert($argumentValue);
194 $initializationPhpCode .= $converted['initialization'];
195 $initializationPhpCode .= sprintf('%s[\'%s\'] = %s;', $argumentsVariableName, $argumentName, $converted['execution']) . chr(10);
196 $alreadyBuiltArguments[$argumentName] = TRUE;
197 }
198 foreach ($node->getUninitializedViewHelper()->prepareArguments() as $argumentName => $argumentDefinition) {
199 if (!isset($alreadyBuiltArguments[$argumentName])) {
200 $initializationPhpCode .= sprintf('%s[\'%s\'] = %s;', $argumentsVariableName, $argumentName, var_export($argumentDefinition->getDefaultValue(), TRUE)) . chr(10);
201 }
202 }
203 // Build up closure which renders the child nodes
204 $renderChildrenClosureVariableName = $this->variableName('renderChildrenClosure');
205 $initializationPhpCode .= sprintf('%s = %s;', $renderChildrenClosureVariableName, $this->wrapChildNodesInClosure($node)) . chr(10);
206 if ($node->getUninitializedViewHelper() instanceof \TYPO3\CMS\Fluid\Core\ViewHelper\Facets\CompilableInterface) {
207 // ViewHelper is compilable
208 $viewHelperInitializationPhpCode = '';
209 $convertedViewHelperExecutionCode = $node->getUninitializedViewHelper()->compile($argumentsVariableName, $renderChildrenClosureVariableName, $viewHelperInitializationPhpCode, $node, $this);
210 $initializationPhpCode .= $viewHelperInitializationPhpCode;
211 if ($convertedViewHelperExecutionCode !== self::SHOULD_GENERATE_VIEWHELPER_INVOCATION) {
212 return array(
213 'initialization' => $initializationPhpCode,
214 'execution' => $convertedViewHelperExecutionCode
215 );
216 }
217 }
218 // ViewHelper is not compilable, so we need to instanciate it directly and render it.
219 $viewHelperVariableName = $this->variableName('viewHelper');
220 $initializationPhpCode .= sprintf('%s = $self->getViewHelper(\'%s\', $renderingContext, \'%s\');', $viewHelperVariableName, $viewHelperVariableName, $node->getViewHelperClassName()) . chr(10);
221 $initializationPhpCode .= sprintf('%s->setArguments(%s);', $viewHelperVariableName, $argumentsVariableName) . chr(10);
222 $initializationPhpCode .= sprintf('%s->setRenderingContext($renderingContext);', $viewHelperVariableName) . chr(10);
223 $initializationPhpCode .= sprintf('%s->setRenderChildrenClosure(%s);', $viewHelperVariableName, $renderChildrenClosureVariableName) . chr(10);
224 $initializationPhpCode .= '// End of ViewHelper ' . $node->getViewHelperClassName() . chr(10);
225 return array(
226 'initialization' => $initializationPhpCode,
227 'execution' => sprintf('%s->initializeArgumentsAndRender()', $viewHelperVariableName)
228 );
229 }
230
231 /**
232 * @param \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\ObjectAccessorNode $node
233 * @return array
234 * @see convert()
235 */
236 protected function convertObjectAccessorNode(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\ObjectAccessorNode $node) {
237 return array(
238 'initialization' => '',
239 'execution' => sprintf('TYPO3\\CMS\\Fluid\\Core\\Parser\\SyntaxTree\\ObjectAccessorNode::getPropertyPath($renderingContext->getTemplateVariableContainer(), \'%s\', $renderingContext)', $node->getObjectPath())
240 );
241 }
242
243 /**
244 * @param \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\ArrayNode $node
245 * @return array
246 * @see convert()
247 */
248 protected function convertArrayNode(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\ArrayNode $node) {
249 $initializationPhpCode = '// Rendering Array' . chr(10);
250 $arrayVariableName = $this->variableName('array');
251 $initializationPhpCode .= sprintf('%s = array();', $arrayVariableName) . chr(10);
252 foreach ($node->getInternalArray() as $key => $value) {
253 if ($value instanceof \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\AbstractNode) {
254 $converted = $this->convert($value);
255 $initializationPhpCode .= $converted['initialization'];
256 $initializationPhpCode .= sprintf('%s[\'%s\'] = %s;', $arrayVariableName, $key, $converted['execution']) . chr(10);
257 } elseif (is_numeric($value)) {
258 // this case might happen for simple values
259 $initializationPhpCode .= sprintf('%s[\'%s\'] = %s;', $arrayVariableName, $key, $value) . chr(10);
260 } else {
261 // this case might happen for simple values
262 $initializationPhpCode .= sprintf('%s[\'%s\'] = \'%s\';', $arrayVariableName, $key, $this->escapeTextForUseInSingleQuotes($value)) . chr(10);
263 }
264 }
265 return array(
266 'initialization' => $initializationPhpCode,
267 'execution' => $arrayVariableName
268 );
269 }
270
271 /**
272 * @param \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\AbstractNode $node
273 * @return array
274 * @see convert()
275 */
276 public function convertListOfSubNodes(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\AbstractNode $node) {
277 switch (count($node->getChildNodes())) {
278 case 0:
279 return array(
280 'initialization' => '',
281 'execution' => 'NULL'
282 );
283 case 1:
284 $converted = $this->convert(current($node->getChildNodes()));
285 return $converted;
286 default:
287 $outputVariableName = $this->variableName('output');
288 $initializationPhpCode = sprintf('%s = \'\';', $outputVariableName) . chr(10);
289 foreach ($node->getChildNodes() as $childNode) {
290 $converted = $this->convert($childNode);
291 $initializationPhpCode .= $converted['initialization'] . chr(10);
292 $initializationPhpCode .= sprintf('%s .= %s;', $outputVariableName, $converted['execution']) . chr(10);
293 }
294 return array(
295 'initialization' => $initializationPhpCode,
296 'execution' => $outputVariableName
297 );
298 }
299 }
300
301 /**
302 * @param \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\BooleanNode $node
303 * @return array
304 * @see convert()
305 */
306 protected function convertBooleanNode(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\BooleanNode $node) {
307 $initializationPhpCode = '// Rendering Boolean node' . chr(10);
308 if ($node->getComparator() !== NULL) {
309 $convertedLeftSide = $this->convert($node->getLeftSide());
310 $convertedRightSide = $this->convert($node->getRightSide());
311 return array(
312 'initialization' => $initializationPhpCode . $convertedLeftSide['initialization'] . $convertedRightSide['initialization'],
313 'execution' => sprintf('TYPO3\\CMS\\Fluid\\Core\\Parser\\SyntaxTree\\BooleanNode::evaluateComparator(\'%s\', %s, %s)', $node->getComparator(), $convertedLeftSide['execution'], $convertedRightSide['execution'])
314 );
315 } else {
316 // simple case, no comparator.
317 $converted = $this->convert($node->getSyntaxTreeNode());
318 return array(
319 'initialization' => $initializationPhpCode . $converted['initialization'],
320 'execution' => sprintf('TYPO3\\CMS\\Fluid\\Core\\Parser\\SyntaxTree\\BooleanNode::convertToBoolean(%s)', $converted['execution'])
321 );
322 }
323 }
324
325 /**
326 * @param string $text
327 * @return string
328 */
329 protected function escapeTextForUseInSingleQuotes($text) {
330 return str_replace(array('\\', '\''), array('\\\\', '\\\''), $text);
331 }
332
333 /**
334 * @param \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\AbstractNode $node
335 * @return string
336 */
337 public function wrapChildNodesInClosure(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\AbstractNode $node) {
338 $closure = '';
339 $closure .= 'function() use ($renderingContext, $self) {' . chr(10);
340 $convertedSubNodes = $this->convertListOfSubNodes($node);
341 $closure .= $convertedSubNodes['initialization'];
342 $closure .= sprintf('return %s;', $convertedSubNodes['execution']) . chr(10);
343 $closure .= '}';
344 return $closure;
345 }
346
347 /**
348 * Returns a unique variable name by appending a global index to the given prefix
349 *
350 * @param string $prefix
351 * @return string
352 */
353 public function variableName($prefix) {
354 return '$' . $prefix . $this->variableCounter++;
355 }
356 }
357
358 ?>