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
;
7 use wcf\util\StringStack
;
8 use wcf\util\StringUtil
;
11 * Compiles template sources into valid PHP code.
14 * @copyright 2001-2012 WoltLab GmbH
15 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
16 * @package com.woltlab.wcf
17 * @subpackage system.template
18 * @category Community Framework
20 class TemplateScriptingCompiler
{
22 * template engine object
23 * @var wcf\system\templateTemplateEngine
28 * PHP functions that can be used in the modifier syntax and are unknown
29 * to PHP's function_exists function
32 protected $unknownPHPFunctions = array('isset', 'unset', 'empty');
35 * PHP functions that can not be used in the modifier syntax
38 protected $disabledPHPFunctions = array(
39 'system', 'exec', 'passthru', 'shell_exec', // command line execution
40 'include', 'require', 'include_once', 'require_once', // includes
41 'eval', 'virtual', 'call_user_func_array', 'call_user_func', 'assert' // code execution
45 * pattern to match variable operators like -> or .
48 protected $variableOperatorPattern;
51 * pattern to match condition operators like == or <
54 protected $conditionOperatorPattern;
57 * negative lookbehind for a backslash
60 protected $escapedPattern;
63 * pattern to match valid variable names
66 protected $validVarnamePattern;
69 * pattern to match constants like CONSTANT or __CONSTANT
72 protected $constantPattern;
75 * pattern to match double quoted strings like "blah" or "quote: \"blah\""
78 protected $doubleQuotePattern;
81 * pattern to match single quoted strings like 'blah' or 'don\'t'
84 protected $singleQuotePattern;
87 * pattern to match single or double quoted strings
90 protected $quotePattern;
93 * pattern to match numbers, true, false and null
96 protected $numericPattern;
99 * pattern to match simple variables like $foo
102 protected $simpleVarPattern;
105 * pattern to match outputs like @$foo or #CONST
108 protected $outputPattern;
111 * identifier of currently compiled template
114 protected $currentIdentifier;
117 * current line number during template compilation
120 protected $currentLineNo;
123 * list of automatically loaded tenplate plugins
126 protected $autoloadPlugins = array();
129 * stack with template tags data
132 protected $tagStack = array();
135 * list of loaded compiler plugin objects
136 * @var array<wcf\system\template\ICompilerTemplatePlugin>
138 protected $compilerPlugins = array();
141 * stack used to compile the capture tag
144 protected $captureStack = array();
147 * left delimiter of template syntax
150 protected $leftDelimiter = '{';
153 * right delimiter of template syntax
156 protected $rightDelimiter = '}';
159 * left delimiter of template syntax used in regular expressions
165 * right delimiter of template syntax used in regular expressions
171 * list of static includes per template
174 protected $staticIncludes = array();
177 * Creates a new TemplateScriptingCompiler object.
179 * @param wcf\system\templateTemplateEngine $template
181 public function __construct(TemplateEngine
$template) {
182 $this->template
= $template;
184 // quote left and right delimiter for use in regular expressions
185 $this->ldq
= preg_quote($this->leftDelimiter
, '~').'(?=\S)';
186 $this->rdq
= '(?<=\S)'.preg_quote($this->rightDelimiter
, '~');
188 // build regular expressions
189 $this->buildPattern();
193 * Compiles the source of a template.
195 * @param string $identifier
196 * @param string $sourceContent
197 * @param array $metaData
198 * @param boolean $isolated
201 public function compileString($identifier, $sourceContent, array $metaData = array(), $isolated = false) {
203 $previousData = array(
204 'autoloadPlugins' => $this->autoloadPlugins
,
205 'currentIdentifier' => $this->currentIdentifier
,
206 'currentLineNo' => $this->currentLineNo
,
207 'literalStack' => $this->literalStack
,
208 'stringStack' => $this->stringStack
,
209 'tagStack' => $this->tagStack
213 $this->staticIncludes
= array();
217 $this->autoloadPlugins
= $this->tagStack
= $this->stringStack
= $this->literalStack
= array();
218 $this->currentIdentifier
= $identifier;
219 $this->currentLineNo
= 1;
222 $sourceContent = $this->applyPrefilters($identifier, $sourceContent);
224 // replace all {literal} Tags with unique hash values
225 $sourceContent = $this->replaceLiterals($sourceContent);
228 $sourceContent = $this->replacePHPTags($sourceContent);
231 $sourceContent = $this->removeComments($sourceContent);
233 // match all template tags
235 preg_match_all("~".$this->ldq
."(.*?)".$this->rdq
."~s", $sourceContent, $matches);
236 $templateTags = $matches[1];
238 // Split content by template tags to obtain non-template content
239 $textBlocks = preg_split("~".$this->ldq
.".*?".$this->rdq
."~s", $sourceContent);
241 // compile the template tags into php-code
242 $compiledTags = array();
243 for ($i = 0, $j = count($templateTags); $i < $j; $i++
) {
244 $this->currentLineNo +
= StringUtil
::countSubstring($textBlocks[$i], "\n");
245 $compiledTags[] = $this->compileTag($templateTags[$i], $identifier, $metaData);
246 $this->currentLineNo +
= StringUtil
::countSubstring($templateTags[$i], "\n");
249 // throw error messages for unclosed tags
250 if (count($this->tagStack
) > 0) {
251 foreach ($this->tagStack
as $tagStack) {
252 throw new SystemException($this->formatSyntaxError('unclosed tag {'.$tagStack[0].'}', $this->currentIdentifier
, $tagStack[1]));
257 $compiledContent = '';
258 // Interleave the compiled contents and text blocks to get the final result.
259 for ($i = 0, $j = count($compiledTags); $i < $j; $i++
) {
260 if ($compiledTags[$i] == '') {
261 // tag result empty, remove first newline from following text block
262 $textBlocks[$i +
1] = preg_replace('%^(\r\n|\r|\n)%', '', $textBlocks[$i +
1]);
264 $compiledContent .= $textBlocks[$i].$compiledTags[$i];
266 $compiledContent .= $textBlocks[$i];
267 $compiledContent = chop($compiledContent);
269 // @todo: INSERT POSTFILTERS HERE!?
271 // reinsert {literal} Tags
272 $compiledContent = $this->reinsertLiterals($compiledContent);
275 $compiledAutoloadPlugins = '';
276 if (count($this->autoloadPlugins
) > 0) {
277 $compiledAutoloadPlugins = "<?php\n";
278 foreach ($this->autoloadPlugins
as $className) {
279 $compiledAutoloadPlugins .= "if (!isset(\$this->pluginObjects['$className'])) {\n";
280 $compiledAutoloadPlugins .= "\$this->pluginObjects['$className'] = new $className;\n";
281 $compiledAutoloadPlugins .= "}\n";
283 $compiledAutoloadPlugins .= "?>";
288 $this->autoloadPlugins
= $previousData['autoloadPlugins'];
289 $this->currentIdentifier
= $previousData['currentIdentifier'];
290 $this->currentLineNo
= $previousData['currentLineNo'];
291 $this->literalStack
= $previousData['literalStack'];
292 $this->stringStack
= $previousData['stringStack'];
293 $this->tagStack
= $previousData['tagStack'];
298 'include' => $this->staticIncludes
300 'template' => $compiledAutoloadPlugins.$compiledContent
305 * Compiles a template tag.
308 * @param string $identifier
309 * @param array $metaData
311 protected function compileTag($tag, $identifier, array &$metaData) {
312 if (preg_match('~^'.$this->outputPattern
.'~s', $tag)) {
314 return $this->compileOutputTag($tag);
318 // replace 'else if' with 'elseif'
319 $tag = preg_replace('~^else\s+if(?=\s)~i', 'elseif', $tag);
321 if (preg_match('~^(/?\w+)~', $tag, $match)) {
322 // build in function or plugin
323 $tagCommand = $match[1];
324 $tagArgs = StringUtil
::substring($tag, StringUtil
::length($tagCommand));
326 switch ($tagCommand) {
328 $this->pushTag('if');
329 return $this->compileIfTag($tagArgs);
332 list($openTag) = end($this->tagStack
);
333 if ($openTag != 'if' && $openTag != 'elseif') {
334 throw new SystemException($this->formatSyntaxError('unxepected {elseif}', $this->currentIdentifier
, $this->currentLineNo
));
336 else if ($openTag == 'if') {
337 $this->pushTag('elseif');
339 return $this->compileIfTag($tagArgs, true);
342 list($openTag) = end($this->tagStack
);
343 if ($openTag != 'if' && $openTag != 'elseif') {
344 throw new SystemException($this->formatSyntaxError('unexpected {else}', $this->currentIdentifier
, $this->currentLineNo
));
346 $this->pushTag('else');
347 return '<?php } else { ?>';
350 list($openTag) = end($this->tagStack
);
351 if ($openTag != 'if' && $openTag != 'elseif' && $openTag != 'else') {
352 throw new SystemException($this->formatSyntaxError('unexpected {/if}', $this->currentIdentifier
, $this->currentLineNo
));
358 return $this->compileIncludeTag($tagArgs, $identifier, $metaData);
361 $this->pushTag('foreach');
362 return $this->compileForeachTag($tagArgs);
365 list($openTag) = end($this->tagStack
);
366 if ($openTag != 'foreach') {
367 throw new SystemException($this->formatSyntaxError('unexpected {foreachelse}', $this->currentIdentifier
, $this->currentLineNo
));
369 $this->pushTag('foreachelse');
370 return '<?php } } else { { ?>';
373 list($openTag) = end($this->tagStack
);
374 if ($openTag != 'foreach' && $openTag != 'foreachelse') {
375 throw new SystemException($this->formatSyntaxError('unexpected {/foreach}', $this->currentIdentifier
, $this->currentLineNo
));
377 $this->popTag('foreach');
378 return "<?php } } ?>";
381 $this->pushTag('section');
382 return $this->compileSectionTag($tagArgs);
385 list($openTag) = end($this->tagStack
);
386 if ($openTag != 'section') {
387 throw new SystemException($this->formatSyntaxError('unexpected {sectionelse}', $this->currentIdentifier
, $this->currentLineNo
));
389 $this->pushTag('sectionelse');
390 return '<?php } } else { { ?>';
393 list($openTag) = end($this->tagStack
);
394 if ($openTag != 'section' && $openTag != 'sectionelse') {
395 throw new SystemException($this->formatSyntaxError('unexpected {/section}', $this->currentIdentifier
, $this->currentLineNo
));
397 $this->popTag('section');
398 return "<?php } } ?>";
401 $this->pushTag('capture');
402 return $this->compileCaptureTag(true, $tagArgs);
405 list($openTag) = end($this->tagStack
);
406 if ($openTag != 'capture') {
407 throw new SystemException($this->formatSyntaxError('unexpected {/capture}', $this->currentIdentifier
, $this->currentLineNo
));
409 $this->popTag('capture');
410 return $this->compileCaptureTag(false);
413 return $this->leftDelimiter
;
416 return $this->rightDelimiter
;
419 // 1) compiler functions first
420 if ($phpCode = $this->compileCompilerPlugin($tagCommand, $tagArgs)) {
423 // 2) block functions
424 if ($phpCode = $this->compileBlockPlugin($tagCommand, $tagArgs)) {
428 if ($phpCode = $this->compileFunctionPlugin($tagCommand, $tagArgs)) {
434 throw new SystemException($this->formatSyntaxError('unknown tag {'.$tag.'}', $this->currentIdentifier
, $this->currentLineNo
));
438 * Compiles a function plugin and returns the output of the plugin or false
439 * if the plugin doesn't exist.
441 * @param string $tagCommand
442 * @param string $tagArgs
445 protected function compileFunctionPlugin($tagCommand, $tagArgs) {
446 $className = $this->template
->getPluginClassName('function', $tagCommand);
447 if (!class_exists($className)) {
450 $this->autoloadPlugins
[$className] = $className;
452 $tagArgs = $this->makeArgString($this->parseTagArgs($tagArgs, $tagCommand));
454 return "<?php echo \$this->pluginObjects['".$className."']->execute(array(".$tagArgs."), \$this); ?>";
458 * Compiles a block plugin and returns the output of the plugin or false
459 * if the plugin doesn't exist.
461 * @param string $tagCommand
462 * @param string $tagArgs
465 protected function compileBlockPlugin($tagCommand, $tagArgs) {
466 // check wheater this is the start ({block}) or the
467 // end tag ({/block})
468 if (substr($tagCommand, 0, 1) == '/') {
469 $tagCommand = substr($tagCommand, 1);
476 $className = $this->template
->getPluginClassName('block', $tagCommand);
477 if (!class_exists($className)) {
480 $this->autoloadPlugins
[$className] = $className;
483 $this->pushTag($tagCommand);
485 $tagArgs = $this->makeArgString($this->parseTagArgs($tagArgs, $tagCommand));
487 $phpCode = "<?php \$this->tagStack[] = array('".$tagCommand."', array(".$tagArgs."));\n";
488 $phpCode .= "\$this->pluginObjects['".$className."']->init(\$this->tagStack[count(\$this->tagStack) - 1][1], \$this);\n";
489 $phpCode .= "while (\$this->pluginObjects['".$className."']->next(\$this)) { ob_start(); ?>";
492 list($openTag) = end($this->tagStack
);
493 if ($openTag != $tagCommand) {
494 throw new SystemException($this->formatSyntaxError('unexpected {/'.$tagCommand.'}', $this->currentIdentifier
, $this->currentLineNo
));
496 $this->popTag($tagCommand);
497 $phpCode = "<?php echo \$this->pluginObjects['".$className."']->execute(\$this->tagStack[count(\$this->tagStack) - 1][1], ob_get_clean(), \$this); }\n";
498 $phpCode .= "array_pop(\$this->tagStack);\n";
505 * Compiles a compiler function/block and returns the output of the plugin
506 * or false if the plugin doesn't exist.
508 * @param string $tagCommand
509 * @param string $tagArgs
512 protected function compileCompilerPlugin($tagCommand, $tagArgs) {
513 // check wheater this is the start ({block}) or the
514 // end tag ({/block})
515 if (substr($tagCommand, 0, 1) == '/') {
516 $tagCommand = substr($tagCommand, 1);
523 $className = $this->template
->getPluginClassName('compiler', $tagCommand);
524 // if necessary load plugin from plugin-dir
525 if (!isset($this->compilerPlugins
[$className])) {
526 if (!class_exists($className)) {
530 $this->compilerPlugins
[$className] = new $className();
532 if (!($this->compilerPlugins
[$className] instanceof ICompilerTemplatePlugin
)) {
533 throw new SystemException($this->formatSyntaxError("Compiler plugin '".$tagCommand."' does not implement the interface 'ICompilerTemplatePlugin'", $this->currentIdentifier
));
539 $tagArgs = $this->parseTagArgs($tagArgs, $tagCommand);
540 $phpCode = $this->compilerPlugins
[$className]->executeStart($tagArgs, $this);
543 $phpCode = $this->compilerPlugins
[$className]->executeEnd($this);
550 * Compiles a capture tag and returns the compiled PHP code.
552 * @param boolean $startTag
553 * @param string $captureTag
556 protected function compileCaptureTag($startTag, $captureTag = null) {
559 $args = $this->parseTagArgs($captureTag, 'capture');
561 if (!isset($args['name'])) {
562 $args['name'] = "'default'";
565 if (!isset($args['assign'])) {
566 if (isset($args['append'])) {
567 $args['assign'] = $args['append'];
571 $args['assign'] = '';
575 $this->captureStack
[] = array('name' => $args['name'], 'variable' => $args['assign'], 'append' => $append);
576 return '<?php ob_start(); ?>';
579 $capture = array_pop($this->captureStack
);
580 $phpCode = "<?php\n";
581 $phpCode .= "\$this->v['tpl']['capture'][".$capture['name']."] = ob_get_clean();\n";
582 if (!empty($capture['variable'])) $phpCode .= "\$this->".($capture['append'] ?
'append' : 'assign')."(".$capture['variable'].", \$this->v['tpl']['capture'][".$capture['name']."]);\n";
589 * Compiles a section tag and returns the compiled PHP code.
591 * @param string $sectionTag
594 protected function compileSectionTag($sectionTag) {
595 $args = $this->parseTagArgs($sectionTag, 'section');
598 if (!isset($args['loop'])) {
599 throw new SystemException($this->formatSyntaxError("missing 'loop' attribute in section tag", $this->currentIdentifier
, $this->currentLineNo
));
601 if (!isset($args['name'])) {
602 throw new SystemException($this->formatSyntaxError("missing 'name' attribute in section tag", $this->currentIdentifier
, $this->currentLineNo
));
604 if (!isset($args['show'])) {
605 $args['show'] = true;
608 $sectionProp = "\$this->v['tpl']['section'][".$args['name']."]";
610 $phpCode = "<?php\n";
611 $phpCode .= "if (".$args['loop'].") {\n";
612 $phpCode .= $sectionProp." = array();\n";
613 $phpCode .= $sectionProp."['loop'] = (is_array(".$args['loop'].") ? count(".$args['loop'].") : max(0, (int)".$args['loop']."));\n";
614 $phpCode .= $sectionProp."['show'] = ".$args['show'].";\n";
615 if (!isset($args['step'])) {
616 $phpCode .= $sectionProp."['step'] = 1;\n";
619 $phpCode .= $sectionProp."['step'] = ".$args['step'].";\n";
621 if (!isset($args['max'])) {
622 $phpCode .= $sectionProp."['max'] = ".$sectionProp."['loop'];\n";
625 $phpCode .= $sectionProp."['max'] = (".$args['max']." < 0 ? ".$sectionProp."['loop'] : ".$args['max'].");\n";
627 if (!isset($args['start'])) {
628 $phpCode .= $sectionProp."['start'] = (".$sectionProp."['step'] > 0 ? 0 : ".$sectionProp."['loop'] - 1);\n";
631 $phpCode .= $sectionProp."['start'] = ".$args['start'].";\n";
632 $phpCode .= "if (".$sectionProp."['start'] < 0) {\n";
633 $phpCode .= $sectionProp."['start'] = max(".$sectionProp."['step'] > 0 ? 0 : -1, ".$sectionProp."['loop'] + ".$sectionProp."['start']);\n}\n";
634 $phpCode .= "else {\n";
635 $phpCode .= $sectionProp."['start'] = min(".$sectionProp."['start'], ".$sectionProp."['step'] > 0 ? ".$sectionProp."['loop'] : ".$sectionProp."['loop'] - 1);\n}\n";
638 if (!isset($args['start']) && !isset($args['step']) && !isset($args['max'])) {
639 $phpCode .= $sectionProp."['total'] = ".$sectionProp."['loop'];\n";
641 $phpCode .= $sectionProp."['total'] = min(ceil((".$sectionProp."['step'] > 0 ? ".$sectionProp."['loop'] - ".$sectionProp."['start'] : ".$sectionProp."['start'] + 1) / abs(".$sectionProp."['step'])), ".$sectionProp."['max']);\n";
643 $phpCode .= "if (".$sectionProp."['total'] == 0) ".$sectionProp."['show'] = false;\n";
644 $phpCode .= "} else {\n";
645 $phpCode .= "".$sectionProp."['total'] = 0;\n";
646 $phpCode .= "".$sectionProp."['show'] = false;}\n";
648 $phpCode .= "if (".$sectionProp."['show']) {\n";
649 $phpCode .= "for (".$sectionProp."['index'] = ".$sectionProp."['start'], ".$sectionProp."['rowNumber'] = 1;\n";
650 $phpCode .= $sectionProp."['rowNumber'] <= ".$sectionProp."['total'];\n";
651 $phpCode .= $sectionProp."['index'] += ".$sectionProp."['step'], ".$sectionProp."['rowNumber']++) {\n";
652 $phpCode .= "\$this->v[".$args['name']."] = ".$sectionProp."['index'];\n";
653 $phpCode .= $sectionProp."['previousIndex'] = ".$sectionProp."['index'] - ".$sectionProp."['step'];\n";
654 $phpCode .= $sectionProp."['nextIndex'] = ".$sectionProp."['index'] + ".$sectionProp."['step'];\n";
655 $phpCode .= $sectionProp."['first'] = (".$sectionProp."['rowNumber'] == 1);\n";
656 $phpCode .= $sectionProp."['last'] = (".$sectionProp."['rowNumber'] == ".$sectionProp."['total']);\n";
663 * Compiles a foreach tag and returns the compiled PHP code.
665 * @param string $foreachTag
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 ".(StringUtil
::substring($args['key'], 0, 1) != '$' ?
"\$this->v[".$args['key']."]" : $args['key'])." => ".(StringUtil
::substring($args['item'], 0, 1) != '$' ?
"\$this->v[".$args['item']."]" : $args['item']).") {\n";
696 $phpCode .= "foreach (".$args['from']." as ".(StringUtil
::substring($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
717 protected function compileIncludeTag($includeTag, $identifier, array &$metaData) {
718 $args = $this->parseTagArgs($includeTag, 'include');
722 if (!isset($args['file'])) {
723 throw new SystemException($this->formatSyntaxError("missing 'file' attribute in include tag", $this->currentIdentifier
, $this->currentLineNo
));
727 $file = $args['file'];
728 unset($args['file']);
730 // special parameters
732 if (isset($args['assign'])) {
733 $assignVar = $args['assign'];
734 unset($args['assign']);
737 if (isset($args['append'])) {
738 $assignVar = $args['append'];
740 unset($args['append']);
744 if (isset($args['sandbox'])) {
745 $sandbox = $args['sandbox'];
746 unset($args['sandbox']);
750 if (isset($args['once'])) {
751 $once = $args['once'];
752 unset($args['once']);
755 $application = '$this->v[\'__APPLICATION\']';
756 if (isset($args['application'])) {
757 $application = $args['application'];
758 unset($args['application']);
761 $templateName = substr($file, 1, -1);
762 // check for static includes
763 if ($sandbox === 'false' && $assignVar === false && $once === false) {
765 if (!in_array($templateName, $this->staticIncludes
)) {
766 $this->staticIncludes
[] = $templateName;
769 // pass remaining tag args as variables
770 $variables = array();
772 foreach ($args as $variable => $value) {
773 if (substr($value, 0, 1) == "'") {
775 $phpCode .= "\$this->v['".$variable."'] = ".$value.";\n";
778 if (preg_match('~^\$this->v\[\'(.*)\'\]$~U', $value, $matches)) {
779 // value is a variable itself
780 $phpCode .= "\$this->v['".$variable."'] = ".$value.";\n";
783 // value is boolean, an integer or anything else
784 $phpCode .= "\$this->v['".$variable."'] = ".$value.";\n";
789 if (!empty($phpCode)) $phpCode = "<?php\n".$phpCode."\n?>";
791 $tplPackageID = WCF
::getTPL()->getPackageID($templateName, $metaData['application']);
792 $sourceFilename = WCF
::getTPL()->getSourceFilename($templateName, $tplPackageID);
793 $metaDataFilename = WCF
::getTPL()->getMetaDataFilename($templateName);
795 $data = $this->compileString($templateName, file_get_contents($sourceFilename), array(
796 'application' => $metaData['application'],
801 return $phpCode . $data['template'];
804 // make argument string
805 $argString = $this->makeArgString($args);
808 $phpCode = "<?php\n";
809 if ($once) $phpCode .= "if (!isset(\$this->v['tpl']['includedTemplates'][".$file."])) {\n";
810 $hash = StringUtil
::getRandomID();
811 $phpCode .= "\$outerTemplateName".$hash." = \$this->v['tpl']['template'];\n";
813 if ($assignVar !== false) {
814 $phpCode .= "ob_start();\n";
817 $phpCode .= '$this->includeTemplate('.$file.', '.$application.', array('.$argString.'), ('.$sandbox.' ? 1 : 0));'."\n";
819 if ($assignVar !== false) {
820 $phpCode .= '$this->'.($append ?
'append' : 'assign').'('.$assignVar.', ob_get_clean());'."\n";
823 $phpCode .= "\$this->v['tpl']['template'] = \$outerTemplateName".$hash.";\n";
824 $phpCode .= "\$this->v['tpl']['includedTemplates'][".$file."] = 1;\n";
825 if ($once) $phpCode .= "}\n";
832 * Parses an argument list and returns the keys and values in an associative
835 * @param string $tagArgs
839 public function parseTagArgs($tagArgs, $tag) {
841 $tagArgs = $this->replaceQuotes($tagArgs);
843 // validate tag arguments
844 if (!preg_match('~^(?:\s+\w+\s*=\s*[^=]*(?=\s|$))*$~s', $tagArgs)) {
845 throw new SystemException($this->formatSyntaxError('syntax error in tag {'.$tag.'}', $this->currentIdentifier
, $this->currentLineNo
));
848 // parse tag arguments
850 // find all variables
851 preg_match_all('~\s+(\w+)\s*=\s*([^=]*)(?=\s|$)~s', $tagArgs, $matches);
853 for ($i = 0, $j = count($matches[1]); $i < $j; $i++
) {
854 $name = $matches[1][$i];
855 $string = $this->compileVariableTag($matches[2][$i], false);
858 foreach (StringStack
::getStack('singleQuote') as $hash => $value) {
859 if (StringUtil
::indexOf($string, $hash) !== false) {
860 $string = StringUtil
::replace($hash, $value, $string);
863 foreach (StringStack
::getStack('doubleQuote') as $hash => $value) {
864 if (StringUtil
::indexOf($string, $hash) !== false) {
865 $string = StringUtil
::replace($hash, $value, $string);
869 $args[$name] = $string;
873 $this->reinsertQuotes('');
879 * Takes an array created by TemplateCompiler::parseTagArgs() and creates
883 * @return string $args
885 public static function makeArgString($args) {
887 foreach ($args as $key => $val) {
888 if ($argString != '') {
891 $argString .= "'$key' => $val";
897 * Returns a formatted syntax error message.
899 * @param string $errorMsg
900 * @param string $file
901 * @param integer $line
904 public static function formatSyntaxError($errorMsg, $file = null, $line = null) {
905 $errorMsg = 'Template compilation failed: '.$errorMsg;
906 if ($file && $line) {
907 $errorMsg .= " in template '$file' on line $line";
909 elseif ($file && !$line) {
910 $errorMsg .= " in template '$file'";
916 * Compiles an {if} tag and returns the compiled PHP code.
918 * @param string $tagArgs
919 * @param boolean $elseif true, if this tag is an else tag
922 protected function compileIfTag($tagArgs, $elseif = false) {
923 $tagArgs = $this->replaceQuotes($tagArgs);
924 $tagArgs = str_replace(' ', '', $tagArgs);
927 preg_match_all('~('.$this->conditionOperatorPattern
.')~', $tagArgs, $matches);
928 $operators = $matches[1];
929 $values = preg_split('~(?:'.$this->conditionOperatorPattern
.')~', $tagArgs);
930 $leftParentheses = 0;
933 for ($i = 0, $j = count($values); $i < $j; $i++
) {
934 $operator = (isset($operators[$i]) ?
$operators[$i] : null);
936 if ($operator !== '!' && $values[$i] == '') {
937 throw new SystemException($this->formatSyntaxError('syntax error in tag {'.($elseif ?
'elseif' : 'if').'}', $this->currentIdentifier
, $this->currentLineNo
));
940 $leftParenthesis = StringUtil
::countSubstring($values[$i], '(');
941 $rightParenthesis = StringUtil
::countSubstring($values[$i], ')');
942 if ($leftParenthesis > $rightParenthesis) {
943 $leftParentheses +
= $leftParenthesis - $rightParenthesis;
944 $value = StringUtil
::substring($values[$i], $leftParenthesis - $rightParenthesis);
945 $result .= str_repeat('(', $leftParenthesis - $rightParenthesis);
947 if (str_replace('(', '', StringUtil
::substring($values[$i], 0, $leftParenthesis - $rightParenthesis)) != '') {
948 throw new SystemException($this->formatSyntaxError('syntax error in tag {'.($elseif ?
'elseif' : 'if').'}', $this->currentIdentifier
, $this->currentLineNo
));
951 else if ($leftParenthesis < $rightParenthesis) {
952 $leftParentheses +
= $leftParenthesis - $rightParenthesis;
953 $value = StringUtil
::substring($values[$i], 0, $leftParenthesis - $rightParenthesis);
955 if ($leftParentheses < 0 ||
str_replace(')', '', StringUtil
::substring($values[$i], $leftParenthesis - $rightParenthesis)) != '') {
956 throw new SystemException($this->formatSyntaxError('syntax error in tag {'.($elseif ?
'elseif' : 'if').'}', $this->currentIdentifier
, $this->currentLineNo
));
959 else $value = $values[$i];
962 $result .= $this->compileVariableTag($value, false);
964 catch (SystemException
$e) {
965 throw new SystemException($this->formatSyntaxError('syntax error in tag {'.($elseif ?
'elseif' : 'if').'}', $this->currentIdentifier
, $this->currentLineNo
), 0, nl2br($e));
968 if ($leftParenthesis < $rightParenthesis) {
969 $result .= str_repeat(')', $rightParenthesis - $leftParenthesis);
972 if ($operator) $result .= ' '.$operator.' ';
975 return '<?php '.($elseif ?
'} elseif' : 'if').' ('.$result.') { ?>';
979 * Adds a tag to the tag stack.
983 public function pushTag($tag) {
984 $this->tagStack
[] = array($tag, $this->currentLineNo
);
988 * Deletes a tag from the tag stack.
991 * @return string $tag
993 public function popTag($tag) {
994 list($openTag, $lineNo) = array_pop($this->tagStack
);
995 if ($tag == $openTag) {
998 if ($tag == 'if' && ($openTag == 'else' ||
$openTag == 'elseif')) {
999 return $this->popTag($tag);
1001 if ($tag == 'foreach' && $openTag == 'foreachelse') {
1002 return $this->popTag($tag);
1004 if ($tag == 'section' && $openTag == 'sectionelse') {
1005 return $this->popTag($tag);
1010 * Compiles an output tag and returns the compiled PHP code.
1012 * @param string $tag
1015 protected function compileOutputTag($tag) {
1016 $encodeHTML = false;
1017 $formatNumeric = false;
1018 if ($tag[0] == '@') {
1019 $tag = StringUtil
::substring($tag, 1);
1021 else if ($tag[0] == '#') {
1022 $tag = StringUtil
::substring($tag, 1);
1023 $formatNumeric = true;
1029 $parsedTag = $this->compileVariableTag($tag);
1031 // the @ operator at the beginning of an output avoids
1032 // the default call of StringUtil::encodeHTML()
1034 $parsedTag = 'wcf\util\StringUtil::encodeHTML('.$parsedTag.')';
1036 // the # operator at the beginning of an output instructs
1037 // the complier to call the StringUtil::formatNumeric() method
1038 else if ($formatNumeric) {
1039 $parsedTag = 'wcf\util\StringUtil::formatNumeric('.$parsedTag.')';
1042 return '<?php echo '.$parsedTag.'; ?>';
1046 * Compiles a variable tag and returns the compiled PHP code.
1048 * @param string $variable
1049 * @param string $type
1050 * @param boolean $allowConstants
1053 protected function compileSimpleVariable($variable, $type = '', $allowConstants = true) {
1054 if ($type == '') $type = $this->getVariableType($variable);
1056 if ($type == 'variable') return '$this->v[\''.substr($variable, 1).'\']';
1057 else if ($type == 'string') return $variable;
1058 else if ($allowConstants && ($variable == 'true' ||
$variable == 'false' ||
$variable == 'null' ||
preg_match('/^[A-Z0-9_]*$/', $variable))) return $variable;
1059 else return "'".$variable."'";
1063 * Compiles a modifier tag and returns the compiled PHP code.
1065 * @param array $data
1068 protected function compileModifier($data) {
1069 if (isset($data['className'])) {
1070 return "\$this->pluginObjects['".$data['className']."']->execute(array(".implode(',', $data['parameter'])."), \$this)";
1073 return $data['name'].'('.implode(',', $data['parameter']).')';
1078 * Returns type of the given variable.
1080 * @param string $variable
1083 protected function getVariableType($variable) {
1084 if (substr($variable, 0, 1) == '$') return 'variable';
1085 else if (substr($variable, 0, 2) == '@@') return 'string';
1086 else return 'constant';
1090 * Compiles a variable tag and returns the compiled PHP code.
1092 * @param string $tag
1095 public function compileVariableTag($tag, $replaceQuotes = true) {
1096 // replace all quotes with unique hash values
1097 $compiledTag = $tag;
1098 if ($replaceQuotes) $compiledTag = $this->replaceQuotes($compiledTag);
1099 // replace numbers and special constants
1100 $compiledTag = $this->replaceConstants($compiledTag);
1103 preg_match_all('~('.$this->variableOperatorPattern
.')~', $compiledTag, $matches);
1104 $operators = $matches[1];
1105 $values = preg_split('~(?:'.$this->variableOperatorPattern
.')~', $compiledTag);
1108 $statusStack = array(0 => 'start');
1110 $modifierData = null;
1111 for ($i = 0, $j = count($values); $i < $j; $i++
) {
1113 $status = end($statusStack);
1114 $operator = (isset($operators[$i]) ?
$operators[$i] : null);
1115 $values[$i] = trim($values[$i]);
1117 if ($values[$i] !== '') {
1118 $variableType = $this->getVariableType($values[$i]);
1122 $result .= $this->compileSimpleVariable($values[$i], $variableType);
1123 $statusStack[0] = $status = $variableType;
1126 case 'object access':
1127 if (/*strpos($values[$i], '$') !== false || */strpos($values[$i], '@@') !== false) {
1128 throw new SystemException($this->formatSyntaxError("unexpected '->".$values[$i]."' in tag '".$tag."'", $this->currentIdentifier
, $this->currentLineNo
));
1130 if (strpos($values[$i], '$') !== false) $result .= '{'.$this->compileSimpleVariable($values[$i], $variableType).'}';
1131 else $result .= $values[$i];
1132 $statusStack[count($statusStack) - 1] = $status = 'object';
1135 case 'object method start':
1136 $statusStack[count($statusStack) - 1] = 'object method';
1137 $result .= $this->compileSimpleVariable($values[$i], $variableType);
1138 $statusStack[] = $status = $variableType;
1141 case 'object method parameter separator':
1142 array_pop($statusStack);
1143 $result .= $this->compileSimpleVariable($values[$i], $variableType);
1144 $statusStack[] = $status = $variableType;
1148 $result .= $this->compileSimpleVariable($values[$i], $variableType, false);
1150 $statusStack[count($statusStack) - 1] = $status = 'variable';
1153 case 'object method':
1154 case 'left parenthesis':
1155 $result .= $this->compileSimpleVariable($values[$i], $variableType);
1156 $statusStack[] = $status = $variableType;
1159 case 'bracket open':
1160 $result .= $this->compileSimpleVariable($values[$i], $variableType, false);
1161 $statusStack[] = $status = $variableType;
1165 $result .= $this->compileSimpleVariable($values[$i], $variableType);
1166 $statusStack[count($statusStack) - 1] = $status = $variableType;
1169 case 'modifier end':
1170 $result .= $this->compileSimpleVariable($values[$i], $variableType);
1171 $statusStack[] = $status = $variableType;
1175 if (strpos($values[$i], '$') !== false ||
strpos($values[$i], '@@') !== false) {
1176 throw new SystemException($this->formatSyntaxError("unknown modifier '".$values[$i]."'", $this->currentIdentifier
, $this->currentLineNo
));
1179 // handle modifier name
1180 $modifierData['name'] = $values[$i];
1181 $className = $this->template
->getPluginClassName('modifier', $modifierData['name']);
1182 if (class_exists($className)) {
1183 $modifierData['className'] = $className;
1184 $this->autoloadPlugins
[$modifierData['className']] = $modifierData['className'];
1186 else if ((!function_exists($modifierData['name']) && !in_array($modifierData['name'], $this->unknownPHPFunctions
)) ||
in_array($modifierData['name'], $this->disabledPHPFunctions
)) {
1187 throw new SystemException($this->formatSyntaxError("unknown modifier '".$values[$i]."'", $this->currentIdentifier
, $this->currentLineNo
));
1190 $statusStack[count($statusStack) - 1] = $status = 'modifier end';
1197 throw new SystemException($this->formatSyntaxError('unknown tag {'.$tag.'}', $this->currentIdentifier
, $this->currentLineNo
));
1203 if ($operator !== null) {
1204 switch ($operator) {
1206 if ($status == 'variable' ||
$status == 'object') {
1207 if ($status == 'object') $statusStack[count($statusStack) - 1] = 'variable';
1209 $statusStack[] = 'dot access';
1213 throw new SystemException($this->formatSyntaxError("unexpected '.' in tag '".$tag."'", $this->currentIdentifier
, $this->currentLineNo
));
1218 if ($status == 'variable' ||
$status == 'object') {
1219 $result .= $operator;
1220 $statusStack[count($statusStack) - 1] = 'object access';
1224 throw new SystemException($this->formatSyntaxError("unexpected '->' in tag '".$tag."'", $this->currentIdentifier
, $this->currentLineNo
));
1229 if ($status == 'object') {
1230 $statusStack[count($statusStack) - 1] = 'variable';
1231 $statusStack[] = 'object method start';
1232 $result .= $operator;
1235 else if ($status == 'math' ||
$status == 'start' ||
$status == 'left parenthesis' ||
$status == 'bracket open' ||
$status == 'modifier end') {
1236 if ($status == 'start') $statusStack[count($statusStack) - 1] = 'constant';
1237 $statusStack[] = 'left parenthesis';
1238 $result .= $operator;
1242 throw new SystemException($this->formatSyntaxError("unexpected '(' in tag '".$tag."'", $this->currentIdentifier
, $this->currentLineNo
));
1245 // right parenthesis
1247 while ($oldStatus = array_pop($statusStack)) {
1248 if ($oldStatus != 'variable' && $oldStatus != 'object' && $oldStatus != 'constant' && $oldStatus != 'string') {
1249 if ($oldStatus == 'object method start' ||
$oldStatus == 'object method' ||
$oldStatus == 'left parenthesis') {
1250 $result .= $operator;
1257 throw new SystemException($this->formatSyntaxError("unexpected ')' in tag '".$tag."'", $this->currentIdentifier
, $this->currentLineNo
));
1262 if ($status == 'variable' ||
$status == 'object') {
1263 if ($status == 'object') $statusStack[count($statusStack) - 1] = 'variable';
1264 $statusStack[] = 'bracket open';
1265 $result .= $operator;
1269 throw new SystemException($this->formatSyntaxError("unexpected '[' in tag '".$tag."'", $this->currentIdentifier
, $this->currentLineNo
));
1274 while ($oldStatus = array_pop($statusStack)) {
1275 if ($oldStatus != 'variable' && $oldStatus != 'object' && $oldStatus != 'constant' && $oldStatus != 'string') {
1276 if ($oldStatus == 'bracket open') {
1277 $result .= $operator;
1284 throw new SystemException($this->formatSyntaxError("unexpected ']' in tag '".$tag."'", $this->currentIdentifier
, $this->currentLineNo
));
1289 // handle previous modifier
1290 if ($modifierData !== null) {
1291 if ($result !== '') $modifierData['parameter'][] = $result;
1292 $result = $this->compileModifier($modifierData);
1295 // clear status stack
1296 while ($oldStatus = array_pop($statusStack)) {
1297 if ($oldStatus != 'variable' && $oldStatus != 'object' && $oldStatus != 'constant' && $oldStatus != 'string' && $oldStatus != 'modifier end') {
1298 throw new SystemException($this->formatSyntaxError("unexpected '|' in tag '".$tag."'", $this->currentIdentifier
, $this->currentLineNo
));
1302 $statusStack = array(0 => 'modifier');
1303 $modifierData = array('name' => '', 'parameter' => array(0 => $result));
1307 // modifier parameter
1309 while ($oldStatus = array_pop($statusStack)) {
1310 if ($oldStatus != 'variable' && $oldStatus != 'object' && $oldStatus != 'constant' && $oldStatus != 'string') {
1311 if ($oldStatus == 'modifier end') {
1312 $statusStack[] = 'modifier end';
1313 if ($result !== '') $modifierData['parameter'][] = $result;
1321 throw new SystemException($this->formatSyntaxError("unexpected ':' in tag '".$tag."'", $this->currentIdentifier
, $this->currentLineNo
));
1325 while ($oldStatus = array_pop($statusStack)) {
1326 if ($oldStatus != 'variable' && $oldStatus != 'object' && $oldStatus != 'constant' && $oldStatus != 'string') {
1327 if ($oldStatus == 'object method') {
1328 $result .= $operator;
1329 $statusStack[] = 'object method';
1330 $statusStack[] = 'object method parameter separator';
1337 throw new SystemException($this->formatSyntaxError("unexpected ',' in tag '".$tag."'", $this->currentIdentifier
, $this->currentLineNo
));
1347 if ($status == 'variable' ||
$status == 'object' ||
$status == 'constant' ||
$status == 'string' ||
$status == 'modifier end') {
1348 $result .= $operator;
1349 $statusStack[count($statusStack) - 1] = 'math';
1353 throw new SystemException($this->formatSyntaxError("unexpected '".$operator."' in tag '".$tag."'", $this->currentIdentifier
, $this->currentLineNo
));
1359 // handle open modifier
1360 if ($modifierData !== null) {
1361 if ($result !== '') $modifierData['parameter'][] = $result;
1362 $result = $this->compileModifier($modifierData);
1365 // reinserts strings
1366 $result = $this->reinsertQuotes($result);
1367 $result = $this->reinsertConstants($result);
1373 * Generates the regexp pattern.
1375 protected function buildPattern() {
1376 $this->variableOperatorPattern
= '\-\>|\.|\(|\)|\[|\]|\||\:|\+|\-|\*|\/|\%|\^|\,';
1377 $this->conditionOperatorPattern
= '===|!==|==|!=|<=|<|>=|(?<!-)>|\|\||&&|!|=';
1378 $this->escapedPattern
= '(?<!\\\\)';
1379 $this->validVarnamePattern
= '(?:[a-zA-Z_][a-zA-Z_0-9]*)';
1380 $this->constantPattern
= '(?:[A-Z_][A-Z_0-9]*)';
1381 $this->doubleQuotePattern
= '"(?:[^"\\\\]+|\\\\.)*"';
1382 $this->singleQuotePattern
= '\'(?:[^\'\\\\]+|\\\\.)*\'';
1383 $this->quotePattern
= '(?:' . $this->doubleQuotePattern
. '|' . $this->singleQuotePattern
. ')';
1384 $this->numericPattern
= '(?i)(?:(?:\-?\d+(?:\.\d+)?)|true|false|null)';
1385 $this->simpleVarPattern
= '(?:\$('.$this->validVarnamePattern
.'))';
1386 $this->outputPattern
= '(?:(?:@|#)?(?:'.$this->constantPattern
.'|'.$this->quotePattern
.'|'.$this->numericPattern
.'|'.$this->simpleVarPattern
.'|\())';
1390 * Returns the instance of the template engine class.
1392 * @return wcf\system\templateTemplateEngine
1394 public function getTemplate() {
1395 return $this->template
;
1399 * Returns the left delimiter for template tags.
1403 public function getLeftDelimiter() {
1404 return $this->leftDelimiter
;
1408 * Returns the right delimiter for template tags.
1412 public function getRightDelimiter() {
1413 return $this->rightDelimiter
;
1417 * Returns the name of the current template.
1421 public function getCurrentIdentifier() {
1422 return $this->currentIdentifier
;
1426 * Returns the current line number.
1430 public function getCurrentLineNo() {
1431 return $this->currentLineNo
;
1435 * Applies the prefilters to the given string.
1437 * @param string $templateName
1438 * @param string $string
1441 public function applyPrefilters($templateName, $string) {
1442 foreach ($this->template
->getPrefilters() as $prefilter) {
1443 if (!is_object($prefilter)) {
1444 $className = $this->template
->getPluginClassName('prefilter', $prefilter);
1445 if (!class_exists($className)) {
1446 throw new SystemException($this->formatSyntaxError('unable to find prefilter class '.$className, $this->currentIdentifier
));
1448 $prefilter = new $className();
1451 if ($prefilter instanceof IPrefilterTemplatePlugin
) {
1452 $string = $prefilter->execute($templateName, $string, $this);
1455 throw new SystemException($this->formatSyntaxError("Prefilter '".(is_object($prefilter) ?
get_class($prefilter) : $prefilter)."' does not implement the interface 'IPrefilterTemplatePlugin'", $this->currentIdentifier
));
1463 * Replaces all {literal} Tags with unique hash values.
1465 * @param string $string
1468 public function replaceLiterals($string) {
1469 return preg_replace_callback("~".$this->ldq
."literal".$this->rdq
."(.*?)".$this->ldq
."/literal".$this->rdq
."~s", array($this, 'replaceLiteralsCallback'), $string);
1473 * Reinserts the literal tags.
1475 * @param string $string
1478 public function reinsertLiterals($string) {
1479 return StringStack
::reinsertStrings($string, 'literal');
1483 * Callback function used in replaceLiterals()
1485 private function replaceLiteralsCallback($matches) {
1486 return StringStack
::pushToStringStack($matches[1], 'literal');
1490 * Removes template comments
1492 * @param string $string
1495 public function removeComments($string) {
1496 return preg_replace("~".$this->ldq
."\*.*?\*".$this->rdq
."~s", '', $string);
1500 * Replaces all quotes with unique hash values.
1502 * @param string $string
1505 public function replaceQuotes($string) {
1506 $string = preg_replace_callback('~\'([^\'\\\\]+|\\\\.)*\'~', array($this, 'replaceSingleQuotesCallback'), $string);
1507 $string = preg_replace_callback('~"([^"\\\\]+|\\\\.)*"~', array($this, 'replaceDoubleQuotesCallback'), $string);
1513 * Callback function used in replaceQuotes()
1515 private function replaceSingleQuotesCallback($matches) {
1516 return StringStack
::pushToStringStack($matches[0], 'singleQuote');
1520 * Callback function used in replaceQuotes()
1522 private function replaceDoubleQuotesCallback($matches) {
1523 // parse unescaped simple vars in double quotes
1524 // replace $foo with {$this->v['foo']}
1525 $matches[0] = preg_replace('~'.$this->escapedPattern
.$this->simpleVarPattern
.'~', '{$this->v[\'\\1\']}', $matches[0]);
1526 return StringStack
::pushToStringStack($matches[0], 'doubleQuote');
1530 * Reinserts the quotes.
1532 * @param string $string
1535 public function reinsertQuotes($string) {
1536 $string = StringStack
::reinsertStrings($string, 'singleQuote');
1537 $string = StringStack
::reinsertStrings($string, 'doubleQuote');
1543 * Replaces all constants with unique hash values.
1545 * @param string $string
1548 public function replaceConstants($string) {
1549 return preg_replace_callback('~(?<=^|'.$this->variableOperatorPattern
.')(?i)((?:\-?\d+(?:\.\d+)?)|true|false|null)(?=$|'.$this->variableOperatorPattern
.')~', array($this, 'replaceConstantsCallback'), $string);
1553 * Callback function used in replaceConstants()
1555 private function replaceConstantsCallback($matches) {
1556 return StringStack
::pushToStringStack($matches[1], 'constants');
1560 * Reinserts the constants.
1562 * @param string $string
1565 public function reinsertConstants($string) {
1566 return StringStack
::reinsertStrings($string, 'constants');
1570 * Replaces all php tags.
1572 * @param string $string
1575 public function replacePHPTags($string) {
1576 if (StringUtil
::indexOf($string, '<?') !== false) {
1577 $string = StringUtil
::replace('<?php', '@@PHP_START_TAG@@', $string);
1578 $string = StringUtil
::replace('<?', '@@PHP_SHORT_START_TAG@@', $string);
1579 $string = StringUtil
::replace('?>', '@@PHP_END_TAG@@', $string);
1580 $string = StringUtil
::replace('@@PHP_END_TAG@@', "<?php echo '?>'; ?>\n", $string);
1581 $string = StringUtil
::replace('@@PHP_SHORT_START_TAG@@', "<?php echo '<?'; ?>\n", $string);
1582 $string = StringUtil
::replace('@@PHP_START_TAG@@', "<?php echo '<?php'; ?>\n", $string);