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