Source for file CurlyBracketsMacros.php
Documentation is available at CurlyBracketsMacros.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 CurlyBracketsFilter.
- 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} capture of filter block
- 48: * - {contentType ...} HTTP Content-Type header
- 49: * - {assign var => value} set template parameter
- 50: * - {default var => value} set default template parameter
- 51: * - {dump $var}
- 52: * - {debugbreak}
- 53: *
- 57: */
- 59: {
- 65: 'snippet' => '<?php } if ($_cb->foo = SnippetHelper::create($control%:macroSnippet%)) { $_cb->snippets[] = $_cb->foo; ?>',
- 66: '/snippet' => '<?php array_pop($_cb->snippets)->finish(); } if (SnippetHelper::$outputAllowed) { ?>',
- 68: 'cache' => '<?php if ($_cb->foo = CachingHelper::create($_cb->key = md5(__FILE__) . __LINE__, $template->getFile(), array(%%))) { $_cb->caches[] = $_cb->foo; ?>',
- 69: '/cache' => '<?php array_pop($_cb->caches)->save(); } if (!empty($_cb->caches)) end($_cb->caches)->addItem($_cb->key); ?>',
- 91: 'ifCurrent' => '<?php %:macroIfCurrent%; if ($presenter->getLastCreatedRequestFlag("current")): ?>',
- 99: 'dump' => '<?php Debug::consoleDump(%:macroDump%, "Template " . str_replace(Environment::getVariable("templatesDir"), "\xE2\x80\xA6", $template->getFile())) ?>',
- 135: /**
- 136: * Constructor.
- 137: */
- 139: {
- 141: }
- 145: /**
- 146: * Initializes parsing.
- 150: */
- 152: {
- 162: // remove comments
- 165: // snippets support (temporary solution)
- 169: $s
- 171: }
- 175: /**
- 176: * Finishes parsing.
- 179: */
- 181: {
- 182: // blocks closing check
- 188: }
- 190: // snippets support (temporary solution)
- 193: // extends support
- 198: . 'if ($_cb->extends) { ob_end_clean(); CurlyBracketsMacros::includeTemplate($_cb->extends, get_defined_vars(), $template)->render(); }' . "\n";
- 199: }
- 201: // named blocks
- 205: $s = preg_replace_callback("#{block($name)} \?>(.*)<\?php {/block$name}#sU", array($this, 'cbNamedBlocks'), $s);
- 206: }
- 208: }
- 210: // internal state holder
- 213: . "\$_cb = CurlyBracketsMacros::initRuntime(\$template, " . var_export($this->extends, TRUE) . ", " . var_export($this->uniq, TRUE) . "); unset(\$_extends);\n"
- 215: }
- 219: /**
- 220: * Process {macro content | modifiers}
- 225: */
- 227: {
- 234: }
- 235: }
- 240: }
- 243: }
- 247: /**
- 248: * Callback for self::macro().
- 249: */
- 251: {
- 258: throw new InvalidStateException("CurlyBrackets macro handler '$textual' is not " . ($able ? 'callable.' : 'valid PHP callback.'));
- 259: }
- 264: }
- 265: }
- 269: /**
- 270: * Process <n:tag attr> (experimental).
- 275: */
- 277: {
- 285: $value = isset($knownTags[$name], $attrs[$knownTags[$name]]) ? $attrs[$knownTags[$name]] : substr(var_export($attrs, TRUE), 8, -1);
- 288: }
- 292: /**
- 293: * Process <tag n:attr> (experimental).
- 298: */
- 300: {
- 309: }
- 311: }
- 315: /********************* macros ****************d*g**/
- 319: /**
- 320: * {$var |modifiers}
- 321: */
- 323: {
- 325: }
- 329: /**
- 330: * {include ...}
- 331: */
- 333: {
- 338: throw new InvalidStateException("Missing destination in {include} on line {$this->filter->line}.");
- 340: } elseif ($destination[0] === '#') { // include #block
- 341: if (!preg_match('#^\\#'.CurlyBracketsFilter::RE_IDENTIFIER.'$#', $destination)) {
- 342: throw new InvalidStateException("Included block name must be alphanumeric string, '$destination' given on line {$this->filter->line}.");
- 343: }
- 345: $parent = $destination === '#parent';
- 346: if ($destination === '#parent' || $destination === '#this') {
- 350: throw new InvalidStateException("Cannot include $name block outside of any block on line {$this->filter->line}.");
- 351: }
- 352: $destination = $item[0];
- 353: }
- 354: $name = var_export($destination, TRUE);
- 355: $params .= 'get_defined_vars()';
- 357: ? "call_user_func(reset(\$_cb->blocks[$name]), $params)"
- 358: : "CurlyBracketsMacros::callBlock" . ($parent ? 'Parent' : '') . "(\$_cb->blocks, $name, $params)";
- 359: return $modifiers
- 360: ? "ob_start(); $cmd; echo " . CurlyBracketsFilter::formatModifiers('ob_get_clean()', $modifiers)
- 361: : $cmd;
- 363: } else { // include "file"
- 364: $destination = CurlyBracketsFilter::formatString($destination);
- 365: $params .= '$template->getParams()';
- 366: return $modifiers
- 367: ? 'echo ' . CurlyBracketsFilter::formatModifiers('CurlyBracketsMacros::includeTemplate(' . $destination . ', ' . $params . ', $_cb->templates[' . var_export($this->uniq, TRUE) . '])->__toString(TRUE)', $modifiers)
- 368: : 'CurlyBracketsMacros::includeTemplate(' . $destination . ', ' . $params . ', $_cb->templates[' . var_export($this->uniq, TRUE) . '])->render()';
- 369: }
- 370: }
- 374: /**
- 375: * {extends ...}
- 376: */
- 377: private function macroExtends($content)
- 378: {
- 379: $destination = CurlyBracketsFilter::fetchToken($content); // destination
- 380: if ($destination === NULL) {
- 381: throw new InvalidStateException("Missing destination in {extends} on line {$this->filter->line}.");
- 382: }
- 384: throw new InvalidStateException("{extends} must be placed outside any block; on line {$this->filter->line}.");
- 385: }
- 387: throw new InvalidStateException("Multiple {extends} declarations are not allowed; on line {$this->filter->line}.");
- 388: }
- 390: return $this->extends ? '$_cb->extends = ' . CurlyBracketsFilter::formatString($destination) : '';
- 391: }
- 395: /**
- 396: * {block ...}
- 397: */
- 398: private function macroBlock($content, $modifiers)
- 399: {
- 400: $name = CurlyBracketsFilter::fetchToken($content); // block [,] [params]
- 402: if ($name === NULL || $name[0] === '$') { // anonymous block or capture
- 406: } elseif ($name[0] === '#') { // #block
- 407: if (!preg_match('#^\\#'.CurlyBracketsFilter::RE_IDENTIFIER.'$#', $name)) {
- 408: throw new InvalidStateException("Block name must be alphanumeric string, '$name' given on line {$this->filter->line}.");
- 411: throw new InvalidStateException("Cannot redeclare block '$name'; on line {$this->filter->line}.");
- 412: }
- 421: return "{block$name}";
- 423: } else {
- 425: }
- 427: } else {
- 428: throw new InvalidStateException("Invalid block parameter '$name' on line {$this->filter->line}.");
- 429: }
- 430: }
- 434: /**
- 435: * {/block ...}
- 436: */
- 437: private function macroBlockEnd($content)
- 438: {
- 443: throw new InvalidStateException("Tag {/block $content} was not expected here on line {$this->filter->line}.");
- 445: } elseif (substr($name, 0, 1) === '#') { // #block
- 446: return "{/block$name}";
- 448: } else { // anonymous block or capture
- 449: return ($name === NULL && $modifiers === '') ? ''
- 450: : ($name === NULL ? 'echo ' : $name . '=') . CurlyBracketsFilter::formatModifiers('ob_get_clean()', $modifiers);
- 451: }
- 452: }
- 456: /**
- 457: * Converts {block#named}...{/block} to functions.
- 458: */
- 459: private function cbNamedBlocks($matches)
- 460: {
- 461: list(, $name, $content) = $matches;
- 462: $func = '_cbb' . substr(md5($this->uniq . $name), 0, 10) . '_' . preg_replace('#[^a-z0-9_]#i', '_', $name);
- 464: . "if (!function_exists(\$_cb->blocks[" . var_export($name, TRUE) . "][] = '$func')) { function $func() { extract(func_get_arg(0))\n?>$content<?php\n}}";
- 465: return '';
- 466: }
- 470: /**
- 471: * {foreach ...}
- 472: */
- 473: private function macroForeach($content)
- 474: {
- 475: return '$iterator = $_cb->its[] = new SmartCachingIterator(' . preg_replace('# +as +#i', ') as ', $content, 1);
- 476: }
- 480: /**
- 481: * {attr ...}
- 482: */
- 483: private function macroAttr($content)
- 484: {
- 485: return preg_replace('#\)\s+#', ')->', $content . ' ');
- 486: }
- 490: /**
- 491: * {contentType ...}
- 492: */
- 493: private function macroContentType($content)
- 494: {
- 495: if (strpos($content, 'html') !== FALSE) {
- 499: } elseif (strpos($content, 'xml') !== FALSE) {
- 503: } elseif (strpos($content, 'javascript') !== FALSE) {
- 507: } elseif (strpos($content, 'css') !== FALSE) {
- 511: } elseif (strpos($content, 'plain') !== FALSE) {
- 515: } else {
- 518: }
- 520: // temporary solution
- 521: return strpos($content, '/') ? 'Environment::getHttpResponse()->setHeader("Content-Type", "' . $content . '")' : '';
- 522: }
- 526: /**
- 527: * {dump ...}
- 528: */
- 529: private function macroDump($content)
- 530: {
- 531: return $content ? "array('$content' => $content)" : 'get_defined_vars()';
- 532: }
- 536: /**
- 537: * {snippet ...}
- 538: */
- 539: private function macroSnippet($content)
- 540: {
- 541: $args = array('');
- 542: if ($snippet = CurlyBracketsFilter::fetchToken($content)) { // [name [,]] [tag]
- 543: $args[] = CurlyBracketsFilter::formatString($snippet);
- 544: }
- 545: if ($content) {
- 546: $args[] = CurlyBracketsFilter::formatString($content);
- 547: }
- 548: return implode(', ', $args);
- 549: }
- 553: /**
- 554: * {widget ...}
- 555: */
- 556: private function macroWidget($content)
- 557: {
- 558: $pair = CurlyBracketsFilter::fetchToken($content); // widget[:method]
- 559: if ($pair === NULL) {
- 560: throw new InvalidStateException("Missing widget name in {widget} on line {$this->filter->line}.");
- 561: }
- 562: $pair = explode(':', $pair, 2);
- 563: $widget = CurlyBracketsFilter::formatString($pair[0]);
- 564: $method = isset($pair[1]) ? ucfirst($pair[1]) : '';
- 565: $method = preg_match('#^('.CurlyBracketsFilter::RE_IDENTIFIER.'|)$#', $method) ? "render$method" : "{\"render$method\"}";
- 566: $param = CurlyBracketsFilter::formatArray($content);
- 567: if (strpos($content, '=>') === FALSE) $param = substr($param, 6, -1); // removes array()
- 569: . "\$control->getWidget($widget)->$method($param)";
- 570: }
- 574: /**
- 575: * {link ...}
- 576: */
- 577: private function macroLink($content, $modifiers)
- 578: {
- 579: return CurlyBracketsFilter::formatModifiers('$control->link(' . $this->formatLink($content) .')', $modifiers);
- 580: }
- 584: /**
- 585: * {plink ...}
- 586: */
- 587: private function macroPlink($content, $modifiers)
- 588: {
- 589: return CurlyBracketsFilter::formatModifiers('$presenter->link(' . $this->formatLink($content) .')', $modifiers);
- 590: }
- 594: /**
- 595: * {ifCurrent ...}
- 596: */
- 597: private function macroIfCurrent($content, $modifiers)
- 598: {
- 599: return $content ? CurlyBracketsFilter::formatModifiers('$presenter->link(' . $this->formatLink($content) .')', $modifiers) : '';
- 600: }
- 604: /**
- 605: * Formats {*link ...} parameters.
- 606: */
- 607: private function formatLink($content)
- 608: {
- 609: return CurlyBracketsFilter::formatString(CurlyBracketsFilter::fetchToken($content)) . CurlyBracketsFilter::formatArray($content, ', '); // destination [,] args
- 610: }
- 614: /**
- 615: * {assign ...}
- 616: */
- 617: private function macroAssign($content, $modifiers)
- 618: {
- 619: if (!$content) {
- 620: throw new InvalidStateException("Missing arguments in {assign} on line {$this->filter->line}.");
- 621: }
- 622: if (strpos($content, '=>') === FALSE) { // back compatibility
- 623: return '$' . ltrim(CurlyBracketsFilter::fetchToken($content), '$') . ' = ' . CurlyBracketsFilter::formatModifiers($content === '' ? 'NULL' : $content, $modifiers);
- 624: }
- 625: return 'extract(' . CurlyBracketsFilter::formatArray($content) . ')';
- 626: }
- 630: /**
- 631: * {default ...}
- 632: */
- 633: private function macroDefault($content)
- 634: {
- 635: if (!$content) {
- 636: throw new InvalidStateException("Missing arguments in {default} on line {$this->filter->line}.");
- 637: }
- 638: return 'extract(' . CurlyBracketsFilter::formatArray($content) . ', EXTR_SKIP)';
- 639: }
- 643: /**
- 644: * Escaping helper.
- 645: */
- 646: private function macroEscape($content)
- 647: {
- 649: }
- 653: /**
- 654: * Just modifiers helper.
- 655: */
- 656: private function macroModifiers($content, $modifiers)
- 657: {
- 658: return CurlyBracketsFilter::formatModifiers($content, $modifiers);
- 659: }
- 663: /********************* run-time helpers ****************d*g**/
- 667: /**
- 668: * Calls block.
- 669: * @param array
- 670: * @param string
- 671: * @param array
- 672: * @return void
- 673: */
- 674: public static function callBlock(& $blocks, $name, $params)
- 675: {
- 676: if (empty($blocks[$name])) {
- 677: throw new InvalidStateException("Call to undefined block '$name'.");
- 678: }
- 679: $block = reset($blocks[$name]);
- 680: $block($params);
- 681: }
- 685: /**
- 686: * Calls parent block.
- 687: * @param array
- 688: * @param string
- 689: * @param array
- 690: * @return void
- 691: */
- 692: public static function callBlockParent(& $blocks, $name, $params)
- 693: {
- 694: if (empty($blocks[$name]) || ($block = next($blocks[$name])) === FALSE) {
- 695: throw new InvalidStateException("Call to undefined parent block '$name'.");
- 696: }
- 697: $block($params);
- 698: }
- 702: /**
- 703: * Includes subtemplate.
- 704: * @param mixed included file name or template
- 705: * @param array parameters
- 706: * @param ITemplate current template
- 707: * @return Template
- 708: */
- 709: public static function includeTemplate($destination, $params, $template)
- 710: {
- 711: if ($destination instanceof ITemplate) {
- 712: $tpl = $destination;
- 714: } elseif ($destination == NULL) { // intentionally ==
- 715: throw new InvalidArgumentException("Template file name was not specified.");
- 717: } else {
- 718: $tpl = clone $template;
- 719: if ($template instanceof IFileTemplate) {
- 720: if (substr($destination, 0, 1) !== '/' && substr($destination, 1, 1) !== ':') {
- 722: }
- 724: }
- 725: }
- 729: }
- 733: /**
- 734: * Initializes state holder $_cb in template.
- 735: * @param ITemplate
- 736: * @param bool
- 737: * @param string
- 738: * @return stdClass
- 739: */
- 740: public static function initRuntime($template, $extends, $realFile)
- 741: {
- 742: $cb = (object) NULL;
- 744: // extends support
- 748: }
- 750: $cb->extends = is_bool($extends) ? $extends : (empty($template->_extends) ? FALSE : $template->_extends);
- 753: // cache support
- 756: }
- 758: return $cb;
- 759: }