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
    • Reflection
    • Security
    • Tokenizer
    • Utils
  • Tracy
    • Bridges
      • Nette
  • none

Classes

  • BlockMacros
  • CoreMacros
  • MacroSet
  • Overview
  • Namespace
  • Class
  • Tree
  • Deprecated
  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\Helpers;
 13: use Latte\MacroNode;
 14: use Latte\PhpWriter;
 15: use Latte\Runtime\SnippetDriver;
 16: 
 17: 
 18: /**
 19:  * Block macros.
 20:  */
 21: class BlockMacros extends MacroSet
 22: {
 23:     /** @var array */
 24:     private $namedBlocks = [];
 25: 
 26:     /** @var array */
 27:     private $blockTypes = [];
 28: 
 29:     /** @var bool */
 30:     private $extends;
 31: 
 32:     /** @var string[] */
 33:     private $imports;
 34: 
 35: 
 36:     public static function install(Latte\Compiler $compiler)
 37:     {
 38:         $me = new static($compiler);
 39:         $me->addMacro('include', [$me, 'macroInclude']);
 40:         $me->addMacro('includeblock', [$me, 'macroIncludeBlock']); // deprecated
 41:         $me->addMacro('import', [$me, 'macroImport'], null, null, self::ALLOWED_IN_HEAD);
 42:         $me->addMacro('extends', [$me, 'macroExtends'], null, null, self::ALLOWED_IN_HEAD);
 43:         $me->addMacro('layout', [$me, 'macroExtends'], null, null, self::ALLOWED_IN_HEAD);
 44:         $me->addMacro('snippet', [$me, 'macroBlock'], [$me, 'macroBlockEnd']);
 45:         $me->addMacro('block', [$me, 'macroBlock'], [$me, 'macroBlockEnd'], null, self::AUTO_CLOSE);
 46:         $me->addMacro('define', [$me, 'macroBlock'], [$me, 'macroBlockEnd']);
 47:         $me->addMacro('snippetArea', [$me, 'macroBlock'], [$me, 'macroBlockEnd']);
 48:         $me->addMacro('ifset', [$me, 'macroIfset'], '}');
 49:         $me->addMacro('elseifset', [$me, 'macroIfset']);
 50:     }
 51: 
 52: 
 53:     /**
 54:      * Initializes before template parsing.
 55:      * @return void
 56:      */
 57:     public function initialize()
 58:     {
 59:         $this->namedBlocks = [];
 60:         $this->blockTypes = [];
 61:         $this->extends = null;
 62:         $this->imports = [];
 63:     }
 64: 
 65: 
 66:     /**
 67:      * Finishes template parsing.
 68:      */
 69:     public function finalize()
 70:     {
 71:         $compiler = $this->getCompiler();
 72:         $functions = [];
 73:         foreach ($this->namedBlocks as $name => $code) {
 74:             $compiler->addMethod(
 75:                 $functions[$name] = $this->generateMethodName($name),
 76:                 '?>' . $compiler->expandTokens($code) . '<?php',
 77:                 '$_args'
 78:             );
 79:         }
 80: 
 81:         if ($this->namedBlocks) {
 82:             $compiler->addProperty('blocks', $functions);
 83:             $compiler->addProperty('blockTypes', $this->blockTypes);
 84:         }
 85: 
 86:         return [
 87:             ($this->extends === null ? '' : '$this->parentName = ' . $this->extends . ';') . implode($this->imports),
 88:         ];
 89:     }
 90: 
 91: 
 92:     /********************* macros ****************d*g**/
 93: 
 94: 
 95:     /**
 96:      * {include block}
 97:      */
 98:     public function macroInclude(MacroNode $node, PhpWriter $writer)
 99:     {
100:         $node->replaced = false;
101:         $destination = $node->tokenizer->fetchWord(); // destination [,] [params]
102:         if (!preg_match('~#|[\w-]+\z~A', $destination)) {
103:             return false;
104:         }
105: 
106:         $destination = ltrim($destination, '#');
107:         $parent = $destination === 'parent';
108:         if ($destination === 'parent' || $destination === 'this') {
109:             for ($item = $node->parentNode; $item && $item->name !== 'block' && !isset($item->data->name); $item = $item->parentNode);
110:             if (!$item) {
111:                 throw new CompileException("Cannot include $destination block outside of any block.");
112:             }
113:             $destination = $item->data->name;
114:         }
115: 
116:         $noEscape = Helpers::removeFilter($node->modifiers, 'noescape');
117:         if (!$noEscape && Helpers::removeFilter($node->modifiers, 'escape')) {
118:             trigger_error('Macro ' . $node->getNotation() . ' provides auto-escaping, remove |escape.');
119:         }
120:         if ($node->modifiers && !$noEscape) {
121:             $node->modifiers .= '|escape';
122:         }
123:         return $writer->write(
124:             '$this->renderBlock' . ($parent ? 'Parent' : '') . '('
125:             . (strpos($destination, '$') === false ? var_export($destination, true) : $destination)
126:             . ', %node.array? + '
127:             . (isset($this->namedBlocks[$destination]) || $parent ? 'get_defined_vars()' : '$this->params')
128:             . ($node->modifiers
129:                 ? ', function ($s, $type) { $_fi = new LR\FilterInfo($type); return %modifyContent($s); }'
130:                 : ($noEscape || $parent ? '' : ', ' . var_export(implode($node->context), true)))
131:             . ');'
132:         );
133:     }
134: 
135: 
136:     /**
137:      * {includeblock "file"}
138:      * @deprecated
139:      */
140:     public function macroIncludeBlock(MacroNode $node, PhpWriter $writer)
141:     {
142:         //trigger_error('Macro {includeblock} is deprecated, use similar macro {import}.', E_USER_DEPRECATED);
143:         $node->replaced = false;
144:         if ($node->modifiers) {
145:             throw new CompileException('Modifiers are not allowed in ' . $node->getNotation());
146:         }
147:         return $writer->write(
148:             'ob_start(function () {}); $this->createTemplate(%node.word, %node.array? + get_defined_vars(), "includeblock")->renderToContentType(%var); echo rtrim(ob_get_clean());',
149:             implode($node->context)
150:         );
151:     }
152: 
153: 
154:     /**
155:      * {import "file"}
156:      */
157:     public function macroImport(MacroNode $node, PhpWriter $writer)
158:     {
159:         if ($node->modifiers) {
160:             throw new CompileException('Modifiers are not allowed in ' . $node->getNotation());
161:         }
162:         $destination = $node->tokenizer->fetchWord();
163:         $this->checkExtraArgs($node);
164:         $code = $writer->write('$this->createTemplate(%word, $this->params, "import")->render();', $destination);
165:         if ($this->getCompiler()->isInHead()) {
166:             $this->imports[] = $code;
167:         } else {
168:             return $code;
169:         }
170:     }
171: 
172: 
173:     /**
174:      * {extends none | $var | "file"}
175:      */
176:     public function macroExtends(MacroNode $node, PhpWriter $writer)
177:     {
178:         $notation = $node->getNotation();
179:         if ($node->modifiers) {
180:             throw new CompileException("Modifiers are not allowed in $notation");
181:         } elseif (!$node->args) {
182:             throw new CompileException("Missing destination in $notation");
183:         } elseif ($node->parentNode) {
184:             throw new CompileException("$notation must be placed outside any macro.");
185:         } elseif ($this->extends !== null) {
186:             throw new CompileException("Multiple $notation declarations are not allowed.");
187:         } elseif ($node->args === 'none') {
188:             $this->extends = 'FALSE';
189:         } else {
190:             $this->extends = $writer->write('%node.word%node.args');
191:         }
192:         if (!$this->getCompiler()->isInHead()) {
193:             trigger_error("$notation must be placed in template head.", E_USER_WARNING);
194:         }
195:     }
196: 
197: 
198:     /**
199:      * {block [name]}
200:      * {snippet [name [,]] [tag]}
201:      * {snippetArea [name]}
202:      * {define name}
203:      */
204:     public function macroBlock(MacroNode $node, PhpWriter $writer)
205:     {
206:         $name = $node->tokenizer->fetchWord();
207: 
208:         if ($node->name === 'block' && $name === false) { // anonymous block
209:             return $node->modifiers === '' ? '' : 'ob_start(function () {})';
210: 
211:         } elseif ($node->name === 'define' && $node->modifiers) {
212:             throw new CompileException('Modifiers are not allowed in ' . $node->getNotation());
213:         }
214: 
215:         $node->data->name = $name = ltrim((string) $name, '#');
216:         if ($name == null) {
217:             if ($node->name === 'define') {
218:                 throw new CompileException('Missing block name.');
219:             }
220: 
221:         } elseif (strpos($name, '$') !== false) { // dynamic block/snippet
222:             if ($node->name === 'snippet') {
223:                 for ($parent = $node->parentNode; $parent && !($parent->name === 'snippet' || $parent->name === 'snippetArea'); $parent = $parent->parentNode);
224:                 if (!$parent) {
225:                     throw new CompileException('Dynamic snippets are allowed only inside static snippet/snippetArea.');
226:                 }
227:                 $parent->data->dynamic = true;
228:                 $node->data->leave = true;
229:                 $node->closingCode = '<?php $this->global->snippetDriver->leave(); ?>';
230:                 $enterCode = '$this->global->snippetDriver->enter(' . $writer->formatWord($name) . ', "' . SnippetDriver::TYPE_DYNAMIC . '");';
231: 
232:                 if ($node->prefix) {
233:                     $node->attrCode = $writer->write("<?php echo ' id=\"' . htmlSpecialChars(\$this->global->snippetDriver->getHtmlId({$writer->formatWord($name)})) . '\"' ?>");
234:                     return $writer->write($enterCode);
235:                 }
236:                 $tag = trim((string) $node->tokenizer->fetchWord(), '<>');
237:                 if ($tag) {
238:                     trigger_error('HTML tag specified in {snippet} is deprecated, use n:snippet.', E_USER_DEPRECATED);
239:                 }
240:                 $tag = $tag ? $tag : 'div';
241:                 $node->closingCode .= "\n</$tag>";
242:                 $this->checkExtraArgs($node);
243:                 return $writer->write("?>\n<$tag id=\"<?php echo htmlSpecialChars(\$this->global->snippetDriver->getHtmlId({$writer->formatWord($name)})) ?>\"><?php " . $enterCode);
244: 
245:             } else {
246:                 $node->data->leave = true;
247:                 $node->data->func = $this->generateMethodName($name);
248:                 $fname = $writer->formatWord($name);
249:                 if ($node->name === 'define') {
250:                     $node->closingCode = '<?php ?>';
251:                 } else {
252:                     if (Helpers::startsWith((string) $node->context[1], Latte\Compiler::CONTEXT_HTML_ATTRIBUTE)) {
253:                         $node->context[1] = '';
254:                         $node->modifiers .= '|escape';
255:                     } elseif ($node->modifiers) {
256:                         $node->modifiers .= '|escape';
257:                     }
258:                     $node->closingCode = $writer->write('<?php $this->renderBlock(%raw, get_defined_vars()'
259:                         . ($node->modifiers ? ', function ($s, $type) { $_fi = new LR\FilterInfo($type); return %modifyContent($s); }' : '') . '); ?>', $fname);
260:                 }
261:                 $blockType = var_export(implode($node->context), true);
262:                 $this->checkExtraArgs($node);
263:                 return "\$this->checkBlockContentType($blockType, $fname);"
264:                     . "\$this->blockQueue[$fname][] = [\$this, '{$node->data->func}'];";
265:             }
266:         }
267: 
268:         // static snippet/snippetArea
269:         if ($node->name === 'snippet' || $node->name === 'snippetArea') {
270:             if ($node->prefix && isset($node->htmlNode->attrs['id'])) {
271:                 throw new CompileException('Cannot combine HTML attribute id with n:snippet.');
272:             }
273:             $node->data->name = $name = '_' . $name;
274:         }
275: 
276:         if (isset($this->namedBlocks[$name])) {
277:             throw new CompileException("Cannot redeclare static {$node->name} '$name'");
278:         }
279:         $extendsCheck = $this->namedBlocks ? '' : 'if ($this->getParentName()) return get_defined_vars();';
280:         $this->namedBlocks[$name] = true;
281: 
282:         if (Helpers::removeFilter($node->modifiers, 'escape')) {
283:             trigger_error('Macro ' . $node->getNotation() . ' provides auto-escaping, remove |escape.');
284:         }
285:         if (Helpers::startsWith((string) $node->context[1], Latte\Compiler::CONTEXT_HTML_ATTRIBUTE)) {
286:             $node->context[1] = '';
287:             $node->modifiers .= '|escape';
288:         } elseif ($node->modifiers) {
289:             $node->modifiers .= '|escape';
290:         }
291:         $this->blockTypes[$name] = implode($node->context);
292: 
293:         $include = '$this->renderBlock(%var, ' . (($node->name === 'snippet' || $node->name === 'snippetArea') ? '$this->params' : 'get_defined_vars()')
294:             . ($node->modifiers ? ', function ($s, $type) { $_fi = new LR\FilterInfo($type); return %modifyContent($s); }' : '') . ')';
295: 
296:         if ($node->name === 'snippet') {
297:             if ($node->prefix) {
298:                 if (isset($node->htmlNode->macroAttrs['foreach'])) {
299:                     trigger_error('Combination of n:snippet with n:foreach is invalid, use n:inner-foreach.', E_USER_WARNING);
300:                 }
301:                 $node->attrCode = $writer->write('<?php echo \' id="\' . htmlSpecialChars($this->global->snippetDriver->getHtmlId(%var)) . \'"\' ?>', (string) substr($name, 1));
302:                 return $writer->write($include, $name);
303:             }
304:             $tag = trim((string) $node->tokenizer->fetchWord(), '<>');
305:             if ($tag) {
306:                 trigger_error('HTML tag specified in {snippet} is deprecated, use n:snippet.', E_USER_DEPRECATED);
307:             }
308:             $tag = $tag ? $tag : 'div';
309:             $this->checkExtraArgs($node);
310:             return $writer->write("?>\n<$tag id=\"<?php echo htmlSpecialChars(\$this->global->snippetDriver->getHtmlId(%var)) ?>\"><?php $include ?>\n</$tag><?php ",
311:                 (string) substr($name, 1), $name
312:             );
313: 
314:         } elseif ($node->name === 'define') {
315:             $tokens = $node->tokenizer;
316:             $args = [];
317:             while ($tokens->isNext()) {
318:                 $args[] = $tokens->expectNextValue($tokens::T_VARIABLE);
319:                 if ($tokens->isNext()) {
320:                     $tokens->expectNextValue(',');
321:                 }
322:             }
323:             if ($args) {
324:                 $node->data->args = 'list(' . implode(', ', $args) . ') = $_args + [' . str_repeat('NULL, ', count($args)) . '];';
325:             }
326:             return $extendsCheck;
327: 
328:         } else { // block, snippetArea
329:             $this->checkExtraArgs($node);
330:             return $writer->write($extendsCheck . $include, $name);
331:         }
332:     }
333: 
334: 
335:     /**
336:      * {/block}
337:      * {/snippet}
338:      * {/snippetArea}
339:      * {/define}
340:      */
341:     public function macroBlockEnd(MacroNode $node, PhpWriter $writer)
342:     {
343:         if (isset($node->data->name)) { // block, snippet, define
344:             if ($asInner = $node->name === 'snippet' && $node->prefix === MacroNode::PREFIX_NONE) {
345:                 $node->content = $node->innerContent;
346:             }
347: 
348:             if (($node->name === 'snippet' || $node->name === 'snippetArea') && strpos($node->data->name, '$') === false) {
349:                 $type = $node->name === 'snippet' ? SnippetDriver::TYPE_STATIC : SnippetDriver::TYPE_AREA;
350:                 $node->content = '<?php $this->global->snippetDriver->enter('
351:                     . $writer->formatWord(substr($node->data->name, 1))
352:                     . ', "' . $type . '"); ?>'
353:                     . preg_replace('#(?<=\n)[ \t]+\z#', '', $node->content) . '<?php $this->global->snippetDriver->leave(); ?>';
354:             }
355:             if (empty($node->data->leave)) {
356:                 if (preg_match('#\$|n:#', $node->content)) {
357:                     $node->content = '<?php ' . (isset($node->data->args) ? 'extract($this->params); ' . $node->data->args : 'extract($_args);') . ' ?>'
358:                         . $node->content;
359:                 }
360:                 $this->namedBlocks[$node->data->name] = $tmp = preg_replace('#^\n+|(?<=\n)[ \t]+\z#', '', $node->content);
361:                 $node->content = substr_replace($node->content, $node->openingCode . "\n", strspn($node->content, "\n"), strlen($tmp));
362:                 $node->openingCode = '<?php ?>';
363: 
364:             } elseif (isset($node->data->func)) {
365:                 $node->content = rtrim($node->content, " \t");
366:                 $this->getCompiler()->addMethod(
367:                     $node->data->func,
368:                     $this->getCompiler()->expandTokens("extract(\$_args);\n?>$node->content<?php"),
369:                     '$_args'
370:                 );
371:                 $node->content = '';
372:             }
373: 
374:             if ($asInner) { // n:snippet -> n:inner-snippet
375:                 $node->innerContent = $node->openingCode . $node->content . $node->closingCode;
376:                 $node->closingCode = $node->openingCode = '<?php ?>';
377:             }
378:             return ' '; // consume next new line
379: 
380:         } elseif ($node->modifiers) { // anonymous block with modifier
381:             $node->modifiers .= '|escape';
382:             return $writer->write('$_fi = new LR\FilterInfo(%var); echo %modifyContent(ob_get_clean());', $node->context[0]);
383:         }
384:     }
385: 
386: 
387:     /**
388:      * {ifset block}
389:      * {elseifset block}
390:      */
391:     public function macroIfset(MacroNode $node, PhpWriter $writer)
392:     {
393:         if ($node->modifiers) {
394:             throw new CompileException('Modifiers are not allowed in ' . $node->getNotation());
395:         }
396:         if (!preg_match('~#|[\w-]+\z~A', $node->args)) {
397:             return false;
398:         }
399:         $list = [];
400:         while (($name = $node->tokenizer->fetchWord()) !== false) {
401:             $list[] = preg_match('~#|[\w-]+\z~A', $name)
402:                 ? '$this->blockQueue["' . ltrim($name, '#') . '"]'
403:                 : $writer->formatArgs(new Latte\MacroTokens($name));
404:         }
405:         return ($node->name === 'elseifset' ? '} else' : '')
406:             . 'if (isset(' . implode(', ', $list) . ')) {';
407:     }
408: 
409: 
410:     private function generateMethodName($blockName)
411:     {
412:         $clean = trim(preg_replace('#\W+#', '_', $blockName), '_');
413:         $name = 'block' . ucfirst($clean);
414:         $methods = array_keys($this->getCompiler()->getMethods());
415:         if (!$clean || in_array(strtolower($name), array_map('strtolower', $methods), true)) {
416:             $name .= '_' . substr(md5($blockName), 0, 5);
417:         }
418:         return $name;
419:     }
420: }
421: 
Nette 2.4-20170829 API API documentation generated by ApiGen 2.8.0