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: * - {var var => value} set template parameter
- 41: * - {assign var => value} set template parameter
- 42: * - {default var => value} set default template parameter
- 43: * - {dump $var}
- 44: * - {debugbreak}
- 45: *
- 48: */
- 50: {
- 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) ?>',
- 90: 'ifCurrent' => '<?php %:macroIfCurrent%; if ($presenter->getLastCreatedRequestFlag("current")): ?>',
- 100: 'dump' => '<?php Debug::consoleDump(%:macroDump%, "Template " . str_replace(Environment::getVariable("appDir"), "\xE2\x80\xA6", $template->getFile())) ?>',
- 101: 'debugbreak' => '<?php if (function_exists("debugbreak")) debugbreak(); elseif (function_exists("xdebug_break")) xdebug_break() ?>',
- 134: /**#@+ @ignore internal block type */
- 138: /**#@-*/
- 142: /**
- 143: * Constructor.
- 144: */
- 146: {
- 148: }
- 152: /**
- 153: * Initializes parsing.
- 157: */
- 159: {
- 169: // remove comments
- 172: // snippets support (temporary solution)
- 176: $s
- 178: }
- 182: /**
- 183: * Finishes parsing.
- 186: */
- 188: {
- 189: // blocks closing check
- 195: }
- 197: // snippets support (temporary solution)
- 200: // extends support
- 205: . 'if ($_cb->extends) { ob_end_clean(); LatteMacros::includeTemplate($_cb->extends, get_defined_vars(), $template)->render(); }' . "\n";
- 206: }
- 208: // named blocks
- 212: $s = preg_replace_callback("#{block ($name)} \?>(.*)<\?php {/block $name}#sU", array($this, 'cbNamedBlocks'), $s);
- 213: }
- 215: }
- 217: // internal state holder
- 220: . "\$_cb = LatteMacros::initRuntime(\$template, " . var_export($this->extends, TRUE) . ", " . var_export($this->uniq, TRUE) . "); unset(\$_extends);\n"
- 222: }
- 226: /**
- 227: * Process {macro content | modifiers}
- 232: */
- 234: {
- 241: }
- 242: }
- 247: }
- 250: }
- 254: /**
- 255: * Callback for self::macro().
- 256: */
- 258: {
- 265: }
- 266: }
- 270: /**
- 271: * Process <n:tag attr> (experimental).
- 276: */
- 278: {
- 288: isset($knownTags[$name], $attrs[$knownTags[$name]]) ? $attrs[$knownTags[$name]] : substr(var_export($attrs, TRUE), 8, -1),
- 291: }
- 295: /**
- 296: * Process <tag n:attr> (experimental).
- 301: */
- 303: {
- 308: }
- 316: }
- 317: }
- 325: }
- 326: }
- 332: }
- 335: }
- 338: }
- 342: /********************* macros ****************d*g**/
- 346: /**
- 347: * {$var |modifiers}
- 348: */
- 350: {
- 352: }
- 356: /**
- 357: * {_$var |modifiers}
- 358: */
- 360: {
- 362: }
- 366: /**
- 367: * {syntax ...}
- 368: */
- 370: {
- 393: default:
- 395: }
- 396: }
- 400: /**
- 401: * {include ...}
- 402: */
- 403: public function macroInclude($content, $modifiers)
- 404: {
- 405: $destination = LatteFilter::fetchToken($content); // destination [,] [params]
- 406: $params = LatteFilter::formatArray($content) . ($content ? ' + ' : '');
- 408: if ($destination === NULL) {
- 409: throw new <a href="../exceptions/InvalidStateException.html">InvalidStateException</a>("Missing destination in {include} on line {$this->filter->line}.");
- 411: } elseif ($destination[0] === '#') { // include #block
- 413: if (!preg_match('#^'.<a href="../Nette-Templates/LatteFilter.html">LatteFilter</a>::RE_IDENTIFIER.'$#', $destination)) {
- 414: throw new <a href="../exceptions/InvalidStateException.html">InvalidStateException</a>("Included block name must be alphanumeric string, '$destination' given on line {$this->filter->line}.");
- 415: }
- 417: $parent = $destination === 'parent';
- 418: if ($destination === 'parent' || $destination === 'this') {
- 421: if (!$item) {
- 422: throw new <a href="../exceptions/InvalidStateException.html">InvalidStateException</a>("Cannot include $destination block outside of any block on line {$this->filter->line}.");
- 423: }
- 424: $destination = $item[1];
- 425: }
- 426: $name = var_export($destination, TRUE);
- 427: $params .= 'get_defined_vars()';
- 428: $cmd = isset($this->namedBlocks[$destination]) && !$parent
- 431: return $modifiers
- 433: : $cmd;
- 435: } else { // include "file"
- 436: $destination = <a href="../Nette-Templates/LatteFilter.html">LatteFilter</a>::formatString($destination);
- 437: $params .= '$template->getParams()';
- 438: return $modifiers
- 439: ? 'echo ' . <a href="../Nette-Templates/LatteFilter.html">LatteFilter</a>::formatModifiers('LatteMacros::includeTemplate(' . $destination . ', ' . $params . ', $_cb->templates[' . var_export($this->uniq, TRUE) . '])->__toString(TRUE)', $modifiers)
- 440: : 'LatteMacros::includeTemplate(' . $destination . ', ' . $params . ', $_cb->templates[' . var_export($this->uniq, TRUE) . '])->render()';
- 441: }
- 442: }
- 446: /**
- 447: * {extends ...}
- 448: */
- 449: public function macroExtends($content)
- 450: {
- 451: $destination = <a href="../Nette-Templates/LatteFilter.html">LatteFilter</a>::fetchToken($content); // destination
- 452: if ($destination === NULL) {
- 453: throw new <a href="../exceptions/InvalidStateException.html">InvalidStateException</a>("Missing destination in {extends} on line {$this->filter->line}.");
- 454: }
- 455: if (!empty($this->blocks)) {
- 456: throw new InvalidStateException("{extends} must be placed outside any block; on line {$this->filter->line}.");
- 457: }
- 458: if ($this->extends !== NULL) {
- 459: throw new InvalidStateException("Multiple {extends} declarations are not allowed; on line {$this->filter->line}.");
- 460: }
- 461: $this->extends = $destination !== 'none';
- 462: return $this->extends ? '$_cb->extends = ' . <a href="../Nette-Templates/LatteFilter.html">LatteFilter</a>::formatString($destination) : '';
- 463: }
- 467: /**
- 468: * {block ...}
- 469: */
- 470: public function macroBlock($content, $modifiers)
- 471: {
- 473: trigger_error("Capturing {block $content} is deprecated; use {capture $content} instead on line {$this->filter->line}.", E_USER_WARNING);
- 474: return $this->macroCapture($content, $modifiers);
- 475: }
- 477: $name = LatteFilter::fetchToken($content); // block [,] [params]
- 479: if ($name === NULL) { // anonymous block
- 480: $this->blocks[] = array(self::BLOCK_ANONYMOUS, NULL, $modifiers);
- 481: return $modifiers === '' ? '' : 'ob_start()';
- 483: } else { // #block
- 485: if (!preg_match('#^'.<a href="../Nette-Templates/LatteFilter.html">LatteFilter</a>::RE_IDENTIFIER.'$#', $name)) {
- 486: throw new <a href="../exceptions/InvalidStateException.html">InvalidStateException</a>("Block name must be alphanumeric string, '$name' given on line {$this->filter->line}.");
- 488: } elseif (isset($this->namedBlocks[$name])) {
- 489: throw new InvalidStateException("Cannot redeclare block '$name'; on line {$this->filter->line}.");
- 490: }
- 492: $top = empty($this->blocks);
- 493: $this->namedBlocks[$name] = $name;
- 494: $this->blocks[] = array(self::BLOCK_NAMED, $name, '');
- 495: if (!$top) {
- 498: } elseif ($this->extends) {
- 501: } else {
- 502: return 'if (!$_cb->extends) { ' . $this->macroInclude('#' . $name, $modifiers) . "; } {block $name}";
- 503: }
- 504: }
- 505: }
- 509: /**
- 510: * {/block}
- 511: */
- 512: public function macroBlockEnd($content)
- 513: {
- 514: list($type, $name, $modifiers) = array_pop($this->blocks);
- 516: if ($type === self::BLOCK_CAPTURE) { // capture - back compatibility
- 517: $this->blocks[] = array($type, $name, $modifiers);
- 518: return $this->macroCaptureEnd($content);
- 519: }
- 521: if (($type !== self::BLOCK_NAMED && $type !== self::BLOCK_ANONYMOUS) || ($content && $content !== $name)) {
- 522: throw new InvalidStateException("Tag {/block $content} was not expected here on line {$this->filter->line}.");
- 524: } elseif ($type === self::BLOCK_NAMED) { // block
- 527: } else { // anonymous block
- 528: return $modifiers === '' ? '' : 'echo ' . <a href="../Nette-Templates/LatteFilter.html">LatteFilter</a>::formatModifiers('ob_get_clean()', $modifiers);
- 529: }
- 530: }
- 534: /**
- 535: * {snippet ...}
- 536: */
- 537: public function macroSnippet($content)
- 538: {
- 539: $args = array('');
- 540: if ($snippet = <a href="../Nette-Templates/LatteFilter.html">LatteFilter</a>::fetchToken($content)) { // [name [,]] [tag]
- 541: $args[] = <a href="../Nette-Templates/LatteFilter.html">LatteFilter</a>::formatString($snippet);
- 542: }
- 543: if ($content) {
- 544: $args[] = <a href="../Nette-Templates/LatteFilter.html">LatteFilter</a>::formatString($content);
- 545: }
- 547: }
- 551: /**
- 552: * {snippet ...}
- 553: */
- 554: public function macroSnippetEnd($content)
- 555: {
- 556: return 'array_pop($_cb->snippets)->finish(); } if (SnippetHelper::$outputAllowed) {';
- 557: }
- 561: /**
- 562: * {capture ...}
- 563: */
- 564: public function macroCapture($content, $modifiers)
- 565: {
- 566: $name = <a href="../Nette-Templates/LatteFilter.html">LatteFilter</a>::fetchToken($content); // $variable
- 569: throw new <a href="../exceptions/InvalidStateException.html">InvalidStateException</a>("Invalid capture block parameter '$name' on line {$this->filter->line}.");
- 570: }
- 572: $this->blocks[] = array(self::BLOCK_CAPTURE, $name, $modifiers);
- 573: return 'ob_start()';
- 574: }
- 578: /**
- 579: * {/capture}
- 580: */
- 581: public function macroCaptureEnd($content)
- 582: {
- 585: if ($type !== self::BLOCK_CAPTURE || ($content && $content !== $name)) {
- 586: throw new <a href="../exceptions/InvalidStateException.html">InvalidStateException</a>("Tag {/capture $content} was not expected here on line {$this->filter->line}.");
- 587: }
- 589: return $name . '=' . <a href="../Nette-Templates/LatteFilter.html">LatteFilter</a>::formatModifiers('ob_get_clean()', $modifiers);
- 590: }
- 594: /**
- 595: * Converts {block named}...{/block} to functions.
- 596: */
- 597: private function cbNamedBlocks($matches)
- 598: {
- 599: list(, $name, $content) = $matches;
- 600: $func = '_cbb' . substr(md5($this->uniq . $name), 0, 10) . '_' . preg_replace('#[^a-z0-9_]#i', '_', $name);
- 602: . "if (!function_exists(\$_cb->blocks[" . var_export($name, TRUE) . "][] = '$func')) { function $func() { extract(func_get_arg(0))\n?>$content<?php\n}}";
- 603: return '';
- 604: }
- 608: /**
- 609: * {foreach ...}
- 610: */
- 611: public function macroForeach($content)
- 612: {
- 613: return '$iterator = $_cb->its[] = new SmartCachingIterator(' . preg_replace('# +as +#i', ') as ', $content, 1);
- 614: }
- 618: /**
- 619: * {attr ...}
- 620: */
- 622: {
- 623: return preg_replace('#\)\s+#', ')->', $content . ' ');
- 624: }
- 628: /**
- 629: * {contentType ...}
- 630: */
- 631: public function macroContentType($content)
- 632: {
- 653: } else {
- 656: }
- 658: // temporary solution
- 660: }
- 664: /**
- 665: * {dump ...}
- 666: */
- 668: {
- 669: return $content ? "array(" . var_export($content, TRUE) . " => $content)" : 'get_defined_vars()';
- 670: }
- 674: /**
- 675: * {widget ...}
- 676: */
- 677: public function macroWidget($content)
- 678: {
- 679: $pair = <a href="../Nette-Templates/LatteFilter.html">LatteFilter</a>::fetchToken($content); // widget[:method]
- 680: if ($pair === NULL) {
- 681: throw new <a href="../exceptions/InvalidStateException.html">InvalidStateException</a>("Missing widget name in {widget} on line {$this->filter->line}.");
- 682: }
- 683: $pair = explode(':', $pair, 2);
- 684: $widget = <a href="../Nette-Templates/LatteFilter.html">LatteFilter</a>::formatString($pair[0]);
- 686: $method = preg_match('#^('.<a href="../Nette-Templates/LatteFilter.html">LatteFilter</a>::RE_IDENTIFIER.'|)$#', $method) ? "render$method" : "{\"render$method\"}";
- 687: $param = LatteFilter::formatArray($content);
- 691: }
- 695: /**
- 696: * {link ...}
- 697: */
- 698: public function macroLink($content, $modifiers)
- 699: {
- 700: return LatteFilter::formatModifiers('$control->link(' . $this->formatLink($content) .')', $modifiers);
- 701: }
- 705: /**
- 706: * {plink ...}
- 707: */
- 708: public function macroPlink($content, $modifiers)
- 709: {
- 710: return <a href="../Nette-Templates/LatteFilter.html">LatteFilter</a>::formatModifiers('$presenter->link(' . $this->formatLink($content) .')', $modifiers);
- 711: }
- 715: /**
- 716: * {ifCurrent ...}
- 717: */
- 718: public function macroIfCurrent($content)
- 719: {
- 720: return $content ? 'try { $presenter->link(' . $this->formatLink($content) . '); } catch (InvalidLinkException $e) {}' : '';
- 721: }
- 725: /**
- 726: * Formats {*link ...} parameters.
- 727: */
- 728: private function formatLink($content)
- 729: {
- 730: 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
- 731: }
- 735: /**
- 736: * {assign ...}
- 737: */
- 738: public function macroAssign($content, $modifiers)
- 739: {
- 740: if (!$content) {
- 741: throw new <a href="../exceptions/InvalidStateException.html">InvalidStateException</a>("Missing arguments in {var} or {assign} on line {$this->filter->line}.");
- 742: }
- 743: if (strpos($content, '=>') === FALSE) { // back compatibility
- 744: 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);
- 745: }
- 746: return 'extract(' . <a href="../Nette-Templates/LatteFilter.html">LatteFilter</a>::formatArray($content) . ')';
- 747: }
- 751: /**
- 752: * {default ...}
- 753: */
- 754: public function macroDefault($content)
- 755: {
- 756: if (!$content) {
- 757: throw new <a href="../exceptions/InvalidStateException.html">InvalidStateException</a>("Missing arguments in {default} on line {$this->filter->line}.");
- 758: }
- 759: return 'extract(' . <a href="../Nette-Templates/LatteFilter.html">LatteFilter</a>::formatArray($content) . ', EXTR_SKIP)';
- 760: }
- 764: /**
- 765: * Escaping helper.
- 766: */
- 767: public function macroEscape($content)
- 768: {
- 770: }
- 774: /**
- 775: * Just modifiers helper.
- 776: */
- 777: public function macroModifiers($content, $modifiers)
- 778: {
- 779: return <a href="../Nette-Templates/LatteFilter.html">LatteFilter</a>::formatModifiers($content, $modifiers);
- 780: }
- 784: /********************* run-time helpers ****************d*g**/
- 788: /**
- 789: * Calls block.
- 790: * @param array
- 791: * @param string
- 792: * @param array
- 793: * @return void
- 794: */
- 795: public static function callBlock(& $blocks, $name, $params)
- 796: {
- 797: if (empty($blocks[$name])) {
- 798: throw new InvalidStateException("Call to undefined block '$name'.");
- 799: }
- 801: $block($params);
- 802: }
- 806: /**
- 807: * Calls parent block.
- 808: * @param array
- 809: * @param string
- 810: * @param array
- 811: * @return void
- 812: */
- 813: public static function callBlockParent(& $blocks, $name, $params)
- 814: {
- 815: if (empty($blocks[$name]) || ($block = next($blocks[$name])) === FALSE) {
- 817: }
- 818: $block($params);
- 819: }
- 823: /**
- 824: * Includes subtemplate.
- 825: * @param mixed included file name or template
- 826: * @param array parameters
- 827: * @param ITemplate current template
- 828: * @return Template
- 829: */
- 830: public static function includeTemplate($destination, $params, $template)
- 831: {
- 832: if ($destination instanceof ITemplate) {
- 833: $tpl = $destination;
- 835: } elseif ($destination == NULL) { // intentionally ==
- 836: throw new InvalidArgumentException("Template file name was not specified.");
- 838: } else {
- 839: $tpl = clone $template;
- 840: if ($template instanceof IFileTemplate) {
- 841: if (substr($destination, 0, 1) !== '/' && substr($destination, 1, 1) !== ':') {
- 842: $destination = dirname($template->getFile()) . '/' . $destination;
- 843: }
- 844: $tpl->setFile($destination);
- 845: }
- 846: }
- 848: $tpl->setParams($params); // interface?
- 849: return $tpl;
- 850: }
- 854: /**
- 855: * Initializes state holder $_cb in template.
- 856: * @param ITemplate
- 857: * @param bool
- 858: * @param string
- 859: * @return stdClass
- 860: */
- 861: public static function initRuntime($template, $extends, $realFile)
- 862: {
- 863: $cb = (object) NULL;
- 865: // extends support
- 866: if (isset($template->_cb)) {
- 867: $cb->blocks = & $template->_cb->blocks;
- 868: $cb->templates = & $template->_cb->templates;
- 869: }
- 870: $cb->templates[$realFile] = $template;
- 871: $cb->extends = is_bool($extends) ? $extends : (empty($template->_extends) ? FALSE : $template->_extends);
- 872: unset($template->_cb, $template->_extends);
- 874: // cache support
- 875: if (!empty($cb->caches)) {
- 877: }
- 879: return $cb;
- 880: }