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

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: 
212:         $node->data->name = $name = ltrim($name, '#');
213:         if ($name == NULL) {
214:             if ($node->name === 'define') {
215:                 throw new CompileException('Missing block name.');
216:             }
217: 
218:         } elseif (strpos($name, '$') !== FALSE) { // dynamic block/snippet
219:             if ($node->name === 'snippet') {
220:                 for ($parent = $node->parentNode; $parent && !($parent->name === 'snippet' || $parent->name === 'snippetArea'); $parent = $parent->parentNode);
221:                 if (!$parent) {
222:                     throw new CompileException('Dynamic snippets are allowed only inside static snippet/snippetArea.');
223:                 }
224:                 $parent->data->dynamic = TRUE;
225:                 $node->data->leave = TRUE;
226:                 $node->closingCode = "<?php \$this->global->snippetDriver->leave(); ?>";
227:                 $enterCode = '$this->global->snippetDriver->enter(' . $writer->formatWord($name) . ', "' . SnippetDriver::TYPE_DYNAMIC . '");';
228: 
229:                 if ($node->prefix) {
230:                     $node->attrCode = $writer->write("<?php echo ' id=\"' . htmlSpecialChars(\$this->global->snippetDriver->getHtmlId({$writer->formatWord($name)})) . '\"' ?>");
231:                     return $writer->write($enterCode);
232:                 }
233:                 $tag = trim($node->tokenizer->fetchWord(), '<>');
234:                 if ($tag) {
235:                     trigger_error('HTML tag specified in {snippet} is deprecated, use n:snippet.', E_USER_DEPRECATED);
236:                 }
237:                 $tag = $tag ? $tag : 'div';
238:                 $node->closingCode .= "\n</$tag>";
239:                 $this->checkExtraArgs($node);
240:                 return $writer->write("?>\n<$tag id=\"<?php echo htmlSpecialChars(\$this->global->snippetDriver->getHtmlId({$writer->formatWord($name)})) ?>\"><?php " . $enterCode);
241: 
242:             } else {
243:                 $node->data->leave = TRUE;
244:                 $node->data->func = $this->generateMethodName($name);
245:                 $fname = $writer->formatWord($name);
246:                 $node->closingCode = '<?php ' . ($node->name === 'define' ? '' : "\$this->renderBlock($fname, get_defined_vars());") . ' ?>';
247:                 $blockType = var_export(implode($node->context), TRUE);
248:                 $this->checkExtraArgs($node);
249:                 return "\$this->checkBlockContentType($blockType, $fname);"
250:                     . "\$this->blockQueue[$fname][] = [\$this, '{$node->data->func}'];";
251:             }
252:         }
253: 
254:         // static snippet/snippetArea
255:         if ($node->name === 'snippet' || $node->name === 'snippetArea') {
256:             if ($node->prefix && isset($node->htmlNode->attrs['id'])) {
257:                 throw new CompileException('Cannot combine HTML attribute id with n:snippet.');
258:             }
259:             $node->data->name = $name = '_' . $name;
260:         }
261: 
262:         if (isset($this->namedBlocks[$name])) {
263:             throw new CompileException("Cannot redeclare static {$node->name} '$name'");
264:         }
265:         $extendsCheck = $this->namedBlocks ? '' : 'if ($this->getParentName()) return get_defined_vars();';
266:         $this->namedBlocks[$name] = TRUE;
267: 
268:         if (Helpers::removeFilter($node->modifiers, 'escape')) {
269:             trigger_error('Macro ' . $node->getNotation() . ' provides auto-escaping, remove |escape.');
270:         }
271:         if (Helpers::startsWith($node->context[1], Latte\Compiler::CONTEXT_HTML_ATTRIBUTE)) {
272:             $node->context[1] = '';
273:             $node->modifiers .= '|escape';
274:         } elseif ($node->modifiers) {
275:             $node->modifiers .= '|escape';
276:         }
277:         $this->blockTypes[$name] = implode($node->context);
278: 
279:         $include = '$this->renderBlock(%var, ' . (($node->name === 'snippet' || $node->name === 'snippetArea') ? '$this->params' : 'get_defined_vars()')
280:             . ($node->modifiers ? ', function ($s, $type) { $_fi = new LR\FilterInfo($type); return %modifyContent($s); }' : '') . ')';
281: 
282:         if ($node->name === 'snippet') {
283:             if ($node->prefix) {
284:                 if (isset($node->htmlNode->macroAttrs['foreach'])) {
285:                     trigger_error('Combination of n:snippet with n:foreach is invalid, use n:inner-foreach.', E_USER_WARNING);
286:                 }
287:                 $node->attrCode = $writer->write('<?php echo \' id="\' . htmlSpecialChars($this->global->snippetDriver->getHtmlId(%var)) . \'"\' ?>', (string) substr($name, 1));
288:                 return $writer->write($include, $name);
289:             }
290:             $tag = trim($node->tokenizer->fetchWord(), '<>');
291:             if ($tag) {
292:                 trigger_error('HTML tag specified in {snippet} is deprecated, use n:snippet.', E_USER_DEPRECATED);
293:             }
294:             $tag = $tag ? $tag : 'div';
295:             $this->checkExtraArgs($node);
296:             return $writer->write("?>\n<$tag id=\"<?php echo htmlSpecialChars(\$this->global->snippetDriver->getHtmlId(%var)) ?>\"><?php $include ?>\n</$tag><?php ",
297:                 (string) substr($name, 1), $name
298:             );
299: 
300:         } elseif ($node->name === 'define') {
301:             $tokens = $node->tokenizer;
302:             $args = [];
303:             while ($tokens->isNext()) {
304:                 $args[] = $tokens->expectNextValue($tokens::T_VARIABLE);
305:                 if ($tokens->isNext()) {
306:                     $tokens->expectNextValue(',');
307:                 }
308:             }
309:             if ($args) {
310:                 $node->data->args = 'list(' . implode(', ', $args) . ') = $_args + [' . str_repeat('NULL, ', count($args)) . '];';
311:             }
312:             return $extendsCheck;
313: 
314:         } else { // block, snippetArea
315:             $this->checkExtraArgs($node);
316:             return $writer->write($extendsCheck . $include, $name);
317:         }
318:     }
319: 
320: 
321:     /**
322:      * {/block}
323:      * {/snippet}
324:      * {/snippetArea}
325:      * {/define}
326:      */
327:     public function macroBlockEnd(MacroNode $node, PhpWriter $writer)
328:     {
329:         if (isset($node->data->name)) { // block, snippet, define
330:             if ($asInner = $node->name === 'snippet' && $node->prefix === MacroNode::PREFIX_NONE) {
331:                 $node->content = $node->innerContent;
332:             }
333: 
334:             if (($node->name === 'snippet' || $node->name === 'snippetArea') && strpos($node->data->name, '$') === FALSE) {
335:                 $type = $node->name === 'snippet' ? SnippetDriver::TYPE_STATIC : SnippetDriver::TYPE_AREA;
336:                 $node->content = '<?php $this->global->snippetDriver->enter('
337:                     . $writer->formatWord(substr($node->data->name, 1))
338:                     . ', "' . $type . '"); ?>'
339:                     . preg_replace('#(?<=\n)[ \t]+\z#', '', $node->content) . '<?php $this->global->snippetDriver->leave(); ?>';
340:             }
341:             if (empty($node->data->leave)) {
342:                 if (preg_match('#\$|n:#', $node->content)) {
343:                     $node->content = '<?php ' . (isset($node->data->args) ? 'extract($this->params); ' . $node->data->args : 'extract($_args);') . ' ?>'
344:                         . $node->content;
345:                 }
346:                 $this->namedBlocks[$node->data->name] = $tmp = preg_replace('#^\n+|(?<=\n)[ \t]+\z#', '', $node->content);
347:                 $node->content = substr_replace($node->content, $node->openingCode . "\n", strspn($node->content, "\n"), strlen($tmp));
348:                 $node->openingCode = '<?php ?>';
349: 
350:             } elseif (isset($node->data->func)) {
351:                 $node->content = rtrim($node->content, " \t");
352:                 $this->getCompiler()->addMethod(
353:                     $node->data->func,
354:                     $this->getCompiler()->expandTokens("extract(\$_args);\n?>$node->content<?php"),
355:                     '$_args'
356:                 );
357:                 $node->content = '';
358:             }
359: 
360:             if ($asInner) { // n:snippet -> n:inner-snippet
361:                 $node->innerContent = $node->openingCode . $node->content . $node->closingCode;
362:                 $node->closingCode = $node->openingCode = '<?php ?>';
363:             }
364:             return ' '; // consume next new line
365: 
366:         } elseif ($node->modifiers) { // anonymous block with modifier
367:             $node->modifiers .= '|escape';
368:             return $writer->write('$_fi = new LR\FilterInfo(%var); echo %modifyContent(ob_get_clean());', $node->context[0]);
369:         }
370:     }
371: 
372: 
373:     /**
374:      * {ifset block}
375:      * {elseifset block}
376:      */
377:     public function macroIfset(MacroNode $node, PhpWriter $writer)
378:     {
379:         if ($node->modifiers) {
380:             throw new CompileException('Modifiers are not allowed in ' . $node->getNotation());
381:         }
382:         if (!preg_match('~#|[\w-]+\z~A', $node->args)) {
383:             return FALSE;
384:         }
385:         $list = [];
386:         while (($name = $node->tokenizer->fetchWord()) !== FALSE) {
387:             $list[] = preg_match('~#|[\w-]+\z~A', $name)
388:                 ? '$this->blockQueue["' . ltrim($name, '#') . '"]'
389:                 : $writer->formatArgs(new Latte\MacroTokens($name));
390:         }
391:         return ($node->name === 'elseifset' ? '} else' : '')
392:             . 'if (isset(' . implode(', ', $list) . ')) {';
393:     }
394: 
395: 
396:     private function generateMethodName($blockName)
397:     {
398:         $clean = trim(preg_replace('#\W+#', '_', $blockName), '_');
399:         $name = 'block' . ucfirst($clean);
400:         $methods = array_keys($this->getCompiler()->getMethods());
401:         if (!$clean || in_array(strtolower($name), array_map('strtolower', $methods))) {
402:             $name .=  '_' . substr(md5($blockName), 0, 5);
403:         }
404:         return $name;
405:     }
406: 
407: }
408: 
Nette 2.4-20160930 API API documentation generated by ApiGen 2.8.0