Source for file LatteMacros.php
Documentation is available at LatteMacros.php
- 1: <?php
- 3: /**
- 4: * Nette Framework
- 5: *
- 11: */
- 15: /**
- 16: * Default macros for filter LatteFilter.
- 17: *
- 18: * - {$variable} with escaping
- 19: * - {!$variable} without escaping
- 20: * - {*comment*} will be removed
- 21: * - {=expression} echo with escaping
- 22: * - {!=expression} echo without escaping
- 23: * - {?expression} evaluate PHP statement
- 24: * - {_expression} echo translation with escaping
- 25: * - {!_expression} echo translation without escaping
- 26: * - {link destination ...} control link
- 27: * - {plink destination ...} presenter link
- 28: * - {if ?} ... {elseif ?} ... {else} ... {/if}
- 29: * - {ifset ?} ... {elseifset ?} ... {/if}
- 30: * - {for ?} ... {/for}
- 31: * - {foreach ?} ... {/foreach}
- 32: * - {include ?}
- 33: * - {cache ?} ... {/cache} cached block
- 34: * - {snippet ?} ... {/snippet ?} control snippet
- 35: * - {attr ?} HTML element attributes
- 36: * - {block|texy} ... {/block} block
- 37: * - {contentType ...} HTTP Content-Type header
- 38: * - {status ...} HTTP status
- 39: * - {capture ?} ... {/capture} capture block to parameter
- 40: * - {assign var => value} set template parameter
- 41: * - {default var => value} set default template parameter
- 42: * - {dump $var}
- 43: * - {debugbreak}
- 44: *
- 47: */
- 49: {
- 64: 'cache' => '<?php if ($_cb->foo = CachingHelper::create($_cb->key = md5(__FILE__) . __LINE__, $template->getFile(), array(%%))) { $_cb->caches[] = $_cb->foo ?>',
- 65: '/cache' => '<?php array_pop($_cb->caches)->save(); } if (!empty($_cb->caches)) end($_cb->caches)->addItem($_cb->key) ?>',
- 89: 'ifCurrent' => '<?php %:macroIfCurrent%; if ($presenter->getLastCreatedRequestFlag("current")): ?>',
- 98: 'dump' => '<?php Debug::consoleDump(%:macroDump%, "Template " . str_replace(Environment::getVariable("appDir"), "\xE2\x80\xA6", $template->getFile())) ?>',
- 99: 'debugbreak' => '<?php if (function_exists("debugbreak")) debugbreak(); elseif (function_exists("xdebug_break")) xdebug_break() ?>',
- 132: /**#@+ @ignore internal block type */
- 136: /**#@-*/
- 140: /**
- 141: * Constructor.
- 142: */
- 144: {
- 146: }
- 150: /**
- 151: * Initializes parsing.
- 155: */
- 157: {
- 167: // remove comments
- 170: // snippets support (temporary solution)
- 174: $s
- 176: }
- 180: /**
- 181: * Finishes parsing.
- 184: */
- 186: {
- 187: // blocks closing check
- 193: }
- 195: // snippets support (temporary solution)
- 198: // extends support
- 203: . 'if ($_cb->extends) { ob_end_clean(); LatteMacros::includeTemplate($_cb->extends, get_defined_vars(), $template)->render(); }' . "\n";
- 204: }
- 206: // named blocks
- 210: $s = preg_replace_callback("#{block ($name)} \?>(.*)<\?php {/block $name}#sU", array($this, 'cbNamedBlocks'), $s);
- 211: }
- 213: }
- 215: // internal state holder
- 218: . "\$_cb = LatteMacros::initRuntime(\$template, " . var_export($this->extends, TRUE) . ", " . var_export($this->uniq, TRUE) . "); unset(\$_extends);\n"
- 220: }
- 224: /**
- 225: * Process {macro content | modifiers}
- 230: */
- 232: {
- 239: }
- 240: }
- 245: }
- 248: }
- 252: /**
- 253: * Callback for self::macro().
- 254: */
- 256: {
- 263: }
- 264: }
- 268: /**
- 269: * Process <n:tag attr> (experimental).
- 274: */
- 276: {
- 286: isset($knownTags[$name], $attrs[$knownTags[$name]]) ? $attrs[$knownTags[$name]] : substr(var_export($attrs, TRUE), 8, -1),
- 289: }
- 293: /**
- 294: * Process <tag n:attr> (experimental).
- 299: */
- 301: {
- 306: }
- 314: }
- 315: }
- 323: }
- 324: }
- 330: }
- 333: }
- 336: }
- 340: /********************* macros ****************d*g**/
- 344: /**
- 345: * {$var |modifiers}
- 346: */
- 348: {
- 350: }
- 354: /**
- 355: * {_$var |modifiers}
- 356: */
- 358: {
- 360: }
- 364: /**
- 365: * {syntax ...}
- 366: */
- 368: {
- 391: default:
- 393: }
- 394: }
- 398: /**
- 399: * {include ...}
- 400: */
- 401: public function macroInclude($content, $modifiers)
- 402: {
- 403: $destination = LatteFilter::fetchToken($content); // destination [,] [params]
- 404: $params = LatteFilter::formatArray($content) . ($content ? ' + ' : '');
- 406: if ($destination === NULL) {
- 407: throw new <a href="../exceptions/InvalidStateException.html">InvalidStateException</a>("Missing destination in {include} on line {$this->filter->line}.");
- 409: } elseif ($destination[0] === '#') { // include #block
- 411: if (!preg_match('#^'.<a href="../Nette-Templates/LatteFilter.html">LatteFilter</a>::RE_IDENTIFIER.'$#', $destination)) {
- 412: throw new <a href="../exceptions/InvalidStateException.html">InvalidStateException</a>("Included block name must be alphanumeric string, '$destination' given on line {$this->filter->line}.");
- 413: }
- 415: $parent = $destination === 'parent';
- 416: if ($destination === 'parent' || $destination === 'this') {
- 419: if (!$item) {
- 420: throw new <a href="../exceptions/InvalidStateException.html">InvalidStateException</a>("Cannot include $destination block outside of any block on line {$this->filter->line}.");
- 421: }
- 422: $destination = $item[1];
- 423: }
- 424: $name = var_export($destination, TRUE);
- 425: $params .= 'get_defined_vars()';
- 426: $cmd = isset($this->namedBlocks[$destination]) && !$parent
- 429: return $modifiers
- 431: : $cmd;
- 433: } else { // include "file"
- 434: $destination = <a href="../Nette-Templates/LatteFilter.html">LatteFilter</a>::formatString($destination);
- 435: $params .= '$template->getParams()';
- 436: return $modifiers
- 437: ? 'echo ' . <a href="../Nette-Templates/LatteFilter.html">LatteFilter</a>::formatModifiers('LatteMacros::includeTemplate(' . $destination . ', ' . $params . ', $_cb->templates[' . var_export($this->uniq, TRUE) . '])->__toString(TRUE)', $modifiers)
- 438: : 'LatteMacros::includeTemplate(' . $destination . ', ' . $params . ', $_cb->templates[' . var_export($this->uniq, TRUE) . '])->render()';
- 439: }
- 440: }
- 444: /**
- 445: * {extends ...}
- 446: */
- 447: public function macroExtends($content)
- 448: {
- 449: $destination = <a href="../Nette-Templates/LatteFilter.html">LatteFilter</a>::fetchToken($content); // destination
- 450: if ($destination === NULL) {
- 451: throw new <a href="../exceptions/InvalidStateException.html">InvalidStateException</a>("Missing destination in {extends} on line {$this->filter->line}.");
- 452: }
- 453: if (!empty($this->blocks)) {
- 454: throw new InvalidStateException("{extends} must be placed outside any block; on line {$this->filter->line}.");
- 455: }
- 456: if ($this->extends !== NULL) {
- 457: throw new InvalidStateException("Multiple {extends} declarations are not allowed; on line {$this->filter->line}.");
- 458: }
- 459: $this->extends = $destination !== 'none';
- 460: return $this->extends ? '$_cb->extends = ' . <a href="../Nette-Templates/LatteFilter.html">LatteFilter</a>::formatString($destination) : '';
- 461: }
- 465: /**
- 466: * {block ...}
- 467: */
- 468: public function macroBlock($content, $modifiers)
- 469: {
- 471: trigger_error("Capturing {block $content} is deprecated; use {capture $content} instead on line {$this->filter->line}.", E_USER_WARNING);
- 472: return $this->macroCapture($content, $modifiers);
- 473: }
- 475: $name = LatteFilter::fetchToken($content); // block [,] [params]
- 477: if ($name === NULL) { // anonymous block
- 478: $this->blocks[] = array(self::BLOCK_ANONYMOUS, NULL, $modifiers);
- 479: return $modifiers === '' ? '' : 'ob_start()';
- 481: } else { // #block
- 483: if (!preg_match('#^'.<a href="../Nette-Templates/LatteFilter.html">LatteFilter</a>::RE_IDENTIFIER.'$#', $name)) {
- 484: throw new <a href="../exceptions/InvalidStateException.html">InvalidStateException</a>("Block name must be alphanumeric string, '$name' given on line {$this->filter->line}.");
- 486: } elseif (isset($this->namedBlocks[$name])) {
- 487: throw new InvalidStateException("Cannot redeclare block '$name'; on line {$this->filter->line}.");
- 488: }
- 490: $top = empty($this->blocks);
- 491: $this->namedBlocks[$name] = $name;
- 492: $this->blocks[] = array(self::BLOCK_NAMED, $name, '');
- 493: if (!$top) {
- 496: } elseif ($this->extends) {
- 499: } else {
- 500: return 'if (!$_cb->extends) { ' . $this->macroInclude('#' . $name, $modifiers) . "; } {block $name}";
- 501: }
- 502: }
- 503: }
- 507: /**
- 508: * {/block}
- 509: */
- 510: public function macroBlockEnd($content)
- 511: {
- 512: list($type, $name, $modifiers) = array_pop($this->blocks);
- 514: if ($type === self::BLOCK_CAPTURE) { // capture - back compatibility
- 515: $this->blocks[] = array($type, $name, $modifiers);
- 516: return $this->macroCaptureEnd($content);
- 517: }
- 519: if (($type !== self::BLOCK_NAMED && $type !== self::BLOCK_ANONYMOUS) || ($content && $content !== $name)) {
- 520: throw new InvalidStateException("Tag {/block $content} was not expected here on line {$this->filter->line}.");
- 522: } elseif ($type === self::BLOCK_NAMED) { // block
- 525: } else { // anonymous block
- 526: return $modifiers === '' ? '' : 'echo ' . <a href="../Nette-Templates/LatteFilter.html">LatteFilter</a>::formatModifiers('ob_get_clean()', $modifiers);
- 527: }
- 528: }
- 532: /**
- 533: * {snippet ...}
- 534: */
- 535: public function macroSnippet($content)
- 536: {
- 537: $args = array('');
- 538: if ($snippet = <a href="../Nette-Templates/LatteFilter.html">LatteFilter</a>::fetchToken($content)) { // [name [,]] [tag]
- 539: $args[] = <a href="../Nette-Templates/LatteFilter.html">LatteFilter</a>::formatString($snippet);
- 540: }
- 541: if ($content) {
- 542: $args[] = <a href="../Nette-Templates/LatteFilter.html">LatteFilter</a>::formatString($content);
- 543: }
- 545: }
- 549: /**
- 550: * {snippet ...}
- 551: */
- 552: public function macroSnippetEnd($content)
- 553: {
- 554: return 'array_pop($_cb->snippets)->finish(); } if (SnippetHelper::$outputAllowed) {';
- 555: }
- 559: /**
- 560: * {capture ...}
- 561: */
- 562: public function macroCapture($content, $modifiers)
- 563: {
- 564: $name = <a href="../Nette-Templates/LatteFilter.html">LatteFilter</a>::fetchToken($content); // $variable
- 567: throw new <a href="../exceptions/InvalidStateException.html">InvalidStateException</a>("Invalid capture block parameter '$name' on line {$this->filter->line}.");
- 568: }
- 570: $this->blocks[] = array(self::BLOCK_CAPTURE, $name, $modifiers);
- 571: return 'ob_start()';
- 572: }
- 576: /**
- 577: * {/capture}
- 578: */
- 579: public function macroCaptureEnd($content)
- 580: {
- 583: if ($type !== self::BLOCK_CAPTURE || ($content && $content !== $name)) {
- 584: throw new <a href="../exceptions/InvalidStateException.html">InvalidStateException</a>("Tag {/capture $content} was not expected here on line {$this->filter->line}.");
- 585: }
- 587: return $name . '=' . <a href="../Nette-Templates/LatteFilter.html">LatteFilter</a>::formatModifiers('ob_get_clean()', $modifiers);
- 588: }
- 592: /**
- 593: * Converts {block named}...{/block} to functions.
- 594: */
- 595: private function cbNamedBlocks($matches)
- 596: {
- 597: list(, $name, $content) = $matches;
- 598: $func = '_cbb' . substr(md5($this->uniq . $name), 0, 10) . '_' . preg_replace('#[^a-z0-9_]#i', '_', $name);
- 600: . "if (!function_exists(\$_cb->blocks[" . var_export($name, TRUE) . "][] = '$func')) { function $func() { extract(func_get_arg(0))\n?>$content<?php\n}}";
- 601: return '';
- 602: }
- 606: /**
- 607: * {foreach ...}
- 608: */
- 609: public function macroForeach($content)
- 610: {
- 611: return '$iterator = $_cb->its[] = new SmartCachingIterator(' . preg_replace('# +as +#i', ') as ', $content, 1);
- 612: }
- 616: /**
- 617: * {attr ...}
- 618: */
- 620: {
- 621: return preg_replace('#\)\s+#', ')->', $content . ' ');
- 622: }
- 626: /**
- 627: * {contentType ...}
- 628: */
- 629: public function macroContentType($content)
- 630: {
- 651: } else {
- 654: }
- 656: // temporary solution
- 658: }
- 662: /**
- 663: * {dump ...}
- 664: */
- 666: {
- 667: return $content ? "array(" . var_export($content, TRUE) . " => $content)" : 'get_defined_vars()';
- 668: }
- 672: /**
- 673: * {widget ...}
- 674: */
- 675: public function macroWidget($content)
- 676: {
- 677: $pair = <a href="../Nette-Templates/LatteFilter.html">LatteFilter</a>::fetchToken($content); // widget[:method]
- 678: if ($pair === NULL) {
- 679: throw new <a href="../exceptions/InvalidStateException.html">InvalidStateException</a>("Missing widget name in {widget} on line {$this->filter->line}.");
- 680: }
- 681: $pair = explode(':', $pair, 2);
- 682: $widget = <a href="../Nette-Templates/LatteFilter.html">LatteFilter</a>::formatString($pair[0]);
- 684: $method = preg_match('#^('.<a href="../Nette-Templates/LatteFilter.html">LatteFilter</a>::RE_IDENTIFIER.'|)$#', $method) ? "render$method" : "{\"render$method\"}";
- 685: $param = LatteFilter::formatArray($content);
- 689: }
- 693: /**
- 694: * {link ...}
- 695: */
- 696: public function macroLink($content, $modifiers)
- 697: {
- 698: return LatteFilter::formatModifiers('$control->link(' . $this->formatLink($content) .')', $modifiers);
- 699: }
- 703: /**
- 704: * {plink ...}
- 705: */
- 706: public function macroPlink($content, $modifiers)
- 707: {
- 708: return <a href="../Nette-Templates/LatteFilter.html">LatteFilter</a>::formatModifiers('$presenter->link(' . $this->formatLink($content) .')', $modifiers);
- 709: }
- 713: /**
- 714: * {ifCurrent ...}
- 715: */
- 716: public function macroIfCurrent($content)
- 717: {
- 718: return $content ? 'try { $presenter->link(' . $this->formatLink($content) . '); } catch (InvalidLinkException $e) {}' : '';
- 719: }
- 723: /**
- 724: * Formats {*link ...} parameters.
- 725: */
- 726: private function formatLink($content)
- 727: {
- 728: return <a href="../Nette-Templates/LatteFilter.html">LatteFilter</a>::formatString(<a href="../Nette-Templates/LatteFilter.html">LatteFilter</a>::fetchToken($content)) . <a href="../Nette-Templates/LatteFilter.html">LatteFilter</a>::formatArray($content, ', '); // destination [,] args
- 729: }
- 733: /**
- 734: * {assign ...}
- 735: */
- 736: public function macroAssign($content, $modifiers)
- 737: {
- 738: if (!$content) {
- 739: throw new <a href="../exceptions/InvalidStateException.html">InvalidStateException</a>("Missing arguments in {assign} on line {$this->filter->line}.");
- 740: }
- 741: if (strpos($content, '=>') === FALSE) { // back compatibility
- 742: return '$' . ltrim(<a href="../Nette-Templates/LatteFilter.html">LatteFilter</a>::fetchToken($content), '$') . ' = ' . <a href="../Nette-Templates/LatteFilter.html">LatteFilter</a>::formatModifiers($content === '' ? 'NULL' : $content, $modifiers);
- 743: }
- 744: return 'extract(' . <a href="../Nette-Templates/LatteFilter.html">LatteFilter</a>::formatArray($content) . ')';
- 745: }
- 749: /**
- 750: * {default ...}
- 751: */
- 752: public function macroDefault($content)
- 753: {
- 754: if (!$content) {
- 755: throw new <a href="../exceptions/InvalidStateException.html">InvalidStateException</a>("Missing arguments in {default} on line {$this->filter->line}.");
- 756: }
- 757: return 'extract(' . <a href="../Nette-Templates/LatteFilter.html">LatteFilter</a>::formatArray($content) . ', EXTR_SKIP)';
- 758: }
- 762: /**
- 763: * Escaping helper.
- 764: */
- 765: public function macroEscape($content)
- 766: {
- 768: }
- 772: /**
- 773: * Just modifiers helper.
- 774: */
- 775: public function macroModifiers($content, $modifiers)
- 776: {
- 777: return <a href="../Nette-Templates/LatteFilter.html">LatteFilter</a>::formatModifiers($content, $modifiers);
- 778: }
- 782: /********************* run-time helpers ****************d*g**/
- 786: /**
- 787: * Calls block.
- 788: * @param array
- 789: * @param string
- 790: * @param array
- 791: * @return void
- 792: */
- 793: public static function callBlock(& $blocks, $name, $params)
- 794: {
- 795: if (empty($blocks[$name])) {
- 796: throw new InvalidStateException("Call to undefined block '$name'.");
- 797: }
- 799: $block($params);
- 800: }
- 804: /**
- 805: * Calls parent block.
- 806: * @param array
- 807: * @param string
- 808: * @param array
- 809: * @return void
- 810: */
- 811: public static function callBlockParent(& $blocks, $name, $params)
- 812: {
- 813: if (empty($blocks[$name]) || ($block = next($blocks[$name])) === FALSE) {
- 815: }
- 816: $block($params);
- 817: }
- 821: /**
- 822: * Includes subtemplate.
- 823: * @param mixed included file name or template
- 824: * @param array parameters
- 825: * @param ITemplate current template
- 826: * @return Template
- 827: */
- 828: public static function includeTemplate($destination, $params, $template)
- 829: {
- 830: if ($destination instanceof ITemplate) {
- 831: $tpl = $destination;
- 833: } elseif ($destination == NULL) { // intentionally ==
- 834: throw new InvalidArgumentException("Template file name was not specified.");
- 836: } else {
- 837: $tpl = clone $template;
- 838: if ($template instanceof IFileTemplate) {
- 839: if (substr($destination, 0, 1) !== '/' && substr($destination, 1, 1) !== ':') {
- 840: $destination = dirname($template->getFile()) . '/' . $destination;
- 841: }
- 842: $tpl->setFile($destination);
- 843: }
- 844: }
- 846: $tpl->setParams($params); // interface?
- 847: return $tpl;
- 848: }
- 852: /**
- 853: * Initializes state holder $_cb in template.
- 854: * @param ITemplate
- 855: * @param bool
- 856: * @param string
- 857: * @return stdClass
- 858: */
- 859: public static function initRuntime($template, $extends, $realFile)
- 860: {
- 861: $cb = (object) NULL;
- 863: // extends support
- 864: if (isset($template->_cb)) {
- 865: $cb->blocks = & $template->_cb->blocks;
- 866: $cb->templates = & $template->_cb->templates;
- 867: }
- 868: $cb->templates[$realFile] = $template;
- 869: $cb->extends = is_bool($extends) ? $extends : (empty($template->_extends) ? FALSE : $template->_extends);
- 870: unset($template->_cb, $template->_extends);
- 872: // cache support
- 873: if (!empty($cb->caches)) {
- 875: }
- 877: return $cb;
- 878: }