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