PageRenderTime 45ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/Nette/Templates/Filters/LatteMacros.php

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