PageRenderTime 54ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 1ms

/www/libs/nette-dev/Templates/Filters/LatteMacros.php

https://github.com/bazo/Mokuji
PHP | 918 lines | 520 code | 192 blank | 206 comment | 75 complexity | 93038870a5ce73ae0f73f2cdd32eee8b MD5 | raw file
Possible License(s): BSD-3-Clause, MIT
  1. <?php
  2. /**
  3. * Nette Framework
  4. *
  5. * @copyright Copyright (c) 2004, 2010 David Grudl
  6. * @license http://nettephp.com/license Nette license
  7. * @link http://nettephp.com
  8. * @category Nette
  9. * @package Nette\Templates
  10. */
  11. /**
  12. * Default macros for filter LatteFilter.
  13. *
  14. * - {$variable} with escaping
  15. * - {!$variable} without escaping
  16. * - {*comment*} will be removed
  17. * - {=expression} echo with escaping
  18. * - {!=expression} echo without escaping
  19. * - {?expression} evaluate PHP statement
  20. * - {_expression} echo translation with escaping
  21. * - {!_expression} echo translation without escaping
  22. * - {link destination ...} control link
  23. * - {plink destination ...} presenter link
  24. * - {if ?} ... {elseif ?} ... {else} ... {/if}
  25. * - {ifset ?} ... {elseifset ?} ... {/if}
  26. * - {for ?} ... {/for}
  27. * - {foreach ?} ... {/foreach}
  28. * - {include ?}
  29. * - {cache ?} ... {/cache} cached block
  30. * - {snippet ?} ... {/snippet ?} control snippet
  31. * - {attr ?} HTML element attributes
  32. * - {block|texy} ... {/block} block
  33. * - {contentType ...} HTTP Content-Type header
  34. * - {status ...} HTTP status
  35. * - {capture ?} ... {/capture} capture block to parameter
  36. * - {assign var => value} set template parameter
  37. * - {default var => value} set default template parameter
  38. * - {dump $var}
  39. * - {debugbreak}
  40. *
  41. * @copyright Copyright (c) 2004, 2010 David Grudl
  42. * @package Nette\Templates
  43. */
  44. class LatteMacros extends Object
  45. {
  46. /** @var array */
  47. public static $defaultMacros = array(
  48. 'syntax' => '%:macroSyntax%',
  49. '/syntax' => '%:macroSyntax%',
  50. 'block' => '<?php %:macroBlock% ?>',
  51. '/block' => '<?php %:macroBlockEnd% ?>',
  52. 'capture' => '<?php %:macroCapture% ?>',
  53. '/capture' => '<?php %:macroCaptureEnd% ?>',
  54. 'snippet' => '<?php %:macroSnippet% ?>',
  55. '/snippet' => '<?php %:macroSnippetEnd% ?>',
  56. 'cache' => '<?php if ($_cb->foo = CachingHelper::create($_cb->key = md5(__FILE__) . __LINE__, $template->getFile(), array(%%))) { $_cb->caches[] = $_cb->foo ?>',
  57. '/cache' => '<?php array_pop($_cb->caches)->save(); } if (!empty($_cb->caches)) end($_cb->caches)->addItem($_cb->key) ?>',
  58. 'if' => '<?php if (%%): ?>',
  59. 'elseif' => '<?php elseif (%%): ?>',
  60. 'else' => '<?php else: ?>',
  61. '/if' => '<?php endif ?>',
  62. 'ifset' => '<?php if (isset(%%)): ?>',
  63. '/ifset' => '<?php endif ?>',
  64. 'elseifset' => '<?php elseif (isset(%%)): ?>',
  65. 'foreach' => '<?php foreach (%:macroForeach%): ?>',
  66. '/foreach' => '<?php endforeach; array_pop($_cb->its); $iterator = end($_cb->its) ?>',
  67. 'for' => '<?php for (%%): ?>',
  68. '/for' => '<?php endfor ?>',
  69. 'while' => '<?php while (%%): ?>',
  70. '/while' => '<?php endwhile ?>',
  71. 'continueIf' => '<?php if (%%) continue ?>',
  72. 'breakIf' => '<?php if (%%) break ?>',
  73. 'include' => '<?php %:macroInclude% ?>',
  74. 'extends' => '<?php %:macroExtends% ?>',
  75. 'layout' => '<?php %:macroExtends% ?>',
  76. 'plink' => '<?php echo %:macroEscape%(%:macroPlink%) ?>',
  77. 'link' => '<?php echo %:macroEscape%(%:macroLink%) ?>',
  78. 'ifCurrent' => '<?php %:macroIfCurrent%; if ($presenter->getLastCreatedRequestFlag("current")): ?>',
  79. 'widget' => '<?php %:macroWidget% ?>',
  80. 'control' => '<?php %:macroWidget% ?>',
  81. 'attr' => '<?php echo Html::el(NULL)->%:macroAttr%attributes() ?>',
  82. 'contentType' => '<?php %:macroContentType% ?>',
  83. 'status' => '<?php Environment::getHttpResponse()->setCode(%%) ?>',
  84. 'assign' => '<?php %:macroAssign% ?>', // deprecated?
  85. 'default' => '<?php %:macroDefault% ?>',
  86. 'dump' => '<?php Debug::barDump(%:macroDump%, "Template " . str_replace(Environment::getVariable("appDir"), "\xE2\x80\xA6", $template->getFile())) ?>',
  87. 'debugbreak' => '<?php if (function_exists("debugbreak")) debugbreak(); elseif (function_exists("xdebug_break")) xdebug_break() ?>',
  88. '!_' => '<?php echo %:macroTranslate% ?>',
  89. '!=' => '<?php echo %:macroModifiers% ?>',
  90. '_' => '<?php echo %:macroEscape%(%:macroTranslate%) ?>',
  91. '=' => '<?php echo %:macroEscape%(%:macroModifiers%) ?>',
  92. '!$' => '<?php echo %:macroVar% ?>',
  93. '!' => '<?php echo %:macroVar% ?>', // deprecated
  94. '$' => '<?php echo %:macroEscape%(%:macroVar%) ?>',
  95. '?' => '<?php %:macroModifiers% ?>', // deprecated?
  96. );
  97. /** @var array */
  98. public $macros;
  99. /** @var LatteFilter */
  100. private $filter;
  101. /** @var array */
  102. private $current;
  103. /** @var array */
  104. private $blocks = array();
  105. /** @var array */
  106. private $namedBlocks = array();
  107. /** @var bool */
  108. private $extends;
  109. /** @var string */
  110. private $uniq;
  111. /** @var bool */
  112. private $oldSnippetMode = TRUE;
  113. /**#@+ @ignore internal block type */
  114. const BLOCK_NAMED = 1;
  115. const BLOCK_CAPTURE = 2;
  116. const BLOCK_ANONYMOUS = 3;
  117. /**#@-*/
  118. /**
  119. * Constructor.
  120. */
  121. public function __construct()
  122. {
  123. $this->macros = self::$defaultMacros;
  124. }
  125. /**
  126. * Initializes parsing.
  127. * @param LatteFilter
  128. * @param string
  129. * @return void
  130. */
  131. public function initialize($filter, & $s)
  132. {
  133. $this->filter = $filter;
  134. $this->blocks = array();
  135. $this->namedBlocks = array();
  136. $this->extends = NULL;
  137. $this->uniq = substr(md5(uniqid('', TRUE)), 0, 10);
  138. $filter->context = LatteFilter::CONTEXT_TEXT;
  139. $filter->escape = 'TemplateHelpers::escapeHtml';
  140. // remove comments
  141. $s = preg_replace('#\\{\\*.*?\\*\\}[\r\n]*#s', '', $s);
  142. // snippets support (temporary solution)
  143. $s = preg_replace(
  144. '#@(\\{[^}]+?\\})#s',
  145. '<?php } ?>$1<?php if (SnippetHelper::\\$outputAllowed) { ?>',
  146. $s
  147. );
  148. }
  149. /**
  150. * Finishes parsing.
  151. * @param string
  152. * @return void
  153. */
  154. public function finalize(& $s)
  155. {
  156. // blocks closing check
  157. if (count($this->blocks) === 1) { // auto-close last block
  158. $s .= $this->macro('/block', '', '');
  159. } elseif ($this->blocks) {
  160. throw new InvalidStateException("There are some unclosed blocks.");
  161. }
  162. // snippets support (temporary solution)
  163. $s = "<?php\nif (SnippetHelper::\$outputAllowed) {\n?>$s<?php\n}\n?>";
  164. // extends support
  165. if ($this->namedBlocks || $this->extends) {
  166. $s = "<?php\n"
  167. . 'if ($_cb->extends) { ob_start(); }' . "\n"
  168. . 'elseif (isset($presenter, $control) && $presenter->isAjax()) { LatteMacros::renderSnippets($control, $_cb, get_defined_vars()); }' . "\n"
  169. . '?>' . $s . "<?php\n"
  170. . 'if ($_cb->extends) { ob_end_clean(); LatteMacros::includeTemplate($_cb->extends, get_defined_vars(), $template)->render(); }' . "\n";
  171. } else {
  172. $s = "<?php\n"
  173. . 'if (isset($presenter, $control) && $presenter->isAjax()) { LatteMacros::renderSnippets($control, $_cb, get_defined_vars()); }' . "\n"
  174. . '?>' . $s;
  175. }
  176. // named blocks
  177. if ($this->namedBlocks) {
  178. foreach (array_reverse($this->namedBlocks, TRUE) as $name => $foo) {
  179. $name = preg_quote($name, '#');
  180. $s = preg_replace_callback("#{block ($name)} \?>(.*)<\?php {/block $name}#sU", array($this, 'cbNamedBlocks'), $s);
  181. }
  182. $s = "<?php\n\n" . implode("\n\n\n", $this->namedBlocks) . "\n\n//\n// end of blocks\n//\n?>" . $s;
  183. }
  184. // internal state holder
  185. $s = "<?php\n"
  186. . "\$_cb = LatteMacros::initRuntime(\$template, " . var_export($this->extends, TRUE) . ", " . var_export($this->uniq, TRUE) . "); unset(\$_extends);\n"
  187. . '?>' . $s;
  188. }
  189. /**
  190. * Process {macro content | modifiers}
  191. * @param string
  192. * @param string
  193. * @param string
  194. * @return string
  195. */
  196. public function macro($macro, $content, $modifiers)
  197. {
  198. if ($macro === '') {
  199. $macro = substr($content, 0, 2);
  200. if (!isset($this->macros[$macro])) {
  201. $macro = substr($content, 0, 1);
  202. if (!isset($this->macros[$macro])) {
  203. return NULL;
  204. }
  205. }
  206. $content = substr($content, strlen($macro));
  207. } elseif (!isset($this->macros[$macro])) {
  208. return NULL;
  209. }
  210. $this->current = array($content, $modifiers);
  211. return preg_replace_callback('#%(.*?)%#', array($this, 'cbMacro'), $this->macros[$macro]);
  212. }
  213. /**
  214. * Callback for self::macro().
  215. */
  216. private function cbMacro($m)
  217. {
  218. list($content, $modifiers) = $this->current;
  219. if ($m[1]) {
  220. return callback($m[1][0] === ':' ? array($this, substr($m[1], 1)) : $m[1])
  221. ->invoke($content, $modifiers);
  222. } else {
  223. return $content;
  224. }
  225. }
  226. /**
  227. * Process <n:tag attr> (experimental).
  228. * @param string
  229. * @param array
  230. * @param bool
  231. * @return string
  232. */
  233. public function tagMacro($name, $attrs, $closing)
  234. {
  235. $knownTags = array(
  236. 'include' => 'block',
  237. 'for' => 'each',
  238. 'block' => 'name',
  239. 'if' => 'cond',
  240. 'elseif' => 'cond',
  241. );
  242. return $this->macro(
  243. $closing ? "/$name" : $name,
  244. isset($knownTags[$name], $attrs[$knownTags[$name]]) ? $attrs[$knownTags[$name]] : substr(var_export($attrs, TRUE), 8, -1),
  245. isset($attrs['modifiers']) ? $attrs['modifiers'] : ''
  246. );
  247. }
  248. /**
  249. * Process <tag n:attr> (experimental).
  250. * @param string
  251. * @param array
  252. * @param bool
  253. * @return string
  254. */
  255. public function attrsMacro($code, $attrs, $closing)
  256. {
  257. $left = $right = '';
  258. foreach ($this->macros as $name => $foo) {
  259. if (!isset($this->macros["/$name"])) { // must be pair-macro
  260. continue;
  261. }
  262. $macro = $closing ? "/$name" : $name;
  263. if (isset($attrs[$name])) {
  264. if ($closing) {
  265. $right .= $this->macro($macro, '', '');
  266. } else {
  267. $left = $this->macro($macro, $attrs[$name], '') . $left;
  268. }
  269. }
  270. $innerName = "inner-$name";
  271. if (isset($attrs[$innerName])) {
  272. if ($closing) {
  273. $left .= $this->macro($macro, '', '');
  274. } else {
  275. $right = $this->macro($macro, $attrs[$innerName], '') . $right;
  276. }
  277. }
  278. $tagName = "tag-$name";
  279. if (isset($attrs[$tagName])) {
  280. $left = $this->macro($name, $attrs[$tagName], '') . $left;
  281. $right .= $this->macro("/$name", '', '');
  282. }
  283. unset($attrs[$name], $attrs[$innerName], $attrs[$tagName]);
  284. }
  285. return $attrs ? NULL : $left . $code . $right;
  286. }
  287. /********************* macros ****************d*g**/
  288. /**
  289. * {$var |modifiers}
  290. */
  291. public function macroVar($var, $modifiers)
  292. {
  293. return LatteFilter::formatModifiers('$' . $var, $modifiers);
  294. }
  295. /**
  296. * {_$var |modifiers}
  297. */
  298. public function macroTranslate($var, $modifiers)
  299. {
  300. return LatteFilter::formatModifiers($var, 'translate|' . $modifiers);
  301. }
  302. /**
  303. * {syntax ...}
  304. */
  305. public function macroSyntax($var)
  306. {
  307. switch ($var) {
  308. case '':
  309. case 'latte':
  310. $this->filter->setDelimiters('\\{(?![\\s\'"{}])', '\\}'); // {...}
  311. break;
  312. case 'double':
  313. $this->filter->setDelimiters('\\{\\{(?![\\s\'"{}])', '\\}\\}'); // {{...}}
  314. break;
  315. case 'asp':
  316. $this->filter->setDelimiters('<%\s*', '\s*%>'); /* <%...%> */
  317. break;
  318. case 'python':
  319. $this->filter->setDelimiters('\\{[{%]\s*', '\s*[%}]\\}'); // {% ... %} | {{ ... }}
  320. break;
  321. case 'off':
  322. $this->filter->setDelimiters('[^\x00-\xFF]', '');
  323. break;
  324. default:
  325. throw new InvalidStateException("Unknown macro syntax '$var' on line {$this->filter->line}.");
  326. }
  327. }
  328. /**
  329. * {include ...}
  330. */
  331. public function macroInclude($content, $modifiers, $isDefinition = FALSE)
  332. {
  333. $destination = LatteFilter::fetchToken($content); // destination [,] [params]
  334. $params = LatteFilter::formatArray($content) . ($content ? ' + ' : '');
  335. if ($destination === NULL) {
  336. throw new InvalidStateException("Missing destination in {include} on line {$this->filter->line}.");
  337. } elseif ($destination[0] === '#') { // include #block
  338. $destination = ltrim($destination, '#');
  339. if (!preg_match('#^'.LatteFilter::RE_IDENTIFIER.'$#', $destination)) {
  340. throw new InvalidStateException("Included block name must be alphanumeric string, '$destination' given on line {$this->filter->line}.");
  341. }
  342. $parent = $destination === 'parent';
  343. if ($destination === 'parent' || $destination === 'this') {
  344. $item = end($this->blocks);
  345. while ($item && $item[0] !== self::BLOCK_NAMED) $item = prev($this->blocks);
  346. if (!$item) {
  347. throw new InvalidStateException("Cannot include $destination block outside of any block on line {$this->filter->line}.");
  348. }
  349. $destination = $item[1];
  350. }
  351. $name = var_export($destination, TRUE);
  352. $params .= $isDefinition ? 'get_defined_vars()' : '$template->getParams()';
  353. $cmd = isset($this->namedBlocks[$destination]) && !$parent
  354. ? "call_user_func(reset(\$_cb->blocks[$name]), \$_cb, $params)"
  355. : "LatteMacros::callBlock" . ($parent ? 'Parent' : '') . "(\$_cb, $name, $params)";
  356. return $modifiers
  357. ? "ob_start(); $cmd; echo " . LatteFilter::formatModifiers('ob_get_clean()', $modifiers)
  358. : $cmd;
  359. } else { // include "file"
  360. $destination = LatteFilter::formatString($destination);
  361. $params .= '$template->getParams()';
  362. return $modifiers
  363. ? 'echo ' . LatteFilter::formatModifiers('LatteMacros::includeTemplate(' . $destination . ', ' . $params . ', $_cb->templates[' . var_export($this->uniq, TRUE) . '])->__toString(TRUE)', $modifiers)
  364. : 'LatteMacros::includeTemplate(' . $destination . ', ' . $params . ', $_cb->templates[' . var_export($this->uniq, TRUE) . '])->render()';
  365. }
  366. }
  367. /**
  368. * {extends ...}
  369. */
  370. public function macroExtends($content)
  371. {
  372. $destination = LatteFilter::fetchToken($content); // destination
  373. if ($destination === NULL) {
  374. throw new InvalidStateException("Missing destination in {extends} on line {$this->filter->line}.");
  375. }
  376. if (!empty($this->blocks)) {
  377. throw new InvalidStateException("{extends} must be placed outside any block; on line {$this->filter->line}.");
  378. }
  379. if ($this->extends !== NULL) {
  380. throw new InvalidStateException("Multiple {extends} declarations are not allowed; on line {$this->filter->line}.");
  381. }
  382. $this->extends = $destination !== 'none';
  383. return $this->extends ? '$_cb->extends = ' . LatteFilter::formatString($destination) : '';
  384. }
  385. /**
  386. * {block ...}
  387. */
  388. public function macroBlock($content, $modifiers)
  389. {
  390. if (substr($content, 0, 1) === '$') { // capture - back compatibility
  391. trigger_error("Capturing {block $content} is deprecated; use {capture $content} instead on line {$this->filter->line}.", E_USER_WARNING);
  392. return $this->macroCapture($content, $modifiers);
  393. }
  394. $name = LatteFilter::fetchToken($content); // block [,] [params]
  395. if ($name === NULL) { // anonymous block
  396. $this->blocks[] = array(self::BLOCK_ANONYMOUS, NULL, $modifiers);
  397. return $modifiers === '' ? '' : 'ob_start()';
  398. } else { // #block
  399. $name = ltrim($name, '#');
  400. if (!preg_match('#^'.LatteFilter::RE_IDENTIFIER.'$#', $name)) {
  401. throw new InvalidStateException("Block name must be alphanumeric string, '$name' given on line {$this->filter->line}.");
  402. } elseif (isset($this->namedBlocks[$name])) {
  403. throw new InvalidStateException("Cannot redeclare block '$name'; on line {$this->filter->line}.");
  404. }
  405. $top = empty($this->blocks);
  406. $this->namedBlocks[$name] = $name;
  407. $this->blocks[] = array(self::BLOCK_NAMED, $name, '');
  408. if ($name[0] === '_') { // snippet - experimental
  409. $tag = LatteFilter::fetchToken($content); // [name [,]] [tag]
  410. $tag = trim($tag, '<>');
  411. $namePhp = var_export(substr($name, 1), TRUE);
  412. if (!$tag) $tag = 'div';
  413. return "?><$tag id=\"<?php echo \$control->getSnippetId($namePhp) ?>\"><?php " . $this->macroInclude('#' . $name, $modifiers) . " ?></$tag><?php {block $name}";
  414. } elseif (!$top) {
  415. return $this->macroInclude('#' . $name, $modifiers, TRUE) . "{block $name}";
  416. } elseif ($this->extends) {
  417. return "{block $name}";
  418. } else {
  419. return 'if (!$_cb->extends) { ' . $this->macroInclude('#' . $name, $modifiers) . "; } {block $name}";
  420. }
  421. }
  422. }
  423. /**
  424. * {/block}
  425. */
  426. public function macroBlockEnd($content)
  427. {
  428. list($type, $name, $modifiers) = array_pop($this->blocks);
  429. if ($type === self::BLOCK_CAPTURE) { // capture - back compatibility
  430. $this->blocks[] = array($type, $name, $modifiers);
  431. return $this->macroCaptureEnd($content);
  432. }
  433. if (($type !== self::BLOCK_NAMED && $type !== self::BLOCK_ANONYMOUS) || ($content && $content !== $name)) {
  434. throw new InvalidStateException("Tag {/block $content} was not expected here on line {$this->filter->line}.");
  435. } elseif ($type === self::BLOCK_NAMED) { // block
  436. return "{/block $name}";
  437. } else { // anonymous block
  438. return $modifiers === '' ? '' : 'echo ' . LatteFilter::formatModifiers('ob_get_clean()', $modifiers);
  439. }
  440. }
  441. /**
  442. * {snippet ...}
  443. */
  444. public function macroSnippet($content)
  445. {
  446. if (substr($content, 0, 1) === ':' || !$this->oldSnippetMode) { // experimental behaviour
  447. $this->oldSnippetMode = FALSE;
  448. return $this->macroBlock('_' . ltrim($content, ':'), '');
  449. }
  450. $args = array('');
  451. if ($snippet = LatteFilter::fetchToken($content)) { // [name [,]] [tag]
  452. $args[] = LatteFilter::formatString($snippet);
  453. }
  454. if ($content) {
  455. $args[] = LatteFilter::formatString($content);
  456. }
  457. return '} if ($_cb->foo = SnippetHelper::create($control' . implode(', ', $args) . ')) { $_cb->snippets[] = $_cb->foo';
  458. }
  459. /**
  460. * {snippet ...}
  461. */
  462. public function macroSnippetEnd($content)
  463. {
  464. if (!$this->oldSnippetMode) {
  465. return $this->macroBlockEnd('', '');
  466. }
  467. return 'array_pop($_cb->snippets)->finish(); } if (SnippetHelper::$outputAllowed) {';
  468. }
  469. /**
  470. * {capture ...}
  471. */
  472. public function macroCapture($content, $modifiers)
  473. {
  474. $name = LatteFilter::fetchToken($content); // $variable
  475. if (substr($name, 0, 1) !== '$') {
  476. throw new InvalidStateException("Invalid capture block parameter '$name' on line {$this->filter->line}.");
  477. }
  478. $this->blocks[] = array(self::BLOCK_CAPTURE, $name, $modifiers);
  479. return 'ob_start()';
  480. }
  481. /**
  482. * {/capture}
  483. */
  484. public function macroCaptureEnd($content)
  485. {
  486. list($type, $name, $modifiers) = array_pop($this->blocks);
  487. if ($type !== self::BLOCK_CAPTURE || ($content && $content !== $name)) {
  488. throw new InvalidStateException("Tag {/capture $content} was not expected here on line {$this->filter->line}.");
  489. }
  490. return $name . '=' . LatteFilter::formatModifiers('ob_get_clean()', $modifiers);
  491. }
  492. /**
  493. * Converts {block named}...{/block} to functions.
  494. */
  495. private function cbNamedBlocks($matches)
  496. {
  497. list(, $name, $content) = $matches;
  498. $func = '_cbb' . substr(md5($this->uniq . $name), 0, 10) . '_' . preg_replace('#[^a-z0-9_]#i', '_', $name);
  499. $this->namedBlocks[$name] = "//\n// block $name\n//\n"
  500. . "if (!function_exists(\$_cb->blocks[" . var_export($name, TRUE) . "][] = '$func')) { function $func(\$_cb) { extract(func_get_arg(1))\n?>$content<?php\n}}";
  501. return '';
  502. }
  503. /**
  504. * {foreach ...}
  505. */
  506. public function macroForeach($content)
  507. {
  508. return '$iterator = $_cb->its[] = new SmartCachingIterator(' . preg_replace('# +as +#i', ') as ', $content, 1);
  509. }
  510. /**
  511. * {attr ...}
  512. */
  513. public function macroAttr($content)
  514. {
  515. return preg_replace('#\)\s+#', ')->', $content . ' ');
  516. }
  517. /**
  518. * {contentType ...}
  519. */
  520. public function macroContentType($content)
  521. {
  522. if (strpos($content, 'html') !== FALSE) {
  523. $this->filter->escape = 'TemplateHelpers::escapeHtml';
  524. $this->filter->context = LatteFilter::CONTEXT_TEXT;
  525. } elseif (strpos($content, 'xml') !== FALSE) {
  526. $this->filter->escape = 'TemplateHelpers::escapeXml';
  527. $this->filter->context = LatteFilter::CONTEXT_NONE;
  528. } elseif (strpos($content, 'javascript') !== FALSE) {
  529. $this->filter->escape = 'TemplateHelpers::escapeJs';
  530. $this->filter->context = LatteFilter::CONTEXT_NONE;
  531. } elseif (strpos($content, 'css') !== FALSE) {
  532. $this->filter->escape = 'TemplateHelpers::escapeCss';
  533. $this->filter->context = LatteFilter::CONTEXT_NONE;
  534. } elseif (strpos($content, 'plain') !== FALSE) {
  535. $this->filter->escape = '';
  536. $this->filter->context = LatteFilter::CONTEXT_NONE;
  537. } else {
  538. $this->filter->escape = '$template->escape';
  539. $this->filter->context = LatteFilter::CONTEXT_NONE;
  540. }
  541. // temporary solution
  542. return strpos($content, '/') ? 'Environment::getHttpResponse()->setHeader("Content-Type", "' . $content . '")' : '';
  543. }
  544. /**
  545. * {dump ...}
  546. */
  547. public function macroDump($content)
  548. {
  549. return $content ? "array(" . var_export($content, TRUE) . " => $content)" : 'get_defined_vars()';
  550. }
  551. /**
  552. * {widget ...}
  553. */
  554. public function macroWidget($content)
  555. {
  556. $pair = LatteFilter::fetchToken($content); // widget[:method]
  557. if ($pair === NULL) {
  558. throw new InvalidStateException("Missing widget name in {widget} on line {$this->filter->line}.");
  559. }
  560. $pair = explode(':', $pair, 2);
  561. $widget = LatteFilter::formatString($pair[0]);
  562. $method = isset($pair[1]) ? ucfirst($pair[1]) : '';
  563. $method = preg_match('#^('.LatteFilter::RE_IDENTIFIER.'|)$#', $method) ? "render$method" : "{\"render$method\"}";
  564. $param = LatteFilter::formatArray($content);
  565. if (strpos($content, '=>') === FALSE) $param = substr($param, 6, -1); // removes array()
  566. return ($widget[0] === '$' ? "if (is_object($widget)) {$widget}->$method($param); else " : '')
  567. . "\$control->getWidget($widget)->$method($param)";
  568. }
  569. /**
  570. * {link ...}
  571. */
  572. public function macroLink($content, $modifiers)
  573. {
  574. return LatteFilter::formatModifiers('$control->link(' . $this->formatLink($content) .')', $modifiers);
  575. }
  576. /**
  577. * {plink ...}
  578. */
  579. public function macroPlink($content, $modifiers)
  580. {
  581. return LatteFilter::formatModifiers('$presenter->link(' . $this->formatLink($content) .')', $modifiers);
  582. }
  583. /**
  584. * {ifCurrent ...}
  585. */
  586. public function macroIfCurrent($content)
  587. {
  588. return $content ? 'try { $presenter->link(' . $this->formatLink($content) . '); } catch (InvalidLinkException $e) {}' : '';
  589. }
  590. /**
  591. * Formats {*link ...} parameters.
  592. */
  593. private function formatLink($content)
  594. {
  595. return LatteFilter::formatString(LatteFilter::fetchToken($content)) . LatteFilter::formatArray($content, ', '); // destination [,] args
  596. }
  597. /**
  598. * {assign ...}
  599. */
  600. public function macroAssign($content, $modifiers)
  601. {
  602. if (!$content) {
  603. throw new InvalidStateException("Missing arguments in {assign} on line {$this->filter->line}.");
  604. }
  605. if (strpos($content, '=>') === FALSE) { // back compatibility
  606. return '$' . ltrim(LatteFilter::fetchToken($content), '$') . ' = ' . LatteFilter::formatModifiers($content === '' ? 'NULL' : $content, $modifiers);
  607. }
  608. return 'extract(' . LatteFilter::formatArray($content) . ')';
  609. }
  610. /**
  611. * {default ...}
  612. */
  613. public function macroDefault($content)
  614. {
  615. if (!$content) {
  616. throw new InvalidStateException("Missing arguments in {default} on line {$this->filter->line}.");
  617. }
  618. return 'extract(' . LatteFilter::formatArray($content) . ', EXTR_SKIP)';
  619. }
  620. /**
  621. * Escaping helper.
  622. */
  623. public function macroEscape($content)
  624. {
  625. return $this->filter->escape;
  626. }
  627. /**
  628. * Just modifiers helper.
  629. */
  630. public function macroModifiers($content, $modifiers)
  631. {
  632. return LatteFilter::formatModifiers($content, $modifiers);
  633. }
  634. /********************* run-time helpers ****************d*g**/
  635. /**
  636. * Calls block.
  637. * @param stdClass
  638. * @param string
  639. * @param array
  640. * @return void
  641. */
  642. public static function callBlock($context, $name, $params)
  643. {
  644. if (empty($context->blocks[$name])) {
  645. throw new InvalidStateException("Call to undefined block '$name'.");
  646. }
  647. $block = reset($context->blocks[$name]);
  648. $block($context, $params);
  649. }
  650. /**
  651. * Calls parent block.
  652. * @param stdClass
  653. * @param string
  654. * @param array
  655. * @return void
  656. */
  657. public static function callBlockParent($context, $name, $params)
  658. {
  659. if (empty($context->blocks[$name]) || ($block = next($context->blocks[$name])) === FALSE) {
  660. throw new InvalidStateException("Call to undefined parent block '$name'.");
  661. }
  662. $block($context, $params);
  663. }
  664. /**
  665. * Includes subtemplate.
  666. * @param mixed included file name or template
  667. * @param array parameters
  668. * @param ITemplate current template
  669. * @return Template
  670. */
  671. public static function includeTemplate($destination, $params, $template)
  672. {
  673. if ($destination instanceof ITemplate) {
  674. $tpl = $destination;
  675. } elseif ($destination == NULL) { // intentionally ==
  676. throw new InvalidArgumentException("Template file name was not specified.");
  677. } else {
  678. $tpl = clone $template;
  679. if ($template instanceof IFileTemplate) {
  680. if (substr($destination, 0, 1) !== '/' && substr($destination, 1, 1) !== ':') {
  681. $destination = dirname($template->getFile()) . '/' . $destination;
  682. }
  683. $tpl->setFile($destination);
  684. }
  685. }
  686. $tpl->setParams($params); // interface?
  687. return $tpl;
  688. }
  689. /**
  690. * Initializes state holder $_cb in template.
  691. * @param ITemplate
  692. * @param bool
  693. * @param string
  694. * @return stdClass
  695. */
  696. public static function initRuntime($template, $extends, $realFile)
  697. {
  698. $cb = (object) NULL;
  699. // extends support
  700. if (isset($template->_cb)) {
  701. $cb->blocks = & $template->_cb->blocks;
  702. $cb->templates = & $template->_cb->templates;
  703. }
  704. $cb->templates[$realFile] = $template;
  705. $cb->extends = is_bool($extends) ? $extends : (empty($template->_extends) ? FALSE : $template->_extends);
  706. unset($template->_cb, $template->_extends);
  707. // cache support
  708. if (!empty($cb->caches)) {
  709. end($cb->caches)->addFile($template->getFile());
  710. }
  711. return $cb;
  712. }
  713. public static function renderSnippets($control, $cb, $params)
  714. {
  715. $payload = $control->getPresenter()->getPayload();
  716. if (isset($cb->blocks)) {
  717. foreach ($cb->blocks as $name => $function) {
  718. if ($name[0] !== '_' || !$control->isControlInvalid(substr($name, 1))) continue;
  719. ob_start();
  720. $function = reset($function);
  721. $function($cb, $params);
  722. $payload->snippets[$control->getSnippetId(substr($name, 1))] = ob_get_clean();
  723. }
  724. }
  725. }
  726. }