TYPO3 CMS  TYPO3_6-2
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 = array();
32 
37  public function setTemplateCache(\TYPO3\CMS\Core\Cache\Frontend\PhpFrontend $templateCache) {
38  $this->templateCache = $templateCache;
39  }
40 
45  public function has($identifier) {
46  $identifier = $this->sanitizeIdentifier($identifier);
47  return $this->templateCache->has($identifier);
48  }
49 
54  public function get($identifier) {
55  $identifier = $this->sanitizeIdentifier($identifier);
56  if (!isset($this->syntaxTreeInstanceCache[$identifier])) {
57  $this->templateCache->requireOnce($identifier);
58  $templateClassName = 'FluidCache_' . $identifier;
59  $this->syntaxTreeInstanceCache[$identifier] = new $templateClassName();
60  }
61  return $this->syntaxTreeInstanceCache[$identifier];
62  }
63 
69  public function store($identifier, \TYPO3\CMS\Fluid\Core\Parser\ParsingState $parsingState) {
70  $identifier = $this->sanitizeIdentifier($identifier);
71  $this->variableCounter = 0;
72  $generatedRenderFunctions = '';
73 
74  if ($parsingState->getVariableContainer()->exists('sections')) {
75  $sections = $parsingState->getVariableContainer()->get('sections');
76  // TODO: refactor to $parsedTemplate->getSections()
77  foreach ($sections as $sectionName => $sectionRootNode) {
78  $generatedRenderFunctions .= $this->generateCodeForSection($this->convertListOfSubNodes($sectionRootNode), 'section_' . sha1($sectionName), 'section ' . $sectionName);
79  }
80  }
81  $generatedRenderFunctions .= $this->generateCodeForSection($this->convertListOfSubNodes($parsingState->getRootNode()), 'render', 'Main Render function');
82  $convertedLayoutNameNode = $parsingState->hasLayout() ? $this->convert($parsingState->getLayoutNameNode()) : array('initialization' => '', 'execution' => 'NULL');
83 
84  $classDefinition = 'class FluidCache_' . $identifier . ' extends \\TYPO3\\CMS\\Fluid\\Core\\Compiler\\AbstractCompiledTemplate';
85 
86  $templateCode = <<<EOD
87 %s {
88 
89 public function getVariableContainer() {
90  // TODO
91  return new \TYPO3\CMS\Fluid\Core\ViewHelper\TemplateVariableContainer();
92 }
93 public function getLayoutName(\TYPO3\CMS\Fluid\Core\Rendering\RenderingContextInterface \$renderingContext) {
94 %s
95 return %s;
96 }
97 public function hasLayout() {
98 return %s;
99 }
100 
101 %s
102 
103 }
104 EOD;
105  $templateCode = sprintf($templateCode,
106  $classDefinition,
107  $convertedLayoutNameNode['initialization'],
108  $convertedLayoutNameNode['execution'],
109  ($parsingState->hasLayout() ? 'TRUE' : 'FALSE'),
110  $generatedRenderFunctions);
111  $this->templateCache->set($identifier, $templateCode);
112  }
113 
121  protected function sanitizeIdentifier($identifier) {
122  return preg_replace('([^a-zA-Z0-9_\\x7f-\\xff])', '_', $identifier);
123  }
124 
131  protected function generateCodeForSection(array $converted, $expectedFunctionName, $comment) {
132  $templateCode = <<<EOD
136 public function %s(\TYPO3\CMS\Fluid\Core\Rendering\RenderingContextInterface \$renderingContext) {
137 \$self = \$this;
138 %s
139 return %s;
140 }
141 
142 EOD;
143  return sprintf($templateCode, $comment, $expectedFunctionName, $converted['initialization'], $converted['execution']);
144  }
145 
155  protected function convert(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\AbstractNode $node) {
156  if ($node instanceof \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\TextNode) {
157  return $this->convertTextNode($node);
158  } elseif ($node instanceof \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\NumericNode) {
159  return $this->convertNumericNode($node);
160  } elseif ($node instanceof \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\ViewHelperNode) {
161  return $this->convertViewHelperNode($node);
162  } elseif ($node instanceof \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\ObjectAccessorNode) {
163  return $this->convertObjectAccessorNode($node);
164  } elseif ($node instanceof \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\ArrayNode) {
165  return $this->convertArrayNode($node);
166  } elseif ($node instanceof \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\RootNode) {
167  return $this->convertListOfSubNodes($node);
168  } elseif ($node instanceof \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\BooleanNode) {
169  return $this->convertBooleanNode($node);
170  } else {
171  throw new \TYPO3\CMS\Fluid\Exception('Syntax tree node type "' . get_class($node) . '" is not supported.');
172  }
173  }
174 
180  protected function convertTextNode(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\TextNode $node) {
181  return array(
182  'initialization' => '',
183  'execution' => '\'' . $this->escapeTextForUseInSingleQuotes($node->getText()) . '\''
184  );
185  }
186 
192  protected function convertNumericNode(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\NumericNode $node) {
193  return array(
194  'initialization' => '',
195  'execution' => $node->getValue()
196  );
197  }
198 
207  protected function convertViewHelperNode(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\ViewHelperNode $node) {
208  $initializationPhpCode = '// Rendering ViewHelper ' . $node->getViewHelperClassName() . chr(10);
209 
210  // Build up $arguments array
211  $argumentsVariableName = $this->variableName('arguments');
212  $initializationPhpCode .= sprintf('%s = array();', $argumentsVariableName) . chr(10);
213 
214  $alreadyBuiltArguments = array();
215  foreach ($node->getArguments() as $argumentName => $argumentValue) {
216  $converted = $this->convert($argumentValue);
217  $initializationPhpCode .= $converted['initialization'];
218  $initializationPhpCode .= sprintf('%s[\'%s\'] = %s;', $argumentsVariableName, $argumentName, $converted['execution']) . chr(10);
219  $alreadyBuiltArguments[$argumentName] = TRUE;
220  }
221 
222  foreach ($node->getUninitializedViewHelper()->prepareArguments() as $argumentName => $argumentDefinition) {
223  if (!isset($alreadyBuiltArguments[$argumentName])) {
224  $initializationPhpCode .= sprintf('%s[\'%s\'] = %s;', $argumentsVariableName, $argumentName, var_export($argumentDefinition->getDefaultValue(), TRUE)) . chr(10);
225  }
226  }
227 
228  // Build up closure which renders the child nodes
229  $renderChildrenClosureVariableName = $this->variableName('renderChildrenClosure');
230  $initializationPhpCode .= sprintf('%s = %s;', $renderChildrenClosureVariableName, $this->wrapChildNodesInClosure($node)) . chr(10);
231 
232  if ($node->getUninitializedViewHelper() instanceof \TYPO3\CMS\Fluid\Core\ViewHelper\Facets\CompilableInterface) {
233  // ViewHelper is compilable
234  $viewHelperInitializationPhpCode = '';
235  $convertedViewHelperExecutionCode = $node->getUninitializedViewHelper()->compile($argumentsVariableName, $renderChildrenClosureVariableName, $viewHelperInitializationPhpCode, $node, $this);
236  $initializationPhpCode .= $viewHelperInitializationPhpCode;
237  if ($convertedViewHelperExecutionCode !== self::SHOULD_GENERATE_VIEWHELPER_INVOCATION) {
238  return array(
239  'initialization' => $initializationPhpCode,
240  'execution' => $convertedViewHelperExecutionCode
241  );
242  }
243  }
244 
245  // ViewHelper is not compilable, so we need to instanciate it directly and render it.
246  $viewHelperVariableName = $this->variableName('viewHelper');
247 
248  $initializationPhpCode .= sprintf('%s = $self->getViewHelper(\'%s\', $renderingContext, \'%s\');', $viewHelperVariableName, $viewHelperVariableName, $node->getViewHelperClassName()) . chr(10);
249  $initializationPhpCode .= sprintf('%s->setArguments(%s);', $viewHelperVariableName, $argumentsVariableName) . chr(10);
250  $initializationPhpCode .= sprintf('%s->setRenderingContext($renderingContext);', $viewHelperVariableName) . chr(10);
251 
252  $initializationPhpCode .= sprintf('%s->setRenderChildrenClosure(%s);', $viewHelperVariableName, $renderChildrenClosureVariableName) . chr(10);
253 
254  $initializationPhpCode .= '// End of ViewHelper ' . $node->getViewHelperClassName() . chr(10);
255 
256  return array(
257  'initialization' => $initializationPhpCode,
258  'execution' => sprintf('%s->initializeArgumentsAndRender()', $viewHelperVariableName)
259  );
260  }
261 
267  protected function convertObjectAccessorNode(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\ObjectAccessorNode $node) {
268  return array(
269  'initialization' => '',
270  'execution' => sprintf('TYPO3\\CMS\\Fluid\\Core\\Parser\\SyntaxTree\\ObjectAccessorNode::getPropertyPath($renderingContext->getTemplateVariableContainer(), \'%s\', $renderingContext)', $node->getObjectPath())
271  );
272  }
273 
279  protected function convertArrayNode(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\ArrayNode $node) {
280  $initializationPhpCode = '// Rendering Array' . chr(10);
281  $arrayVariableName = $this->variableName('array');
282 
283  $initializationPhpCode .= sprintf('%s = array();', $arrayVariableName) . chr(10);
284 
285  foreach ($node->getInternalArray() as $key => $value) {
286  if ($value instanceof \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\AbstractNode) {
287  $converted = $this->convert($value);
288  $initializationPhpCode .= $converted['initialization'];
289  $initializationPhpCode .= sprintf('%s[\'%s\'] = %s;', $arrayVariableName, $key, $converted['execution']) . chr(10);
290  } elseif (is_numeric($value)) {
291  // this case might happen for simple values
292  $initializationPhpCode .= sprintf('%s[\'%s\'] = %s;', $arrayVariableName, $key, $value) . chr(10);
293  } else {
294  // this case might happen for simple values
295  $initializationPhpCode .= sprintf('%s[\'%s\'] = \'%s\';', $arrayVariableName, $key, $this->escapeTextForUseInSingleQuotes($value)) . chr(10);
296  }
297  }
298  return array(
299  'initialization' => $initializationPhpCode,
300  'execution' => $arrayVariableName
301  );
302  }
303 
309  public function convertListOfSubNodes(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\AbstractNode $node) {
310  switch (count($node->getChildNodes())) {
311  case 0:
312  return array(
313  'initialization' => '',
314  'execution' => 'NULL'
315  );
316  case 1:
317  $converted = $this->convert(current($node->getChildNodes()));
318 
319  return $converted;
320  default:
321  $outputVariableName = $this->variableName('output');
322  $initializationPhpCode = sprintf('%s = \'\';', $outputVariableName) . chr(10);
323 
324  foreach ($node->getChildNodes() as $childNode) {
325  $converted = $this->convert($childNode);
326 
327  $initializationPhpCode .= $converted['initialization'] . chr(10);
328  $initializationPhpCode .= sprintf('%s .= %s;', $outputVariableName, $converted['execution']) . chr(10);
329  }
330 
331  return array(
332  'initialization' => $initializationPhpCode,
333  'execution' => $outputVariableName
334  );
335  }
336  }
337 
343  protected function convertBooleanNode(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\BooleanNode $node) {
344  $initializationPhpCode = '// Rendering Boolean node' . chr(10);
345  if ($node->getComparator() !== NULL) {
346  $convertedLeftSide = $this->convert($node->getLeftSide());
347  $convertedRightSide = $this->convert($node->getRightSide());
348 
349  return array(
350  'initialization' => $initializationPhpCode . $convertedLeftSide['initialization'] . $convertedRightSide['initialization'],
351  'execution' => sprintf('TYPO3\\CMS\\Fluid\\Core\\Parser\\SyntaxTree\\BooleanNode::evaluateComparator(\'%s\', %s, %s)', $node->getComparator(), $convertedLeftSide['execution'], $convertedRightSide['execution'])
352  );
353  } else {
354  // simple case, no comparator.
355  $converted = $this->convert($node->getSyntaxTreeNode());
356  return array(
357  'initialization' => $initializationPhpCode . $converted['initialization'],
358  'execution' => sprintf('TYPO3\\CMS\\Fluid\\Core\\Parser\\SyntaxTree\\BooleanNode::convertToBoolean(%s)', $converted['execution'])
359  );
360  }
361  }
362 
367  protected function escapeTextForUseInSingleQuotes($text) {
368  return str_replace(array('\\', '\''), array('\\\\', '\\\''), $text);
369  }
370 
375  public function wrapChildNodesInClosure(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\AbstractNode $node) {
376  $closure = '';
377  $closure .= 'function() use ($renderingContext, $self) {' . chr(10);
378  $convertedSubNodes = $this->convertListOfSubNodes($node);
379  $closure .= $convertedSubNodes['initialization'];
380  $closure .= sprintf('return %s;', $convertedSubNodes['execution']) . chr(10);
381  $closure .= '}';
382  return $closure;
383  }
384 
391  public function variableName($prefix) {
392  return '$' . $prefix . $this->variableCounter++;
393  }
394 }
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)