TYPO3 CMS  TYPO3_6-2
TemplateParser.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 
18 
19  static public $SCAN_PATTERN_NAMESPACEDECLARATION = '/(?<!\\\\){namespace\\s*(?P<identifier>[a-zA-Z]+[a-zA-Z0-9]*)\\s*=\\s*(?P<phpNamespace>(?:[A-Za-z0-9\.]+|Tx)(?:LEGACY_NAMESPACE_SEPARATOR\\w+|FLUID_NAMESPACE_SEPARATOR\\w+)+)\\s*}/m';
20  static public $SCAN_PATTERN_XMLNSDECLARATION = '/\sxmlns:(?P<identifier>.*?)="(?P<xmlNamespace>.*?)"/m';
21 
29 
35  (
36  (?: <\\/? # Start dynamic tags
37  (?:(?:NAMESPACE):[a-zA-Z0-9\\.]+) # A tag consists of the namespace prefix and word characters
38  (?: # Begin tag arguments
39  \\s*[a-zA-Z0-9:-]+ # Argument Keys
40  = # =
41  (?> # either... If we have found an argument, we will not back-track (That does the Atomic Bracket)
42  "(?:\\\\"|[^"])*" # a double-quoted string
43  |\'(?:\\\\\'|[^\'])*\' # or a single quoted string
44  )\\s* #
45  )* # Tag arguments can be replaced many times.
46  \\s*
47  \\/?> # Closing tag
48  )
49  |(?: # Start match CDATA section
50  <!\\[CDATA\\[.*?\\]\\]>
51  )
52  )/xs';
53 
58  ^< # A Tag begins with <
59  (?P<NamespaceIdentifier>NAMESPACE): # Then comes the Namespace prefix followed by a :
60  (?P<MethodIdentifier> # Now comes the Name of the ViewHelper
61  [a-zA-Z0-9\\.]+
62  )
63  (?P<Attributes> # Begin Tag Attributes
64  (?: # A tag might have multiple attributes
65  \\s*
66  [a-zA-Z0-9:-]+ # The attribute name
67  = # =
68  (?> # either... # If we have found an argument, we will not back-track (That does the Atomic Bracket)
69  "(?:\\\\"|[^"])*" # a double-quoted string
70  |\'(?:\\\\\'|[^\'])*\' # or a single quoted string
71  ) #
72  \\s*
73  )* # A tag might have multiple attributes
74  ) # End Tag Attributes
75  \\s*
76  (?P<Selfclosing>\\/?) # A tag might be selfclosing
77  >$/x';
78 
83  static public $SCAN_PATTERN_TEMPLATE_CLOSINGVIEWHELPERTAG = '/^<\\/(?P<NamespaceIdentifier>NAMESPACE):(?P<MethodIdentifier>[a-zA-Z0-9\\.]+)\\s*>$/';
84 
88  static public $SPLIT_PATTERN_TAGARGUMENTS = '/
89  (?: #
90  \\s* #
91  (?P<Argument> # The attribute name
92  [a-zA-Z0-9:-]+ #
93  ) #
94  = # =
95  (?> # If we have found an argument, we will not back-track (That does the Atomic Bracket)
96  (?P<ValueQuoted> # either...
97  (?:"(?:\\\\"|[^"])*") # a double-quoted string
98  |(?:\'(?:\\\\\'|[^\'])*\') # or a single quoted string
99  )
100  )\\s*
101  )
102  /xs';
103 
108  static public $SCAN_PATTERN_CDATA = '/^<!\\[CDATA\\[(.*?)\\]\\]>$/s';
109 
115  (
116  { # Start of shorthand syntax
117  (?: # Shorthand syntax is either composed of...
118  [a-zA-Z0-9\\->_:,.()] # Various characters
119  |"(?:\\\\"|[^"])*" # Double-quoted strings
120  |\'(?:\\\\\'|[^\'])*\' # Single-quoted strings
121  |(?R) # Other shorthand syntaxes inside, albeit not in a quoted string
122  |\\s+ # Spaces
123  )+
124  } # End of shorthand syntax
125  )/x';
126 
136  ^{ # Start of shorthand syntax
137  # A shorthand syntax is either...
138  (?P<Object>[a-zA-Z0-9\\-_.]*) # ... an object accessor
139  \\s*(?P<Delimiter>(?:->)?)\\s*
140 
141  (?P<ViewHelper> # ... a ViewHelper
142  [a-zA-Z0-9]+ # Namespace prefix of ViewHelper (as in $SCAN_PATTERN_TEMPLATE_VIEWHELPERTAG)
143  :
144  [a-zA-Z0-9\\.]+ # Method Identifier (as in $SCAN_PATTERN_TEMPLATE_VIEWHELPERTAG)
145  \\( # Opening parameter brackets of ViewHelper
146  (?P<ViewHelperArguments> # Start submatch for ViewHelper arguments. This is taken from $SCAN_PATTERN_SHORTHANDSYNTAX_ARRAYS
147  (?:
148  \\s*[a-zA-Z0-9\\-_]+ # The keys of the array
149  \\s*:\\s* # Key|Value delimiter :
150  (?: # Possible value options:
151  "(?:\\\\"|[^"])*" # Double qouoted string
152  |\'(?:\\\\\'|[^\'])*\' # Single quoted string
153  |[a-zA-Z0-9\\-_.]+ # variable identifiers
154  |{(?P>ViewHelperArguments)} # Another sub-array
155  ) # END possible value options
156  \\s*,? # There might be a , to separate different parts of the array
157  )* # The above cycle is repeated for all array elements
158  ) # End ViewHelper Arguments submatch
159  \\) # Closing parameter brackets of ViewHelper
160  )?
161  (?P<AdditionalViewHelpers> # There can be more than one ViewHelper chained, by adding more -> and the ViewHelper (recursively)
162  (?:
163  \\s*->\\s*
164  (?P>ViewHelper)
165  )*
166  )
167  }$/x';
168 
173 
174  (?P<NamespaceIdentifier>[a-zA-Z0-9]+) # Namespace prefix of ViewHelper (as in $SCAN_PATTERN_TEMPLATE_VIEWHELPERTAG)
175  :
176  (?P<MethodIdentifier>[a-zA-Z0-9\\.]+)
177  \\( # Opening parameter brackets of ViewHelper
178  (?P<ViewHelperArguments> # Start submatch for ViewHelper arguments. This is taken from $SCAN_PATTERN_SHORTHANDSYNTAX_ARRAYS
179  (?:
180  \\s*[a-zA-Z0-9\\-_]+ # The keys of the array
181  \\s*:\\s* # Key|Value delimiter :
182  (?: # Possible value options:
183  "(?:\\\\"|[^"])*" # Double qouoted string
184  |\'(?:\\\\\'|[^\'])*\' # Single quoted string
185  |[a-zA-Z0-9\\-_.]+ # variable identifiers
186  |{(?P>ViewHelperArguments)} # Another sub-array
187  ) # END possible value options
188  \\s*,? # There might be a , to separate different parts of the array
189  )* # The above cycle is repeated for all array elements
190  ) # End ViewHelper Arguments submatch
191  \\) # Closing parameter brackets of ViewHelper
192  /x';
193 
202  (?P<Recursion> # Start the recursive part of the regular expression - describing the array syntax
203  { # Each array needs to start with {
204  (?P<Array> # Start submatch
205  (?:
206  \\s*[a-zA-Z0-9\\-_]+ # The keys of the array
207  \\s*:\\s* # Key|Value delimiter :
208  (?: # Possible value options:
209  "(?:\\\\"|[^"])*" # Double qouoted string
210  |\'(?:\\\\\'|[^\'])*\' # Single quoted string
211  |[a-zA-Z0-9\\-_.]+ # variable identifiers
212  |(?P>Recursion) # Another sub-array
213  ) # END possible value options
214  \\s*,? # There might be a , to separate different parts of the array
215  )* # The above cycle is repeated for all array elements
216  ) # End array submatch
217  } # Each array ends with }
218  )$/x';
219 
225  (?P<ArrayPart> # Start submatch
226  (?P<Key>[a-zA-Z0-9\\-_]+) # The keys of the array
227  \\s*:\\s* # Key|Value delimiter :
228  (?: # Possible value options:
229  (?P<QuotedString> # Quoted string
230  (?:"(?:\\\\"|[^"])*")
231  |(?:\'(?:\\\\\'|[^\'])*\')
232  )
233  |(?P<VariableIdentifier>[a-zA-Z][a-zA-Z0-9\\-_.]*) # variable identifiers have to start with a letter
234  |(?P<Number>[0-9.]+) # Number
235  |{\\s*(?P<Subarray>(?:(?P>ArrayPart)\\s*,?\\s*)+)\\s*} # Another sub-array
236  ) # END possible value options
237  ) # End array part submatch
238  /x';
239 
244  static public $SCAN_PATTERN_DEFAULT_XML_NAMESPACE = '/^http\:\/\/typo3\.org\/ns\/(?P<PhpNamespace>.+)$/s';
245 
250  protected $namespaces = array(
251  'f' => 'TYPO3\\CMS\\Fluid\\ViewHelpers'
252  );
253 
258  protected $objectManager;
259 
263  protected $configuration;
264 
268  protected $settings;
269 
274 
279  public function __construct() {
280  self::$SCAN_PATTERN_NAMESPACEDECLARATION = str_replace(
281  array(
282  'LEGACY_NAMESPACE_SEPARATOR',
283  'FLUID_NAMESPACE_SEPARATOR'
284  ),
285  array(
287  preg_quote(\TYPO3\CMS\Fluid\Fluid::NAMESPACE_SEPARATOR)
288  ),
289  self::$SCAN_PATTERN_NAMESPACEDECLARATION
290  );
291  }
292 
298  public function injectSettings(array $settings) {
299  $this->settings = $settings;
300  }
301 
308  public function setConfiguration(\TYPO3\CMS\Fluid\Core\Parser\Configuration $configuration = NULL) {
309  $this->configuration = $configuration;
310  }
311 
324  public function parse($templateString) {
325  if (!is_string($templateString)) {
326  throw new \TYPO3\CMS\Fluid\Core\Parser\Exception('Parse requires a template string as argument, ' . gettype($templateString) . ' given.', 1224237899);
327  }
328  $this->reset();
329 
330  $templateString = $this->extractNamespaceDefinitions($templateString);
331  $splitTemplate = $this->splitTemplateAtDynamicTags($templateString);
332 
333  $parsingState = $this->buildObjectTree($splitTemplate, self::CONTEXT_OUTSIDE_VIEWHELPER_ARGUMENTS);
334 
335  $variableContainer = $parsingState->getVariableContainer();
336  if ($variableContainer !== NULL && $variableContainer->exists('layoutName')) {
337  $parsingState->setLayoutNameNode($variableContainer->get('layoutName'));
338  }
339 
340  return $parsingState;
341  }
342 
348  public function getNamespaces() {
349  return $this->namespaces;
350  }
351 
357  protected function reset() {
358  $this->namespaces = array(
359  'f' => 'TYPO3\\CMS\\Fluid\\ViewHelpers'
360  );
361  }
362 
371  protected function extractNamespaceDefinitions($templateString) {
372  $matches = array();
373  preg_match_all(self::$SCAN_PATTERN_XMLNSDECLARATION, $templateString, $matches, PREG_SET_ORDER);
374  foreach ($matches as $match) {
375  // skip reserved "f" namespace identifier
376  if ($match['identifier'] === 'f') {
377  continue;
378  }
379  if (array_key_exists($match['identifier'], $this->namespaces)) {
380  throw new \TYPO3\CMS\Fluid\Core\Parser\Exception(sprintf('Namespace identifier "%s" is already registered. Do not re-declare namespaces!', $match['identifier']), 1331135889);
381  }
382  if (isset($this->settings['namespaces'][$match['xmlNamespace']])) {
383  $phpNamespace = $this->settings['namespaces'][$match['xmlNamespace']];
384  } else {
385  $matchedPhpNamespace = array();
386  if (preg_match(self::$SCAN_PATTERN_DEFAULT_XML_NAMESPACE, $match['xmlNamespace'], $matchedPhpNamespace) === 0) {
387  continue;
388  }
389  $phpNamespace = str_replace('/', '\\', $matchedPhpNamespace['PhpNamespace']);
390  }
391  $this->namespaces[$match['identifier']] = $phpNamespace;
392  }
393  $matches = array();
394  preg_match_all(self::$SCAN_PATTERN_NAMESPACEDECLARATION, $templateString, $matches, PREG_SET_ORDER);
395  foreach ($matches as $match) {
396  if (array_key_exists($match['identifier'], $this->namespaces)) {
397  throw new \TYPO3\CMS\Fluid\Core\Parser\Exception(sprintf('Namespace identifier "%s" is already registered. Do not re-declare namespaces!', $match['identifier']), 1224241246);
398  }
399  $this->namespaces[$match['identifier']] = $match['phpNamespace'];
400  }
401  if ($matches !== array()) {
402  $templateString = preg_replace(self::$SCAN_PATTERN_NAMESPACEDECLARATION, '', $templateString);
403  }
404 
405  return $templateString;
406  }
407 
414  protected function splitTemplateAtDynamicTags($templateString) {
415  $regularExpression = $this->prepareTemplateRegularExpression(self::$SPLIT_PATTERN_TEMPLATE_DYNAMICTAGS);
416  return preg_split($regularExpression, $templateString, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
417  }
418 
427  protected function buildObjectTree($splitTemplate, $context) {
428  $regularExpression_openingViewHelperTag = $this->prepareTemplateRegularExpression(self::$SCAN_PATTERN_TEMPLATE_VIEWHELPERTAG);
429  $regularExpression_closingViewHelperTag = $this->prepareTemplateRegularExpression(self::$SCAN_PATTERN_TEMPLATE_CLOSINGVIEWHELPERTAG);
430 
431  $state = $this->objectManager->get('TYPO3\\CMS\\Fluid\\Core\\Parser\\ParsingState');
432  $rootNode = $this->objectManager->get('TYPO3\\CMS\\Fluid\\Core\\Parser\\SyntaxTree\\RootNode');
433  $state->setRootNode($rootNode);
434  $state->pushNodeToStack($rootNode);
435 
436  foreach ($splitTemplate as $templateElement) {
437  $matchedVariables = array();
438  if (preg_match(self::$SCAN_PATTERN_CDATA, $templateElement, $matchedVariables) > 0) {
439  $this->textHandler($state, $matchedVariables[1]);
440  } elseif (preg_match($regularExpression_openingViewHelperTag, $templateElement, $matchedVariables) > 0) {
441  $this->openingViewHelperTagHandler($state, $matchedVariables['NamespaceIdentifier'], $matchedVariables['MethodIdentifier'], $matchedVariables['Attributes'], ($matchedVariables['Selfclosing'] === '' ? FALSE : TRUE));
442  } elseif (preg_match($regularExpression_closingViewHelperTag, $templateElement, $matchedVariables) > 0) {
443  $this->closingViewHelperTagHandler($state, $matchedVariables['NamespaceIdentifier'], $matchedVariables['MethodIdentifier']);
444  } else {
445  $this->textAndShorthandSyntaxHandler($state, $templateElement, $context);
446  }
447  }
448 
449  if ($state->countNodeStack() !== 1) {
450  throw new \TYPO3\CMS\Fluid\Core\Parser\Exception('Not all tags were closed!', 1238169398);
451  }
452  return $state;
453  }
454 
465  protected function openingViewHelperTagHandler(\TYPO3\CMS\Fluid\Core\Parser\ParsingState $state, $namespaceIdentifier, $methodIdentifier, $arguments, $selfclosing) {
466  $argumentsObjectTree = $this->parseArguments($arguments);
467  $this->initializeViewHelperAndAddItToStack($state, $namespaceIdentifier, $methodIdentifier, $argumentsObjectTree);
468 
469  if ($selfclosing) {
470  $node = $state->popNodeFromStack();
471  $this->callInterceptor($node, \TYPO3\CMS\Fluid\Core\Parser\InterceptorInterface::INTERCEPT_CLOSING_VIEWHELPER, $state);
472  }
473  }
474 
486  protected function initializeViewHelperAndAddItToStack(\TYPO3\CMS\Fluid\Core\Parser\ParsingState $state, $namespaceIdentifier, $methodIdentifier, $argumentsObjectTree) {
487  if (!array_key_exists($namespaceIdentifier, $this->namespaces)) {
488  throw new \TYPO3\CMS\Fluid\Core\Parser\Exception('Namespace could not be resolved. This exception should never be thrown!', 1224254792);
489  }
490  $viewHelper = $this->objectManager->get($this->resolveViewHelperName($namespaceIdentifier, $methodIdentifier));
491  $this->viewHelperNameToImplementationClassNameRuntimeCache[$namespaceIdentifier][$methodIdentifier] = get_class($viewHelper);
492 
493  // The following three checks are only done *in an uncached template*, and not needed anymore in the cached version
494  $expectedViewHelperArguments = $viewHelper->prepareArguments();
495  $this->abortIfUnregisteredArgumentsExist($expectedViewHelperArguments, $argumentsObjectTree);
496  $this->abortIfRequiredArgumentsAreMissing($expectedViewHelperArguments, $argumentsObjectTree);
497  $this->rewriteBooleanNodesInArgumentsObjectTree($expectedViewHelperArguments, $argumentsObjectTree);
498 
499  $currentViewHelperNode = $this->objectManager->get('TYPO3\\CMS\\Fluid\\Core\\Parser\\SyntaxTree\\ViewHelperNode', $viewHelper, $argumentsObjectTree);
500 
501  $state->getNodeFromStack()->addChildNode($currentViewHelperNode);
502 
503  if ($viewHelper instanceof \TYPO3\CMS\Fluid\Core\ViewHelper\Facets\ChildNodeAccessInterface && !($viewHelper instanceof \TYPO3\CMS\Fluid\Core\ViewHelper\Facets\CompilableInterface)) {
504  $state->setCompilable(FALSE);
505  }
506 
507  // PostParse Facet
508  if ($viewHelper instanceof \TYPO3\CMS\Fluid\Core\ViewHelper\Facets\PostParseInterface) {
509  // Don't just use $viewHelper::postParseEvent(...),
510  // as this will break with PHP < 5.3.
511  call_user_func(array($viewHelper, 'postParseEvent'), $currentViewHelperNode, $argumentsObjectTree, $state->getVariableContainer());
512  }
513 
514  $this->callInterceptor($currentViewHelperNode, \TYPO3\CMS\Fluid\Core\Parser\InterceptorInterface::INTERCEPT_OPENING_VIEWHELPER, $state);
515 
516  $state->pushNodeToStack($currentViewHelperNode);
517  }
518 
527  protected function abortIfUnregisteredArgumentsExist($expectedArguments, $actualArguments) {
528  $expectedArgumentNames = array();
529  foreach ($expectedArguments as $expectedArgument) {
530  $expectedArgumentNames[] = $expectedArgument->getName();
531  }
532 
533  foreach ($actualArguments as $argumentName => $_) {
534  if (!in_array($argumentName, $expectedArgumentNames)) {
535  throw new \TYPO3\CMS\Fluid\Core\Parser\Exception('Argument "' . $argumentName . '" was not registered.', 1237823695);
536  }
537  }
538  }
539 
547  protected function abortIfRequiredArgumentsAreMissing($expectedArguments, $actualArguments) {
548  $actualArgumentNames = array_keys($actualArguments);
549  foreach ($expectedArguments as $expectedArgument) {
550  if ($expectedArgument->isRequired() && !in_array($expectedArgument->getName(), $actualArgumentNames)) {
551  throw new \TYPO3\CMS\Fluid\Core\Parser\Exception('Required argument "' . $expectedArgument->getName() . '" was not supplied.', 1237823699);
552  }
553  }
554  }
555 
563  protected function rewriteBooleanNodesInArgumentsObjectTree($argumentDefinitions, &$argumentsObjectTree) {
564  foreach ($argumentDefinitions as $argumentName => $argumentDefinition) {
565  if ($argumentDefinition->getType() === 'boolean' && isset($argumentsObjectTree[$argumentName])) {
566  $argumentsObjectTree[$argumentName] = new \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\BooleanNode($argumentsObjectTree[$argumentName]);
567  }
568  }
569  }
570 
578  protected function resolveViewHelperName($namespaceIdentifier, $methodIdentifier) {
579  if (isset($this->viewHelperNameToImplementationClassNameRuntimeCache[$namespaceIdentifier][$methodIdentifier])) {
580  $name = $this->viewHelperNameToImplementationClassNameRuntimeCache[$namespaceIdentifier][$methodIdentifier];
581  } else {
582  $explodedViewHelperName = explode('.', $methodIdentifier);
583  $namespaceSeparator = strpos($this->namespaces[$namespaceIdentifier], \TYPO3\CMS\Fluid\Fluid::NAMESPACE_SEPARATOR) !== FALSE ? \TYPO3\CMS\Fluid\Fluid::NAMESPACE_SEPARATOR : \TYPO3\CMS\Fluid\Fluid::LEGACY_NAMESPACE_SEPARATOR;
584  if (count($explodedViewHelperName) > 1) {
585  $className = implode($namespaceSeparator, array_map('ucfirst', $explodedViewHelperName));
586  } else {
587  $className = ucfirst($explodedViewHelperName[0]);
588  }
589  $className .= 'ViewHelper';
590  $name = $this->namespaces[$namespaceIdentifier] . $namespaceSeparator . $className;
592  // The name isn't cached in viewHelperNameToImplementationClassNameRuntimeCache here because the
593  // class could be overloaded by extbase object manager. Thus the cache is filled in
594  // initializeViewHelperAndAddItToStack after getting the real object from the object manager.
595  }
596  return $name;
597  }
598 
608  protected function closingViewHelperTagHandler(\TYPO3\CMS\Fluid\Core\Parser\ParsingState $state, $namespaceIdentifier, $methodIdentifier) {
609  if (!array_key_exists($namespaceIdentifier, $this->namespaces)) {
610  throw new \TYPO3\CMS\Fluid\Core\Parser\Exception('Namespace could not be resolved. This exception should never be thrown!', 1224256186);
611  }
612  $lastStackElement = $state->popNodeFromStack();
613  if (!($lastStackElement instanceof \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\ViewHelperNode)) {
614  throw new \TYPO3\CMS\Fluid\Core\Parser\Exception('You closed a templating tag which you never opened!', 1224485838);
615  }
616  if ($lastStackElement->getViewHelperClassName() != $this->resolveViewHelperName($namespaceIdentifier, $methodIdentifier)) {
617  throw new \TYPO3\CMS\Fluid\Core\Parser\Exception('Templating tags not properly nested. Expected: ' . $lastStackElement->getViewHelperClassName() . '; Actual: ' . $this->resolveViewHelperName($namespaceIdentifier, $methodIdentifier), 1224485398);
618  }
619  $this->callInterceptor($lastStackElement, \TYPO3\CMS\Fluid\Core\Parser\InterceptorInterface::INTERCEPT_CLOSING_VIEWHELPER, $state);
620  }
621 
635  protected function objectAccessorHandler(\TYPO3\CMS\Fluid\Core\Parser\ParsingState $state, $objectAccessorString, $delimiter, $viewHelperString, $additionalViewHelpersString) {
636  $viewHelperString .= $additionalViewHelpersString;
637  $numberOfViewHelpers = 0;
638 
639  // The following post-processing handles a case when there is only a ViewHelper, and no Object Accessor.
640  // Resolves bug #5107.
641  if (strlen($delimiter) === 0 && strlen($viewHelperString) > 0) {
642  $viewHelperString = $objectAccessorString . $viewHelperString;
643  $objectAccessorString = '';
644  }
645 
646  // ViewHelpers
647  $matches = array();
648  if (strlen($viewHelperString) > 0 && preg_match_all(self::$SPLIT_PATTERN_SHORTHANDSYNTAX_VIEWHELPER, $viewHelperString, $matches, PREG_SET_ORDER) > 0) {
649  // The last ViewHelper has to be added first for correct chaining.
650  foreach (array_reverse($matches) as $singleMatch) {
651  if (strlen($singleMatch['ViewHelperArguments']) > 0) {
652  $arguments = $this->postProcessArgumentsForObjectAccessor(
653  $this->recursiveArrayHandler($singleMatch['ViewHelperArguments'])
654  );
655  } else {
656  $arguments = array();
657  }
658  $this->initializeViewHelperAndAddItToStack($state, $singleMatch['NamespaceIdentifier'], $singleMatch['MethodIdentifier'], $arguments);
659  $numberOfViewHelpers++;
660  }
661  }
662 
663  // Object Accessor
664  if (strlen($objectAccessorString) > 0) {
665 
666  $node = $this->objectManager->get('TYPO3\\CMS\\Fluid\\Core\\Parser\\SyntaxTree\\ObjectAccessorNode', $objectAccessorString);
667  $this->callInterceptor($node, \TYPO3\CMS\Fluid\Core\Parser\InterceptorInterface::INTERCEPT_OBJECTACCESSOR, $state);
668 
669  $state->getNodeFromStack()->addChildNode($node);
670  }
671 
672  // Close ViewHelper Tags if needed.
673  for ($i=0; $i<$numberOfViewHelpers; $i++) {
674  $node = $state->popNodeFromStack();
675  $this->callInterceptor($node, \TYPO3\CMS\Fluid\Core\Parser\InterceptorInterface::INTERCEPT_CLOSING_VIEWHELPER, $state);
676  }
677  }
678 
687  protected function callInterceptor(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\NodeInterface &$node, $interceptionPoint, \TYPO3\CMS\Fluid\Core\Parser\ParsingState $state) {
688  if ($this->configuration !== NULL) {
689  // $this->configuration is UNSET inside the arguments of a ViewHelper.
690  // That's why the interceptors are only called if the object accesor is not inside a ViewHelper Argument
691  // This could be a problem if We have a ViewHelper as an argument to another ViewHelper, and an ObjectAccessor nested inside there.
692  // TODO: Clean up this.
693  $interceptors = $this->configuration->getInterceptors($interceptionPoint);
694  if (count($interceptors) > 0) {
695  foreach ($interceptors as $interceptor) {
696  $node = $interceptor->process($node, $interceptionPoint, $state);
697  }
698  }
699  }
700  }
701 
710  protected function postProcessArgumentsForObjectAccessor(array $arguments) {
711  foreach ($arguments as $argumentName => $argumentValue) {
712  if (!($argumentValue instanceof \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\AbstractNode)) {
713  $arguments[$argumentName] = $this->objectManager->get('TYPO3\\CMS\\Fluid\\Core\\Parser\\SyntaxTree\\TextNode', (string) $argumentValue);
714  }
715  }
716  return $arguments;
717  }
718 
728  protected function parseArguments($argumentsString) {
729  $argumentsObjectTree = array();
730  $matches = array();
731  if (preg_match_all(self::$SPLIT_PATTERN_TAGARGUMENTS, $argumentsString, $matches, PREG_SET_ORDER) > 0) {
732  $configurationBackup = $this->configuration;
733  $this->configuration = NULL;
734  foreach ($matches as $singleMatch) {
735  $argument = $singleMatch['Argument'];
736  $value = $this->unquoteString($singleMatch['ValueQuoted']);
737  $argumentsObjectTree[$argument] = $this->buildArgumentObjectTree($value);
738  }
739  $this->configuration = $configurationBackup;
740  }
741  return $argumentsObjectTree;
742  }
743 
754  protected function buildArgumentObjectTree($argumentString) {
755  if (strpos($argumentString, '{') === FALSE && strpos($argumentString, '<') === FALSE) {
756  return $this->objectManager->get('TYPO3\\CMS\\Fluid\\Core\\Parser\\SyntaxTree\\TextNode', $argumentString);
757  }
758  $splitArgument = $this->splitTemplateAtDynamicTags($argumentString);
759  $rootNode = $this->buildObjectTree($splitArgument, self::CONTEXT_INSIDE_VIEWHELPER_ARGUMENTS)->getRootNode();
760  return $rootNode;
761  }
762 
772  protected function unquoteString($quotedValue) {
773  switch ($quotedValue[0]) {
774  case '"':
775  $value = str_replace('\\"', '"', preg_replace('/(^"|"$)/', '', $quotedValue));
776  break;
777  case "'":
778  $value = str_replace("\\'", "'", preg_replace('/(^\'|\'$)/', '', $quotedValue));
779  break;
780  default:
781  $value = $quotedValue;
782  }
783  return str_replace('\\\\', '\\', $value);
784  }
785 
794  protected function prepareTemplateRegularExpression($regularExpression) {
795  return str_replace('NAMESPACE', implode('|', array_keys($this->namespaces)), $regularExpression);
796  }
797 
808  protected function textAndShorthandSyntaxHandler(\TYPO3\CMS\Fluid\Core\Parser\ParsingState $state, $text, $context) {
809  $sections = preg_split($this->prepareTemplateRegularExpression(self::$SPLIT_PATTERN_SHORTHANDSYNTAX), $text, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
810 
811  foreach ($sections as $section) {
812  $matchedVariables = array();
813  if (preg_match(self::$SCAN_PATTERN_SHORTHANDSYNTAX_OBJECTACCESSORS, $section, $matchedVariables) > 0) {
814  $this->objectAccessorHandler($state, $matchedVariables['Object'], $matchedVariables['Delimiter'], isset($matchedVariables['ViewHelper']) ? $matchedVariables['ViewHelper'] : '', isset($matchedVariables['AdditionalViewHelpers']) ? $matchedVariables['AdditionalViewHelpers'] : '');
815  } elseif ($context === self::CONTEXT_INSIDE_VIEWHELPER_ARGUMENTS && preg_match(self::$SCAN_PATTERN_SHORTHANDSYNTAX_ARRAYS, $section, $matchedVariables) > 0) {
816  // We only match arrays if we are INSIDE viewhelper arguments
817  $this->arrayHandler($state, $matchedVariables['Array']);
818  } else {
819  $this->textHandler($state, $section);
820  }
821  }
822  }
823 
832  protected function arrayHandler(\TYPO3\CMS\Fluid\Core\Parser\ParsingState $state, $arrayText) {
833  $state->getNodeFromStack()->addChildNode(
834  $this->objectManager->get('TYPO3\\CMS\\Fluid\\Core\\Parser\\SyntaxTree\\ArrayNode', $this->recursiveArrayHandler($arrayText))
835  );
836  }
837 
852  protected function recursiveArrayHandler($arrayText) {
853  $matches = array();
854  if (preg_match_all(self::$SPLIT_PATTERN_SHORTHANDSYNTAX_ARRAY_PARTS, $arrayText, $matches, PREG_SET_ORDER) > 0) {
855  $arrayToBuild = array();
856  foreach ($matches as $singleMatch) {
857  $arrayKey = $singleMatch['Key'];
858  if (!empty($singleMatch['VariableIdentifier'])) {
859  $arrayToBuild[$arrayKey] = $this->objectManager->get('TYPO3\\CMS\\Fluid\\Core\\Parser\\SyntaxTree\\ObjectAccessorNode', $singleMatch['VariableIdentifier']);
860  } elseif (array_key_exists('Number', $singleMatch) && (!empty($singleMatch['Number']) || $singleMatch['Number'] === '0')) {
861  $arrayToBuild[$arrayKey] = floatval($singleMatch['Number']);
862  } elseif ((array_key_exists('QuotedString', $singleMatch) && !empty($singleMatch['QuotedString']))) {
863  $argumentString = $this->unquoteString($singleMatch['QuotedString']);
864  $arrayToBuild[$arrayKey] = $this->buildArgumentObjectTree($argumentString);
865  } elseif (array_key_exists('Subarray', $singleMatch) && !empty($singleMatch['Subarray'])) {
866  $arrayToBuild[$arrayKey] = $this->objectManager->get('TYPO3\\CMS\\Fluid\\Core\\Parser\\SyntaxTree\\ArrayNode', $this->recursiveArrayHandler($singleMatch['Subarray']));
867  } else {
868  throw new \TYPO3\CMS\Fluid\Core\Parser\Exception('This exception should never be thrown, as the array value has to be of some type (Value given: "' . var_export($singleMatch, TRUE) . '"). Please post your template to the bugtracker at forge.typo3.org.', 1225136013);
869  }
870  }
871  return $arrayToBuild;
872  } else {
873  throw new \TYPO3\CMS\Fluid\Core\Parser\Exception('This exception should never be thrown, there is most likely some error in the regular expressions. Please post your template to the bugtracker at forge.typo3.org.', 1225136014);
874  }
875  }
876 
884  protected function textHandler(\TYPO3\CMS\Fluid\Core\Parser\ParsingState $state, $text) {
885  $node = $this->objectManager->get('TYPO3\\CMS\\Fluid\\Core\\Parser\\SyntaxTree\\TextNode', $text);
886  $this->callInterceptor($node, \TYPO3\CMS\Fluid\Core\Parser\InterceptorInterface::INTERCEPT_TEXT, $state);
887 
888  $state->getNodeFromStack()->addChildNode($node);
889  }
890 }
arrayHandler(\TYPO3\CMS\Fluid\Core\Parser\ParsingState $state, $arrayText)
rewriteBooleanNodesInArgumentsObjectTree($argumentDefinitions, &$argumentsObjectTree)
textAndShorthandSyntaxHandler(\TYPO3\CMS\Fluid\Core\Parser\ParsingState $state, $text, $context)
callInterceptor(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\NodeInterface &$node, $interceptionPoint, \TYPO3\CMS\Fluid\Core\Parser\ParsingState $state)
resolveViewHelperName($namespaceIdentifier, $methodIdentifier)
textHandler(\TYPO3\CMS\Fluid\Core\Parser\ParsingState $state, $text)
abortIfRequiredArgumentsAreMissing($expectedArguments, $actualArguments)
initializeViewHelperAndAddItToStack(\TYPO3\CMS\Fluid\Core\Parser\ParsingState $state, $namespaceIdentifier, $methodIdentifier, $argumentsObjectTree)
const NAMESPACE_SEPARATOR
Definition: Fluid.php:19
openingViewHelperTagHandler(\TYPO3\CMS\Fluid\Core\Parser\ParsingState $state, $namespaceIdentifier, $methodIdentifier, $arguments, $selfclosing)
objectAccessorHandler(\TYPO3\CMS\Fluid\Core\Parser\ParsingState $state, $objectAccessorString, $delimiter, $viewHelperString, $additionalViewHelpersString)
closingViewHelperTagHandler(\TYPO3\CMS\Fluid\Core\Parser\ParsingState $state, $namespaceIdentifier, $methodIdentifier)
abortIfUnregisteredArgumentsExist($expectedArguments, $actualArguments)
const LEGACY_NAMESPACE_SEPARATOR
Definition: Fluid.php:18
setConfiguration(\TYPO3\CMS\Fluid\Core\Parser\Configuration $configuration=NULL)