Source for file LatteMacros.php

Documentation is available at LatteMacros.php

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