TYPO3 CMS  TYPO3_7-6
TemplateCompiler.php
Go to the documentation of this file.
1 <?php
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 
15 {
16  const SHOULD_GENERATE_VIEWHELPER_INVOCATION = '##should_gen_viewhelper##';
17 
21  protected $templateCache;
22 
26  protected $variableCounter = 0;
27 
31  protected $syntaxTreeInstanceCache = [];
32 
37  public function setTemplateCache(\TYPO3\CMS\Core\Cache\Frontend\PhpFrontend $templateCache)
38  {
39  $this->templateCache = $templateCache;
40  }
41 
46  public function has($identifier)
47  {
48  $identifier = $this->sanitizeIdentifier($identifier);
49  return $this->templateCache->has($identifier);
50  }
51 
56  public function get($identifier)
57  {
58  $identifier = $this->sanitizeIdentifier($identifier);
59  if (!isset($this->syntaxTreeInstanceCache[$identifier])) {
60  $this->templateCache->requireOnce($identifier);
61  $templateClassName = 'FluidCache_' . $identifier;
62  $this->syntaxTreeInstanceCache[$identifier] = new $templateClassName();
63  }
64  return $this->syntaxTreeInstanceCache[$identifier];
65  }
66 
72  public function store($identifier, \TYPO3\CMS\Fluid\Core\Parser\ParsingState $parsingState)
73  {
74  $identifier = $this->sanitizeIdentifier($identifier);
75  $this->variableCounter = 0;
76  $generatedRenderFunctions = '';
77 
78  if ($parsingState->getVariableContainer()->exists('sections')) {
79  $sections = $parsingState->getVariableContainer()->get('sections');
80  // @todo refactor to $parsedTemplate->getSections()
81  foreach ($sections as $sectionName => $sectionRootNode) {
82  $generatedRenderFunctions .= $this->generateCodeForSection($this->convertListOfSubNodes($sectionRootNode), 'section_' . sha1($sectionName), 'section ' . $sectionName);
83  }
84  }
85  $generatedRenderFunctions .= $this->generateCodeForSection($this->convertListOfSubNodes($parsingState->getRootNode()), 'render', 'Main Render function');
86  $convertedLayoutNameNode = $parsingState->hasLayout() ? $this->convert($parsingState->getLayoutNameNode()) : ['initialization' => '', 'execution' => 'NULL'];
87 
88  $classDefinition = 'class FluidCache_' . $identifier . ' extends \\TYPO3\\CMS\\Fluid\\Core\\Compiler\\AbstractCompiledTemplate';
89 
90  $templateCode = <<<EOD
91 %s {
92 
93 public function getVariableContainer() {
94  // @todo
95  return new \TYPO3\CMS\Fluid\Core\ViewHelper\TemplateVariableContainer();
96 }
97 public function getLayoutName(\TYPO3\CMS\Fluid\Core\Rendering\RenderingContextInterface \$renderingContext) {
98 \$currentVariableContainer = \$renderingContext->getTemplateVariableContainer();
99 \$self = \$this;
100 %s
101 return %s;
102 }
103 public function hasLayout() {
104 return %s;
105 }
106 
107 %s
108 
109 }
110 EOD;
111  $templateCode = sprintf($templateCode,
112  $classDefinition,
113  $convertedLayoutNameNode['initialization'],
114  $convertedLayoutNameNode['execution'],
115  ($parsingState->hasLayout() ? 'TRUE' : 'FALSE'),
116  $generatedRenderFunctions);
117  $this->templateCache->set($identifier, $templateCode);
118  }
119 
127  protected function sanitizeIdentifier($identifier)
128  {
129  return preg_replace('([^a-zA-Z0-9_\\x7f-\\xff])', '_', $identifier);
130  }
131 
138  protected function generateCodeForSection(array $converted, $expectedFunctionName, $comment)
139  {
140  $templateCode = <<<EOD
144 public function %s(\TYPO3\CMS\Fluid\Core\Rendering\RenderingContextInterface \$renderingContext) {
145 \$self = \$this;
146 \$currentVariableContainer = \$renderingContext->getTemplateVariableContainer();
147 
148 %s
149 
150 return %s;
151 }
152 
153 EOD;
154  return sprintf($templateCode, $comment, $expectedFunctionName, $converted['initialization'], $converted['execution']);
155  }
156 
166  protected function convert(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\AbstractNode $node)
167  {
168  if ($node instanceof \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\TextNode) {
169  return $this->convertTextNode($node);
170  } elseif ($node instanceof \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\NumericNode) {
171  return $this->convertNumericNode($node);
172  } elseif ($node instanceof \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\ViewHelperNode) {
173  return $this->convertViewHelperNode($node);
174  } elseif ($node instanceof \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\ObjectAccessorNode) {
175  return $this->convertObjectAccessorNode($node);
176  } elseif ($node instanceof \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\ArrayNode) {
177  return $this->convertArrayNode($node);
178  } elseif ($node instanceof \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\RootNode) {
179  return $this->convertListOfSubNodes($node);
180  } elseif ($node instanceof \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\BooleanNode) {
181  return $this->convertBooleanNode($node);
182  } else {
183  throw new \TYPO3\CMS\Fluid\Exception('Syntax tree node type "' . get_class($node) . '" is not supported.');
184  }
185  }
186 
192  protected function convertTextNode(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\TextNode $node)
193  {
194  return [
195  'initialization' => '',
196  'execution' => '\'' . $this->escapeTextForUseInSingleQuotes($node->getText()) . '\''
197  ];
198  }
199 
205  protected function convertNumericNode(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\NumericNode $node)
206  {
207  return [
208  'initialization' => '',
209  'execution' => $node->getValue()
210  ];
211  }
212 
221  protected function convertViewHelperNode(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\ViewHelperNode $node)
222  {
223  $initializationPhpCode = '// Rendering ViewHelper ' . $node->getViewHelperClassName() . LF;
224 
225  // Build up $arguments array
226  $argumentsVariableName = $this->variableName('arguments');
227  $initializationPhpCode .= sprintf('%s = array();', $argumentsVariableName) . LF;
228 
229  $alreadyBuiltArguments = [];
230  foreach ($node->getArguments() as $argumentName => $argumentValue) {
231  $converted = $this->convert($argumentValue);
232  $initializationPhpCode .= $converted['initialization'];
233  $initializationPhpCode .= sprintf('%s[\'%s\'] = %s;', $argumentsVariableName, $argumentName, $converted['execution']) . LF;
234  $alreadyBuiltArguments[$argumentName] = true;
235  }
236 
237  foreach ($node->getUninitializedViewHelper()->prepareArguments() as $argumentName => $argumentDefinition) {
238  if (!isset($alreadyBuiltArguments[$argumentName])) {
239  $initializationPhpCode .= sprintf('%s[\'%s\'] = %s;', $argumentsVariableName, $argumentName, var_export($argumentDefinition->getDefaultValue(), true)) . LF;
240  }
241  }
242 
243  // Build up closure which renders the child nodes
244  $renderChildrenClosureVariableName = $this->variableName('renderChildrenClosure');
245  $initializationPhpCode .= sprintf('%s = %s;', $renderChildrenClosureVariableName, $this->wrapChildNodesInClosure($node)) . LF;
246 
247  if ($node->getUninitializedViewHelper() instanceof \TYPO3\CMS\Fluid\Core\ViewHelper\Facets\CompilableInterface) {
248  // ViewHelper is compilable
249  $viewHelperInitializationPhpCode = '';
250  $convertedViewHelperExecutionCode = $node->getUninitializedViewHelper()->compile($argumentsVariableName, $renderChildrenClosureVariableName, $viewHelperInitializationPhpCode, $node, $this);
251  $initializationPhpCode .= $viewHelperInitializationPhpCode;
252  if ($convertedViewHelperExecutionCode !== self::SHOULD_GENERATE_VIEWHELPER_INVOCATION) {
253  return [
254  'initialization' => $initializationPhpCode,
255  'execution' => $convertedViewHelperExecutionCode
256  ];
257  }
258  }
259 
260  // ViewHelper is not compilable, so we need to instanciate it directly and render it.
261  $viewHelperVariableName = $this->variableName('viewHelper');
262 
263  $initializationPhpCode .= sprintf('%s = $self->getViewHelper(\'%s\', $renderingContext, \'%s\');', $viewHelperVariableName, $viewHelperVariableName, $node->getViewHelperClassName()) . LF;
264  $initializationPhpCode .= sprintf('%s->setArguments(%s);', $viewHelperVariableName, $argumentsVariableName) . LF;
265  $initializationPhpCode .= sprintf('%s->setRenderingContext($renderingContext);', $viewHelperVariableName) . LF;
266 
267  $initializationPhpCode .= sprintf('%s->setRenderChildrenClosure(%s);', $viewHelperVariableName, $renderChildrenClosureVariableName) . LF;
268 
269  $initializationPhpCode .= '// End of ViewHelper ' . $node->getViewHelperClassName() . LF;
270 
271  return [
272  'initialization' => $initializationPhpCode,
273  'execution' => sprintf('%s->initializeArgumentsAndRender()', $viewHelperVariableName)
274  ];
275  }
276 
282  protected function convertObjectAccessorNode(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\ObjectAccessorNode $node)
283  {
284  $objectPathSegments = explode('.', $node->getObjectPath());
285  $firstPathElement = array_shift($objectPathSegments);
286  if ($objectPathSegments === []) {
287  return [
288  'initialization' => '',
289  'execution' => sprintf('$currentVariableContainer->getOrNull(\'%s\')', $firstPathElement)
290  ];
291  } else {
292  $executionCode = '\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\ObjectAccessorNode::getPropertyPath($currentVariableContainer->getOrNull(\'%s\'), \'%s\', $renderingContext)';
293  return [
294  'initialization' => '',
295  'execution' => sprintf($executionCode, $firstPathElement, implode('.', $objectPathSegments))
296  ];
297  }
298  }
299 
305  protected function convertArrayNode(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\ArrayNode $node)
306  {
307  $initializationPhpCode = '// Rendering Array' . LF;
308  $arrayVariableName = $this->variableName('array');
309 
310  $initializationPhpCode .= sprintf('%s = array();', $arrayVariableName) . LF;
311 
312  foreach ($node->getInternalArray() as $key => $value) {
313  if ($value instanceof \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\AbstractNode) {
314  $converted = $this->convert($value);
315  $initializationPhpCode .= $converted['initialization'];
316  $initializationPhpCode .= sprintf('%s[\'%s\'] = %s;', $arrayVariableName, $key, $converted['execution']) . LF;
317  } elseif (is_numeric($value)) {
318  // this case might happen for simple values
319  $initializationPhpCode .= sprintf('%s[\'%s\'] = %s;', $arrayVariableName, $key, $value) . LF;
320  } else {
321  // this case might happen for simple values
322  $initializationPhpCode .= sprintf('%s[\'%s\'] = \'%s\';', $arrayVariableName, $key, $this->escapeTextForUseInSingleQuotes($value)) . LF;
323  }
324  }
325  return [
326  'initialization' => $initializationPhpCode,
327  'execution' => $arrayVariableName
328  ];
329  }
330 
336  public function convertListOfSubNodes(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\AbstractNode $node)
337  {
338  switch (count($node->getChildNodes())) {
339  case 0:
340  return [
341  'initialization' => '',
342  'execution' => 'NULL'
343  ];
344  case 1:
345  $converted = $this->convert(current($node->getChildNodes()));
346 
347  return $converted;
348  default:
349  $outputVariableName = $this->variableName('output');
350  $initializationPhpCode = sprintf('%s = \'\';', $outputVariableName) . LF;
351 
352  foreach ($node->getChildNodes() as $childNode) {
353  $converted = $this->convert($childNode);
354 
355  $initializationPhpCode .= $converted['initialization'] . LF;
356  $initializationPhpCode .= sprintf('%s .= %s;', $outputVariableName, $converted['execution']) . LF;
357  }
358 
359  return [
360  'initialization' => $initializationPhpCode,
361  'execution' => $outputVariableName
362  ];
363  }
364  }
365 
371  protected function convertBooleanNode(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\BooleanNode $node)
372  {
373  $initializationPhpCode = '// Rendering Boolean node' . LF;
374  if ($node->getComparator() !== null) {
375  $convertedLeftSide = $this->convert($node->getLeftSide());
376  $convertedRightSide = $this->convert($node->getRightSide());
377 
378  return [
379  'initialization' => $initializationPhpCode . $convertedLeftSide['initialization'] . $convertedRightSide['initialization'],
380  'execution' => sprintf(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\BooleanNode::class . '::evaluateComparator(\'%s\', %s, %s)', $node->getComparator(), $convertedLeftSide['execution'], $convertedRightSide['execution'])
381  ];
382  } else {
383  // simple case, no comparator.
384  $converted = $this->convert($node->getSyntaxTreeNode());
385  return [
386  'initialization' => $initializationPhpCode . $converted['initialization'],
387  'execution' => sprintf(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\BooleanNode::class . '::convertToBoolean(%s)', $converted['execution'])
388  ];
389  }
390  }
391 
396  protected function escapeTextForUseInSingleQuotes($text)
397  {
398  return str_replace(['\\', '\''], ['\\\\', '\\\''], $text);
399  }
400 
405  public function wrapChildNodesInClosure(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\AbstractNode $node)
406  {
407  $convertedSubNodes = $this->convertListOfSubNodes($node);
408  if ($convertedSubNodes['execution'] === 'NULL') {
409  return 'function() {return NULL;}';
410  }
411 
412  $closure = '';
413  $closure .= 'function() use ($renderingContext, $self) {' . LF;
414  $closure .= '$currentVariableContainer = $renderingContext->getTemplateVariableContainer();' . LF;
415  $closure .= $convertedSubNodes['initialization'];
416  $closure .= sprintf('return %s;', $convertedSubNodes['execution']) . LF;
417  $closure .= '}';
418  return $closure;
419  }
420 
427  public function variableName($prefix)
428  {
429  return '$' . $prefix . $this->variableCounter++;
430  }
431 }
convertBooleanNode(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\BooleanNode $node)
convertArrayNode(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\ArrayNode $node)
generateCodeForSection(array $converted, $expectedFunctionName, $comment)
wrapChildNodesInClosure(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\AbstractNode $node)
convertNumericNode(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\NumericNode $node)
convertObjectAccessorNode(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\ObjectAccessorNode $node)
convert(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\AbstractNode $node)
store($identifier, \TYPO3\CMS\Fluid\Core\Parser\ParsingState $parsingState)
convertViewHelperNode(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\ViewHelperNode $node)
convertTextNode(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\TextNode $node)
setTemplateCache(\TYPO3\CMS\Core\Cache\Frontend\PhpFrontend $templateCache)
convertListOfSubNodes(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\AbstractNode $node)