PageRenderTime 27ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 1ms

/wcfsetup/install/files/lib/system/template/TemplateScriptingCompiler.class.php

https://github.com/KomHunter2/WCF
PHP | 1505 lines | 882 code | 205 blank | 418 comment | 301 complexity | 9492c5f651912c4e334a299a1cc75bac MD5 | raw file
  1. <?php
  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;
  8. /**
  9. * TemplateScriptingCompiler compiles template source in valid php code.
  10. *
  11. * @author Marcel Werk
  12. * @copyright 2001-2009 WoltLab GmbH
  13. * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  14. * @package com.woltlab.wcf
  15. * @subpackage system.template
  16. * @category Community Framework
  17. */
  18. class TemplateScriptingCompiler {
  19. /**
  20. * template engine object
  21. * @var wcf\system\templateTemplateEngine
  22. */
  23. protected $template;
  24. /**
  25. * PHP functions that can be used in the modifier syntax and are unknown
  26. * to the function_exists PHP method
  27. * @var array<string>
  28. */
  29. protected $unknownPHPFunctions = array('isset', 'unset', 'empty');
  30. /**
  31. * PHP functions that can not be used in the modifier syntax
  32. * @var array<string>
  33. */
  34. protected $disabledPHPFunctions = array(
  35. 'system', 'exec', 'passthru', 'shell_exec', // command line execution
  36. 'include', 'require', 'include_once', 'require_once', // includes
  37. 'eval', 'virtual', 'call_user_func_array', 'call_user_func', 'assert' // code execution
  38. );
  39. /**
  40. * pattern to match variable operators like -> or .
  41. * @var string
  42. */
  43. protected $variableOperatorPattern;
  44. /**
  45. * pattern to match condition operators like == or <
  46. * @var string
  47. */
  48. protected $conditionOperatorPattern;
  49. /**
  50. * negative lookbehind for a backslash
  51. * @var string
  52. */
  53. protected $escapedPattern;
  54. /**
  55. * pattern to match valid variable names
  56. * @var string
  57. */
  58. protected $validVarnamePattern;
  59. /**
  60. * pattern to match constants like CONSTANT or __CONSTANT
  61. * @var string
  62. */
  63. protected $constantPattern;
  64. /**
  65. * pattern to match double quoted strings like "blah" or "quote: \"blah\""
  66. * @var string
  67. */
  68. protected $doubleQuotePattern;
  69. /**
  70. * pattern to match single quoted strings like 'blah' or 'don\'t'
  71. * @var string
  72. */
  73. protected $singleQuotePattern;
  74. /**
  75. * pattern to match single or double quoted strings
  76. * @var string
  77. */
  78. protected $quotePattern;
  79. /**
  80. * pattern to match numbers, true, false and null
  81. * @var string
  82. */
  83. protected $numericPattern;
  84. /**
  85. * pattern to match simple variables like $foo
  86. * @var string
  87. */
  88. protected $simpleVarPattern;
  89. /**
  90. * pattern to match outputs like @$foo or #CONST
  91. * @var string
  92. */
  93. protected $outputPattern;
  94. /**
  95. * identifier of currently compiled template
  96. * @var string
  97. */
  98. protected $currentIdentifier;
  99. /**
  100. * current line number during template compilation
  101. * @var string
  102. */
  103. protected $currentLineNo;
  104. protected $modifiers = array();
  105. /**
  106. * list of automatically loaded tenplate plugins
  107. * @var array<string>
  108. */
  109. protected $autoloadPlugins = array();
  110. /**
  111. * stack with template tags data
  112. * @var array
  113. */
  114. protected $tagStack = array();
  115. /**
  116. * list of loaded compiler plugin objects
  117. * @var array<wcf\system\template\ICompilerTemplatePlugin>
  118. */
  119. protected $compilerPlugins = array();
  120. /**
  121. * stack used to compile the capture tag
  122. * @var array
  123. */
  124. protected $captureStack = array();
  125. /**
  126. * left delimiter of template syntax
  127. * @var string
  128. */
  129. protected $leftDelimiter = '{';
  130. /**
  131. * right delimiter of template syntax
  132. * @var string
  133. */
  134. protected $rightDelimiter = '}';
  135. /**
  136. * left delimiter of template syntax used in regular expressions
  137. * @var string
  138. */
  139. protected $ldq;
  140. /**
  141. * right delimiter of template syntax used in regular expressions
  142. * @var string
  143. */
  144. protected $rdq;
  145. /**
  146. * Creates a new TemplateScriptingCompiler object.
  147. *
  148. * @param wcf\system\templateTemplateEngine $template
  149. */
  150. public function __construct(TemplateEngine $template) {
  151. $this->template = $template;
  152. // quote left and right delimiter for use in regular expressions
  153. $this->ldq = preg_quote($this->leftDelimiter, '~').'(?=\S)';
  154. $this->rdq = '(?<=\S)'.preg_quote($this->rightDelimiter, '~');
  155. // build regular expressions
  156. $this->buildPattern();
  157. }
  158. /**
  159. * Compiles the source of a template.
  160. *
  161. * @param string $identifier
  162. * @param string $sourceContent
  163. * @return string
  164. */
  165. public function compileString($identifier, $sourceContent) {
  166. // reset vars
  167. $this->autoloadPlugins = $this->tagStack = $this->stringStack = $this->literalStack = array();
  168. $this->currentIdentifier = $identifier;
  169. $this->currentLineNo = 1;
  170. // apply prefilters
  171. $sourceContent = $this->applyPrefilters($identifier, $sourceContent);
  172. // replace all {literal} Tags with unique hash values
  173. $sourceContent = $this->replaceLiterals($sourceContent);
  174. // handle <?php tags
  175. $sourceContent = $this->replacePHPTags($sourceContent);
  176. // remove comments
  177. $sourceContent = $this->removeComments($sourceContent);
  178. // match all template tags
  179. $matches = array();
  180. preg_match_all("~".$this->ldq."(.*?)".$this->rdq."~s", $sourceContent, $matches);
  181. $templateTags = $matches[1];
  182. // Split content by template tags to obtain non-template content
  183. $textBlocks = preg_split("~".$this->ldq.".*?".$this->rdq."~s", $sourceContent);
  184. // compile the template tags into php-code
  185. $compiledTags = array();
  186. for ($i = 0, $j = count($templateTags); $i < $j; $i++) {
  187. $this->currentLineNo += StringUtil::countSubstring($textBlocks[$i], "\n");
  188. $compiledTags[] = $this->compileTag($templateTags[$i]);
  189. $this->currentLineNo += StringUtil::countSubstring($templateTags[$i], "\n");
  190. }
  191. // throw error messages for unclosed tags
  192. if (count($this->tagStack) > 0) {
  193. foreach ($this->tagStack as $tagStack) {
  194. throw new SystemException($this->formatSyntaxError('unclosed tag {'.$tagStack[0].'}', $this->currentIdentifier, $tagStack[1]));
  195. }
  196. return false;
  197. }
  198. $compiledContent = '';
  199. // Interleave the compiled contents and text blocks to get the final result.
  200. for ($i = 0, $j = count($compiledTags); $i < $j; $i++) {
  201. if ($compiledTags[$i] == '') {
  202. // tag result empty, remove first newline from following text block
  203. $textBlocks[$i + 1] = preg_replace('%^(\r\n|\r|\n)%', '', $textBlocks[$i + 1]);
  204. }
  205. $compiledContent .= $textBlocks[$i].$compiledTags[$i];
  206. }
  207. $compiledContent .= $textBlocks[$i];
  208. $compiledContent = chop($compiledContent);
  209. // INSERT POSTFILTERS HERE!
  210. // reinsert {literal} Tags
  211. $compiledContent = $this->reinsertLiterals($compiledContent);
  212. // include Plugins
  213. $compiledAutoloadPlugins = '';
  214. if (count($this->autoloadPlugins) > 0) {
  215. $compiledAutoloadPlugins = "<?php\n";
  216. foreach ($this->autoloadPlugins as $className/* => $fileName*/) {
  217. $compiledAutoloadPlugins .= "use ".$className.";\n";
  218. $compiledAutoloadPlugins .= "if (!isset(\$this->pluginObjects['$className'])) {\n";
  219. /*
  220. if (WCF_DIR != '' && strpos($fileName, WCF_DIR) === 0) {
  221. $compiledAutoloadPlugins .= "require_once(WCF_DIR.'".StringUtil::replace(WCF_DIR, '', $fileName)."');\n";
  222. }
  223. else {
  224. $compiledAutoloadPlugins .= "require_once('".$fileName."');\n";
  225. }
  226. */
  227. $compiledAutoloadPlugins .= "\$this->pluginObjects['$className'] = new $className;\n";
  228. $compiledAutoloadPlugins .= "}\n";
  229. }
  230. $compiledAutoloadPlugins .= "?>";
  231. }
  232. return $compiledAutoloadPlugins.$compiledContent;
  233. }
  234. /**
  235. * Compiles a template tag.
  236. *
  237. * @param string $tag
  238. */
  239. protected function compileTag($tag) {
  240. if (preg_match('~^'.$this->outputPattern.'~s', $tag)) {
  241. // variable output
  242. return $this->compileOutputTag($tag);
  243. }
  244. $match = array();
  245. // replace 'else if' with 'elseif'
  246. $tag = preg_replace('~^else\s+if(?=\s)~i', 'elseif', $tag);
  247. if (preg_match('~^(/?\w+)~', $tag, $match)) {
  248. // build in function or plugin
  249. $tagCommand = $match[1];
  250. $tagArgs = StringUtil::substring($tag, StringUtil::length($tagCommand));
  251. switch ($tagCommand) {
  252. case 'if':
  253. $this->pushTag('if');
  254. return $this->compileIfTag($tagArgs);
  255. case 'elseif':
  256. list($openTag) = end($this->tagStack);
  257. if ($openTag != 'if' && $openTag != 'elseif') {
  258. throw new SystemException($this->formatSyntaxError('unxepected {elseif}', $this->currentIdentifier, $this->currentLineNo));
  259. }
  260. else if ($openTag == 'if') {
  261. $this->pushTag('elseif');
  262. }
  263. return $this->compileIfTag($tagArgs, true);
  264. case 'else':
  265. list($openTag) = end($this->tagStack);
  266. if ($openTag != 'if' && $openTag != 'elseif') {
  267. throw new SystemException($this->formatSyntaxError('unexpected {else}', $this->currentIdentifier, $this->currentLineNo));
  268. }
  269. else {
  270. $this->pushTag('else');
  271. return '<?php } else { ?>';
  272. }
  273. case '/if':
  274. list($openTag) = end($this->tagStack);
  275. if ($openTag != 'if' && $openTag != 'elseif' && $openTag != 'else') {
  276. throw new SystemException($this->formatSyntaxError('unexpected {/if}', $this->currentIdentifier, $this->currentLineNo));
  277. }
  278. else {
  279. $this->popTag('if');
  280. }
  281. return '<?php } ?>';
  282. case 'include':
  283. return $this->compileIncludeTag($tagArgs);
  284. case 'foreach':
  285. $this->pushTag('foreach');
  286. return $this->compileForeachTag($tagArgs);
  287. case 'foreachelse':
  288. list($openTag) = end($this->tagStack);
  289. if ($openTag != 'foreach') {
  290. throw new SystemException($this->formatSyntaxError('unexpected {foreachelse}', $this->currentIdentifier, $this->currentLineNo));
  291. }
  292. else {
  293. $this->pushTag('foreachelse');
  294. return '<?php } } else { { ?>';
  295. }
  296. case '/foreach':
  297. $this->popTag('foreach');
  298. return "<?php } } ?>";
  299. case 'section':
  300. $this->pushTag('section');
  301. return $this->compileSectionTag($tagArgs);
  302. case 'sectionelse':
  303. list($openTag) = end($this->tagStack);
  304. if ($openTag != 'section') {
  305. throw new SystemException($this->formatSyntaxError('unexpected {sectionelse}', $this->currentIdentifier, $this->currentLineNo));
  306. }
  307. else {
  308. $this->pushTag('sectionelse');
  309. return '<?php } } else { { ?>';
  310. }
  311. case '/section':
  312. $this->popTag('section');
  313. return "<?php } } ?>";
  314. case 'capture':
  315. $this->pushTag('capture');
  316. return $this->compileCaptureTag(true, $tagArgs);
  317. case '/capture':
  318. $this->popTag('capture');
  319. return $this->compileCaptureTag(false);
  320. case 'ldelim':
  321. return $this->leftDelimiter;
  322. case 'rdelim':
  323. return $this->rightDelimiter;
  324. default:
  325. // 1) compiler functions first
  326. if ($phpCode = $this->compileCompilerPlugin($tagCommand, $tagArgs)) {
  327. return $phpCode;
  328. }
  329. // 2) block functions
  330. if ($phpCode = $this->compileBlockPlugin($tagCommand, $tagArgs)) {
  331. return $phpCode;
  332. }
  333. // 3) functions
  334. if ($phpCode = $this->compileFunctionPlugin($tagCommand, $tagArgs)) {
  335. return $phpCode;
  336. }
  337. }
  338. }
  339. throw new SystemException($this->formatSyntaxError('unknown tag {'.$tag.'}', $this->currentIdentifier, $this->currentLineNo));
  340. }
  341. /**
  342. * Compiles a function plugin.
  343. *
  344. * @param string $tagCommand
  345. * @param string $tagArgs
  346. * @return mixed false, if the plugin does not exist
  347. * otherwise the php output of the plugin
  348. */
  349. protected function compileFunctionPlugin($tagCommand, $tagArgs) {
  350. $className = $this->template->getPluginClassName('function', $tagCommand);
  351. if (!class_exists($className)) {
  352. return false;
  353. }
  354. $this->autoloadPlugins[$className] = $className;
  355. $tagArgs = $this->makeArgString($this->parseTagArgs($tagArgs, $tagCommand));
  356. return "<?php echo \$this->pluginObjects['".$className."']->execute(array(".$tagArgs."), \$this); ?>";
  357. }
  358. /**
  359. * Compiles a block plugin.
  360. *
  361. * @param string $tagCommand
  362. * @param string $tagArgs
  363. * @return mixed false, if the plugin does not exist
  364. * otherwise the php output of the plugin
  365. */
  366. protected function compileBlockPlugin($tagCommand, $tagArgs) {
  367. // check wheater this is the start ({block}) or the
  368. // end tag ({/block})
  369. if (substr($tagCommand, 0, 1) == '/') {
  370. $tagCommand = substr($tagCommand, 1);
  371. $startTag = false;
  372. }
  373. else {
  374. $startTag = true;
  375. }
  376. $className = $this->template->getPluginClassName('block', $tagCommand);
  377. if (!class_exists($className)) {
  378. return false;
  379. }
  380. $this->autoloadPlugins[$className] = $className;
  381. if ($startTag) {
  382. $this->pushTag($tagCommand);
  383. $tagArgs = $this->makeArgString($this->parseTagArgs($tagArgs, $tagCommand));
  384. $phpCode = "<?php \$this->tagStack[] = array('".$tagCommand."', array(".$tagArgs."));\n";
  385. $phpCode .= "\$this->pluginObjects['".$className."']->init(\$this->tagStack[count(\$this->tagStack) - 1][1], \$this);\n";
  386. $phpCode .= "while (\$this->pluginObjects['".$className."']->next(\$this)) { ob_start(); ?>";
  387. }
  388. else {
  389. $this->popTag($tagCommand);
  390. $phpCode = "<?php \$blockContent = ob_get_contents();\n";
  391. $phpCode .= "ob_end_clean();\n";
  392. $phpCode .= "echo \$this->pluginObjects['".$className."']->execute(\$this->tagStack[count(\$this->tagStack) - 1][1], \$blockContent, \$this); }\n";
  393. $phpCode .= "array_pop(\$this->tagStack);\n";
  394. $phpCode .= "unset(\$blockContent, \$blockRepeat); ?>";
  395. }
  396. return $phpCode;
  397. }
  398. /**
  399. * Compiles a compiler function/block.
  400. *
  401. * @param string $tagCommand
  402. * @param string $tagArgs
  403. * @return mixed false, if the plugin does not exist
  404. * otherwise the php output of the plugin
  405. */
  406. protected function compileCompilerPlugin($tagCommand, $tagArgs) {
  407. // check wheater this is the start ({block}) or the
  408. // end tag ({/block})
  409. if (substr($tagCommand, 0, 1) == '/') {
  410. $tagCommand = substr($tagCommand, 1);
  411. $startTag = false;
  412. }
  413. else {
  414. $startTag = true;
  415. }
  416. $className = $this->template->getPluginClassName('compiler', $tagCommand);
  417. // if necessary load plugin from plugin-dir
  418. if (!isset($this->compilerPlugins[$className])) {
  419. if (!class_exists($className)) {
  420. return false;
  421. }
  422. $this->compilerPlugins[$className] = new $className();
  423. if (!($this->compilerPlugins[$className] instanceof ICompilerTemplatePlugin)) {
  424. throw new SystemException($this->formatSyntaxError("Compiler plugin '".$tagCommand."' does not implement the interface 'ICompilerTemplatePlugin'", $this->currentIdentifier));
  425. }
  426. }
  427. // execute plugin
  428. if ($startTag) {
  429. $tagArgs = $this->parseTagArgs($tagArgs, $tagCommand);
  430. $phpCode = $this->compilerPlugins[$className]->executeStart($tagArgs, $this);
  431. }
  432. else {
  433. $phpCode = $this->compilerPlugins[$className]->executeEnd($this);
  434. }
  435. return $phpCode;
  436. }
  437. /**
  438. * Compiles a capture tag.
  439. *
  440. * @param boolean $startTag
  441. * @param string $captureTag
  442. * @return string phpCode
  443. */
  444. protected function compileCaptureTag($startTag, $captureTag = null) {
  445. if ($startTag) {
  446. $append = false;
  447. $args = $this->parseTagArgs($captureTag, 'capture');
  448. if (!isset($args['name'])) {
  449. $args['name'] = "'default'";
  450. }
  451. if (!isset($args['assign'])) {
  452. if (isset($args['append'])) {
  453. $args['assign'] = $args['append'];
  454. $append = true;
  455. }
  456. else {
  457. $args['assign'] = '';
  458. }
  459. }
  460. $this->captureStack[] = array('name' => $args['name'], 'variable' => $args['assign'], 'append' => $append);
  461. return '<?php ob_start(); ?>';
  462. }
  463. else {
  464. $capture = array_pop($this->captureStack);
  465. $phpCode = "<?php\n";
  466. $phpCode .= "\$this->v['tpl']['capture'][".$capture['name']."] = ob_get_contents();\nob_end_clean();\n";
  467. if (!empty($capture['variable'])) $phpCode .= "\$this->".($capture['append'] ? 'append' : 'assign')."(".$capture['variable'].", \$this->v['tpl']['capture'][".$capture['name']."]);\n";
  468. $phpCode .= "?>";
  469. return $phpCode;
  470. }
  471. }
  472. /**
  473. * Compiles a section tag.
  474. *
  475. * @param string $sectionTag
  476. * @return string phpCode
  477. */
  478. protected function compileSectionTag($sectionTag) {
  479. $args = $this->parseTagArgs($sectionTag, 'section');
  480. // check arguments
  481. if (!isset($args['loop'])) {
  482. throw new SystemException($this->formatSyntaxError("missing 'loop' attribute in section tag", $this->currentIdentifier, $this->currentLineNo));
  483. }
  484. if (!isset($args['name'])) {
  485. throw new SystemException($this->formatSyntaxError("missing 'name' attribute in section tag", $this->currentIdentifier, $this->currentLineNo));
  486. }
  487. if (!isset($args['show'])) {
  488. $args['show'] = true;
  489. }
  490. $sectionProp = "\$this->v['tpl']['section'][".$args['name']."]";
  491. $phpCode = "<?php\n";
  492. $phpCode .= "if (".$args['loop'].") {\n";
  493. $phpCode .= $sectionProp." = array();\n";
  494. $phpCode .= $sectionProp."['loop'] = (is_array(".$args['loop'].") ? count(".$args['loop'].") : max(0, (int)".$args['loop']."));\n";
  495. $phpCode .= $sectionProp."['show'] = ".$args['show'].";\n";
  496. if (!isset($args['step'])) {
  497. $phpCode .= $sectionProp."['step'] = 1;\n";
  498. }
  499. else {
  500. $phpCode .= $sectionProp."['step'] = ".$args['step'].";\n";
  501. }
  502. if (!isset($args['max'])) {
  503. $phpCode .= $sectionProp."['max'] = ".$sectionProp."['loop'];\n";
  504. }
  505. else {
  506. $phpCode .= $sectionProp."['max'] = (".$args['max']." < 0 ? ".$sectionProp."['loop'] : ".$args['max'].");\n";
  507. }
  508. if (!isset($args['start'])) {
  509. $phpCode .= $sectionProp."['start'] = (".$sectionProp."['step'] > 0 ? 0 : ".$sectionProp."['loop'] - 1);\n";
  510. }
  511. else {
  512. $phpCode .= $sectionProp."['start'] = ".$args['start'].";\n";
  513. $phpCode .= "if (".$sectionProp."['start'] < 0) {\n";
  514. $phpCode .= $sectionProp."['start'] = max(".$sectionProp."['step'] > 0 ? 0 : -1, ".$sectionProp."['loop'] + ".$sectionProp."['start']);\n}\n";
  515. $phpCode .= "else {\n";
  516. $phpCode .= $sectionProp."['start'] = min(".$sectionProp."['start'], ".$sectionProp."['step'] > 0 ? ".$sectionProp."['loop'] : ".$sectionProp."['loop'] - 1);\n}\n";
  517. }
  518. if (!isset($args['start']) && !isset($args['step']) && !isset($args['max'])) {
  519. $phpCode .= $sectionProp."['total'] = ".$sectionProp."['loop'];\n";
  520. } else {
  521. $phpCode .= $sectionProp."['total'] = min(ceil((".$sectionProp."['step'] > 0 ? ".$sectionProp."['loop'] - ".$sectionProp."['start'] : ".$sectionProp."['start'] + 1) / abs(".$sectionProp."['step'])), ".$sectionProp."['max']);\n";
  522. }
  523. $phpCode .= "if (".$sectionProp."['total'] == 0) ".$sectionProp."['show'] = false;\n";
  524. $phpCode .= "} else {\n";
  525. $phpCode .= "".$sectionProp."['total'] = 0;\n";
  526. $phpCode .= "".$sectionProp."['show'] = false;}\n";
  527. $phpCode .= "if (".$sectionProp."['show']) {\n";
  528. $phpCode .= "for (".$sectionProp."['index'] = ".$sectionProp."['start'], ".$sectionProp."['rowNumber'] = 1;\n";
  529. $phpCode .= $sectionProp."['rowNumber'] <= ".$sectionProp."['total'];\n";
  530. $phpCode .= $sectionProp."['index'] += ".$sectionProp."['step'], ".$sectionProp."['rowNumber']++) {\n";
  531. $phpCode .= "\$this->v[".$args['name']."] = ".$sectionProp."['index'];\n";
  532. $phpCode .= $sectionProp."['previousIndex'] = ".$sectionProp."['index'] - ".$sectionProp."['step'];\n";
  533. $phpCode .= $sectionProp."['nextIndex'] = ".$sectionProp."['index'] + ".$sectionProp."['step'];\n";
  534. $phpCode .= $sectionProp."['first'] = (".$sectionProp."['rowNumber'] == 1);\n";
  535. $phpCode .= $sectionProp."['last'] = (".$sectionProp."['rowNumber'] == ".$sectionProp."['total']);\n";
  536. $phpCode .= "?>";
  537. return $phpCode;
  538. }
  539. /**
  540. * Compiles a foreach tag.
  541. *
  542. * @param string $foreachTag
  543. * @return string phpCode
  544. */
  545. protected function compileForeachTag($foreachTag) {
  546. $args = $this->parseTagArgs($foreachTag, 'foreach');
  547. // check arguments
  548. if (!isset($args['from'])) {
  549. throw new SystemException($this->formatSyntaxError("missing 'from' attribute in foreach tag", $this->currentIdentifier, $this->currentLineNo));
  550. }
  551. if (!isset($args['item'])) {
  552. throw new SystemException($this->formatSyntaxError("missing 'item' attribute in foreach tag", $this->currentIdentifier, $this->currentLineNo));
  553. }
  554. $foreachProp = '';
  555. if (isset($args['name'])) {
  556. $foreachProp = "\$this->v['tpl']['foreach'][".$args['name']."]";
  557. }
  558. $phpCode = "<?php\n";
  559. if (!empty($foreachProp)) {
  560. $phpCode .= $foreachProp."['total'] = count(".$args['from'].");\n";
  561. $phpCode .= $foreachProp."['show'] = (".$foreachProp."['total'] > 0 ? true : false);\n";
  562. $phpCode .= $foreachProp."['iteration'] = 0;\n";
  563. }
  564. $phpCode .= "if (count(".$args['from'].") > 0) {\n";
  565. if (isset($args['key'])) {
  566. $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";
  567. }
  568. else {
  569. $phpCode .= "foreach (".$args['from']." as ".(StringUtil::substring($args['item'], 0, 1) != '$' ? "\$this->v[".$args['item']."]" : $args['item']).") {\n";
  570. }
  571. if (!empty($foreachProp)) {
  572. $phpCode .= $foreachProp."['first'] = (".$foreachProp."['iteration'] == 0 ? true : false);\n";
  573. $phpCode .= $foreachProp."['last'] = ((".$foreachProp."['iteration'] == ".$foreachProp."['total'] - 1) ? true : false);\n";
  574. $phpCode .= $foreachProp."['iteration']++;\n";
  575. }
  576. $phpCode .= "?>";
  577. return $phpCode;
  578. }
  579. /**
  580. * Compiles an include tag.
  581. *
  582. * @param string $includeTag
  583. * @return string phpCode
  584. */
  585. protected function compileIncludeTag($includeTag) {
  586. $args = $this->parseTagArgs($includeTag, 'include');
  587. $append = false;
  588. // check arguments
  589. if (!isset($args['file'])) {
  590. throw new SystemException($this->formatSyntaxError("missing 'file' attribute in include tag", $this->currentIdentifier, $this->currentLineNo));
  591. }
  592. // get filename
  593. $file = $args['file'];
  594. unset($args['file']);
  595. // special parameters
  596. $assignVar = false;
  597. if (isset($args['assign'])) {
  598. $assignVar = $args['assign'];
  599. unset($args['assign']);
  600. }
  601. if (isset($args['append'])) {
  602. $assignVar = $args['append'];
  603. $append = true;
  604. unset($args['append']);
  605. }
  606. $sandbox = true;
  607. if (isset($args['sandbox'])) {
  608. $sandbox = $args['sandbox'];
  609. unset($args['sandbox']);
  610. }
  611. $once = false;
  612. if (isset($args['once'])) {
  613. $once = $args['once'];
  614. unset($args['once']);
  615. }
  616. // make argument string
  617. $argString = $this->makeArgString($args);
  618. // build phpCode
  619. $phpCode = "<?php\n";
  620. if ($once) $phpCode .= "if (!isset(\$this->v['tpl']['includedTemplates'][".$file."])) {\n";
  621. $hash = StringUtil::getRandomID();
  622. $phpCode .= "\$outerTemplateName".$hash." = \$this->v['tpl']['template'];\n";
  623. if ($assignVar !== false) {
  624. $phpCode .= "ob_start();\n";
  625. }
  626. $phpCode .= '$this->includeTemplate('.$file.', array('.$argString.'), ('.$sandbox.' ? 1 : 0), $this->v[\'__PACKAGE_ID\']);'."\n";
  627. if ($assignVar !== false) {
  628. $phpCode .= '$this->'.($append ? 'append' : 'assign').'('.$assignVar.', ob_get_contents()); ob_end_clean();'."\n";
  629. }
  630. $phpCode .= "\$this->v['tpl']['template'] = \$outerTemplateName".$hash.";\n";
  631. $phpCode .= "\$this->v['tpl']['includedTemplates'][".$file."] = 1;\n";
  632. if ($once) $phpCode .= "}\n";
  633. $phpCode .= '?>';
  634. return $phpCode;
  635. }
  636. /**
  637. * Parses an argument list and returns
  638. * the keys and values in an associative array.
  639. *
  640. * @param string $tagArgs
  641. * @param string $tag
  642. * @return array $tagArgs
  643. */
  644. public function parseTagArgs($tagArgs, $tag) {
  645. // replace strings
  646. $tagArgs = $this->replaceQuotes($tagArgs);
  647. // validate tag arguments
  648. if (!preg_match('~^(?:\s+\w+\s*=\s*[^=]*(?=\s|$))*$~s', $tagArgs)) {
  649. throw new SystemException($this->formatSyntaxError('syntax error in tag {'.$tag.'}', $this->currentIdentifier, $this->currentLineNo));
  650. }
  651. // parse tag arguments
  652. $matches = array();
  653. // find all variables
  654. preg_match_all('~\s+(\w+)\s*=\s*([^=]*)(?=\s|$)~s', $tagArgs, $matches);
  655. $args = array();
  656. for ($i = 0, $j = count($matches[1]); $i < $j; $i++) {
  657. $name = $matches[1][$i];
  658. $string = $this->compileVariableTag($matches[2][$i], false);
  659. // reinserts strings
  660. foreach (StringStack::getStack('singleQuote') as $hash => $value) {
  661. if (StringUtil::indexOf($string, $hash) !== false) {
  662. $string = StringUtil::replace($hash, $value, $string);
  663. }
  664. }
  665. foreach (StringStack::getStack('doubleQuote') as $hash => $value) {
  666. if (StringUtil::indexOf($string, $hash) !== false) {
  667. $string = StringUtil::replace($hash, $value, $string);
  668. }
  669. }
  670. $args[$name] = $string;
  671. }
  672. // clear stack
  673. $this->reinsertQuotes('');
  674. return $args;
  675. }
  676. /**
  677. * Takes an array created by TemplateCompiler::parseTagArgs()
  678. * and creates a string.
  679. *
  680. * @param array $args
  681. * @return string $args
  682. */
  683. public static function makeArgString($args) {
  684. $argString = '';
  685. foreach ($args as $key => $val) {
  686. if ($argString != '') {
  687. $argString .= ', ';
  688. }
  689. $argString .= "'$key' => $val";
  690. }
  691. return $argString;
  692. }
  693. /**
  694. * Formats a syntax error message.
  695. *
  696. * @param string $errorMsg
  697. * @param string $file
  698. * @param integer $line
  699. * @return string formatted error message
  700. */
  701. public static function formatSyntaxError($errorMsg, $file = null, $line = null) {
  702. $errorMsg = 'Template compilation failed: '.$errorMsg;
  703. if ($file && $line) {
  704. $errorMsg .= " in template '$file' on line $line";
  705. }
  706. elseif ($file && !$line) {
  707. $errorMsg .= " in template '$file'";
  708. }
  709. return $errorMsg;
  710. }
  711. /**
  712. * Compiles an {if} Tag
  713. *
  714. * @param string $tagArgs
  715. * @param boolean $elseif true, if this tag is an else tag
  716. * @return string php code of this tag
  717. */
  718. protected function compileIfTag($tagArgs, $elseif = false) {
  719. $tagArgs = $this->replaceQuotes($tagArgs);
  720. $tagArgs = str_replace(' ', '', $tagArgs);
  721. // split tags
  722. preg_match_all('~('.$this->conditionOperatorPattern.')~', $tagArgs, $matches);
  723. $operators = $matches[1];
  724. $values = preg_split('~(?:'.$this->conditionOperatorPattern.')~', $tagArgs);
  725. $leftParentheses = 0;
  726. $result = '';
  727. for ($i = 0, $j = count($values); $i < $j; $i++) {
  728. $operator = (isset($operators[$i]) ? $operators[$i] : null);
  729. if ($operator !== '!' && $values[$i] == '') {
  730. throw new SystemException($this->formatSyntaxError('syntax error in tag {'.($elseif ? 'elseif' : 'if').'}', $this->currentIdentifier, $this->currentLineNo));
  731. }
  732. $leftParenthesis = StringUtil::countSubstring($values[$i], '(');
  733. $rightParenthesis = StringUtil::countSubstring($values[$i], ')');
  734. if ($leftParenthesis > $rightParenthesis) {
  735. $leftParentheses += $leftParenthesis - $rightParenthesis;
  736. $value = StringUtil::substring($values[$i], $leftParenthesis - $rightParenthesis);
  737. $result .= str_repeat('(', $leftParenthesis - $rightParenthesis);
  738. if (str_replace('(', '', StringUtil::substring($values[$i], 0, $leftParenthesis - $rightParenthesis)) != '') {
  739. throw new SystemException($this->formatSyntaxError('syntax error in tag {'.($elseif ? 'elseif' : 'if').'}', $this->currentIdentifier, $this->currentLineNo));
  740. }
  741. }
  742. else if ($leftParenthesis < $rightParenthesis) {
  743. $leftParentheses += $leftParenthesis - $rightParenthesis;
  744. $value = StringUtil::substring($values[$i], 0, $leftParenthesis - $rightParenthesis);
  745. if ($leftParentheses < 0 || str_replace(')', '', StringUtil::substring($values[$i], $leftParenthesis - $rightParenthesis)) != '') {
  746. throw new SystemException($this->formatSyntaxError('syntax error in tag {'.($elseif ? 'elseif' : 'if').'}', $this->currentIdentifier, $this->currentLineNo));
  747. }
  748. }
  749. else $value = $values[$i];
  750. try {
  751. $result .= $this->compileVariableTag($value, false);
  752. }
  753. catch (SystemException $e) {
  754. throw new SystemException($this->formatSyntaxError('syntax error in tag {'.($elseif ? 'elseif' : 'if').'}', $this->currentIdentifier, $this->currentLineNo));
  755. }
  756. if ($leftParenthesis < $rightParenthesis) {
  757. $result .= str_repeat(')', $rightParenthesis - $leftParenthesis);
  758. }
  759. if ($operator) $result .= ' '.$operator.' ';
  760. }
  761. return '<?php '.($elseif ? '} elseif' : 'if').' ('.$result.') { ?>';
  762. }
  763. /**
  764. * Adds a tag to the tag stack.
  765. *
  766. * @param string $tag
  767. */
  768. public function pushTag($tag) {
  769. $this->tagStack[] = array($tag, $this->currentLineNo);
  770. }
  771. /**
  772. * Deletes a tag from the tag stack.
  773. *
  774. * @param string $tag
  775. * @return string $tag
  776. */
  777. public function popTag($tag) {
  778. list($openTag, $lineNo) = array_pop($this->tagStack);
  779. if ($tag == $openTag) {
  780. return $openTag;
  781. }
  782. if ($tag == 'if' && ($openTag == 'else' || $openTag == 'elseif')) {
  783. return $this->popTag($tag);
  784. }
  785. if ($tag == 'foreach' && $openTag == 'foreachelse') {
  786. return $this->popTag($tag);
  787. }
  788. if ($tag == 'section' && $openTag == 'sectionelse') {
  789. return $this->popTag($tag);
  790. }
  791. }
  792. /**
  793. * Compiles an output tag.
  794. *
  795. * @param string $tag
  796. * @return string php code of this tag
  797. */
  798. protected function compileOutputTag($tag) {
  799. $encodeHTML = false;
  800. $formatNumeric = false;
  801. if ($tag[0] == '@') {
  802. $tag = StringUtil::substring($tag, 1);
  803. }
  804. else if ($tag[0] == '#') {
  805. $tag = StringUtil::substring($tag, 1);
  806. $formatNumeric = true;
  807. }
  808. else {
  809. $encodeHTML = true;
  810. }
  811. $parsedTag = $this->compileVariableTag($tag);
  812. // the @ operator at the beginning of an output avoids
  813. // the default call of StringUtil::encodeHTML()
  814. if ($encodeHTML) {
  815. $parsedTag = 'wcf\util\StringUtil::encodeHTML('.$parsedTag.')';
  816. }
  817. // the # operator at the beginning of an output instructs
  818. // the complier to call the StringUtil::formatNumeric() method
  819. else if ($formatNumeric) {
  820. $parsedTag = 'wcf\util\StringUtil::formatNumeric('.$parsedTag.')';
  821. }
  822. return '<?php echo '.$parsedTag.'; ?>';
  823. }
  824. /**
  825. * Compiles a variable tag.
  826. *
  827. * @param string $variable
  828. * @param string $type
  829. * @param boolean $allowConstants
  830. * @return string
  831. */
  832. protected function compileSimpleVariable($variable, $type = '', $allowConstants = true) {
  833. if ($type == '') $type = $this->getVariableType($variable);
  834. if ($type == 'variable') return '$this->v[\''.substr($variable, 1).'\']';
  835. else if ($type == 'string') return $variable;
  836. else if ($allowConstants && ($variable == 'true' || $variable == 'false' || $variable == 'null' || preg_match('/^[A-Z0-9_]*$/', $variable))) return $variable;
  837. else return "'".$variable."'";
  838. }
  839. /**
  840. * Compiles a modifier tag.
  841. *
  842. * @param array $data
  843. * @return string
  844. */
  845. protected function compileModifier($data) {
  846. if (isset($data['className'])) {
  847. return "\$this->pluginObjects['".$data['className']."']->execute(array(".implode(',', $data['parameter'])."), \$this)";
  848. }
  849. else {
  850. return $data['name'].'('.implode(',', $data['parameter']).')';
  851. }
  852. }
  853. /**
  854. * Returns type of the given variable
  855. *
  856. * @param string $variable
  857. * @return string
  858. */
  859. protected function getVariableType($variable) {
  860. if (substr($variable, 0, 1) == '$') return 'variable';
  861. else if (substr($variable, 0, 2) == '@@') return 'string';
  862. else return 'constant';
  863. }
  864. /**
  865. * Compiles a variable tag.
  866. *
  867. * @param string $tag
  868. * @return string
  869. */
  870. public function compileVariableTag($tag, $replaceQuotes = true) {
  871. // replace all quotes with unique hash values
  872. $compiledTag = $tag;
  873. if ($replaceQuotes) $compiledTag = $this->replaceQuotes($compiledTag);
  874. // replace numbers and special constants
  875. $compiledTag = $this->replaceConstants($compiledTag);
  876. // split tags
  877. preg_match_all('~('.$this->variableOperatorPattern.')~', $compiledTag, $matches);
  878. $operators = $matches[1];
  879. $values = preg_split('~(?:'.$this->variableOperatorPattern.')~', $compiledTag);
  880. // parse tags
  881. $statusStack = array(0 => 'start');
  882. $result = '';
  883. $modifierData = null;
  884. for ($i = 0, $j = count($values); $i < $j; $i++) {
  885. // check value
  886. $status = end($statusStack);
  887. $operator = (isset($operators[$i]) ? $operators[$i] : null);
  888. $values[$i] = trim($values[$i]);
  889. if ($values[$i] !== '') {
  890. $variableType = $this->getVariableType($values[$i]);
  891. switch ($status) {
  892. case 'start':
  893. $result .= $this->compileSimpleVariable($values[$i], $variableType);
  894. $statusStack[0] = $status = $variableType;
  895. break;
  896. case 'object access':
  897. if (/*strpos($values[$i], '$') !== false || */strpos($values[$i], '@@') !== false) {
  898. throw new SystemException($this->formatSyntaxError("unexpected '->".$values[$i]."' in tag '".$tag."'", $this->currentIdentifier, $this->currentLineNo));
  899. }
  900. if (strpos($values[$i], '$') !== false) $result .= '{'.$this->compileSimpleVariable($values[$i], $variableType).'}';
  901. else $result .= $values[$i];
  902. $statusStack[count($statusStack) - 1] = $status = 'object';
  903. break;
  904. case 'object method start':
  905. $statusStack[count($statusStack) - 1] = 'object method';
  906. $result .= $this->compileSimpleVariable($values[$i], $variableType);
  907. $statusStack[] = $status = $variableType;
  908. break;
  909. case 'object method parameter separator':
  910. array_pop($statusStack);
  911. $result .= $this->compileSimpleVariable($values[$i], $variableType);
  912. $statusStack[] = $status = $variableType;
  913. break;
  914. case 'dot access':
  915. $result .= $this->compileSimpleVariable($values[$i], $variableType, false);
  916. $result .= ']';
  917. $statusStack[count($statusStack) - 1] = $status = 'variable';
  918. break;
  919. case 'object method':
  920. case 'left parenthesis':
  921. $result .= $this->compileSimpleVariable($values[$i], $variableType);
  922. $statusStack[] = $status = $variableType;
  923. break;
  924. case 'bracket open':
  925. $result .= $this->compileSimpleVariable($values[$i], $variableType, false);
  926. $statusStack[] = $status = $variableType;
  927. break;
  928. case 'math':
  929. $result .= $this->compileSimpleVariable($values[$i], $variableType);
  930. $statusStack[count($statusStack) - 1] = $status = $variableType;
  931. break;
  932. case 'modifier end':
  933. $result .= $this->compileSimpleVariable($values[$i], $variableType);
  934. $statusStack[] = $status = $variableType;
  935. break;
  936. case 'modifier':
  937. if (strpos($values[$i], '$') !== false || strpos($values[$i], '@@') !== false) {
  938. throw new SystemException($this->formatSyntaxError("unknown modifier '".$values[$i]."'", $this->currentIdentifier, $this->currentLineNo));
  939. }
  940. // handle modifier name
  941. $modifierData['name'] = $values[$i];
  942. $className = $this->template->getPluginClassName('modifier', $modifierData['name']);
  943. if (class_exists($className)) {
  944. $modifierData['className'] = $className;
  945. $this->autoloadPlugins[$modifierData['className']] = $modifierData['className'];
  946. }
  947. else if ((!function_exists($modifierData['name']) && !in_array($modifierData['name'], $this->unknownPHPFunctions)) || in_array($modifierData['name'], $this->disabledPHPFunctions)) {
  948. throw new SystemException($this->formatSyntaxError("unknown modifier '".$values[$i]."'", $this->currentIdentifier, $this->currentLineNo));
  949. }
  950. $statusStack[count($statusStack) - 1] = $status = 'modifier end';
  951. break;
  952. case 'object':
  953. case 'constant':
  954. case 'variable':
  955. case 'string':
  956. throw new SystemException($this->formatSyntaxError('unknown tag {'.$tag.'}', $this->currentIdentifier, $this->currentLineNo));
  957. break;
  958. }
  959. }
  960. // check operator
  961. if ($operator !== null) {
  962. switch ($operator) {
  963. case '.':
  964. if ($status == 'variable' || $status == 'object') {
  965. if ($status == 'object') $statusStack[count($statusStack) - 1] = 'variable';
  966. $result .= '[';
  967. $statusStack[] = 'dot access';
  968. break;
  969. }
  970. throw new SystemException($this->formatSyntaxError("unexpected '.' in tag '".$tag."'", $this->currentIdentifier, $this->currentLineNo));
  971. break;
  972. // object access
  973. case '->':
  974. if ($status == 'variable' || $status == 'object') {
  975. $result .= $operator;
  976. $statusStack[count($statusStack) - 1] = 'object access';
  977. break;
  978. }
  979. throw new SystemException($this->formatSyntaxError("unexpected '->' in tag '".$tag."'", $this->currentIdentifier, $this->currentLineNo));
  980. break;
  981. // left parenthesis
  982. case '(':
  983. if ($status == 'object') {
  984. $statusStack[count($statusStack) - 1] = 'variable';
  985. $statusStack[] = 'object method start';
  986. $result .= $operator;
  987. break;
  988. }
  989. else if ($status == 'math' || $status == 'start' || $status == 'left parenthesis' || $status == 'bracket open' || $status == 'modifier end') {
  990. if ($status == 'start') $statusStack[count($statusStack) - 1] = 'constant';
  991. $statusStack[] = 'left parenthesis';
  992. $result .= $operator;
  993. break;
  994. }
  995. throw new SystemException($this->formatSyntaxError("unexpected '(' in tag '".$tag."'", $this->currentIdentifier, $this->currentLineNo));
  996. break;
  997. // right parenthesis
  998. case ')':
  999. while ($oldStatus = array_pop($statusStack)) {
  1000. if ($oldStatus != 'variable' && $oldStatus != 'object' && $oldStatus != 'constant' && $oldStatus != 'string') {
  1001. if ($oldStatus == 'object method start' || $oldStatus == 'object method' || $oldStatus == 'left parenthesis') {
  1002. $result .= $operator;
  1003. break 2;
  1004. }
  1005. else break;
  1006. }
  1007. }
  1008. throw new SystemException($this->formatSyntaxError("unexpected ')' in tag '".$tag."'", $this->currentIdentifier, $this->currentLineNo));
  1009. break;
  1010. // bracket open
  1011. case '[':
  1012. if ($status == 'variable' || $status == 'object') {
  1013. if ($status == 'object') $statusStack[count($statusStack) - 1] = 'variable';
  1014. $statusStack[] = 'bracket open';
  1015. $result .= $operator;
  1016. break;
  1017. }
  1018. throw new SystemException($this->formatSyntaxError("unexpected '[' in tag '".$tag."'", $this->currentIdentifier, $this->currentLineNo));
  1019. break;
  1020. // bracket close
  1021. case ']':
  1022. while ($oldStatus = array_pop($statusStack)) {
  1023. if ($oldStatus != 'variable' && $oldStatus != 'object' && $oldStatus != 'constant' && $oldStatus != 'string') {
  1024. if ($oldStatus == 'bracket open') {
  1025. $result .= $operator;
  1026. break 2;
  1027. }
  1028. else break;
  1029. }
  1030. }
  1031. throw new SystemException($this->formatSyntaxError("unexpected ']' in tag '".$tag."'", $this->currentIdentifier, $this->currentLineNo));
  1032. break;
  1033. // modifier
  1034. case '|':
  1035. // handle previous modifier
  1036. if ($modifierData !== null) {
  1037. if ($result !== '') $modifierData['parameter'][] = $result;
  1038. $result = $this->compileModifier($modifierData);
  1039. }
  1040. // clear status stack
  1041. while ($oldStatus = array_pop($statusStack)) {
  1042. if ($oldStatus != 'variable' && $oldStatus != 'object' && $oldStatus != 'constant' && $oldStatus != 'string' && $oldStatus != 'modifier end') {
  1043. throw new SystemException($this->formatSyntaxError("unexpected '|' in tag '".$tag."'", $this->currentIdentifier, $this->currentLineNo));
  1044. }
  1045. }
  1046. $statusStack = array(0 => 'modifier');
  1047. $modifierData = array('name' => '', 'parameter' => array(0 => $result));
  1048. $result = '';
  1049. break;
  1050. // modifier parameter
  1051. case ':':
  1052. while ($oldStatus = array_pop($statusStack)) {
  1053. if ($oldStatus != 'variable' && $oldStatus != 'object' && $oldStatus != 'constant' && $oldStatus != 'string') {
  1054. if ($oldStatus == 'modifier end') {
  1055. $statusStack[] = 'modifier end';
  1056. if ($result !== '') $modifierData['parameter'][] = $result;
  1057. $result = '';
  1058. break 2;
  1059. }
  1060. else break;
  1061. }
  1062. }
  1063. throw new SystemException($this->formatSyntaxError("unexpected ':' in tag '".$tag."'", $this->currentIdentifier, $this->currentLineNo));
  1064. break;
  1065. case ',':
  1066. while ($oldStatus = array_pop($statusStack)) {
  1067. if ($oldStatus != 'variable' && $oldStatus != 'object' && $oldStatus != 'constant' && $oldStatus != 'string') {
  1068. if ($oldStatus == 'object method') {
  1069. $result .= $operator;
  1070. $statusStack[] = 'object method';
  1071. $statusStack[] = 'object method parameter separator';
  1072. break 2;
  1073. }
  1074. else break;
  1075. }
  1076. }
  1077. throw new SystemException($this->formatSyntaxError("unexpected ',' in tag '".$tag."'", $this->currentIdentifier, $this->currentLineNo));
  1078. break;
  1079. // math operators
  1080. case '+':
  1081. case '-':
  1082. case '*':
  1083. case '/':
  1084. case '%':
  1085. case '^':
  1086. if ($status == 'variable' || $status == 'object' || $status == 'constant' || $status == 'string' || $status == 'modifier end') {
  1087. $result .= $operator;
  1088. $statusStack[count($statusStack) - 1] = 'math';
  1089. break;
  1090. }
  1091. throw new SystemException($this->formatSyntaxError("unexpected '".$operator."' in tag '".$tag."'", $this->currentIdentifier, $this->currentLineNo));
  1092. break;
  1093. }
  1094. }
  1095. }
  1096. // handle open modifier
  1097. if ($modifierData !== null) {
  1098. if ($result !== '') $modifierData['parameter'][] = $result;
  1099. $result = $this->compileModifier($modifierData);
  1100. }
  1101. // reinserts strings
  1102. $result = $this->reinsertQuotes($result);
  1103. $result = $this->reinsertConstants($result);
  1104. return $result;
  1105. }
  1106. /**
  1107. * Generates the regexp pattern.
  1108. */
  1109. protected function buildPattern() {
  1110. $this->variableOperatorPattern = '\-\>|\.|\(|\)|\[|\]|\||\:|\+|\-|\*|\/|\%|\^|\,';
  1111. $this->conditionOperatorPattern = '===|!==|==|!=|<=|<|>=|(?<!-)>|\|\||&&|!|=';
  1112. $this->escapedPattern = '(?<!\\\\)';
  1113. $this->validVarnamePattern = '(?:[a-zA-Z_][a-zA-Z_0-9]*)';
  1114. $this->constantPattern = '(?:[A-Z_][A-Z_0-9]*)';
  1115. //$this->doubleQuotePattern = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"';
  1116. $this->doubleQuotePattern = '"(?:[^"\\\\]+|\\\\.)*"';
  1117. //$this->singleQuotePattern = '\'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\'';
  1118. $this->singleQuotePattern = '\'(?:[^\'\\\\]+|\\\\.)*\'';
  1119. $this->quotePattern = '(?:' . $this->doubleQuotePattern . '|' . $this->singleQuotePattern . ')';
  1120. $this->numericPattern = '(?i)(?:(?:\-?\d+(?:\.\d+)?)|true|false|null)';
  1121. $this->simpleVarPattern = '(?:\$('.$this->validVarnamePattern.'))';
  1122. $this->outputPattern = '(?:(?:@|#)?(?:'.$this->constantPattern.'|'.$this->quotePattern.'|'.$this->numericPattern.'|'.$this->simpleVarPattern.'|\())';
  1123. }
  1124. /**
  1125. * Returns the instance of the template engine class.
  1126. *
  1127. * @return wcf\system\templateTemplateEngine
  1128. */
  1129. public function getTemplate() {
  1130. return $this->template;
  1131. }
  1132. /**
  1133. * Returns the left delimiter for template tags.
  1134. *
  1135. * @return string
  1136. */
  1137. public function getLeftDelimiter() {
  1138. return $this->leftDelimiter;
  1139. }
  1140. /**
  1141. * Returns the right delimiter for template tags.
  1142. *
  1143. * @return string
  1144. */
  1145. public function getRightDelimiter() {
  1146. return $this->rightDelimiter;
  1147. }
  1148. /**
  1149. * Returns the name of the current template.
  1150. *
  1151. * @return string
  1152. */
  1153. public function getCurrentIdentifier() {
  1154. return $this->currentIdentifier;
  1155. }
  1156. /**
  1157. * Returns the current line number.
  1158. *
  1159. * @return integer
  1160. */
  1161. public function getCurrentLineNo() {
  1162. return $this->currentLineNo;
  1163. }
  1164. /**
  1165. * Applies the prefilters to the given string.
  1166. *
  1167. * @param string $templateName
  1168. * @param string $string
  1169. * @return string
  1170. */
  1171. public function applyPrefilters($templateName, $string) {
  1172. foreach ($this->template->getPrefilters() as $prefilter) {
  1173. if (!is_object($prefilter)) {
  1174. $className = $this->template->getPluginClassName('prefilter', $prefilter);
  1175. if (!class_exists($className)) {
  1176. throw new SystemException($this->formatSyntaxError('unable to find prefilter class '.$className, $this->currentIdentifier));
  1177. }
  1178. $prefilter = new $className();
  1179. }
  1180. if ($prefilter instanceof IPrefilterTemplatePlugin) {
  1181. $string = $prefilter->execute($templateName, $string, $this);
  1182. }
  1183. else {
  1184. throw new SystemException($this->formatSyntaxError("Prefilter '".(is_object($prefilter) ? get_class($prefilter) : $prefilter)."' does not implement the interface 'IPrefilterTemplatePlugin'", $this->currentIdentifier));
  1185. }
  1186. }
  1187. return $string;
  1188. }
  1189. /**
  1190. * Replaces all {literal} Tags with unique hash values.
  1191. *
  1192. * @param string $string
  1193. * @return string
  1194. */
  1195. public function replaceLiterals($string) {
  1196. return preg_replace_callback("~".$this->ldq."literal".$this->rdq."(.*?)".$this->ldq."/literal".$this->rdq."~s", array($this, 'replaceLiteralsCallback'), $string);
  1197. }
  1198. /**
  1199. * Reinserts the literal tags.
  1200. *
  1201. * @param string $string
  1202. * @return string
  1203. */
  1204. public function reinsertLiterals($string) {
  1205. return StringStack::reinsertStrings($string, 'literal');
  1206. }
  1207. /**
  1208. * Callback function used in replaceLiterals()
  1209. */
  1210. private function replaceLiteralsCallback($matches) {
  1211. return StringStack::pushToStringStack($matches[1], 'literal');
  1212. }
  1213. /**
  1214. * Removes template comments
  1215. *
  1216. * @param string $string
  1217. * @return string
  1218. */
  1219. public function removeComments($string) {
  1220. return preg_replace("~".$this->ldq."\*.*?\*".$this->rdq."~s", '', $string);
  1221. }
  1222. /**
  1223. * Replaces all quotes with unique hash values.
  1224. *
  1225. * @param string $string
  1226. * @return string
  1227. */
  1228. public function replaceQuotes($string) {
  1229. $string = preg_replace_callback('~\'([^\'\\\\]+|\\\\.)*\'~', array($this, 'replaceSingleQuotesCallback'), $string);
  1230. $string = preg_replace_callback('~"([^"\\\\]+|\\\\.)*"~', array($this, 'replaceDoubleQuotesCallback'), $string);
  1231. return $string;
  1232. }
  1233. /**
  1234. * Callback function used in replaceQuotes()
  1235. */
  1236. private function replaceSingleQuotesCallback($matches) {
  1237. return StringStack::pushToStringStack($matches[0], 'singleQuote');
  1238. }
  1239. /**
  1240. * Callback function used in replaceQuotes()
  1241. */
  1242. private function replaceDoubleQuotesCallback($matches) {
  1243. // parse unescaped simple vars in double quotes
  1244. // replace $foo with {$this->v['foo']}
  1245. $matches[0] = preg_replace('~'.$this->escapedPattern.$this->simpleVarPattern.'~', '{$this->v[\'\\1\']}', $matches[0]);
  1246. return StringStack::pushToStringStack($matches[0], 'doubleQuote');
  1247. }
  1248. /**
  1249. * Reinserts the quotes.
  1250. *
  1251. * @param string $string
  1252. * @return string
  1253. */
  1254. public function reinsertQuotes($string) {
  1255. $string = StringStack::reinsertStrings($string, 'singleQuote');
  1256. $string = StringStack::reinsertStrings($string, 'doubleQuote');
  1257. return $string;
  1258. }
  1259. /**
  1260. * Replaces all constants with unique hash values.
  1261. *
  1262. * @param string $string
  1263. * @return string
  1264. */
  1265. public function replaceConstants($string) {
  1266. return preg_replace_callback('~(?<=^|'.$this->variableOperatorPattern.')(?i)((?:\-?\d+(?:\.\d+)?)|true|false|null)(?=$|'.$this->variableOperatorPattern.')~', array($this, 'replaceConstantsCallback'), $string);
  1267. }
  1268. /**
  1269. * Callback function used in replaceConstants()
  1270. */
  1271. private function replaceConstantsCallback($matches) {
  1272. return StringStack::pushToStringStack($matches[1], 'constants');
  1273. }
  1274. /**
  1275. * Reinserts the constants.
  1276. *
  1277. * @param string $string
  1278. * @return string
  1279. */
  1280. public function reinsertConstants($string) {
  1281. return StringStack::reinsertStrings($string, 'constants');
  1282. }
  1283. /**
  1284. * Replaces all php tags.
  1285. *
  1286. * @param string $string
  1287. * @return string
  1288. */
  1289. public function replacePHPTags($string) {
  1290. if (StringUtil::indexOf($string, '<?') !== false) {
  1291. $string = StringUtil::replace('<?php', '@@PHP_START_TAG@@', $string);
  1292. $string = StringUtil::replace('<?', '@@PHP_SHORT_START_TAG@@', $string);
  1293. $string = StringUtil::replace('?>', '@@PHP_END_TAG@@', $string);
  1294. $string = StringUtil::replace('@@PHP_END_TAG@@', "<?php echo '?>'; ?>\n", $string);
  1295. $string = StringUtil::replace('@@PHP_SHORT_START_TAG@@', "<?php echo '<?'; ?>\n", $string);
  1296. $string = StringUtil::replace('@@PHP_START_TAG@@', "<?php echo '<?php'; ?>\n", $string);
  1297. }
  1298. return $string;
  1299. }
  1300. }