2 namespace wcf\system\template
;
3 use wcf\system\exception\SystemException
;
4 use wcf\system\template\plugin\ICompilerTemplatePlugin
;
5 use wcf\system\template\plugin\IPrefilterTemplatePlugin
;
6 use wcf\util\StringStack
;
7 use wcf\util\StringUtil
;
10 * Compiles template sources into valid PHP code.
13 * @copyright 2001-2016 WoltLab GmbH
14 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
15 * @package com.woltlab.wcf
16 * @subpackage system.template
17 * @category Community Framework
19 class TemplateScriptingCompiler
{
21 * template engine object
27 * PHP functions that can be used in the modifier syntax and are unknown
28 * to PHP's function_exists function
31 protected $unknownPHPFunctions = ['isset', 'unset', 'empty'];
34 * PHP functions that can not be used in the modifier syntax
37 protected $disabledPHPFunctions = [
38 'system', 'exec', 'passthru', 'shell_exec', // command line execution
39 'include', 'require', 'include_once', 'require_once', // includes
40 'eval', 'virtual', 'call_user_func_array', 'call_user_func', 'assert' // code execution
44 * pattern to match variable operators like -> or .
47 protected $variableOperatorPattern;
50 * pattern to match condition operators like == or <
53 protected $conditionOperatorPattern;
56 * negative lookbehind for a backslash
59 protected $escapedPattern;
62 * pattern to match valid variable names
65 protected $validVarnamePattern;
68 * pattern to match constants like CONSTANT or __CONSTANT
71 protected $constantPattern;
74 * pattern to match double quoted strings like "blah" or "quote: \"blah\""
77 protected $doubleQuotePattern;
80 * pattern to match single quoted strings like 'blah' or 'don\'t'
83 protected $singleQuotePattern;
86 * pattern to match single or double quoted strings
89 protected $quotePattern;
92 * pattern to match numbers, true, false and null
95 protected $numericPattern;
98 * pattern to match simple variables like $foo
101 protected $simpleVarPattern;
104 * pattern to match outputs like @$foo or #CONST
107 protected $outputPattern;
110 * identifier of currently compiled template
113 protected $currentIdentifier;
116 * current line number during template compilation
119 protected $currentLineNo;
122 * list of automatically loaded tenplate plugins
125 protected $autoloadPlugins = [];
128 * stack with template tags data
131 protected $tagStack = [];
134 * list of loaded compiler plugin objects
135 * @var ICompilerTemplatePlugin[]
137 protected $compilerPlugins = [];
140 * stack used to compile the capture tag
143 protected $captureStack = [];
146 * left delimiter of template syntax
149 protected $leftDelimiter = '{';
152 * right delimiter of template syntax
155 protected $rightDelimiter = '}';
158 * left delimiter of template syntax used in regular expressions
164 * right delimiter of template syntax used in regular expressions
170 * list of static includes per template
173 protected $staticIncludes = [];
176 * Creates a new TemplateScriptingCompiler object.
178 * @param TemplateEngine $template
180 public function __construct(TemplateEngine
$template) {
181 $this->template
= $template;
183 // quote left and right delimiter for use in regular expressions
184 $this->ldq
= preg_quote($this->leftDelimiter
, '~').'(?=\S)';
185 $this->rdq
= '(?<=\S)'.preg_quote($this->rightDelimiter
, '~');
187 // build regular expressions
188 $this->buildPattern();
192 * Compiles the source of a template.
194 * @param string $identifier
195 * @param string $sourceContent
196 * @param array $metaData
197 * @param boolean $isolated
199 * @throws SystemException
201 public function compileString($identifier, $sourceContent, array $metaData = [], $isolated = false) {
204 'autoloadPlugins' => $this->autoloadPlugins
,
205 'currentIdentifier' => $this->currentIdentifier
,
206 'currentLineNo' => $this->currentLineNo
,
207 'tagStack' => $this->tagStack
211 $this->staticIncludes
= [];
215 $this->autoloadPlugins
= $this->tagStack
= [];
216 $this->currentIdentifier
= $identifier;
217 $this->currentLineNo
= 1;
220 $sourceContent = $this->applyPrefilters($identifier, $sourceContent);
222 // replace all {literal} Tags with unique hash values
223 $sourceContent = $this->replaceLiterals($sourceContent);
226 $sourceContent = $this->replacePHPTags($sourceContent);
229 $sourceContent = $this->removeComments($sourceContent);
231 // match all template tags
233 preg_match_all("~".$this->ldq
."(.*?)".$this->rdq
."~s", $sourceContent, $matches);
234 $templateTags = $matches[1];
236 // Split content by template tags to obtain non-template content
237 $textBlocks = preg_split("~".$this->ldq
.".*?".$this->rdq
."~s", $sourceContent);
239 // compile the template tags into php-code
241 for ($i = 0, $j = count($templateTags); $i < $j; $i++
) {
242 $this->currentLineNo +
= mb_substr_count($textBlocks[$i], "\n");
243 $compiledTags[] = $this->compileTag($templateTags[$i], $identifier, $metaData);
244 $this->currentLineNo +
= mb_substr_count($templateTags[$i], "\n");
247 // throw error messages for unclosed tags
248 if (count($this->tagStack
) > 0) {
249 foreach ($this->tagStack
as $tagStack) {
250 throw new SystemException($this->formatSyntaxError('unclosed tag {'.$tagStack[0].'}', $this->currentIdentifier
, $tagStack[1]));
255 $compiledContent = '';
256 // Interleave the compiled contents and text blocks to get the final result.
257 for ($i = 0, $j = count($compiledTags); $i < $j; $i++
) {
258 if ($compiledTags[$i] == '') {
259 // tag result empty, remove first newline from following text block
260 $textBlocks[$i +
1] = preg_replace('%^(\r\n|\r|\n)%', '', $textBlocks[$i +
1]);
262 $compiledContent .= $textBlocks[$i].$compiledTags[$i];
264 $compiledContent .= $textBlocks[$i];
265 $compiledContent = chop($compiledContent);
267 // reinsert {literal} Tags
268 $compiledContent = $this->reinsertLiterals($compiledContent);
271 $compiledAutoloadPlugins = '';
272 if (count($this->autoloadPlugins
) > 0) {
273 $compiledAutoloadPlugins = "<?php\n";
274 foreach ($this->autoloadPlugins
as $className) {
275 $compiledAutoloadPlugins .= "if (!isset(\$this->pluginObjects['$className'])) {\n";
276 $compiledAutoloadPlugins .= "\$this->pluginObjects['$className'] = new $className;\n";
277 $compiledAutoloadPlugins .= "}\n";
279 $compiledAutoloadPlugins .= "?>";
284 $this->autoloadPlugins
= $previousData['autoloadPlugins'];
285 $this->currentIdentifier
= $previousData['currentIdentifier'];
286 $this->currentLineNo
= $previousData['currentLineNo'];
287 $this->tagStack
= $previousData['tagStack'];
292 'include' => $this->staticIncludes
294 'template' => $compiledAutoloadPlugins.$compiledContent
299 * Compiles a template tag.
302 * @param string $identifier
303 * @param array $metaData
305 * @throws SystemException
307 protected function compileTag($tag, $identifier, array &$metaData) {
308 if (preg_match('~^'.$this->outputPattern
.'~s', $tag)) {
310 return $this->compileOutputTag($tag);
314 // replace 'else if' with 'elseif'
315 $tag = preg_replace('~^else\s+if(?=\s)~i', 'elseif', $tag);
317 if (preg_match('~^(/?\w+)~', $tag, $match)) {
318 // build in function or plugin
319 $tagCommand = $match[1];
320 $tagArgs = mb_substr($tag, mb_strlen($tagCommand));
322 switch ($tagCommand) {
324 $this->pushTag('if');
325 return $this->compileIfTag($tagArgs);
328 list($openTag) = end($this->tagStack
);
329 if ($openTag != 'if' && $openTag != 'elseif') {
330 throw new SystemException($this->formatSyntaxError('unxepected {elseif}', $this->currentIdentifier
, $this->currentLineNo
));
332 else if ($openTag == 'if') {
333 $this->pushTag('elseif');
335 return $this->compileIfTag($tagArgs, true);
338 list($openTag) = end($this->tagStack
);
339 if ($openTag != 'if' && $openTag != 'elseif') {
340 throw new SystemException($this->formatSyntaxError('unexpected {else}', $this->currentIdentifier
, $this->currentLineNo
));
342 $this->pushTag('else');
343 return '<?php } else { ?>';
346 list($openTag) = end($this->tagStack
);
347 if ($openTag != 'if' && $openTag != 'elseif' && $openTag != 'else') {
348 throw new SystemException($this->formatSyntaxError('unexpected {/if}', $this->currentIdentifier
, $this->currentLineNo
));
354 return $this->compileIncludeTag($tagArgs, $identifier, $metaData);
357 $this->pushTag('foreach');
358 return $this->compileForeachTag($tagArgs);
361 list($openTag) = end($this->tagStack
);
362 if ($openTag != 'foreach') {
363 throw new SystemException($this->formatSyntaxError('unexpected {foreachelse}', $this->currentIdentifier
, $this->currentLineNo
));
365 $this->pushTag('foreachelse');
366 return '<?php } } else { { ?>';
369 list($openTag) = end($this->tagStack
);
370 if ($openTag != 'foreach' && $openTag != 'foreachelse') {
371 throw new SystemException($this->formatSyntaxError('unexpected {/foreach}', $this->currentIdentifier
, $this->currentLineNo
));
373 $this->popTag('foreach');
374 return "<?php } } ?>";
377 $this->pushTag('section');
378 return $this->compileSectionTag($tagArgs);
381 list($openTag) = end($this->tagStack
);
382 if ($openTag != 'section') {
383 throw new SystemException($this->formatSyntaxError('unexpected {sectionelse}', $this->currentIdentifier
, $this->currentLineNo
));
385 $this->pushTag('sectionelse');
386 return '<?php } } else { { ?>';
389 list($openTag) = end($this->tagStack
);
390 if ($openTag != 'section' && $openTag != 'sectionelse') {
391 throw new SystemException($this->formatSyntaxError('unexpected {/section}', $this->currentIdentifier
, $this->currentLineNo
));
393 $this->popTag('section');
394 return "<?php } } ?>";
397 $this->pushTag('capture');
398 return $this->compileCaptureTag(true, $tagArgs);
401 list($openTag) = end($this->tagStack
);
402 if ($openTag != 'capture') {
403 throw new SystemException($this->formatSyntaxError('unexpected {/capture}', $this->currentIdentifier
, $this->currentLineNo
));
405 $this->popTag('capture');
406 return $this->compileCaptureTag(false);
409 return $this->leftDelimiter
;
412 return $this->rightDelimiter
;
415 // 1) compiler functions first
416 if ($phpCode = $this->compileCompilerPlugin($tagCommand, $tagArgs)) {
419 // 2) block functions
420 if ($phpCode = $this->compileBlockPlugin($tagCommand, $tagArgs)) {
424 if ($phpCode = $this->compileFunctionPlugin($tagCommand, $tagArgs)) {
430 throw new SystemException($this->formatSyntaxError('unknown tag {'.$tag.'}', $this->currentIdentifier
, $this->currentLineNo
));
434 * Compiles a function plugin and returns the output of the plugin or false
435 * if the plugin doesn't exist.
437 * @param string $tagCommand
438 * @param string $tagArgs
441 protected function compileFunctionPlugin($tagCommand, $tagArgs) {
442 $className = $this->template
->getPluginClassName('function', $tagCommand);
443 if (!class_exists($className)) {
446 $this->autoloadPlugins
[$className] = $className;
448 $tagArgs = $this->makeArgString($this->parseTagArgs($tagArgs, $tagCommand));
450 return "<?php echo \$this->pluginObjects['".$className."']->execute(array(".$tagArgs."), \$this); ?>";
454 * Compiles a block plugin and returns the output of the plugin or false
455 * if the plugin doesn't exist.
457 * @param string $tagCommand
458 * @param string $tagArgs
460 * @throws SystemException
462 protected function compileBlockPlugin($tagCommand, $tagArgs) {
463 // check wheater this is the start ({block}) or the
464 // end tag ({/block})
465 if (substr($tagCommand, 0, 1) == '/') {
466 $tagCommand = substr($tagCommand, 1);
473 $className = $this->template
->getPluginClassName('block', $tagCommand);
474 if (!class_exists($className)) {
477 $this->autoloadPlugins
[$className] = $className;
480 $this->pushTag($tagCommand);
482 $tagArgs = $this->makeArgString($this->parseTagArgs($tagArgs, $tagCommand));
484 $phpCode = "<?php \$this->tagStack[] = array('".$tagCommand."', array(".$tagArgs."));\n";
485 $phpCode .= "\$this->pluginObjects['".$className."']->init(\$this->tagStack[count(\$this->tagStack) - 1][1], \$this);\n";
486 $phpCode .= "while (\$this->pluginObjects['".$className."']->next(\$this)) { ob_start(); ?>";
489 list($openTag) = end($this->tagStack
);
490 if ($openTag != $tagCommand) {
491 throw new SystemException($this->formatSyntaxError('unexpected {/'.$tagCommand.'}', $this->currentIdentifier
, $this->currentLineNo
));
493 $this->popTag($tagCommand);
494 $phpCode = "<?php echo \$this->pluginObjects['".$className."']->execute(\$this->tagStack[count(\$this->tagStack) - 1][1], ob_get_clean(), \$this); }\n";
495 $phpCode .= "array_pop(\$this->tagStack);?>";
502 * Compiles a compiler function/block and returns the output of the plugin
503 * or false if the plugin doesn't exist.
505 * @param string $tagCommand
506 * @param string $tagArgs
508 * @throws SystemException
510 protected function compileCompilerPlugin($tagCommand, $tagArgs) {
511 // check wheater this is the start ({block}) or the
512 // end tag ({/block})
513 if (substr($tagCommand, 0, 1) == '/') {
514 $tagCommand = substr($tagCommand, 1);
521 $className = $this->template
->getPluginClassName('compiler', $tagCommand);
522 // if necessary load plugin from plugin-dir
523 if (!isset($this->compilerPlugins
[$className])) {
524 if (!class_exists($className)) {
528 $this->compilerPlugins
[$className] = new $className();
530 if (!($this->compilerPlugins
[$className] instanceof ICompilerTemplatePlugin
)) {
531 throw new SystemException($this->formatSyntaxError("Compiler plugin '".$tagCommand."' does not implement the interface 'ICompilerTemplatePlugin'", $this->currentIdentifier
));
537 $tagArgs = $this->parseTagArgs($tagArgs, $tagCommand);
538 $phpCode = $this->compilerPlugins
[$className]->executeStart($tagArgs, $this);
541 $phpCode = $this->compilerPlugins
[$className]->executeEnd($this);
548 * Compiles a capture tag and returns the compiled PHP code.
550 * @param boolean $startTag
551 * @param string $captureTag
554 protected function compileCaptureTag($startTag, $captureTag = null) {
557 $args = $this->parseTagArgs($captureTag, 'capture');
559 if (!isset($args['name'])) {
560 $args['name'] = "'default'";
563 if (!isset($args['assign'])) {
564 if (isset($args['append'])) {
565 $args['assign'] = $args['append'];
569 $args['assign'] = '';
573 $this->captureStack
[] = ['name' => $args['name'], 'variable' => $args['assign'], 'append' => $append];
574 return '<?php ob_start(); ?>';
577 $capture = array_pop($this->captureStack
);
578 $phpCode = "<?php\n";
579 $phpCode .= "\$this->v['tpl']['capture'][".$capture['name']."] = ob_get_clean();\n";
580 if (!empty($capture['variable'])) $phpCode .= "\$this->".($capture['append'] ?
'append' : 'assign')."(".$capture['variable'].", \$this->v['tpl']['capture'][".$capture['name']."]);\n";
587 * Compiles a section tag and returns the compiled PHP code.
589 * @param string $sectionTag
591 * @throws SystemException
593 protected function compileSectionTag($sectionTag) {
594 $args = $this->parseTagArgs($sectionTag, 'section');
597 if (!isset($args['loop'])) {
598 throw new SystemException($this->formatSyntaxError("missing 'loop' attribute in section tag", $this->currentIdentifier
, $this->currentLineNo
));
600 if (!isset($args['name'])) {
601 throw new SystemException($this->formatSyntaxError("missing 'name' attribute in section tag", $this->currentIdentifier
, $this->currentLineNo
));
603 if (!isset($args['show'])) {
604 $args['show'] = true;
607 $sectionProp = "\$this->v['tpl']['section'][".$args['name']."]";
609 $phpCode = "<?php\n";
610 $phpCode .= "if (".$args['loop'].") {\n";
611 $phpCode .= $sectionProp." = array();\n";
612 $phpCode .= $sectionProp."['loop'] = (is_array(".$args['loop'].") ? count(".$args['loop'].") : max(0, (int)".$args['loop']."));\n";
613 $phpCode .= $sectionProp."['show'] = ".$args['show'].";\n";
614 if (!isset($args['step'])) {
615 $phpCode .= $sectionProp."['step'] = 1;\n";
618 $phpCode .= $sectionProp."['step'] = ".$args['step'].";\n";
620 if (!isset($args['max'])) {
621 $phpCode .= $sectionProp."['max'] = ".$sectionProp."['loop'];\n";
624 $phpCode .= $sectionProp."['max'] = (".$args['max']." < 0 ? ".$sectionProp."['loop'] : ".$args['max'].");\n";
626 if (!isset($args['start'])) {
627 $phpCode .= $sectionProp."['start'] = (".$sectionProp."['step'] > 0 ? 0 : ".$sectionProp."['loop'] - 1);\n";
630 $phpCode .= $sectionProp."['start'] = ".$args['start'].";\n";
631 $phpCode .= "if (".$sectionProp."['start'] < 0) {\n";
632 $phpCode .= $sectionProp."['start'] = max(".$sectionProp."['step'] > 0 ? 0 : -1, ".$sectionProp."['loop'] + ".$sectionProp."['start']);\n}\n";
633 $phpCode .= "else {\n";
634 $phpCode .= $sectionProp."['start'] = min(".$sectionProp."['start'], ".$sectionProp."['step'] > 0 ? ".$sectionProp."['loop'] : ".$sectionProp."['loop'] - 1);\n}\n";
637 if (!isset($args['start']) && !isset($args['step']) && !isset($args['max'])) {
638 $phpCode .= $sectionProp."['total'] = ".$sectionProp."['loop'];\n";
640 $phpCode .= $sectionProp."['total'] = min(ceil((".$sectionProp."['step'] > 0 ? ".$sectionProp."['loop'] - ".$sectionProp."['start'] : ".$sectionProp."['start'] + 1) / abs(".$sectionProp."['step'])), ".$sectionProp."['max']);\n";
642 $phpCode .= "if (".$sectionProp."['total'] == 0) ".$sectionProp."['show'] = false;\n";
643 $phpCode .= "} else {\n";
644 $phpCode .= "".$sectionProp."['total'] = 0;\n";
645 $phpCode .= "".$sectionProp."['show'] = false;}\n";
647 $phpCode .= "if (".$sectionProp."['show']) {\n";
648 $phpCode .= "for (".$sectionProp."['index'] = ".$sectionProp."['start'], ".$sectionProp."['rowNumber'] = 1;\n";
649 $phpCode .= $sectionProp."['rowNumber'] <= ".$sectionProp."['total'];\n";
650 $phpCode .= $sectionProp."['index'] += ".$sectionProp."['step'], ".$sectionProp."['rowNumber']++) {\n";
651 $phpCode .= "\$this->v[".$args['name']."] = ".$sectionProp."['index'];\n";
652 $phpCode .= $sectionProp."['previousIndex'] = ".$sectionProp."['index'] - ".$sectionProp."['step'];\n";
653 $phpCode .= $sectionProp."['nextIndex'] = ".$sectionProp."['index'] + ".$sectionProp."['step'];\n";
654 $phpCode .= $sectionProp."['first'] = (".$sectionProp."['rowNumber'] == 1);\n";
655 $phpCode .= $sectionProp."['last'] = (".$sectionProp."['rowNumber'] == ".$sectionProp."['total']);\n";
662 * Compiles a foreach tag and returns the compiled PHP code.
664 * @param string $foreachTag
666 * @throws SystemException
668 protected function compileForeachTag($foreachTag) {
669 $args = $this->parseTagArgs($foreachTag, 'foreach');
672 if (!isset($args['from'])) {
673 throw new SystemException($this->formatSyntaxError("missing 'from' attribute in foreach tag", $this->currentIdentifier
, $this->currentLineNo
));
675 if (!isset($args['item'])) {
676 throw new SystemException($this->formatSyntaxError("missing 'item' attribute in foreach tag", $this->currentIdentifier
, $this->currentLineNo
));
680 if (isset($args['name'])) {
681 $foreachProp = "\$this->v['tpl']['foreach'][".$args['name']."]";
684 $phpCode = "<?php\n";
685 if (!empty($foreachProp)) {
686 $phpCode .= $foreachProp."['total'] = count(".$args['from'].");\n";
687 $phpCode .= $foreachProp."['show'] = (".$foreachProp."['total'] > 0 ? true : false);\n";
688 $phpCode .= $foreachProp."['iteration'] = 0;\n";
690 $phpCode .= "if (count(".$args['from'].") > 0) {\n";
692 if (isset($args['key'])) {
693 $phpCode .= "foreach (".$args['from']." as ".(mb_substr($args['key'], 0, 1) != '$' ?
"\$this->v[".$args['key']."]" : $args['key'])." => ".(mb_substr($args['item'], 0, 1) != '$' ?
"\$this->v[".$args['item']."]" : $args['item']).") {\n";
696 $phpCode .= "foreach (".$args['from']." as ".(mb_substr($args['item'], 0, 1) != '$' ?
"\$this->v[".$args['item']."]" : $args['item']).") {\n";
699 if (!empty($foreachProp)) {
700 $phpCode .= $foreachProp."['first'] = (".$foreachProp."['iteration'] == 0 ? true : false);\n";
701 $phpCode .= $foreachProp."['last'] = ((".$foreachProp."['iteration'] == ".$foreachProp."['total'] - 1) ? true : false);\n";
702 $phpCode .= $foreachProp."['iteration']++;\n";
710 * Compiles an include tag and returns the compiled PHP code.
712 * @param string $includeTag
713 * @param string $identifier
714 * @param array $metaData
716 * @throws SystemException
718 protected function compileIncludeTag($includeTag, $identifier, array $metaData) {
719 $args = $this->parseTagArgs($includeTag, 'include');
723 if (!isset($args['file'])) {
724 throw new SystemException($this->formatSyntaxError("missing 'file' attribute in include tag", $this->currentIdentifier
, $this->currentLineNo
));
728 $file = $args['file'];
729 unset($args['file']);
731 // special parameters
733 if (isset($args['assign'])) {
734 $assignVar = $args['assign'];
735 unset($args['assign']);
738 if (isset($args['append'])) {
739 $assignVar = $args['append'];
741 unset($args['append']);
745 if (isset($args['once'])) {
746 $once = $args['once'];
747 unset($args['once']);
750 $application = "'wcf'";
751 if (isset($args['application'])) {
752 $application = $args['application'];
753 unset($args['application']);
756 if (preg_match('~^(\'|\")(.*)\1$~', $application, $matches)) {
757 $application = $matches[2];
761 if (isset($args['sandbox'])) {
762 $sandbox = $args['sandbox'];
763 unset($args['sandbox']);
766 $sandbox = ($sandbox === 'true' ||
$sandbox === true ||
$sandbox == 1);
768 $staticInclude = true;
769 if ($sandbox ||
$assignVar !== false ||
$once !== false ||
strpos($application, '$') !== false ||
strpos($file, '$') !== false) {
770 $staticInclude = false;
773 $templateName = substr($file, 1, -1);
775 // check for static includes
776 if ($staticInclude) {
778 if (!isset($this->staticIncludes
[$application])) {
779 $this->staticIncludes
[$application] = [];
782 if (!in_array($templateName, $this->staticIncludes
[$application])) {
783 $this->staticIncludes
[$application][] = $templateName;
786 // pass remaining tag args as variables
788 foreach ($args as $variable => $value) {
789 if (substr($value, 0, 1) == "'") {
791 $phpCode .= "\$this->v['".$variable."'] = ".$value.";\n";
794 if (preg_match('~^\$this->v\[\'(.*)\'\]$~U', $value, $matches)) {
795 // value is a variable itself
796 $phpCode .= "\$this->v['".$variable."'] = ".$value.";\n";
799 // value is boolean, an integer or anything else
800 $phpCode .= "\$this->v['".$variable."'] = ".$value.";\n";
805 if (!empty($phpCode)) $phpCode = "<?php\n".$phpCode."\n?>";
807 $sourceFilename = $this->template
->getSourceFilename($templateName, $application);
809 $data = $this->compileString($templateName, file_get_contents($sourceFilename), [
810 'application' => $application,
815 return $phpCode . $data['template'];
818 // make argument string
819 $argString = $this->makeArgString($args);
822 $phpCode = "<?php\n";
823 if ($once) $phpCode .= "if (!isset(\$this->v['tpl']['includedTemplates'][".$file."])) {\n";
824 $hash = StringUtil
::getRandomID();
825 $phpCode .= "\$outerTemplateName".$hash." = \$this->v['tpl']['template'];\n";
827 if ($assignVar !== false) {
828 $phpCode .= "ob_start();\n";
831 if (strpos($application, '$') === false) {
832 $application = "'" . $application . "'";
834 $phpCode .= '$this->includeTemplate('.$file.', '.$application.', array('.$argString.'), '.($sandbox ?
1 : 0).');'."\n";
836 if ($assignVar !== false) {
837 $phpCode .= '$this->'.($append ?
'append' : 'assign').'('.$assignVar.', ob_get_clean());'."\n";
840 $phpCode .= "\$this->v['tpl']['template'] = \$outerTemplateName".$hash.";\n";
841 $phpCode .= "\$this->v['tpl']['includedTemplates'][".$file."] = 1;\n";
842 if ($once) $phpCode .= "}\n";
849 * Parses an argument list and returns the keys and values in an associative
852 * @param string $tagArgs
855 * @throws SystemException
857 public function parseTagArgs($tagArgs, $tag) {
859 $tagArgs = $this->replaceQuotes($tagArgs);
861 // validate tag arguments
862 if (!preg_match('~^(?:\s+\w+\s*=\s*[^=]*(?=\s|$))*$~s', $tagArgs)) {
863 throw new SystemException($this->formatSyntaxError('syntax error in tag {'.$tag.'}', $this->currentIdentifier
, $this->currentLineNo
));
866 // parse tag arguments
868 // find all variables
869 preg_match_all('~\s+(\w+)\s*=\s*([^=]*)(?=\s|$)~s', $tagArgs, $matches);
871 for ($i = 0, $j = count($matches[1]); $i < $j; $i++
) {
872 $name = $matches[1][$i];
873 $string = $this->compileVariableTag($matches[2][$i], false);
876 foreach (StringStack
::getStack('singleQuote') as $hash => $value) {
877 if (mb_strpos($string, $hash) !== false) {
878 $string = str_replace($hash, $value, $string);
881 foreach (StringStack
::getStack('doubleQuote') as $hash => $value) {
882 if (mb_strpos($string, $hash) !== false) {
883 $string = str_replace($hash, $value, $string);
887 $args[$name] = $string;
891 $this->reinsertQuotes('');
897 * Takes an array created by TemplateCompiler::parseTagArgs() and creates
901 * @return string $args
903 public static function makeArgString($args) {
905 foreach ($args as $key => $val) {
906 if ($argString != '') {
909 $argString .= "'$key' => $val";
915 * Returns a formatted syntax error message.
917 * @param string $errorMsg
918 * @param string $file
919 * @param integer $line
922 public static function formatSyntaxError($errorMsg, $file = null, $line = null) {
923 $errorMsg = 'Template compilation failed: '.$errorMsg;
924 if ($file && $line) {
925 $errorMsg .= " in template '$file' on line $line";
927 else if ($file && !$line) {
928 $errorMsg .= " in template '$file'";
934 * Compiles an {if} tag and returns the compiled PHP code.
936 * @param string $tagArgs
937 * @param boolean $elseif true, if this tag is an else tag
939 * @throws SystemException
941 protected function compileIfTag($tagArgs, $elseif = false) {
942 $tagArgs = $this->replaceQuotes($tagArgs);
943 $tagArgs = str_replace([' ', "\n"], '', $tagArgs);
946 preg_match_all('~('.$this->conditionOperatorPattern
.')~', $tagArgs, $matches);
947 $operators = $matches[1];
948 $values = preg_split('~(?:'.$this->conditionOperatorPattern
.')~', $tagArgs);
949 $leftParentheses = 0;
952 for ($i = 0, $j = count($values); $i < $j; $i++
) {
953 $operator = (isset($operators[$i]) ?
$operators[$i] : null);
955 if ($operator !== '!' && $values[$i] == '') {
956 throw new SystemException($this->formatSyntaxError('syntax error in tag {'.($elseif ?
'elseif' : 'if').'}', $this->currentIdentifier
, $this->currentLineNo
));
959 $leftParenthesis = mb_substr_count($values[$i], '(');
960 $rightParenthesis = mb_substr_count($values[$i], ')');
961 if ($leftParenthesis > $rightParenthesis) {
962 $leftParentheses +
= $leftParenthesis - $rightParenthesis;
963 $value = mb_substr($values[$i], $leftParenthesis - $rightParenthesis);
964 $result .= str_repeat('(', $leftParenthesis - $rightParenthesis);
966 if (str_replace('(', '', mb_substr($values[$i], 0, $leftParenthesis - $rightParenthesis)) != '') {
967 throw new SystemException($this->formatSyntaxError('syntax error in tag {'.($elseif ?
'elseif' : 'if').'}', $this->currentIdentifier
, $this->currentLineNo
));
970 else if ($leftParenthesis < $rightParenthesis) {
971 $leftParentheses +
= $leftParenthesis - $rightParenthesis;
972 $value = mb_substr($values[$i], 0, $leftParenthesis - $rightParenthesis);
974 if ($leftParentheses < 0 ||
str_replace(')', '', mb_substr($values[$i], $leftParenthesis - $rightParenthesis)) != '') {
975 throw new SystemException($this->formatSyntaxError('syntax error in tag {'.($elseif ?
'elseif' : 'if').'}', $this->currentIdentifier
, $this->currentLineNo
));
978 else $value = $values[$i];
981 $result .= $this->compileVariableTag($value, false);
983 catch (SystemException
$e) {
984 throw new SystemException($this->formatSyntaxError('syntax error in tag {'.($elseif ?
'elseif' : 'if').'}', $this->currentIdentifier
, $this->currentLineNo
), 0, nl2br($e));
987 if ($leftParenthesis < $rightParenthesis) {
988 $result .= str_repeat(')', $rightParenthesis - $leftParenthesis);
991 if ($operator) $result .= ' '.$operator.' ';
994 return '<?php '.($elseif ?
'} elseif' : 'if').' ('.$result.') { ?>';
998 * Adds a tag to the tag stack.
1000 * @param string $tag
1002 public function pushTag($tag) {
1003 $this->tagStack
[] = [$tag, $this->currentLineNo
];
1007 * Deletes a tag from the tag stack.
1009 * @param string $tag
1010 * @return string $tag
1012 public function popTag($tag) {
1013 list($openTag, ) = array_pop($this->tagStack
);
1014 if ($tag == $openTag) {
1017 if ($tag == 'if' && ($openTag == 'else' ||
$openTag == 'elseif')) {
1018 return $this->popTag($tag);
1020 if ($tag == 'foreach' && $openTag == 'foreachelse') {
1021 return $this->popTag($tag);
1023 if ($tag == 'section' && $openTag == 'sectionelse') {
1024 return $this->popTag($tag);
1029 * Compiles an output tag and returns the compiled PHP code.
1031 * @param string $tag
1033 * @throws SystemException
1035 protected function compileOutputTag($tag) {
1036 $encodeHTML = false;
1037 $formatNumeric = false;
1038 if ($tag[0] == '@') {
1039 $tag = mb_substr($tag, 1);
1041 else if ($tag[0] == '#') {
1042 $tag = mb_substr($tag, 1);
1043 $formatNumeric = true;
1049 // check for forbidden constants
1050 if (preg_match('~^(RELATIVE_)?([A-Z]+)_DIR$~', $tag, $matches)) {
1051 $application = mb_strtolower($matches[2]);
1052 if ($application == 'wcf') {
1056 $application = "'{$application}'";
1059 throw new SystemException("Accessing internal constant '".$tag."' is disallowed, please use '\$__wcf->getPath(".$application.")' instead");
1062 $parsedTag = $this->compileVariableTag($tag);
1064 // the @ operator at the beginning of an output avoids
1065 // the default call of StringUtil::encodeHTML()
1067 $parsedTag = 'wcf\util\StringUtil::encodeHTML('.$parsedTag.')';
1069 // the # operator at the beginning of an output instructs
1070 // the complier to call the StringUtil::formatNumeric() method
1071 else if ($formatNumeric) {
1072 $parsedTag = 'wcf\util\StringUtil::formatNumeric('.$parsedTag.')';
1075 return '<?php echo '.$parsedTag.'; ?>';
1079 * Compiles a variable tag and returns the compiled PHP code.
1081 * @param string $variable
1082 * @param string $type
1083 * @param boolean $allowConstants
1086 protected function compileSimpleVariable($variable, $type = '', $allowConstants = true) {
1087 if ($type == '') $type = $this->getVariableType($variable);
1089 if ($type == 'variable') return '$this->v[\''.substr($variable, 1).'\']';
1090 else if ($type == 'string') return $variable;
1091 else if ($allowConstants && ($variable == 'true' ||
$variable == 'false' ||
$variable == 'null' ||
preg_match('/^[A-Z0-9_]*$/', $variable))) return $variable;
1092 else return "'".$variable."'";
1096 * Compiles a modifier tag and returns the compiled PHP code.
1098 * @param array $data
1101 protected function compileModifier($data) {
1102 if (isset($data['className'])) {
1103 return "\$this->pluginObjects['".$data['className']."']->execute(array(".implode(',', $data['parameter'])."), \$this)";
1106 return $data['name'].'('.implode(',', $data['parameter']).')';
1111 * Returns type of the given variable.
1113 * @param string $variable
1116 protected function getVariableType($variable) {
1117 if (substr($variable, 0, 1) == '$') return 'variable';
1118 else if (substr($variable, 0, 2) == '@@') return 'string';
1119 else return 'constant';
1123 * Compiles a variable tag and returns the compiled PHP code.
1125 * @param string $tag
1126 * @param boolean $replaceQuotes
1128 * @throws SystemException
1130 public function compileVariableTag($tag, $replaceQuotes = true) {
1131 // replace all quotes with unique hash values
1132 $compiledTag = $tag;
1133 if ($replaceQuotes) $compiledTag = $this->replaceQuotes($compiledTag);
1134 // replace numbers and special constants
1135 $compiledTag = $this->replaceConstants($compiledTag);
1138 preg_match_all('~('.$this->variableOperatorPattern
.')~', $compiledTag, $matches);
1139 $operators = $matches[1];
1140 $values = preg_split('~(?:'.$this->variableOperatorPattern
.')~', $compiledTag);
1143 $statusStack = [0 => 'start'];
1145 $modifierData = null;
1146 for ($i = 0, $j = count($values); $i < $j; $i++
) {
1148 $status = end($statusStack);
1149 $operator = (isset($operators[$i]) ?
$operators[$i] : null);
1150 $values[$i] = trim($values[$i]);
1152 if ($values[$i] !== '') {
1153 $variableType = $this->getVariableType($values[$i]);
1157 $result .= $this->compileSimpleVariable($values[$i], $variableType);
1158 $statusStack[0] = $status = $variableType;
1161 case 'object access':
1162 if (/*strpos($values[$i], '$') !== false || */strpos($values[$i], '@@') !== false) {
1163 throw new SystemException($this->formatSyntaxError("unexpected '->".$values[$i]."' in tag '".$tag."'", $this->currentIdentifier
, $this->currentLineNo
));
1165 if (strpos($values[$i], '$') !== false) $result .= '{'.$this->compileSimpleVariable($values[$i], $variableType).'}';
1166 else $result .= $values[$i];
1167 $statusStack[count($statusStack) - 1] = $status = 'object';
1170 case 'object method start':
1171 $statusStack[count($statusStack) - 1] = 'object method';
1172 $result .= $this->compileSimpleVariable($values[$i], $variableType);
1173 $statusStack[] = $status = $variableType;
1176 case 'object method parameter separator':
1177 array_pop($statusStack);
1178 $result .= $this->compileSimpleVariable($values[$i], $variableType);
1179 $statusStack[] = $status = $variableType;
1183 $result .= $this->compileSimpleVariable($values[$i], $variableType, false);
1185 $statusStack[count($statusStack) - 1] = $status = 'variable';
1188 case 'object method':
1189 case 'left parenthesis':
1190 $result .= $this->compileSimpleVariable($values[$i], $variableType);
1191 $statusStack[] = $status = $variableType;
1194 case 'bracket open':
1195 $result .= $this->compileSimpleVariable($values[$i], $variableType, false);
1196 $statusStack[] = $status = $variableType;
1200 $result .= $this->compileSimpleVariable($values[$i], $variableType);
1201 $statusStack[count($statusStack) - 1] = $status = $variableType;
1204 case 'modifier end':
1205 $result .= $this->compileSimpleVariable($values[$i], $variableType);
1206 $statusStack[] = $status = $variableType;
1210 if (strpos($values[$i], '$') !== false ||
strpos($values[$i], '@@') !== false) {
1211 throw new SystemException($this->formatSyntaxError("unknown modifier '".$values[$i]."'", $this->currentIdentifier
, $this->currentLineNo
));
1214 // handle modifier name
1215 $modifierData['name'] = $values[$i];
1216 $className = $this->template
->getPluginClassName('modifier', $modifierData['name']);
1217 if (class_exists($className)) {
1218 $modifierData['className'] = $className;
1219 $this->autoloadPlugins
[$modifierData['className']] = $modifierData['className'];
1221 else if ((!function_exists($modifierData['name']) && !in_array($modifierData['name'], $this->unknownPHPFunctions
)) ||
in_array($modifierData['name'], $this->disabledPHPFunctions
)) {
1222 throw new SystemException($this->formatSyntaxError("unknown modifier '".$values[$i]."'", $this->currentIdentifier
, $this->currentLineNo
));
1225 $statusStack[count($statusStack) - 1] = $status = 'modifier end';
1232 throw new SystemException($this->formatSyntaxError('unknown tag {'.$tag.'}', $this->currentIdentifier
, $this->currentLineNo
));
1238 if ($operator !== null) {
1239 switch ($operator) {
1241 if ($status == 'variable' ||
$status == 'object') {
1242 if ($status == 'object') $statusStack[count($statusStack) - 1] = 'variable';
1244 $statusStack[] = 'dot access';
1248 throw new SystemException($this->formatSyntaxError("unexpected '.' in tag '".$tag."'", $this->currentIdentifier
, $this->currentLineNo
));
1253 if ($status == 'variable' ||
$status == 'object') {
1254 $result .= $operator;
1255 $statusStack[count($statusStack) - 1] = 'object access';
1259 throw new SystemException($this->formatSyntaxError("unexpected '->' in tag '".$tag."'", $this->currentIdentifier
, $this->currentLineNo
));
1264 if ($status == 'object') {
1265 $statusStack[count($statusStack) - 1] = 'variable';
1266 $statusStack[] = 'object method start';
1267 $result .= $operator;
1270 else if ($status == 'math' ||
$status == 'start' ||
$status == 'left parenthesis' ||
$status == 'bracket open' ||
$status == 'modifier end') {
1271 if ($status == 'start') $statusStack[count($statusStack) - 1] = 'constant';
1272 $statusStack[] = 'left parenthesis';
1273 $result .= $operator;
1277 throw new SystemException($this->formatSyntaxError("unexpected '(' in tag '".$tag."'", $this->currentIdentifier
, $this->currentLineNo
));
1280 // right parenthesis
1282 while ($oldStatus = array_pop($statusStack)) {
1283 if ($oldStatus != 'variable' && $oldStatus != 'object' && $oldStatus != 'constant' && $oldStatus != 'string') {
1284 if ($oldStatus == 'object method start' ||
$oldStatus == 'object method' ||
$oldStatus == 'left parenthesis') {
1285 $result .= $operator;
1292 throw new SystemException($this->formatSyntaxError("unexpected ')' in tag '".$tag."'", $this->currentIdentifier
, $this->currentLineNo
));
1297 if ($status == 'variable' ||
$status == 'object') {
1298 if ($status == 'object') $statusStack[count($statusStack) - 1] = 'variable';
1299 $statusStack[] = 'bracket open';
1300 $result .= $operator;
1304 throw new SystemException($this->formatSyntaxError("unexpected '[' in tag '".$tag."'", $this->currentIdentifier
, $this->currentLineNo
));
1309 while ($oldStatus = array_pop($statusStack)) {
1310 if ($oldStatus != 'variable' && $oldStatus != 'object' && $oldStatus != 'constant' && $oldStatus != 'string') {
1311 if ($oldStatus == 'bracket open') {
1312 $result .= $operator;
1319 throw new SystemException($this->formatSyntaxError("unexpected ']' in tag '".$tag."'", $this->currentIdentifier
, $this->currentLineNo
));
1324 // handle previous modifier
1325 if ($modifierData !== null) {
1326 if ($result !== '') $modifierData['parameter'][] = $result;
1327 $result = $this->compileModifier($modifierData);
1330 // clear status stack
1331 while ($oldStatus = array_pop($statusStack)) {
1332 if ($oldStatus != 'variable' && $oldStatus != 'object' && $oldStatus != 'constant' && $oldStatus != 'string' && $oldStatus != 'modifier end') {
1333 throw new SystemException($this->formatSyntaxError("unexpected '|' in tag '".$tag."'", $this->currentIdentifier
, $this->currentLineNo
));
1337 $statusStack = [0 => 'modifier'];
1338 $modifierData = ['name' => '', 'parameter' => [0 => $result]];
1342 // modifier parameter
1344 while ($oldStatus = array_pop($statusStack)) {
1345 if ($oldStatus != 'variable' && $oldStatus != 'object' && $oldStatus != 'constant' && $oldStatus != 'string') {
1346 if ($oldStatus == 'modifier end') {
1347 $statusStack[] = 'modifier end';
1348 if ($result !== '') $modifierData['parameter'][] = $result;
1356 throw new SystemException($this->formatSyntaxError("unexpected ':' in tag '".$tag."'", $this->currentIdentifier
, $this->currentLineNo
));
1360 while ($oldStatus = array_pop($statusStack)) {
1361 if ($oldStatus != 'variable' && $oldStatus != 'object' && $oldStatus != 'constant' && $oldStatus != 'string') {
1362 if ($oldStatus == 'object method') {
1363 $result .= $operator;
1364 $statusStack[] = 'object method';
1365 $statusStack[] = 'object method parameter separator';
1372 throw new SystemException($this->formatSyntaxError("unexpected ',' in tag '".$tag."'", $this->currentIdentifier
, $this->currentLineNo
));
1382 if ($status == 'variable' ||
$status == 'object' ||
$status == 'constant' ||
$status == 'string' ||
$status == 'modifier end') {
1383 $result .= $operator;
1384 $statusStack[count($statusStack) - 1] = 'math';
1388 throw new SystemException($this->formatSyntaxError("unexpected '".$operator."' in tag '".$tag."'", $this->currentIdentifier
, $this->currentLineNo
));
1394 // handle open modifier
1395 if ($modifierData !== null) {
1396 if ($result !== '') $modifierData['parameter'][] = $result;
1397 $result = $this->compileModifier($modifierData);
1400 // reinserts strings
1401 $result = $this->reinsertQuotes($result);
1402 $result = $this->reinsertConstants($result);
1408 * Generates the regexp pattern.
1410 protected function buildPattern() {
1411 $this->variableOperatorPattern
= '\-\>|\.|\(|\)|\[|\]|\||\:|\+|\-|\*|\/|\%|\^|\,';
1412 $this->conditionOperatorPattern
= '===|!==|==|!=|<=|<|>=|(?<!-)>|\|\||&&|!|=';
1413 $this->escapedPattern
= '(?<!\\\\)';
1414 $this->validVarnamePattern
= '(?:[a-zA-Z_][a-zA-Z_0-9]*)';
1415 $this->constantPattern
= '(?:[A-Z_][A-Z_0-9]*)';
1416 $this->doubleQuotePattern
= '"(?:[^"\\\\]+|\\\\.)*"';
1417 $this->singleQuotePattern
= '\'(?:[^\'\\\\]+|\\\\.)*\'';
1418 $this->quotePattern
= '(?:' . $this->doubleQuotePattern
. '|' . $this->singleQuotePattern
. ')';
1419 $this->numericPattern
= '(?i)(?:(?:\-?\d+(?:\.\d+)?)|true|false|null)';
1420 $this->simpleVarPattern
= '(?:\$('.$this->validVarnamePattern
.'))';
1421 $this->outputPattern
= '(?:(?:@|#)?(?:'.$this->constantPattern
.'|'.$this->quotePattern
.'|'.$this->numericPattern
.'|'.$this->simpleVarPattern
.'|\())';
1425 * Returns the instance of the template engine class.
1427 * @return TemplateEngine
1429 public function getTemplate() {
1430 return $this->template
;
1434 * Returns the left delimiter for template tags.
1438 public function getLeftDelimiter() {
1439 return $this->leftDelimiter
;
1443 * Returns the right delimiter for template tags.
1447 public function getRightDelimiter() {
1448 return $this->rightDelimiter
;
1452 * Returns the name of the current template.
1456 public function getCurrentIdentifier() {
1457 return $this->currentIdentifier
;
1461 * Returns the current line number.
1465 public function getCurrentLineNo() {
1466 return $this->currentLineNo
;
1470 * Applies the prefilters to the given string.
1472 * @param string $templateName
1473 * @param string $string
1475 * @throws SystemException
1477 public function applyPrefilters($templateName, $string) {
1478 foreach ($this->template
->getPrefilters() as $prefilter) {
1479 if (!is_object($prefilter)) {
1480 $className = $this->template
->getPluginClassName('prefilter', $prefilter);
1481 if (!class_exists($className)) {
1482 throw new SystemException($this->formatSyntaxError('unable to find prefilter class '.$className, $this->currentIdentifier
));
1484 $prefilter = new $className();
1487 if ($prefilter instanceof IPrefilterTemplatePlugin
) {
1488 $string = $prefilter->execute($templateName, $string, $this);
1491 throw new SystemException($this->formatSyntaxError("Prefilter '".(is_object($prefilter) ?
get_class($prefilter) : $prefilter)."' does not implement the interface 'IPrefilterTemplatePlugin'", $this->currentIdentifier
));
1499 * Replaces all {literal} Tags with unique hash values.
1501 * @param string $string
1504 public function replaceLiterals($string) {
1505 return preg_replace_callback("~".$this->ldq
."literal".$this->rdq
."(.*?)".$this->ldq
."/literal".$this->rdq
."~s", [$this, 'replaceLiteralsCallback'], $string);
1509 * Reinserts the literal tags.
1511 * @param string $string
1514 public function reinsertLiterals($string) {
1515 return StringStack
::reinsertStrings($string, 'literal');
1519 * Callback function used in replaceLiterals()
1521 * @param string[] $matches
1524 private function replaceLiteralsCallback($matches) {
1525 return StringStack
::pushToStringStack($matches[1], 'literal');
1529 * Removes template comments
1531 * @param string $string
1534 public function removeComments($string) {
1535 return preg_replace("~".$this->ldq
."\*.*?\*".$this->rdq
."~s", '', $string);
1539 * Replaces all quotes with unique hash values.
1541 * @param string $string
1544 public function replaceQuotes($string) {
1545 $string = preg_replace_callback('~\'([^\'\\\\]+|\\\\.)*\'~', [$this, 'replaceSingleQuotesCallback'], $string);
1546 $string = preg_replace_callback('~"([^"\\\\]+|\\\\.)*"~', [$this, 'replaceDoubleQuotesCallback'], $string);
1552 * Callback function used in replaceQuotes()
1554 * @param string[] $matches
1557 private function replaceSingleQuotesCallback($matches) {
1558 return StringStack
::pushToStringStack($matches[0], 'singleQuote');
1562 * Callback function used in replaceQuotes()
1564 * @param string[] $matches
1567 private function replaceDoubleQuotesCallback($matches) {
1568 // parse unescaped simple vars in double quotes
1569 // replace $foo with {$this->v['foo']}
1570 $matches[0] = preg_replace('~'.$this->escapedPattern
.$this->simpleVarPattern
.'~', '{$this->v[\'\\1\']}', $matches[0]);
1571 return StringStack
::pushToStringStack($matches[0], 'doubleQuote');
1575 * Reinserts the quotes.
1577 * @param string $string
1580 public function reinsertQuotes($string) {
1581 $string = StringStack
::reinsertStrings($string, 'singleQuote');
1582 $string = StringStack
::reinsertStrings($string, 'doubleQuote');
1588 * Replaces all constants with unique hash values.
1590 * @param string $string
1593 public function replaceConstants($string) {
1594 return preg_replace_callback('~(?<=^|'.$this->variableOperatorPattern
.')(?i)((?:\-?\d+(?:\.\d+)?)|true|false|null)(?=$|'.$this->variableOperatorPattern
.')~', [$this, 'replaceConstantsCallback'], $string);
1598 * Callback function used in replaceConstants()
1600 * @param string[] $matches
1603 private function replaceConstantsCallback($matches) {
1604 return StringStack
::pushToStringStack($matches[1], 'constants');
1608 * Reinserts the constants.
1610 * @param string $string
1613 public function reinsertConstants($string) {
1614 return StringStack
::reinsertStrings($string, 'constants');
1618 * Replaces all php tags.
1620 * @param string $string
1623 public function replacePHPTags($string) {
1624 if (mb_strpos($string, '<?') !== false) {
1625 $string = str_replace('<?php', '@@PHP_START_TAG@@', $string);
1626 $string = str_replace('<?', '@@PHP_SHORT_START_TAG@@', $string);
1627 $string = str_replace('?>', '@@PHP_END_TAG@@', $string);
1628 $string = str_replace('@@PHP_END_TAG@@', "<?php echo '?>'; ?>\n", $string);
1629 $string = str_replace('@@PHP_SHORT_START_TAG@@', "<?php echo '<?'; ?>\n", $string);
1630 $string = str_replace('@@PHP_START_TAG@@', "<?php echo '<?php'; ?>\n", $string);