Source for file LatteMacros.php
Documentation is available at LatteMacros.php
- 1: <?php
- 3: /**
- 4: * Nette Framework
- 5: *
- 6: * Copyright (c) 2004, 2009 David Grudl (http://davidgrudl.com)
- 7: *
- 8: * This source file is subject to the "Nette license" that is bundled
- 9: * with this package in the file license.txt.
- 10: *
- 11: * For more information please see http://nettephp.com
- 12: *
- 18: */
- 26: /**
- 27: * Default macros for filter LatteFilter.
- 28: *
- 29: * - {$variable} with escaping
- 30: * - {!$variable} without escaping
- 31: * - {*comment*} will be removed
- 32: * - {=expression} echo with escaping
- 33: * - {!=expression} echo without escaping
- 34: * - {?expression} evaluate PHP statement
- 35: * - {_expression} echo translation with escaping
- 36: * - {!_expression} echo translation without escaping
- 37: * - {link destination ...} control link
- 38: * - {plink destination ...} presenter link
- 39: * - {if ?} ... {elseif ?} ... {else} ... {/if}
- 40: * - {ifset ?} ... {elseifset ?} ... {/if}
- 41: * - {for ?} ... {/for}
- 42: * - {foreach ?} ... {/foreach}
- 43: * - {include ?}
- 44: * - {cache ?} ... {/cache} cached block
- 45: * - {snippet ?} ... {/snippet ?} control snippet
- 46: * - {attr ?} HTML element attributes
- 47: * - {block|texy} ... {/block} block
- 48: * - {contentType ...} HTTP Content-Type header
- 49: * - {capture ?} ... {/capture} capture block to parameter
- 50: * - {assign var => value} set template parameter
- 51: * - {default var => value} set default template parameter
- 52: * - {dump $var}
- 53: * - {debugbreak}
- 54: *
- 58: */
- 60: {
- 72: 'snippet' => '<?php } if ($_cb->foo = NSnippetHelper::create($control%:macroSnippet%)) { $_cb->snippets[] = $_cb->foo ?>',
- 73: '/snippet' => '<?php array_pop($_cb->snippets)->finish(); } if (NSnippetHelper::$outputAllowed) { ?>',
- 75: 'cache' => '<?php if ($_cb->foo = NCachingHelper::create($_cb->key = md5(__FILE__) . __LINE__, $template->getFile(), array(%%))) { $_cb->caches[] = $_cb->foo ?>',
- 76: '/cache' => '<?php array_pop($_cb->caches)->save(); } if (!empty($_cb->caches)) end($_cb->caches)->addItem($_cb->key) ?>',
- 99: 'ifCurrent' => '<?php %:macroIfCurrent%; if ($presenter->getLastCreatedRequestFlag("current")): ?>',
- 107: 'dump' => '<?php NDebug::consoleDump(%:macroDump%, "NTemplate " . str_replace(NEnvironment::getVariable("templatesDir"), "\xE2\x80\xA6", $template->getFile())) ?>',
- 141: /**#@+ @ignore internal block type */
- 145: /**#@-*/
- 149: /**
- 150: * Constructor.
- 151: */
- 153: {
- 155: }
- 159: /**
- 160: * Initializes parsing.
- 164: */
- 166: {
- 176: // remove comments
- 179: // snippets support (temporary solution)
- 183: $s
- 185: }
- 189: /**
- 190: * Finishes parsing.
- 193: */
- 195: {
- 196: // blocks closing check
- 202: }
- 204: // snippets support (temporary solution)
- 207: // extends support
- 212: . 'if ($_cb->extends) { ob_end_clean(); LatteMacros::includeTemplate($_cb->extends, get_defined_vars(), $template)->render(); }' . "\n";
- 213: }
- 215: // named blocks
- 219: $s = preg_replace_callback("#{block($name)} \?>(.*)<\?php {/block$name}#sU", array($this, 'cbNamedBlocks'), $s);
- 220: }
- 222: }
- 224: // internal state holder
- 227: . "\$_cb = LatteMacros::initRuntime(\$template, " . var_export($this->extends, TRUE) . ", " . var_export($this->uniq, TRUE) . "); unset(\$_extends);\n"
- 229: }
- 233: /**
- 234: * Process {macro content | modifiers}
- 239: */
- 241: {
- 248: }
- 249: }
- 254: }
- 257: }
- 261: /**
- 262: * Callback for self::macro().
- 263: */
- 265: {
- 272: throw new InvalidStateException("Latte macro handler '$textual' is not " . ($able ? 'callable.' : 'valid PHP callback.'));
- 273: }
- 278: }
- 279: }
- 283: /**
- 284: * Process <n:tag attr> (experimental).
- 289: */
- 291: {
- 301: isset($knownTags[$name], $attrs[$knownTags[$name]]) ? $attrs[$knownTags[$name]] : substr(var_export($attrs, TRUE), 8, -1),
- 304: }
- 308: /**
- 309: * Process <tag n:attr> (experimental).
- 314: */
- 316: {
- 321: }
- 329: }
- 330: }
- 338: }
- 339: }
- 345: }
- 348: }
- 351: }
- 355: /********************* macros ****************d*g**/
- 359: /**
- 360: * {$var |modifiers}
- 361: */
- 363: {
- 365: }
- 369: /**
- 370: * {syntax ...}
- 371: */
- 373: {
- 396: default:
- 398: }
- 399: }
- 403: /**
- 404: * {include ...}
- 405: */
- 406: private function macroInclude($content, $modifiers)
- 407: {
- 408: $destination = LatteFilter::fetchToken($content); // destination [,] [params]
- 409: $params = LatteFilter::formatArray($content) . ($content ? ' + ' : '');
- 411: if ($destination === NULL) {
- 412: throw new <a href="../Nette/InvalidStateException.html">InvalidStateException</a>("Missing destination in {include} on line {$this->filter->line}.");
- 414: } elseif ($destination[0] === '#') { // include #block
- 415: if (!preg_match('#^\\#'.<a href="../Templates/LatteFilter.html">LatteFilter</a>::RE_IDENTIFIER.'$#', $destination)) {
- 416: throw new <a href="../Nette/InvalidStateException.html">InvalidStateException</a>("Included block name must be alphanumeric string, '$destination' given on line {$this->filter->line}.");
- 417: }
- 419: $parent = $destination === '#parent';
- 420: if ($destination === '#parent' || $destination === '#this') {
- 423: if (!$item) {
- 424: throw new <a href="../Nette/InvalidStateException.html">InvalidStateException</a>("Cannot include $destination block outside of any block on line {$this->filter->line}.");
- 425: }
- 426: $destination = $item[1];
- 427: }
- 428: $name = var_export($destination, TRUE);
- 429: $params .= 'get_defined_vars()';
- 430: $cmd = isset($this->namedBlocks[$destination]) && !$parent
- 433: return $modifiers
- 435: : $cmd;
- 437: } else { // include "file"
- 438: $destination = <a href="../Templates/LatteFilter.html">LatteFilter</a>::formatString($destination);
- 439: $params .= '$template->getParams()';
- 440: return $modifiers
- 441: ? 'echo ' . <a href="../Templates/LatteFilter.html">LatteFilter</a>::formatModifiers('LatteMacros::includeTemplate(' . $destination . ', ' . $params . ', $_cb->templates[' . var_export($this->uniq, TRUE) . '])->__toString(TRUE)', $modifiers)
- 442: : 'LatteMacros::includeTemplate(' . $destination . ', ' . $params . ', $_cb->templates[' . var_export($this->uniq, TRUE) . '])->render()';
- 443: }
- 444: }
- 448: /**
- 449: * {extends ...}
- 450: */
- 451: private function macroExtends($content)
- 452: {
- 453: $destination = <a href="../Templates/LatteFilter.html">LatteFilter</a>::fetchToken($content); // destination
- 454: if ($destination === NULL) {
- 455: throw new <a href="../Nette/InvalidStateException.html">InvalidStateException</a>("Missing destination in {extends} on line {$this->filter->line}.");
- 456: }
- 457: if (!empty($this->blocks)) {
- 458: throw new InvalidStateException("{extends} must be placed outside any block; on line {$this->filter->line}.");
- 459: }
- 460: if ($this->extends !== NULL) {
- 461: throw new InvalidStateException("Multiple {extends} declarations are not allowed; on line {$this->filter->line}.");
- 462: }
- 463: $this->extends = $destination !== 'none';
- 464: return $this->extends ? '$_cb->extends = ' . <a href="../Templates/LatteFilter.html">LatteFilter</a>::formatString($destination) : '';
- 465: }
- 469: /**
- 470: * {block ...}
- 471: */
- 472: private function macroBlock($content, $modifiers)
- 473: {
- 475: trigger_error("Capturing {block $content} is deprecated; use {capture $content} instead on line {$this->filter->line}.", E_USER_WARNING);
- 476: return $this->macroCapture($content, $modifiers);
- 477: }
- 479: $name = LatteFilter::fetchToken($content); // block [,] [params]
- 481: if ($name === NULL) { // anonymous block
- 482: $this->blocks[] = array(self::BLOCK_ANONYMOUS, NULL, $modifiers);
- 483: return $modifiers === '' ? '' : 'ob_start()';
- 485: } elseif ($name[0] === '#') { // #block
- 486: if (!preg_match('#^\\#'.<a href="../Templates/LatteFilter.html">LatteFilter</a>::RE_IDENTIFIER.'$#', $name)) {
- 487: throw new <a href="../Nette/InvalidStateException.html">InvalidStateException</a>("Block name must be alphanumeric string, '$name' given on line {$this->filter->line}.");
- 489: } elseif (isset($this->namedBlocks[$name])) {
- 490: throw new InvalidStateException("Cannot redeclare block '$name'; on line {$this->filter->line}.");
- 491: }
- 493: $top = empty($this->blocks);
- 494: $this->namedBlocks[$name] = $name;
- 495: $this->blocks[] = array(self::BLOCK_NAMED, $name, '');
- 496: if (!$top) {
- 499: } elseif ($this->extends) {
- 502: } else {
- 504: }
- 506: } else {
- 507: throw new InvalidStateException("Invalid block parameter '$name' on line {$this->filter->line}.");
- 508: }
- 509: }
- 513: /**
- 514: * {/block}
- 515: */
- 516: private function macroBlockEnd($content)
- 517: {
- 518: list($type, $name, $modifiers) = array_pop($this->blocks);
- 520: if ($type === self::BLOCK_CAPTURE) { // capture - back compatibility
- 521: $this->blocks[] = array($type, $name, $modifiers);
- 522: return $this->macroCaptureEnd($content);
- 523: }
- 525: if (($type !== self::BLOCK_NAMED && $type !== self::BLOCK_ANONYMOUS) || ($content && $content !== $name)) {
- 526: throw new InvalidStateException("Tag {/block $content} was not expected here on line {$this->filter->line}.");
- 528: } elseif ($type === self::BLOCK_NAMED) { // #block
- 531: } else { // anonymous block
- 532: return $modifiers === '' ? '' : 'echo ' . <a href="../Templates/LatteFilter.html">LatteFilter</a>::formatModifiers('ob_get_clean()', $modifiers);
- 533: }
- 534: }
- 538: /**
- 539: * {capture ...}
- 540: */
- 541: private function macroCapture($content, $modifiers)
- 542: {
- 543: $name = <a href="../Templates/LatteFilter.html">LatteFilter</a>::fetchToken($content); // $variable
- 546: throw new <a href="../Nette/InvalidStateException.html">InvalidStateException</a>("Invalid capture block parameter '$name' on line {$this->filter->line}.");
- 547: }
- 549: $this->blocks[] = array(self::BLOCK_CAPTURE, $name, $modifiers);
- 550: return 'ob_start()';
- 551: }
- 555: /**
- 556: * {/capture}
- 557: */
- 558: private function macroCaptureEnd($content)
- 559: {
- 562: if ($type !== self::BLOCK_CAPTURE || ($content && $content !== $name)) {
- 563: throw new <a href="../Nette/InvalidStateException.html">InvalidStateException</a>("Tag {/capture $content} was not expected here on line {$this->filter->line}.");
- 564: }
- 566: return $name . '=' . <a href="../Templates/LatteFilter.html">LatteFilter</a>::formatModifiers('ob_get_clean()', $modifiers);
- 567: }
- 571: /**
- 572: * Converts {block#named}...{/block} to functions.
- 573: */
- 574: private function cbNamedBlocks($matches)
- 575: {
- 576: list(, $name, $content) = $matches;
- 577: $func = '_cbb' . substr(md5($this->uniq . $name), 0, 10) . '_' . preg_replace('#[^a-z0-9_]#i', '_', $name);
- 579: . "if (!function_exists(\$_cb->blocks[" . var_export($name, TRUE) . "][] = '$func')) { function $func() { extract(func_get_arg(0))\n?>$content<?php\n}}";
- 580: return '';
- 581: }
- 585: /**
- 586: * {foreach ...}
- 587: */
- 588: private function macroForeach($content)
- 589: {
- 590: return '$iterator = $_cb->its[] = new NSmartCachingIterator(' . preg_replace('# +as +#i', ') as ', $content, 1);
- 591: }
- 595: /**
- 596: * {attr ...}
- 597: */
- 598: private function macroAttr($content)
- 599: {
- 600: return preg_replace('#\)\s+#', ')->', $content . ' ');
- 601: }
- 605: /**
- 606: * {contentType ...}
- 607: */
- 608: private function macroContentType($content)
- 609: {
- 630: } else {
- 633: }
- 635: // temporary solution
- 637: }
- 641: /**
- 642: * {dump ...}
- 643: */
- 644: private function macroDump($content)
- 645: {
- 647: }
- 651: /**
- 652: * {snippet ...}
- 653: */
- 654: private function macroSnippet($content)
- 655: {
- 656: $args = array('');
- 657: if ($snippet = <a href="../Templates/LatteFilter.html">LatteFilter</a>::fetchToken($content)) { // [name [,]] [tag]
- 658: $args[] = <a href="../Templates/LatteFilter.html">LatteFilter</a>::formatString($snippet);
- 659: }
- 660: if ($content) {
- 661: $args[] = <a href="../Templates/LatteFilter.html">LatteFilter</a>::formatString($content);
- 662: }
- 664: }
- 668: /**
- 669: * {widget ...}
- 670: */
- 671: private function macroWidget($content)
- 672: {
- 673: $pair = <a href="../Templates/LatteFilter.html">LatteFilter</a>::fetchToken($content); // widget[:method]
- 674: if ($pair === NULL) {
- 675: throw new <a href="../Nette/InvalidStateException.html">InvalidStateException</a>("Missing widget name in {widget} on line {$this->filter->line}.");
- 676: }
- 677: $pair = explode(':', $pair, 2);
- 678: $widget = <a href="../Templates/LatteFilter.html">LatteFilter</a>::formatString($pair[0]);
- 680: $method = preg_match('#^('.<a href="../Templates/LatteFilter.html">LatteFilter</a>::RE_IDENTIFIER.'|)$#', $method) ? "render$method" : "{\"render$method\"}";
- 681: $param = LatteFilter::formatArray($content);
- 685: }
- 689: /**
- 690: * {link ...}
- 691: */
- 692: private function macroLink($content, $modifiers)
- 693: {
- 694: return LatteFilter::formatModifiers('$control->link(' . $this->formatLink($content) .')', $modifiers);
- 695: }
- 699: /**
- 700: * {plink ...}
- 701: */
- 702: private function macroPlink($content, $modifiers)
- 703: {
- 704: return <a href="../Templates/LatteFilter.html">LatteFilter</a>::formatModifiers('$presenter->link(' . $this->formatLink($content) .')', $modifiers);
- 705: }
- 709: /**
- 710: * {ifCurrent ...}
- 711: */
- 712: private function macroIfCurrent($content, $modifiers)
- 713: {
- 714: return $content ? <a href="../Templates/LatteFilter.html">LatteFilter</a>::formatModifiers('$presenter->link(' . $this->formatLink($content) .')', $modifiers) : '';
- 715: }
- 719: /**
- 720: * Formats {*link ...} parameters.
- 721: */
- 722: private function formatLink($content)
- 723: {
- 724: return <a href="../Templates/LatteFilter.html">LatteFilter</a>::formatString(<a href="../Templates/LatteFilter.html">LatteFilter</a>::fetchToken($content)) . <a href="../Templates/LatteFilter.html">LatteFilter</a>::formatArray($content, ', '); // destination [,] args
- 725: }
- 729: /**
- 730: * {assign ...}
- 731: */
- 732: private function macroAssign($content, $modifiers)
- 733: {
- 734: if (!$content) {
- 735: throw new <a href="../Nette/InvalidStateException.html">InvalidStateException</a>("Missing arguments in {assign} on line {$this->filter->line}.");
- 736: }
- 737: if (strpos($content, '=>') === FALSE) { // back compatibility
- 738: return '$' . ltrim(<a href="../Templates/LatteFilter.html">LatteFilter</a>::fetchToken($content), '$') . ' = ' . <a href="../Templates/LatteFilter.html">LatteFilter</a>::formatModifiers($content === '' ? 'NULL' : $content, $modifiers);
- 739: }
- 740: return 'extract(' . <a href="../Templates/LatteFilter.html">LatteFilter</a>::formatArray($content) . ')';
- 741: }
- 745: /**
- 746: * {default ...}
- 747: */
- 748: private function macroDefault($content)
- 749: {
- 750: if (!$content) {
- 751: throw new <a href="../Nette/InvalidStateException.html">InvalidStateException</a>("Missing arguments in {default} on line {$this->filter->line}.");
- 752: }
- 753: return 'extract(' . <a href="../Templates/LatteFilter.html">LatteFilter</a>::formatArray($content) . ', EXTR_SKIP)';
- 754: }
- 758: /**
- 759: * Escaping helper.
- 760: */
- 761: private function macroEscape($content)
- 762: {
- 764: }
- 768: /**
- 769: * Just modifiers helper.
- 770: */
- 771: private function macroModifiers($content, $modifiers)
- 772: {
- 773: return <a href="../Templates/LatteFilter.html">LatteFilter</a>::formatModifiers($content, $modifiers);
- 774: }
- 778: /********************* run-time helpers ****************d*g**/
- 782: /**
- 783: * Calls block.
- 784: * @param array
- 785: * @param string
- 786: * @param array
- 787: * @return void
- 788: */
- 789: public static function callBlock(& $blocks, $name, $params)
- 790: {
- 791: if (empty($blocks[$name])) {
- 792: throw new InvalidStateException("Call to undefined block '$name'.");
- 793: }
- 795: $block($params);
- 796: }
- 800: /**
- 801: * Calls parent block.
- 802: * @param array
- 803: * @param string
- 804: * @param array
- 805: * @return void
- 806: */
- 807: public static function callBlockParent(& $blocks, $name, $params)
- 808: {
- 809: if (empty($blocks[$name]) || ($block = next($blocks[$name])) === FALSE) {
- 811: }
- 812: $block($params);
- 813: }
- 817: /**
- 818: * Includes subtemplate.
- 819: * @param mixed included file name or template
- 820: * @param array parameters
- 821: * @param ITemplate current template
- 822: * @return NTemplate
- 823: */
- 824: public static function includeTemplate($destination, $params, $template)
- 825: {
- 826: if ($destination instanceof ITemplate) {
- 827: $tpl = $destination;
- 829: } elseif ($destination == NULL) { // intentionally ==
- 830: throw new InvalidArgumentException("NTemplate file name was not specified.");
- 832: } else {
- 833: $tpl = clone $template;
- 834: if ($template instanceof IFileTemplate) {
- 835: if (substr($destination, 0, 1) !== '/' && substr($destination, 1, 1) !== ':') {
- 836: $destination = dirname($template->getFile()) . '/' . $destination;
- 837: }
- 838: $tpl->setFile($destination);
- 839: }
- 840: }
- 842: $tpl->setParams($params); // interface?
- 843: return $tpl;
- 844: }
- 848: /**
- 849: * Initializes state holder $_cb in template.
- 850: * @param ITemplate
- 851: * @param bool
- 852: * @param string
- 853: * @return stdClass
- 854: */
- 855: public static function initRuntime($template, $extends, $realFile)
- 856: {
- 857: $cb = (object) NULL;
- 859: // extends support
- 860: if (isset($template->_cb)) {
- 861: $cb->blocks = & $template->_cb->blocks;
- 862: $cb->templates = & $template->_cb->templates;
- 863: }
- 864: $cb->templates[$realFile] = $template;
- 865: $cb->extends = is_bool($extends) ? $extends : (empty($template->_extends) ? FALSE : $template->_extends);
- 866: unset($template->_cb, $template->_extends);
- 868: // cache support
- 869: if (!empty($cb->caches)) {
- 871: }
- 873: return $cb;
- 874: }