Namespaces

  • Latte
    • Loaders
    • Macros
    • Runtime
  • Nette
    • Application
      • Responses
      • Routers
      • UI
    • Bridges
      • ApplicationDI
      • ApplicationLatte
      • ApplicationTracy
      • CacheDI
      • CacheLatte
      • DatabaseDI
      • DatabaseTracy
      • DITracy
      • FormsDI
      • FormsLatte
      • Framework
      • HttpDI
      • HttpTracy
      • MailDI
      • ReflectionDI
      • SecurityDI
      • SecurityTracy
    • Caching
      • Storages
    • ComponentModel
    • Database
      • Conventions
      • Drivers
      • Table
    • DI
      • Config
        • Adapters
      • Extensions
    • Forms
      • Controls
      • Rendering
    • Http
    • Iterators
    • Loaders
    • Localization
    • Mail
    • Neon
    • PhpGenerator
      • Traits
    • Reflection
    • Security
    • Tokenizer
    • Utils
  • Tracy
    • Bridges
      • Nette
  • none

Classes

  • BlockMacros
  • CoreMacros
  • MacroSet
  • Overview
  • Namespace
  • Class
  • Tree
  • Deprecated
  • Other releases
  1: <?php
  2: 
  3: /**
  4:  * This file is part of the Latte (https://latte.nette.org)
  5:  * Copyright (c) 2008 David Grudl (https://davidgrudl.com)
  6:  */
  7: 
  8: namespace Latte\Macros;
  9: 
 10: use Latte;
 11: use Latte\CompileException;
 12: use Latte\Engine;
 13: use Latte\Helpers;
 14: use Latte\MacroNode;
 15: use Latte\PhpWriter;
 16: 
 17: 
 18: /**
 19:  * Basic macros for Latte.
 20:  *
 21:  * - {if ?} ... {elseif ?} ... {else} ... {/if}
 22:  * - {ifset ?} ... {elseifset ?} ... {/ifset}
 23:  * - {for ?} ... {/for}
 24:  * - {foreach ?} ... {/foreach}
 25:  * - {$variable} with escaping
 26:  * - {=expression} echo with escaping
 27:  * - {php expression} evaluate PHP statement
 28:  * - {_expression} echo translation with escaping
 29:  * - {attr ?} HTML element attributes
 30:  * - {capture ?} ... {/capture} capture block to parameter
 31:  * - {spaceless} ... {/spaceless} compress whitespaces
 32:  * - {var var => value} set template parameter
 33:  * - {default var => value} set default template parameter
 34:  * - {dump $var}
 35:  * - {debugbreak}
 36:  * - {contentType ...} HTTP Content-Type header
 37:  * - {status ...} HTTP status
 38:  * - {l} {r} to display { }
 39:  */
 40: class CoreMacros extends MacroSet
 41: {
 42:     /** @var array */
 43:     private $overwrittenVars;
 44: 
 45: 
 46:     public static function install(Latte\Compiler $compiler)
 47:     {
 48:         $me = new static($compiler);
 49: 
 50:         $me->addMacro('if', [$me, 'macroIf'], [$me, 'macroEndIf']);
 51:         $me->addMacro('elseif', '} elseif (%node.args) {');
 52:         $me->addMacro('else', [$me, 'macroElse']);
 53:         $me->addMacro('ifset', 'if (isset(%node.args)) {', '}');
 54:         $me->addMacro('elseifset', '} elseif (isset(%node.args)) {');
 55:         $me->addMacro('ifcontent', [$me, 'macroIfContent'], [$me, 'macroEndIfContent']);
 56: 
 57:         $me->addMacro('switch', '$this->global->switch[] = (%node.args); if (FALSE) {', '} array_pop($this->global->switch)');
 58:         $me->addMacro('case', '} elseif (end($this->global->switch) === (%node.args)) {');
 59: 
 60:         $me->addMacro('foreach', '', [$me, 'macroEndForeach']);
 61:         $me->addMacro('for', 'for (%node.args) {', '}');
 62:         $me->addMacro('while', [$me, 'macroWhile'], [$me, 'macroEndWhile']);
 63:         $me->addMacro('continueIf', [$me, 'macroBreakContinueIf']);
 64:         $me->addMacro('breakIf', [$me, 'macroBreakContinueIf']);
 65:         $me->addMacro('first', 'if ($iterator->isFirst(%node.args)) {', '}');
 66:         $me->addMacro('last', 'if ($iterator->isLast(%node.args)) {', '}');
 67:         $me->addMacro('sep', 'if (!$iterator->isLast(%node.args)) {', '}');
 68: 
 69:         $me->addMacro('var', [$me, 'macroVar']);
 70:         $me->addMacro('default', [$me, 'macroVar']);
 71:         $me->addMacro('dump', [$me, 'macroDump']);
 72:         $me->addMacro('debugbreak', [$me, 'macroDebugbreak']);
 73:         $me->addMacro('l', '?>{<?php');
 74:         $me->addMacro('r', '?>}<?php');
 75: 
 76:         $me->addMacro('_', [$me, 'macroTranslate'], [$me, 'macroTranslate']);
 77:         $me->addMacro('=', [$me, 'macroExpr']);
 78:         $me->addMacro('?', [$me, 'macroExpr']);
 79: 
 80:         $me->addMacro('capture', [$me, 'macroCapture'], [$me, 'macroCaptureEnd']);
 81:         $me->addMacro('spaceless', [$me, 'macroSpaceless'], [$me, 'macroSpaceless']);
 82:         $me->addMacro('include', [$me, 'macroInclude']);
 83:         $me->addMacro('use', [$me, 'macroUse']);
 84:         $me->addMacro('contentType', [$me, 'macroContentType'], null, null, self::ALLOWED_IN_HEAD);
 85:         $me->addMacro('status', [$me, 'macroStatus']);
 86:         $me->addMacro('php', [$me, 'macroExpr']);
 87: 
 88:         $me->addMacro('class', null, null, [$me, 'macroClass']);
 89:         $me->addMacro('attr', null, null, [$me, 'macroAttr']);
 90:     }
 91: 
 92: 
 93:     /**
 94:      * Initializes before template parsing.
 95:      * @return void
 96:      */
 97:     public function initialize()
 98:     {
 99:         $this->overwrittenVars = [];
100:     }
101: 
102: 
103:     /**
104:      * Finishes template parsing.
105:      * @return array|null [prolog, epilog]
106:      */
107:     public function finalize()
108:     {
109:         $code = '';
110:         foreach ($this->overwrittenVars as $var => $lines) {
111:             $s = var_export($var, true);
112:             $code .= 'if (isset($this->params[' . var_export($var, true)
113:             . "])) trigger_error('Variable $" . addcslashes($var, "'") . ' overwritten in foreach on line ' . implode(', ', $lines) . "'); ";
114:         }
115:         return [$code];
116:     }
117: 
118: 
119:     /********************* macros ****************d*g**/
120: 
121: 
122:     /**
123:      * {if ...}
124:      */
125:     public function macroIf(MacroNode $node, PhpWriter $writer)
126:     {
127:         if ($node->modifiers) {
128:             throw new CompileException('Modifiers are not allowed in ' . $node->getNotation());
129:         }
130:         if ($node->data->capture = ($node->args === '')) {
131:             return 'ob_start(function () {})';
132:         }
133:         if ($node->prefix === $node::PREFIX_TAG) {
134:             return $writer->write($node->htmlNode->closing ? 'if (array_pop($this->global->ifs)) {' : 'if ($this->global->ifs[] = (%node.args)) {');
135:         }
136:         return $writer->write('if (%node.args) {');
137:     }
138: 
139: 
140:     /**
141:      * {/if ...}
142:      */
143:     public function macroEndIf(MacroNode $node, PhpWriter $writer)
144:     {
145:         if ($node->data->capture) {
146:             if ($node->args === '') {
147:                 throw new CompileException('Missing condition in {if} macro.');
148:             }
149:             return $writer->write('if (%node.args) '
150:                 . (isset($node->data->else) ? '{ ob_end_clean(); echo ob_get_clean(); }' : 'echo ob_get_clean();')
151:                 . ' else '
152:                 . (isset($node->data->else) ? '{ $this->global->else = ob_get_clean(); ob_end_clean(); echo $this->global->else; }' : 'ob_end_clean();')
153:             );
154:         }
155:         return '}';
156:     }
157: 
158: 
159:     /**
160:      * {else}
161:      */
162:     public function macroElse(MacroNode $node, PhpWriter $writer)
163:     {
164:         if ($node->modifiers) {
165:             throw new CompileException('Modifiers are not allowed in ' . $node->getNotation());
166:         } elseif ($node->args) {
167:             $hint = Helpers::startsWith($node->args, 'if') ? ', did you mean {elseif}?' : '';
168:             throw new CompileException('Arguments are not allowed in ' . $node->getNotation() . $hint);
169:         }
170:         $ifNode = $node->parentNode;
171:         if ($ifNode && $ifNode->name === 'if' && $ifNode->data->capture) {
172:             if (isset($ifNode->data->else)) {
173:                 throw new CompileException('Macro {if} supports only one {else}.');
174:             }
175:             $ifNode->data->else = true;
176:             return 'ob_start(function () {})';
177:         }
178:         return '} else {';
179:     }
180: 
181: 
182:     /**
183:      * n:ifcontent
184:      */
185:     public function macroIfContent(MacroNode $node, PhpWriter $writer)
186:     {
187:         if (!$node->prefix || $node->prefix !== MacroNode::PREFIX_NONE) {
188:             throw new CompileException('Unknown ' . $node->getNotation() . ", use n:{$node->name} attribute.");
189:         }
190:     }
191: 
192: 
193:     /**
194:      * n:ifcontent
195:      */
196:     public function macroEndIfContent(MacroNode $node, PhpWriter $writer)
197:     {
198:         $node->openingCode = '<?php ob_start(function () {}); ?>';
199:         $node->innerContent = '<?php ob_start(); ?>' . $node->innerContent . '<?php $this->global->ifcontent = ob_get_flush(); ?>';
200:         $node->closingCode = '<?php if (rtrim($this->global->ifcontent) === "") ob_end_clean(); else echo ob_get_clean(); ?>';
201:     }
202: 
203: 
204:     /**
205:      * {_$var |modifiers}
206:      */
207:     public function macroTranslate(MacroNode $node, PhpWriter $writer)
208:     {
209:         if ($node->closing) {
210:             if (strpos($node->content, '<?php') === false) {
211:                 $value = var_export($node->content, true);
212:                 $node->content = '';
213:             } else {
214:                 $node->openingCode = '<?php ob_start(function () {}) ?>' . $node->openingCode;
215:                 $value = 'ob_get_clean()';
216:             }
217: 
218:             return $writer->write('$_fi = new LR\FilterInfo(%var); echo %modifyContent($this->filters->filterContent("translate", $_fi, %raw))', $node->context[0], $value);
219: 
220:         } elseif ($node->empty = ($node->args !== '')) {
221:             return $writer->write('echo %modify(call_user_func($this->filters->translate, %node.args))');
222:         }
223:     }
224: 
225: 
226:     /**
227:      * {include "file" [,] [params]}
228:      */
229:     public function macroInclude(MacroNode $node, PhpWriter $writer)
230:     {
231:         $node->replaced = false;
232:         $noEscape = Helpers::removeFilter($node->modifiers, 'noescape');
233:         if (!$noEscape && Helpers::removeFilter($node->modifiers, 'escape')) {
234:             trigger_error('Macro {include} provides auto-escaping, remove |escape.');
235:         }
236:         if ($node->modifiers && !$noEscape) {
237:             $node->modifiers .= '|escape';
238:         }
239:         return $writer->write(
240:             '/* line ' . $node->startLine . ' */
241:             $this->createTemplate(%node.word, %node.array? + $this->params, "include")->renderToContentType(%raw);',
242:             $node->modifiers
243:                 ? $writer->write('function ($s, $type) { $_fi = new LR\FilterInfo($type); return %modifyContent($s); }')
244:                 : var_export($noEscape ? null : implode($node->context), true)
245:         );
246:     }
247: 
248: 
249:     /**
250:      * {use class MacroSet}
251:      */
252:     public function macroUse(MacroNode $node, PhpWriter $writer)
253:     {
254:         trigger_error('Macro {use} is deprecated.', E_USER_DEPRECATED);
255:         if ($node->modifiers) {
256:             throw new CompileException('Modifiers are not allowed in ' . $node->getNotation());
257:         }
258:         call_user_func(Helpers::checkCallback([$node->tokenizer->fetchWord(), 'install']), $this->getCompiler())
259:             ->initialize();
260:     }
261: 
262: 
263:     /**
264:      * {capture $variable}
265:      */
266:     public function macroCapture(MacroNode $node, PhpWriter $writer)
267:     {
268:         $variable = $node->tokenizer->fetchWord();
269:         if (!Helpers::startsWith($variable, '$')) {
270:             throw new CompileException("Invalid capture block variable '$variable'");
271:         }
272:         $this->checkExtraArgs($node);
273:         $node->data->variable = $variable;
274:         return 'ob_start(function () {})';
275:     }
276: 
277: 
278:     /**
279:      * {/capture}
280:      */
281:     public function macroCaptureEnd(MacroNode $node, PhpWriter $writer)
282:     {
283:         $body = in_array($node->context[0], [Engine::CONTENT_HTML, Engine::CONTENT_XHTML], true)
284:             ? 'ob_get_length() ? new LR\\Html(ob_get_clean()) : ob_get_clean()'
285:             : 'ob_get_clean()';
286:         return $writer->write("\$_fi = new LR\\FilterInfo(%var); %raw = %modifyContent($body);", $node->context[0], $node->data->variable);
287:     }
288: 
289: 
290:     /**
291:      * {spaceless} ... {/spaceless}
292:      */
293:     public function macroSpaceless(MacroNode $node, PhpWriter $writer)
294:     {
295:         if ($node->modifiers || $node->args) {
296:             throw new CompileException('Modifiers and arguments are not allowed in ' . $node->getNotation());
297:         }
298:         $node->openingCode = in_array($node->context[0], [Engine::CONTENT_HTML, Engine::CONTENT_XHTML], true)
299:             ? '<?php ob_start(function ($s, $phase) { static $strip = true; return LR\Filters::spacelessHtml($s, $phase, $strip); }, 4096); ?>'
300:             : "<?php ob_start('Latte\\Runtime\\Filters::spacelessText', 4096); ?>";
301:         $node->closingCode = '<?php ob_end_flush(); ?>';
302:     }
303: 
304: 
305:     /**
306:      * {while ...}
307:      */
308:     public function macroWhile(MacroNode $node, PhpWriter $writer)
309:     {
310:         if ($node->modifiers) {
311:             throw new CompileException('Modifiers are not allowed in ' . $node->getNotation());
312:         }
313:         if ($node->data->do = ($node->args === '')) {
314:             return 'do {';
315:         }
316:         return $writer->write('while (%node.args) {');
317:     }
318: 
319: 
320:     /**
321:      * {/while ...}
322:      */
323:     public function macroEndWhile(MacroNode $node, PhpWriter $writer)
324:     {
325:         if ($node->data->do) {
326:             if ($node->args === '') {
327:                 throw new CompileException('Missing condition in {while} macro.');
328:             }
329:             return $writer->write('} while (%node.args);');
330:         }
331:         return '}';
332:     }
333: 
334: 
335:     /**
336:      * {foreach ...}
337:      */
338:     public function macroEndForeach(MacroNode $node, PhpWriter $writer)
339:     {
340:         $noCheck = Helpers::removeFilter($node->modifiers, 'nocheck');
341:         $noIterator = Helpers::removeFilter($node->modifiers, 'noiterator');
342:         if ($node->modifiers) {
343:             throw new CompileException('Only modifiers |noiterator and |nocheck are allowed here.');
344:         }
345:         $node->openingCode = '<?php $iterations = 0; ';
346:         $args = $writer->formatArgs();
347:         if (!$noCheck) {
348:             preg_match('#.+\s+as\s*\$(\w+)(?:\s*=>\s*\$(\w+))?#i', $args, $m);
349:             for ($i = 1; $i < count($m); $i++) {
350:                 $this->overwrittenVars[$m[$i]][] = $node->startLine;
351:             }
352:         }
353:         if (!$noIterator && preg_match('#\W(\$iterator|include|require|get_defined_vars)\W#', $this->getCompiler()->expandTokens($node->content))) {
354:             $node->openingCode .= 'foreach ($iterator = $this->global->its[] = new LR\CachingIterator('
355:                 . preg_replace('#(.*)\s+as\s+#i', '$1) as ', $args, 1) . ') { ?>';
356:             $node->closingCode = '<?php $iterations++; } array_pop($this->global->its); $iterator = end($this->global->its); ?>';
357:         } else {
358:             $node->openingCode .= 'foreach (' . $args . ') { ?>';
359:             $node->closingCode = '<?php $iterations++; } ?>';
360:         }
361:     }
362: 
363: 
364:     /**
365:      * {breakIf ...}
366:      * {continueIf ...}
367:      */
368:     public function macroBreakContinueIf(MacroNode $node, PhpWriter $writer)
369:     {
370:         if ($node->modifiers) {
371:             throw new CompileException('Modifiers are not allowed in ' . $node->getNotation());
372:         }
373:         $cmd = str_replace('If', '', $node->name);
374:         if ($node->parentNode && $node->parentNode->prefix === $node::PREFIX_NONE) {
375:             return $writer->write("if (%node.args) { echo \"</{$node->parentNode->htmlNode->name}>\\n\"; $cmd; }");
376:         }
377:         return $writer->write("if (%node.args) $cmd;");
378:     }
379: 
380: 
381:     /**
382:      * n:class="..."
383:      */
384:     public function macroClass(MacroNode $node, PhpWriter $writer)
385:     {
386:         if (isset($node->htmlNode->attrs['class'])) {
387:             throw new CompileException('It is not possible to combine class with n:class.');
388:         }
389:         return $writer->write('if ($_tmp = array_filter(%node.array)) echo \' class="\', %escape(implode(" ", array_unique($_tmp))), \'"\'');
390:     }
391: 
392: 
393:     /**
394:      * n:attr="..."
395:      */
396:     public function macroAttr(MacroNode $node, PhpWriter $writer)
397:     {
398:         return $writer->write('$_tmp = %node.array; echo LR\Filters::htmlAttributes(isset($_tmp[0]) && is_array($_tmp[0]) ? $_tmp[0] : $_tmp);');
399:     }
400: 
401: 
402:     /**
403:      * {dump ...}
404:      */
405:     public function macroDump(MacroNode $node, PhpWriter $writer)
406:     {
407:         if ($node->modifiers) {
408:             throw new CompileException('Modifiers are not allowed in ' . $node->getNotation());
409:         }
410:         $args = $writer->formatArgs();
411:         return $writer->write(
412:             'Tracy\Debugger::barDump(' . ($args ? "($args)" : 'get_defined_vars()') . ', %var);',
413:             $args ?: 'variables'
414:         );
415:     }
416: 
417: 
418:     /**
419:      * {debugbreak ...}
420:      */
421:     public function macroDebugbreak(MacroNode $node, PhpWriter $writer)
422:     {
423:         if ($node->modifiers) {
424:             throw new CompileException('Modifiers are not allowed in ' . $node->getNotation());
425:         }
426:         if (function_exists($func = 'debugbreak') || function_exists($func = 'xdebug_break')) {
427:             return $writer->write($node->args == null ? "$func()" : "if (%node.args) $func();");
428:         }
429:     }
430: 
431: 
432:     /**
433:      * {var ...}
434:      * {default ...}
435:      */
436:     public function macroVar(MacroNode $node, PhpWriter $writer)
437:     {
438:         if ($node->modifiers) {
439:             throw new CompileException('Modifiers are not allowed in ' . $node->getNotation());
440:         }
441:         if ($node->args === '' && $node->parentNode && $node->parentNode->name === 'switch') {
442:             return '} else {';
443:         }
444: 
445:         $var = true;
446:         $tokens = $writer->preprocess();
447:         $res = new Latte\MacroTokens;
448:         while ($tokens->nextToken()) {
449:             if ($var && $tokens->isCurrent($tokens::T_SYMBOL, $tokens::T_VARIABLE)) {
450:                 if ($node->name === 'default') {
451:                     $res->append("'" . ltrim($tokens->currentValue(), '$') . "'");
452:                 } else {
453:                     $res->append('$' . ltrim($tokens->currentValue(), '$'));
454:                 }
455:                 $var = null;
456: 
457:             } elseif ($tokens->isCurrent('=', '=>') && $tokens->depth === 0) {
458:                 $res->append($node->name === 'default' ? '=>' : '=');
459:                 $var = false;
460: 
461:             } elseif ($tokens->isCurrent(',') && $tokens->depth === 0) {
462:                 if ($var === null) {
463:                     $res->append($node->name === 'default' ? '=>NULL' : '=NULL');
464:                 }
465:                 $res->append($node->name === 'default' ? ',' : ';');
466:                 $var = true;
467: 
468:             } elseif ($var === null && $node->name === 'default' && !$tokens->isCurrent($tokens::T_WHITESPACE)) {
469:                 throw new CompileException("Unexpected '{$tokens->currentValue()}' in {default $node->args}");
470: 
471:             } else {
472:                 $res->append($tokens->currentToken());
473:             }
474:         }
475:         if ($var === null) {
476:             $res->append($node->name === 'default' ? '=>NULL' : '=NULL');
477:         }
478:         $out = $writer->quotingPass($res)->joinAll();
479:         return $node->name === 'default' ? "extract([$out], EXTR_SKIP)" : "$out;";
480:     }
481: 
482: 
483:     /**
484:      * {= ...}
485:      * {php ...}
486:      */
487:     public function macroExpr(MacroNode $node, PhpWriter $writer)
488:     {
489:         if ($node->name === '?') {
490:             trigger_error('Macro {? ...} is deprecated, use {php ...}.', E_USER_DEPRECATED);
491:         }
492:         return $writer->write($node->name === '='
493:             ? "echo %modify(%node.args) /* line $node->startLine */"
494:             : '%modify(%node.args);'
495:         );
496:     }
497: 
498: 
499:     /**
500:      * {contentType ...}
501:      */
502:     public function macroContentType(MacroNode $node, PhpWriter $writer)
503:     {
504:         if (
505:             !$this->getCompiler()->isInHead()
506:             && !($node->htmlNode && strtolower($node->htmlNode->name) === 'script' && strpos($node->args, 'html') !== false)
507:         ) {
508:             throw new CompileException($node->getNotation() . ' is allowed only in template header.');
509:         }
510:         $compiler = $this->getCompiler();
511:         if ($node->modifiers) {
512:             throw new CompileException('Modifiers are not allowed in ' . $node->getNotation());
513:         } elseif (strpos($node->args, 'xhtml') !== false) {
514:             $type = $compiler::CONTENT_XHTML;
515:         } elseif (strpos($node->args, 'html') !== false) {
516:             $type = $compiler::CONTENT_HTML;
517:         } elseif (strpos($node->args, 'xml') !== false) {
518:             $type = $compiler::CONTENT_XML;
519:         } elseif (strpos($node->args, 'javascript') !== false) {
520:             $type = $compiler::CONTENT_JS;
521:         } elseif (strpos($node->args, 'css') !== false) {
522:             $type = $compiler::CONTENT_CSS;
523:         } elseif (strpos($node->args, 'calendar') !== false) {
524:             $type = $compiler::CONTENT_ICAL;
525:         } else {
526:             $type = $compiler::CONTENT_TEXT;
527:         }
528:         $compiler->setContentType($type);
529: 
530:         if (strpos($node->args, '/') && !$node->htmlNode) {
531:             return $writer->write('if (empty($this->global->coreCaptured) && in_array($this->getReferenceType(), ["extends", NULL], TRUE)) header(%var);', "Content-Type: $node->args");
532:         }
533:     }
534: 
535: 
536:     /**
537:      * {status ...}
538:      */
539:     public function macroStatus(MacroNode $node, PhpWriter $writer)
540:     {
541:         trigger_error('Macro {status} is deprecated.', E_USER_DEPRECATED);
542:         if ($node->modifiers) {
543:             throw new CompileException('Modifiers are not allowed in ' . $node->getNotation());
544:         }
545:         return $writer->write((substr($node->args, -1) === '?' ? 'if (!headers_sent()) ' : '') .
546:             'http_response_code(%0.var);', (int) $node->args
547:         );
548:     }
549: }
550: 
Nette 2.4-20180918 API API documentation generated by ApiGen 2.8.0