Source for file CurlyBracketsFilter.php

Documentation is available at CurlyBracketsFilter.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 https://nette.org
  12. 12:  *
  13. 13:  * @copyright  Copyright (c) 2004, 2009 David Grudl
  14. 14:  * @license    https://nette.org/license  Nette license
  15. 15:  * @link       https://nette.org
  16. 16:  * @category   Nette
  17. 17:  * @package    Nette\Templates
  18. 18:  * @version    $Id$
  19. 19:  */
  20. 20:  
  21. 21:  
  22. 22:  
  23. 23: require_once dirname(__FILE__'/../../Object.php';
  24. 24:  
  25. 25:  
  26. 26:  
  27. 27: /**
  28. 28:  * Template filter curlyBrackets: support for {...} in template.
  29. 29:  *
  30. 30:  * - {$variable} with escaping
  31. 31:  * - {!$variable} without escaping
  32. 32:  * - {*comment*} will be removed
  33. 33:  * - {=expression} echo with escaping
  34. 34:  * - {!=expression} echo without escaping
  35. 35:  * - {?expression} evaluate PHP statement
  36. 36:  * - {_expression} echo translation with escaping
  37. 37:  * - {!_expression} echo translation without escaping
  38. 38:  * - {link destination ...} control link
  39. 39:  * - {plink destination ...} presenter link
  40. 40:  * - {ajaxlink destination ...} ajax link
  41. 41:  * - {if ?} ... {elseif ?} ... {else} ... {/if} // or <%else%>, <%/if%>, <%/foreach%> ?
  42. 42:  * - {for ?} ... {/for}
  43. 43:  * - {foreach ?} ... {/foreach}
  44. 44:  * - {include ?}
  45. 45:  * - {cache ?} ... {/cache} cached block
  46. 46:  * - {snippet ?} ... {/snippet ?} control snippet
  47. 47:  * - {attr ?} HTML element attributes
  48. 48:  * - {block|texy} ... {/block} capture of filter block
  49. 49:  * - {contentType ...} HTTP Content-Type header
  50. 50:  * - {assign $var value} set template parameter
  51. 51:  * - {debugbreak}
  52. 52:  *
  53. 53:  * @author     David Grudl
  54. 54:  * @copyright  Copyright (c) 2004, 2009 David Grudl
  55. 55:  * @package    Nette\Templates
  56. 56:  */
  57. 57: class CurlyBracketsFilter extends Object
  58. 58: {
  59. 59:  
  60. 60:     /** @var array */
  61. 61:     public static $macros array(
  62. 62:         'block' => '<?php %:macroBlock% ?>',
  63. 63:         '/block' => '<?php %:macroBlockEnd% ?>',
  64. 64:  
  65. 65:         'snippet' => '<?php } if ($_cb->foo = $template->snippet($control%:macroSnippet%)) { $_cb->snippets[] = $_cb->foo; ?>',
  66. 66:         '/snippet' => '<?php array_pop($_cb->snippets)->finish(); } if (SnippetHelper::$outputAllowed) { ?>',
  67. 67:  
  68. 68:         'cache' => '<?php if ($_cb->foo = $template->cache($_cb->key = md5(__FILE__) . __LINE__, $template->getFile(), array(%%))) { $_cb->caches[] = $_cb->foo; ?>',
  69. 69:         '/cache' => '<?php array_pop($_cb->caches)->save(); } if (!empty($_cb->caches)) end($_cb->caches)->addItem($_cb->key); ?>',
  70. 70:  
  71. 71:         'if' => '<?php if (%%): ?>',
  72. 72:         'elseif' => '<?php elseif (%%): ?>',
  73. 73:         'else' => '<?php else: ?>',
  74. 74:         '/if' => '<?php endif ?>',
  75. 75:         'foreach' => '<?php foreach (%:macroForeach%): ?>',
  76. 76:         '/foreach' => '<?php endforeach; array_pop($_cb->its); $iterator = end($_cb->its) ?>',
  77. 77:         'for' => '<?php for (%%): ?>',
  78. 78:         '/for' => '<?php endfor ?>',
  79. 79:         'while' => '<?php while (%%): ?>',
  80. 80:         '/while' => '<?php endwhile ?>',
  81. 81:  
  82. 82:         'include' => '<?php %:macroInclude% ?>',
  83. 83:         'extends' => '<?php %:macroExtends% ?>',
  84. 84:  
  85. 85:         'ajaxlink' => '<?php echo %:macroEscape%(%:macroAjaxlink%) ?>',
  86. 86:         'plink' => '<?php echo %:macroEscape%(%:macroPlink%) ?>',
  87. 87:         'link' => '<?php echo %:macroEscape%(%:macroLink%) ?>',
  88. 88:         'ifCurrent' => '<?php %:macroIfCurrent%; if ($presenter->getLastCreatedRequestFlag("current")): ?>',
  89. 89:  
  90. 90:         'attr' => '<?php echo Html::el(NULL)->%:macroAttr%attributes() ?>',
  91. 91:         'contentType' => '<?php %:macroContentType% ?>',
  92. 92:         'assign' => '<?php %:macroAssign% ?>',
  93. 93:         'debugbreak' => '<?php if (function_exists("debugbreak")) debugbreak() ?>',
  94. 94:  
  95. 95:         '!_' => '<?php echo $template->translate(%:macroModifiers%) ?>',
  96. 96:         '!=' => '<?php echo %:macroModifiers% ?>',
  97. 97:         '_' => '<?php echo %:macroEscape%($template->translate(%:macroModifiers%)) ?>',
  98. 98:         '=' => '<?php echo %:macroEscape%(%:macroModifiers%) ?>',
  99. 99:         '!$' => '<?php echo %:macroVar% ?>',
  100. 100:         '!' => '<?php echo %:macroVar% ?>',
  101. 101:         '$' => '<?php echo %:macroEscape%(%:macroVar%) ?>',
  102. 102:         '?' => '<?php %:macroModifiers% ?>',
  103. 103:     );
  104. 104:  
  105. 105:     /** @var array */
  106. 106:     private $blocks array();
  107. 107:  
  108. 108:     /** @var array */
  109. 109:     private $namedBlocks array();
  110. 110:  
  111. 111:     /** @var string */
  112. 112:     private $context$escape$tag;
  113. 113:  
  114. 114:     /**#@+ Context-aware escaping states */
  115. 115:     const CONTEXT_TEXT = 1;
  116. 116:     const CONTEXT_CDATA = 2;
  117. 117:     const CONTEXT_TAG = 3;
  118. 118:     const CONTEXT_ATTRIBUTE_SINGLE = "'";
  119. 119:     const CONTEXT_ATTRIBUTE_DOUBLE = '"';
  120. 120:     const CONTEXT_NONE = 4;
  121. 121:     /**#@-*/
  122. 122:  
  123. 123:  
  124. 124:  
  125. 125:     /**
  126. 126:      * Invokes filter.
  127. 127:      * @param  string 
  128. 128:      * @return string 
  129. 129:      */
  130. 130:     public static function invoke($s)
  131. 131:     {
  132. 132:         $filter new self;
  133. 133:         return $filter->__invoke($s);
  134. 134:     }
  135. 135:  
  136. 136:  
  137. 137:  
  138. 138:     /**
  139. 139:      * Invokes filter.
  140. 140:      * @param  string 
  141. 141:      * @return string 
  142. 142:      */
  143. 143:     public function __invoke($s)
  144. 144:     {
  145. 145:         $this->blocks array();
  146. 146:         $this->namedBlocks array();
  147. 147:  
  148. 148:         // context-aware escaping
  149. 149:         $this->context self::CONTEXT_TEXT;
  150. 150:         $this->escape 'TemplateHelpers::escapeHtml';
  151. 151:         $this->tag NULL;
  152. 152:  
  153. 153:         // remove comments
  154. 154:         $s preg_replace('#\\{\\*.*?\\*\\}[\r\n]*#s'''$s);
  155. 155:  
  156. 156:         // snippets support (temporary solution)
  157. 157:         $s "<?php\nif (SnippetHelper::\$outputAllowed) {\n?>\n$s<?php\n}\n?>"// \n$s is required by following RE
  158. 158:         $s preg_replace(
  159. 159:             '#@(\\{[^}]+?\\})#s',
  160. 160:             '<?php } ?>$1<?php if (SnippetHelper::\\$outputAllowed) { ?>',
  161. 161:             $s
  162. 162:         );
  163. 163:  
  164. 164:         // process all {tags} and <tags/>
  165. 165:         $s preg_replace_callback('~
  166. 166:                 <(/?)([a-z]+)|                          ## 1,2) start tag: <tag </tag ; ignores <!-- <!DOCTYPE
  167. 167:                 (>)|                                    ## 3) end tag
  168. 168:                 (?<=\\s)(style|on[a-z]+)\s*=\s*(["\'])| ## 4,5) attribute
  169. 169:                 (["\'])|                                ## 6) attribute delimiter
  170. 170:                 (\n[ \t]*)?\\{([^\\s\'"{}][^}]*?)(\\|[a-z](?:[^\'"}\s|]+|\\|[a-z]|\'[^\']*\'|"[^"]*")*)?\\}([ \t]*(?=\r|\n))? ## 7,8,9,10) indent & macro & modifiers & newline
  171. 171:             ~xsi',
  172. 172:             array($this'cbContent'),
  173. 173:             $s
  174. 174:         );
  175. 175:  
  176. 176:         // named blocks
  177. 177:         if ($this->namedBlocks{
  178. 178:             foreach (array_reverse($this->namedBlocksTRUEas $name => $foo{
  179. 179:                 $s preg_replace_callback("#{block\#($name)} \?>(.*)<\?php {/block\#$name}#sU"array($this'cbNamedBlocks')$s);
  180. 180:             }
  181. 181:             preg_match('#function (\S+)\(#'reset($this->namedBlocks)$m);
  182. 182:             $s "<?php\nif (!function_exists('$m[1]')) {\n\nimplode("\n\n\n"$this->namedBlocks"\n\n} ?>" $s;
  183. 183:         }
  184. 184:  
  185. 185:         // internal state holder
  186. 186:         $s "<?php "
  187. 187:             
  188. 188:             . "\$_cb = CurlyBracketsFilter::initState(\$template) ?>" $s;
  189. 189:  
  190. 190:         return $s;
  191. 191:     }
  192. 192:  
  193. 193:  
  194. 194:  
  195. 195:     /**
  196. 196:      * Searches for curly brackets, HTML tags and attributes.
  197. 197:      */
  198. 198:     private function cbContent($matches)
  199. 199:     {
  200. 200:         //    [1] => /
  201. 201:         //    [2] => tag
  202. 202:         //    [3] => >
  203. 203:         //    [4] => style|on...
  204. 204:         //    [5] => '"
  205. 205:         //    [6] => '"
  206. 206:         //    [7] => indent
  207. 207:         //    [8] => {macro
  208. 208:         //    [9] => {...|modifiers}
  209. 209:         //    [10] => newline?
  210. 210:  
  211. 211:         if (!empty($matches[8])) // {macro|var|modifiers}
  212. 212:             $matches[NULL;
  213. 213:             list(, , , , , , , $indent$macro$modifiers$matches;
  214. 214:             foreach (self::$macros as $key => $val{
  215. 215:                 if (strncmp($macro$keystrlen($key)) === 0{
  216. 216:                     $var substr($macrostrlen($key));
  217. 217:                     if (preg_match('#[a-zA-Z0-9]$#'$key&& preg_match('#^[a-zA-Z0-9._-]#'$var)) {
  218. 218:                         continue;
  219. 219:                     }
  220. 220:                     $result $this->macro($keytrim($var)$modifiers);
  221. 221:                     $nl isset($matches[10]"\n" ''// double newline
  222. 222:                     if ($nl && $indent && strncmp($result'<?php echo '11)) {
  223. 223:                         return "\n" $result// remove indent, single newline
  224. 224:                     else {
  225. 225:                         return $indent $result $nl;
  226. 226:                     }
  227. 227:                 }
  228. 228:             }
  229. 229:             throw new InvalidStateException("Unknown macro '$matches[0]'.");
  230. 230:  
  231. 231:         elseif ($this->context === self::CONTEXT_NONE{
  232. 232:             // skip analyse
  233. 233:  
  234. 234:         elseif (!empty($matches[6])) // (attribute) '"
  235. 235:             if ($this->context === $matches[6]{
  236. 236:                 $this->context self::CONTEXT_TAG;
  237. 237:                 $this->escape 'TemplateHelpers::escapeHtml';
  238. 238:             elseif ($this->context === self::CONTEXT_TAG{
  239. 239:                 $this->context $matches[6];
  240. 240:             }
  241. 241:  
  242. 242:         elseif (!empty($matches[4])) // (style|on...) '"
  243. 243:             if ($this->context === self::CONTEXT_TAG{
  244. 244:                 $this->context $matches[5]// self::CONTEXT_ATTRIBUTE_SINGLE || self::CONTEXT_ATTRIBUTE_DOUBLE
  245. 245:                 $this->escape strncasecmp($matches[4]'on'2'TemplateHelpers::escapeHtmlCss' 'TemplateHelpers::escapeHtmlJs';
  246. 246:             }
  247. 247:  
  248. 248:         elseif (!empty($matches[3])) // >
  249. 249:             if ($this->context === self::CONTEXT_TAG{
  250. 250:                 if ($this->tag === 'script' || $this->tag === 'style'{
  251. 251:                     $this->context self::CONTEXT_CDATA;
  252. 252:                     $this->escape $this->tag === 'script' 'TemplateHelpers::escapeJs' 'TemplateHelpers::escapeCss';
  253. 253:                 else {
  254. 254:                     $this->context self::CONTEXT_TEXT;
  255. 255:                     $this->escape 'TemplateHelpers::escapeHtml';
  256. 256:                 }
  257. 257:             }
  258. 258:  
  259. 259:         elseif (empty($matches[1])) // <tag
  260. 260:             if ($this->context === self::CONTEXT_TEXT{
  261. 261:                 $this->context self::CONTEXT_TAG;
  262. 262:                 $this->escape 'TemplateHelpers::escapeHtml';
  263. 263:                 $this->tag strtolower($matches[2]);
  264. 264:             }
  265. 265:  
  266. 266:         else // </tag
  267. 267:             if ($this->context === self::CONTEXT_TEXT || ($this->context === self::CONTEXT_CDATA && $this->tag === strtolower($matches[2]))) {
  268. 268:                 $this->context self::CONTEXT_TAG;
  269. 269:                 $this->escape 'TemplateHelpers::escapeHtml';
  270. 270:                 $this->tag NULL;
  271. 271:             }
  272. 272:         }
  273. 273:         return $matches[0];
  274. 274:     }
  275. 275:  
  276. 276:  
  277. 277:  
  278. 278:     /**
  279. 279:      * Process specified macro.
  280. 280:      */
  281. 281:     public function macro($macro$var$modifiers)
  282. 282:     {
  283. 283:         $this->_cbMacro array($var$modifiers);
  284. 284:         return preg_replace_callback('#%(.*?)%#'array($this'cbMacro')self::$macros[$macro]);
  285. 285:     }
  286. 286:  
  287. 287:  
  288. 288:  
  289. 289:     /** @var array */
  290. 290:     private $_cbMacro;
  291. 291:  
  292. 292:     /**
  293. 293:      * Callback for self::macro().
  294. 294:      */
  295. 295:     private function cbMacro($m)
  296. 296:     {
  297. 297:         list($var$modifiers$this->_cbMacro;
  298. 298:         if ($m[1]{
  299. 299:             $callback $m[1][0=== ':' array($thissubstr($m[1]1)) $m[1];
  300. 300:             fixCallback($callback);
  301. 301:             if (!is_callable($callback)) {
  302. 302:                 $able is_callable($callbackTRUE$textual);
  303. 303:                 throw new InvalidStateException("CurlyBrackets macro handler '$textual' is not ($able 'callable.' 'valid PHP callback.'));
  304. 304:             }
  305. 305:             return call_user_func($callback$var$modifiers);
  306. 306:  
  307. 307:         else {
  308. 308:             return $var;
  309. 309:         }
  310. 310:     }
  311. 311:  
  312. 312:  
  313. 313:  
  314. 314:     /**
  315. 315:      * {$var |modifiers}
  316. 316:      */
  317. 317:     private function macroVar($var$modifiers)
  318. 318:     {
  319. 319:         return $this->macroModifiers('$' $var$modifiers);
  320. 320:     }
  321. 321:  
  322. 322:  
  323. 323:  
  324. 324:     /**
  325. 325:      * {include ...}
  326. 326:      */
  327. 327:     private function macroInclude($var$modifiers)
  328. 328:     {
  329. 329:         if (substr($var01=== '#'{
  330. 330:             preg_match('#^.([^\s,]+),?\s*(.*)$()#'$var$m)// #name[,] [params]
  331. 331:             list($name$params$m;
  332. 332:  
  333. 333:             if (!preg_match('#^[a-zA-Z0-9_]+$#'$name)) {
  334. 334:                 throw new InvalidStateException("Included block name must be alphanumeric string, '$name' given.");
  335. 335:             }
  336. 336:  
  337. 337:             $params ($params "array($params) + '''$template->getParams()'// or get_defined_vars() ?
  338. 338:             $cmd $name === 'parent' 'next' 'reset';
  339. 339:             if ($name === 'parent' || $name === 'this'{
  340. 340:                 $item end($this->blocks);
  341. 341:                 while ($item && $item[0][0!== '#'$item prev($this->blocks);
  342. 342:                 if (!$item{
  343. 343:                     throw new InvalidStateException("Cannot include $name block outside of any block.");
  344. 344:                 }
  345. 345:                 $name substr($item[0]1);
  346. 346:             }
  347. 347:             $name var_export($nameTRUE);
  348. 348:             $cmd "call_user_func($cmd(\$_cb->blks[$name]), $params)";
  349. 349:             return $modifiers $this->macroBlock(''$modifiers$cmd ";" $this->macroBlockEnd(NULL$cmd;
  350. 350:         }
  351. 351:  
  352. 352:         return 'echo ' $this->macroModifiers('$template->subTemplate(' $this->formatVars($var')->__toString(TRUE)'$modifiers);
  353. 353:     }
  354. 354:  
  355. 355:  
  356. 356:  
  357. 357:     /**
  358. 358:      * {extends ...}
  359. 359:      */
  360. 360:     private function macroExtends($var)
  361. 361:     {
  362. 362:         return $this->macroInclude($var'''; return';
  363. 363:     }
  364. 364:  
  365. 365:  
  366. 366:  
  367. 367:     /**
  368. 368:      * {block ...}
  369. 369:      */
  370. 370:     private function macroBlock($var$modifiers)
  371. 371:     {
  372. 372:         if (substr($var01=== '#'// named block
  373. 373:             $name substr($var1);
  374. 374:             if (!preg_match('#^[a-zA-Z0-9_]+$#'$name)) {
  375. 375:                 throw new InvalidStateException("Block name must be alphanumeric string, '$name' given.");
  376. 376:  
  377. 377:             elseif (isset($this->namedBlocks[$name])) {
  378. 378:                 throw new InvalidStateException("Cannot redeclare block '$name'.");
  379. 379:             }
  380. 380:  
  381. 381:             $this->namedBlocks[$name$name;
  382. 382:             $this->blocks[array($var'');
  383. 383:             return $this->macroInclude($var$modifiers"{block#$name}";
  384. 384:         }
  385. 385:  
  386. 386:         if ($var === '' || $var[0=== '$'// capture or modifier
  387. 387:             $this->blocks[array($var$modifiers);
  388. 388:             return ($var === '' && $modifiers === '''' 'ob_start(); try {';
  389. 389:         }
  390. 390:  
  391. 391:         throw new InvalidStateException("Invalid block parameter '$var'.");
  392. 392:     }
  393. 393:  
  394. 394:  
  395. 395:  
  396. 396:     /**
  397. 397:      * {/block ...}
  398. 398:      */
  399. 399:     private function macroBlockEnd($optVar)
  400. 400:     {
  401. 401:         list($var$modifiersarray_pop($this->blocks);
  402. 402:  
  403. 403:         if ($optVar && $optVar !== $var{
  404. 404:             throw new InvalidStateException("Tag {/block $var} was not expected here.");
  405. 405:  
  406. 406:         elseif (substr($var01=== '#'// named block
  407. 407:             return "{/block$var}";
  408. 408:  
  409. 409:         else // capture or modifier
  410. 410:             return ($var === '' && $modifiers === ''''
  411. 411:                 : '} catch (Exception $_e) { ob_end_clean(); throw $_e; } '
  412. 412:                 . ($var === '' 'echo ' $var '=')
  413. 413:                 . $this->macroModifiers('ob_get_clean()'$modifiers);
  414. 414:         }
  415. 415:     }
  416. 416:  
  417. 417:  
  418. 418:  
  419. 419:     /**
  420. 420:      * Converts {block#named}...{/block} to functions.
  421. 421:      */
  422. 422:     private function cbNamedBlocks($matches)
  423. 423:     {
  424. 424:         list($name$content$matches;
  425. 425:         $func '_cbb' substr(md5(uniqid($name))015'_' $name;
  426. 426:         $this->namedBlocks[$name"\$_cb->blks[" var_export($nameTRUE"][] = '$func';\n"
  427. 427:             . "function $func() { extract(func_get_arg(0)) // block #$name\n?>$content<?php\n}";
  428. 428:         return '';
  429. 429:     }
  430. 430:  
  431. 431:  
  432. 432:  
  433. 433:     /**
  434. 434:      * {foreach ...}
  435. 435:      */
  436. 436:     private function macroForeach($var)
  437. 437:     {
  438. 438:         return '$iterator = $_cb->its[] = new SmartCachingIterator(' preg_replace('# +as +#i'') as '$var1);
  439. 439:     }
  440. 440:  
  441. 441:  
  442. 442:  
  443. 443:     /**
  444. 444:      * {attr ...}
  445. 445:      */
  446. 446:     private function macroAttr($var)
  447. 447:     {
  448. 448:         return str_replace(') '')->'$var ' ');
  449. 449:     }
  450. 450:  
  451. 451:  
  452. 452:  
  453. 453:     /**
  454. 454:      * {contentType ...}
  455. 455:      */
  456. 456:     private function macroContentType($var)
  457. 457:     {
  458. 458:         if (strpos($var'html'!== FALSE{
  459. 459:             $this->escape 'TemplateHelpers::escapeHtml';
  460. 460:             $this->context self::CONTEXT_TEXT;
  461. 461:  
  462. 462:         elseif (strpos($var'xml'!== FALSE{
  463. 463:             $this->escape 'TemplateHelpers::escapeXml';
  464. 464:             $this->context self::CONTEXT_NONE;
  465. 465:  
  466. 466:         elseif (strpos($var'javascript'!== FALSE{
  467. 467:             $this->escape 'TemplateHelpers::escapeJs';
  468. 468:             $this->context self::CONTEXT_NONE;
  469. 469:  
  470. 470:         elseif (strpos($var'css'!== FALSE{
  471. 471:             $this->escape 'TemplateHelpers::escapeCss';
  472. 472:             $this->context self::CONTEXT_NONE;
  473. 473:  
  474. 474:         else {
  475. 475:             $this->escape '$template->escape';
  476. 476:             $this->context self::CONTEXT_NONE;
  477. 477:         }
  478. 478:  
  479. 479:         // temporary solution
  480. 480:         return strpos($var'/''Environment::getHttpResponse()->setHeader("Content-Type", "' $var '")' '';
  481. 481:     }
  482. 482:  
  483. 483:  
  484. 484:  
  485. 485:     /**
  486. 486:      * {snippet ...}
  487. 487:      */
  488. 488:     private function macroSnippet($var)
  489. 489:     {
  490. 490:         if (preg_match('#^([^\s,]+),?\s*(.*)$#'$var$m)) // [name[,]] [tag]
  491. 491:             $var ', "' $m[1'"' ($m[2', ' var_export($m[2]TRUE'');
  492. 492:         }
  493. 493:         return $var;
  494. 494:     }
  495. 495:  
  496. 496:  
  497. 497:  
  498. 498:     /**
  499. 499:      * {link ...}
  500. 500:      */
  501. 501:     private function macroLink($var$modifiers)
  502. 502:     {
  503. 503:         return $this->macroModifiers('$control->link(' $this->formatVars($var.')'$modifiers);
  504. 504:     }
  505. 505:  
  506. 506:  
  507. 507:  
  508. 508:     /**
  509. 509:      * {plink ...}
  510. 510:      */
  511. 511:     private function macroPlink($var$modifiers)
  512. 512:     {
  513. 513:         return $this->macroModifiers('$presenter->link(' $this->formatVars($var.')'$modifiers);
  514. 514:     }
  515. 515:  
  516. 516:  
  517. 517:  
  518. 518:     /**
  519. 519:      * {ifCurrent ...}
  520. 520:      */
  521. 521:     private function macroIfCurrent($var$modifiers)
  522. 522:     {
  523. 523:         return $var $this->macroModifiers('$presenter->link(' $this->formatVars($var.')'$modifiers'';
  524. 524:     }
  525. 525:  
  526. 526:  
  527. 527:  
  528. 528:     /**
  529. 529:      * {ajaxlink ...}
  530. 530:      */
  531. 531:     private function macroAjaxlink($var$modifiers)
  532. 532:     {
  533. 533:         return $this->macroModifiers('$control->ajaxlink(' $this->formatVars($var.')'$modifiers);
  534. 534:     }
  535. 535:  
  536. 536:  
  537. 537:  
  538. 538:     /**
  539. 539:      * {assign ...}
  540. 540:      */
  541. 541:     private function macroAssign($var$modifiers)
  542. 542:     {
  543. 543:         preg_match('#^\\$?(\S+)\s*(.*)$#'$var$m)// [$]params value
  544. 544:         return '$template->' $m[1' = $' $m[1' = ' $this->macroModifiers($m[2=== '' 'NULL' $m[2]$modifiers);
  545. 545:     }
  546. 546:  
  547. 547:  
  548. 548:  
  549. 549:     /**
  550. 550:      * Escaping helper.
  551. 551:      */
  552. 552:     private function macroEscape($var)
  553. 553:     {
  554. 554:         return $this->escape;
  555. 555:     }
  556. 556:  
  557. 557:  
  558. 558:  
  559. 559:     /**
  560. 560:      * Applies modifiers.
  561. 561:      */
  562. 562:     public function macroModifiers($var$modifiers)
  563. 563:     {
  564. 564:         if (!$modifiersreturn $var;
  565. 565:         preg_match_all(
  566. 566:             '#[^\'"}\s|:]+|[|:]|\'[^\']*\'|"[^"]*"#s',
  567. 567:             $modifiers '|',
  568. 568:             $tokens
  569. 569:         );
  570. 570:         $state FALSE;
  571. 571:         foreach ($tokens[0as $token{
  572. 572:             if ($token === ':' || $token === '|'{
  573. 573:                 if (!isset($prev)) {
  574. 574:                     continue;
  575. 575:  
  576. 576:                 elseif ($state === FALSE{
  577. 577:                     $var "\$template->$prev($var";
  578. 578:  
  579. 579:                 else {
  580. 580:                     $var .= ', ' $prev;
  581. 581:                 }
  582. 582:  
  583. 583:                 if ($token === '|'{
  584. 584:                     $var .= ')';
  585. 585:                     $state FALSE;
  586. 586:  
  587. 587:                 else {
  588. 588:                     $state TRUE;
  589. 589:                 }
  590. 590:             else {
  591. 591:                 $prev $token;
  592. 592:             }
  593. 593:         }
  594. 594:         return $var;
  595. 595:     }
  596. 596:  
  597. 597:  
  598. 598:  
  599. 599:     /**
  600. 600:      * Formats {*link ...} parameters.
  601. 601:      */
  602. 602:     private function formatVars($var)
  603. 603:     {
  604. 604:         if (preg_match('#^([^\s,]+),?\s*(.*)$#'$var$m)) // destination[,] args
  605. 605:             $var strspn($m[1]'\'"$'$m[1"'$m[1]'";
  606. 606:             if ($m[2]{
  607. 607:                 $var .= ', ' (strpos($m[2]'=>'=== FALSE $m[2"array($m[2])");
  608. 608:             }
  609. 609:         }
  610. 610:         return $var;
  611. 611:     }
  612. 612:  
  613. 613:  
  614. 614:  
  615. 615:     /**
  616. 616:      * Initializes state holder $_cb in template.
  617. 617:      * @param  ITemplate 
  618. 618:      * @return stdClass 
  619. 619:      */
  620. 620:     public static function initState($template)
  621. 621:     {
  622. 622:         if (!isset($template->_cb)) {
  623. 623:             $template->_cb = (object) NULL;
  624. 624:         }
  625. 625:         if (!empty($template->_cb->caches)) // cache support
  626. 626:             end($template->_cb->caches)->addFile($template->getFile());
  627. 627:         }
  628. 628:         return $template->_cb;
  629. 629:     }
  630. 630: