Namespaces

  • Nette
    • Application
      • Diagnostics
      • Responses
      • Routers
      • UI
    • Caching
      • Storages
    • ComponentModel
    • Database
      • Diagnostics
      • Drivers
      • Reflection
      • Table
    • DI
      • Config
        • Adapters
      • Diagnostics
      • Extensions
    • Diagnostics
    • Forms
      • Controls
      • Rendering
    • Http
      • Diagnostics
    • Iterators
    • Latte
      • Macros
    • Loaders
    • Localization
    • Mail
    • PhpGenerator
    • Reflection
    • Security
      • Diagnostics
    • Templating
    • Utils
  • NetteModule
  • none

Classes

  • CacheMacro
  • CoreMacros
  • FormMacros
  • MacroSet
  • UIMacros
  • Overview
  • Namespace
  • Class
  • Tree
  • Deprecated
  • Other releases
  • Nette homepage
  1: <?php
  2: 
  3: /**
  4:  * This file is part of the Nette Framework (https://nette.org)
  5:  * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
  6:  */
  7: 
  8: namespace Nette\Latte\Macros;
  9: 
 10: use Nette;
 11: use Nette\Latte;
 12: use Nette\Latte\MacroNode;
 13: use Nette\Latte\PhpWriter;
 14: use Nette\Latte\CompileException;
 15: use Nette\Utils\Strings;
 16: 
 17: 
 18: /**
 19:  * Macros for Nette\Application\UI.
 20:  *
 21:  * - {link destination ...} control link
 22:  * - {plink destination ...} presenter link
 23:  * - {snippet ?} ... {/snippet ?} control snippet
 24:  * - {contentType ...} HTTP Content-Type header
 25:  * - {status ...} HTTP status
 26:  *
 27:  * @author     David Grudl
 28:  */
 29: class UIMacros extends MacroSet
 30: {
 31:     /** @var array */
 32:     private $namedBlocks = array();
 33: 
 34:     /** @var bool */
 35:     private $extends;
 36: 
 37: 
 38:     public static function install(Latte\Compiler $compiler)
 39:     {
 40:         $me = new static($compiler);
 41:         $me->addMacro('include', array($me, 'macroInclude'));
 42:         $me->addMacro('includeblock', array($me, 'macroIncludeBlock'));
 43:         $me->addMacro('extends', array($me, 'macroExtends'));
 44:         $me->addMacro('layout', array($me, 'macroExtends'));
 45:         $me->addMacro('block', array($me, 'macroBlock'), array($me, 'macroBlockEnd'));
 46:         $me->addMacro('define', array($me, 'macroBlock'), array($me, 'macroBlockEnd'));
 47:         $me->addMacro('snippet', array($me, 'macroBlock'), array($me, 'macroBlockEnd'));
 48:         $me->addMacro('snippetArea', array($me, 'macroBlock'), array($me, 'macroBlockEnd'));
 49:         $me->addMacro('ifset', array($me, 'macroIfset'), '}');
 50: 
 51:         $me->addMacro('widget', array($me, 'macroControl')); // deprecated - use control
 52:         $me->addMacro('control', array($me, 'macroControl'));
 53: 
 54:         $me->addMacro('href', NULL, NULL, function (MacroNode $node, PhpWriter $writer) use ($me) {
 55:             return ' ?> href="<?php ' . $me->macroLink($node, $writer) . ' ?>"<?php ';
 56:         });
 57:         $me->addMacro('plink', array($me, 'macroLink'));
 58:         $me->addMacro('link', array($me, 'macroLink'));
 59:         $me->addMacro('ifCurrent', array($me, 'macroIfCurrent'), '}'); // deprecated; use n:class="$presenter->linkCurrent ? ..."
 60: 
 61:         $me->addMacro('contentType', array($me, 'macroContentType'));
 62:         $me->addMacro('status', array($me, 'macroStatus'));
 63:     }
 64: 
 65: 
 66:     /**
 67:      * Initializes before template parsing.
 68:      * @return void
 69:      */
 70:     public function initialize()
 71:     {
 72:         $this->namedBlocks = array();
 73:         $this->extends = NULL;
 74:     }
 75: 
 76: 
 77:     /**
 78:      * Finishes template parsing.
 79:      * @return array(prolog, epilog)
 80:      */
 81:     public function finalize()
 82:     {
 83:         // try close last block
 84:         $last = $this->getCompiler()->getMacroNode();
 85:         if ($last && $last->name === 'block') {
 86:             $this->getCompiler()->closeMacro($last->name);
 87:         }
 88: 
 89:         $epilog = $prolog = array();
 90: 
 91:         if ($this->namedBlocks) {
 92:             foreach ($this->namedBlocks as $name => $code) {
 93:                 $func = '_lb' . substr(md5($this->getCompiler()->getTemplateId() . $name), 0, 10) . '_' . preg_replace('#[^a-z0-9_]#i', '_', $name);
 94:                 $snippet = $name[0] === '_';
 95:                 $prolog[] = "//\n// block $name\n//\n"
 96:                     . "if (!function_exists(\$_l->blocks[" . var_export($name, TRUE) . "][] = '$func')) { "
 97:                     . "function $func(\$_l, \$_args) { foreach (\$_args as \$__k => \$__v) \$\$__k = \$__v"
 98:                     . ($snippet ? '; $_control->redrawControl(' . var_export((string) substr($name, 1), TRUE) . ', FALSE)' : '')
 99:                     . "\n?>$code<?php\n}}";
100:             }
101:             $prolog[] = "//\n// end of blocks\n//";
102:         }
103: 
104:         if ($this->namedBlocks || $this->extends) {
105:             $prolog[] = "// template extending and snippets support";
106: 
107:             $prolog[] = '$_l->extends = '
108:                 . ($this->extends ? $this->extends : 'empty($template->_extended) && isset($_control) && $_control instanceof Nette\Application\UI\Presenter ? $_control->findLayoutTemplateFile() : NULL')
109:                 . '; $template->_extended = $_extended = TRUE;';
110: 
111:             $prolog[] = '
112: if ($_l->extends) {
113:     ' . ($this->namedBlocks ? 'ob_start();' : 'return Nette\Latte\Macros\CoreMacros::includeTemplate($_l->extends, get_defined_vars(), $template)->render();') . '
114: 
115: } elseif (!empty($_control->snippetMode)) {
116:     return Nette\Latte\Macros\UIMacros::renderSnippets($_control, $_l, get_defined_vars());
117: }';
118:         } else {
119:             $prolog[] = '
120: // snippets support
121: if (!empty($_control->snippetMode)) {
122:     return Nette\Latte\Macros\UIMacros::renderSnippets($_control, $_l, get_defined_vars());
123: }';
124:         }
125: 
126:         return array(implode("\n\n", $prolog), implode("\n", $epilog));
127:     }
128: 
129: 
130:     /********************* macros ****************d*g**/
131: 
132: 
133:     /**
134:      * {include #block}
135:      */
136:     public function macroInclude(MacroNode $node, PhpWriter $writer)
137:     {
138:         $destination = $node->tokenizer->fetchWord(); // destination [,] [params]
139:         if (!preg_match('~#|[\w-]+\z~A', $destination)) {
140:             return FALSE;
141:         }
142: 
143:         $destination = ltrim($destination, '#');
144:         $parent = $destination === 'parent';
145:         if ($destination === 'parent' || $destination === 'this') {
146:             for ($item = $node->parentNode; $item && $item->name !== 'block' && !isset($item->data->name); $item = $item->parentNode);
147:             if (!$item) {
148:                 throw new CompileException("Cannot include $destination block outside of any block.");
149:             }
150:             $destination = $item->data->name;
151:         }
152: 
153:         $name = Strings::contains($destination, '$') ? $destination : var_export($destination, TRUE);
154:         if (isset($this->namedBlocks[$destination]) && !$parent) {
155:             $cmd = "call_user_func(reset(\$_l->blocks[$name]), \$_l, %node.array? + get_defined_vars())";
156:         } else {
157:             $cmd = 'Nette\Latte\Macros\UIMacros::callBlock' . ($parent ? 'Parent' : '') . "(\$_l, $name, %node.array? + " . ($parent ? 'get_defined_vars' : '$template->getParameters') . '())';
158:         }
159: 
160:         if ($node->modifiers) {
161:             return $writer->write("ob_start(); $cmd; echo %modify(ob_get_clean())");
162:         } else {
163:             return $writer->write($cmd);
164:         }
165:     }
166: 
167: 
168:     /**
169:      * {includeblock "file"}
170:      */
171:     public function macroIncludeBlock(MacroNode $node, PhpWriter $writer)
172:     {
173:         return $writer->write('echo rtrim(Nette\Latte\Macros\CoreMacros::includeTemplate(%node.word, %node.array? + get_defined_vars(), $_l->templates[%var]))',
174:             $this->getCompiler()->getTemplateId());
175:     }
176: 
177: 
178:     /**
179:      * {extends auto | none | $var | "file"}
180:      */
181:     public function macroExtends(MacroNode $node, PhpWriter $writer)
182:     {
183:         if (!$node->args) {
184:             throw new CompileException('Missing destination in {' . $node->name . '}');
185:         }
186:         if (!empty($node->parentNode)) {
187:             throw new CompileException('{' . $node->name . '} must be placed outside any macro.');
188:         }
189:         if ($this->extends !== NULL) {
190:             throw new CompileException('Multiple {' . $node->name . '} declarations are not allowed.');
191:         }
192:         if ($node->args === 'none') {
193:             $this->extends = 'FALSE';
194:         } elseif ($node->args === 'auto') {
195:             $this->extends = '$_presenter->findLayoutTemplateFile()';
196:         } else {
197:             $this->extends = $writer->write('%node.word%node.args');
198:         }
199:         return;
200:     }
201: 
202: 
203:     /**
204:      * {block [[#]name]}
205:      * {snippet [name [,]] [tag]}
206:      * {snippetArea [name]}
207:      * {define [#]name}
208:      */
209:     public function macroBlock(MacroNode $node, PhpWriter $writer)
210:     {
211:         $name = $node->tokenizer->fetchWord();
212: 
213:         if ($node->name === 'block' && $name === FALSE) { // anonymous block
214:             return $node->modifiers === '' ? '' : 'ob_start()';
215:         }
216: 
217:         $node->data->name = $name = ltrim($name, '#');
218:         if ($name == NULL) {
219:             if ($node->name === 'define') {
220:                 throw new CompileException("Missing block name.");
221:             }
222: 
223:         } elseif (Strings::contains($name, '$')) { // dynamic block/snippet
224:             if ($node->name === 'snippet') {
225:                 for ($parent = $node->parentNode; $parent && !($parent->name === 'snippet' || $parent->name === 'snippetArea'); $parent = $parent->parentNode);
226:                 if (!$parent) {
227:                     throw new CompileException("Dynamic snippets are allowed only inside static snippet/snippetArea.");
228:                 }
229:                 $parent->data->dynamic = TRUE;
230:                 $node->data->leave = TRUE;
231:                 $node->closingCode = "<?php \$_dynSnippets[\$_dynSnippetId] = ob_get_flush() ?>";
232: 
233:                 if ($node->prefix) {
234:                     $node->attrCode = $writer->write("<?php echo ' id=\"' . (\$_dynSnippetId = \$_control->getSnippetId({$writer->formatWord($name)})) . '\"' ?>");
235:                     return $writer->write('ob_start()');
236:                 }
237:                 $tag = trim($node->tokenizer->fetchWord(), '<>');
238:                 $tag = $tag ? $tag : 'div';
239:                 $node->closingCode .= "\n</$tag>";
240:                 return $writer->write("?>\n<$tag id=\"<?php echo \$_dynSnippetId = \$_control->getSnippetId({$writer->formatWord($name)}) ?>\"><?php ob_start()");
241: 
242:             } else {
243:                 $node->data->leave = TRUE;
244:                 $fname = $writer->formatWord($name);
245:                 $node->closingCode = "<?php }} " . ($node->name === 'define' ? '' : "call_user_func(reset(\$_l->blocks[$fname]), \$_l, get_defined_vars())") . " ?>";
246:                 $func = '_lb' . substr(md5($this->getCompiler()->getTemplateId() . $name), 0, 10) . '_' . preg_replace('#[^a-z0-9_]#i', '_', $name);
247:                 return "\n\n//\n// block $name\n//\n"
248:                     . "if (!function_exists(\$_l->blocks[$fname]['{$this->getCompiler()->getTemplateId()}'] = '$func')) { "
249:                     . "function $func(\$_l, \$_args) { foreach (\$_args as \$__k => \$__v) \$\$__k = \$__v";
250:             }
251:         }
252: 
253:         // static snippet/snippetArea
254:         if ($node->name === 'snippet' || $node->name === 'snippetArea') {
255:             if ($node->prefix && isset($node->htmlNode->attrs['id'])) {
256:                 throw new CompileException('Cannot combine HTML attribute id with n:snippet.');
257:             }
258:             $node->data->name = $name = '_' . $name;
259:         }
260: 
261:         if (isset($this->namedBlocks[$name])) {
262:             throw new CompileException("Cannot redeclare static {$node->name} '$name'");
263:         }
264: 
265:         $prolog = $this->namedBlocks ? '' : "if (\$_l->extends) { ob_end_clean(); return Nette\\Latte\\Macros\\CoreMacros::includeTemplate(\$_l->extends, get_defined_vars(), \$template)->render(); }\n";
266:         $this->namedBlocks[$name] = TRUE;
267: 
268:         $include = 'call_user_func(reset($_l->blocks[%var]), $_l, ' . (($node->name === 'snippet' || $node->name === 'snippetArea') ? '$template->getParameters()' : 'get_defined_vars()') . ')';
269:         if ($node->modifiers) {
270:             $include = "ob_start(); $include; echo %modify(ob_get_clean())";
271:         }
272: 
273:         if ($node->name === 'snippet') {
274:             if ($node->prefix) {
275:                 $node->attrCode = $writer->write('<?php echo \' id="\' . $_control->getSnippetId(%var) . \'"\' ?>', (string) substr($name, 1));
276:                 return $writer->write($prolog . $include, $name);
277:             }
278:             $tag = trim($node->tokenizer->fetchWord(), '<>');
279:             $tag = $tag ? $tag : 'div';
280:             return $writer->write("$prolog ?>\n<$tag id=\"<?php echo \$_control->getSnippetId(%var) ?>\"><?php $include ?>\n</$tag><?php ",
281:                 (string) substr($name, 1), $name
282:             );
283: 
284:         } elseif ($node->name === 'define') {
285:             return $prolog;
286: 
287:         } else { // block, snippetArea
288:             return $writer->write($prolog . $include, $name);
289:         }
290:     }
291: 
292: 
293:     /**
294:      * {/block}
295:      * {/snippet}
296:      * {/snippetArea}
297:      * {/define}
298:      */
299:     public function macroBlockEnd(MacroNode $node, PhpWriter $writer)
300:     {
301:         if (isset($node->data->name)) { // block, snippet, define
302:             if ($node->name === 'snippet' && $node->prefix === MacroNode::PREFIX_NONE // n:snippet -> n:inner-snippet
303:                 && preg_match('#^.*? n:\w+>\n?#s', $node->content, $m1) && preg_match('#[ \t]*<[^<]+\z#s', $node->content, $m2)
304:             ) {
305:                 $node->openingCode = $m1[0] . $node->openingCode;
306:                 $node->content = substr($node->content, strlen($m1[0]), -strlen($m2[0]));
307:                 $node->closingCode .= $m2[0];
308:             }
309: 
310:             if (empty($node->data->leave)) {
311:                 if ($node->name === 'snippetArea') {
312:                     $node->content = "<?php \$_control->snippetMode = isset(\$_snippetMode) && \$_snippetMode; ?>{$node->content}<?php \$_control->snippetMode = FALSE; ?>";
313:                 }
314:                 if (!empty($node->data->dynamic)) {
315:                     $node->content .= '<?php if (isset($_dynSnippets)) return $_dynSnippets; ?>';
316:                 }
317:                 if ($node->name === 'snippetArea') {
318:                     $node->content .= '<?php return FALSE; ?>';
319:                 }
320:                 $this->namedBlocks[$node->data->name] = $tmp = rtrim(ltrim($node->content, "\n"), " \t");
321:                 $node->content = substr_replace($node->content, $node->openingCode . "\n", strspn($node->content, "\n"), strlen($tmp));
322:                 $node->openingCode = "<?php ?>";
323:             }
324: 
325:         } elseif ($node->modifiers) { // anonymous block with modifier
326:             return $writer->write('echo %modify(ob_get_clean())');
327:         }
328:     }
329: 
330: 
331:     /**
332:      * {ifset #block}
333:      */
334:     public function macroIfset(MacroNode $node, PhpWriter $writer)
335:     {
336:         if (!Strings::contains($node->args, '#')) {
337:             return FALSE;
338:         }
339:         $list = array();
340:         while (($name = $node->tokenizer->fetchWord()) !== FALSE) {
341:             $list[] = $name[0] === '#' ? '$_l->blocks["' . substr($name, 1) . '"]' : $name;
342:         }
343:         return 'if (isset(' . implode(', ', $list) . ')) {';
344:     }
345: 
346: 
347:     /**
348:      * {control name[:method] [params]}
349:      */
350:     public function macroControl(MacroNode $node, PhpWriter $writer)
351:     {
352:         if ($node->name === 'widget') {
353:             trigger_error('Macro {widget} is deprecated; use {control} instead.', E_USER_DEPRECATED);
354:         }
355:         $words = $node->tokenizer->fetchWords();
356:         if (!$words) {
357:             throw new CompileException("Missing control name in {control}");
358:         }
359:         $name = $writer->formatWord($words[0]);
360:         $method = isset($words[1]) ? ucfirst($words[1]) : '';
361:         $method = Strings::match($method, '#^\w*\z#') ? "render$method" : "{\"render$method\"}";
362:         $param = $writer->formatArray();
363:         if (!Strings::contains($node->args, '=>')) {
364:             $param = substr($param, 6, -1); // removes array()
365:         }
366:         return ($name[0] === '$' ? "if (is_object($name)) \$_ctrl = $name; else " : '')
367:             . '$_ctrl = $_control->getComponent(' . $name . '); '
368:             . 'if ($_ctrl instanceof Nette\Application\UI\IRenderable) $_ctrl->redrawControl(NULL, FALSE); '
369:             . ($node->modifiers === '' ? "\$_ctrl->$method($param)" : $writer->write("ob_start(); \$_ctrl->$method($param); echo %modify(ob_get_clean())"));
370:     }
371: 
372: 
373:     /**
374:      * {link destination [,] [params]}
375:      * {plink destination [,] [params]}
376:      * n:href="destination [,] [params]"
377:      */
378:     public function macroLink(MacroNode $node, PhpWriter $writer)
379:     {
380:         $node->modifiers = preg_replace('#\|safeurl\s*(?=\||\z)#i', '', $node->modifiers);
381:         return $writer->using($node, $this->getCompiler())
382:             ->write('echo %escape(%modify(' . ($node->name === 'plink' ? '$_presenter' : '$_control') . '->link(%node.word, %node.array?)))');
383:     }
384: 
385: 
386:     /**
387:      * {ifCurrent destination [,] [params]}
388:      */
389:     public function macroIfCurrent(MacroNode $node, PhpWriter $writer)
390:     {
391:         return $writer->write(($node->args ? 'try { $_presenter->link(%node.word, %node.array?); } catch (Nette\Application\UI\InvalidLinkException $e) {}' : '')
392:             . '; if ($_presenter->getLastCreatedRequestFlag("current")) {');
393:     }
394: 
395: 
396:     /**
397:      * {contentType ...}
398:      */
399:     public function macroContentType(MacroNode $node, PhpWriter $writer)
400:     {
401:         if (Strings::contains($node->args, 'xhtml')) {
402:             $this->getCompiler()->setContentType(Latte\Compiler::CONTENT_XHTML);
403: 
404:         } elseif (Strings::contains($node->args, 'html')) {
405:             $this->getCompiler()->setContentType(Latte\Compiler::CONTENT_HTML);
406: 
407:         } elseif (Strings::contains($node->args, 'xml')) {
408:             $this->getCompiler()->setContentType(Latte\Compiler::CONTENT_XML);
409: 
410:         } elseif (Strings::contains($node->args, 'javascript')) {
411:             $this->getCompiler()->setContentType(Latte\Compiler::CONTENT_JS);
412: 
413:         } elseif (Strings::contains($node->args, 'css')) {
414:             $this->getCompiler()->setContentType(Latte\Compiler::CONTENT_CSS);
415: 
416:         } elseif (Strings::contains($node->args, 'calendar')) {
417:             $this->getCompiler()->setContentType(Latte\Compiler::CONTENT_ICAL);
418: 
419:         } else {
420:             $this->getCompiler()->setContentType(Latte\Compiler::CONTENT_TEXT);
421:         }
422: 
423:         // temporary solution
424:         if (Strings::contains($node->args, '/')) {
425:             return $writer->write('$netteHttpResponse->setHeader("Content-Type", %var)', $node->args);
426:         }
427:     }
428: 
429: 
430:     /**
431:      * {status ...}
432:      */
433:     public function macroStatus(MacroNode $node, PhpWriter $writer)
434:     {
435:         return $writer->write((substr($node->args, -1) === '?' ? 'if (!$netteHttpResponse->isSent()) ' : '') .
436:             '$netteHttpResponse->setCode(%var)', (int) $node->args
437:         );
438:     }
439: 
440: 
441:     /********************* run-time writers ****************d*g**/
442: 
443: 
444:     /**
445:      * Calls block.
446:      * @return void
447:      */
448:     public static function callBlock(\stdClass $context, $name, array $params)
449:     {
450:         if (empty($context->blocks[$name])) {
451:             throw new Nette\InvalidStateException("Cannot include undefined block '$name'.");
452:         }
453:         $block = reset($context->blocks[$name]);
454:         $block($context, $params);
455:     }
456: 
457: 
458:     /**
459:      * Calls parent block.
460:      * @return void
461:      */
462:     public static function callBlockParent(\stdClass $context, $name, array $params)
463:     {
464:         if (empty($context->blocks[$name]) || ($block = next($context->blocks[$name])) === FALSE) {
465:             throw new Nette\InvalidStateException("Cannot include undefined parent block '$name'.");
466:         }
467:         $block($context, $params);
468:         prev($context->blocks[$name]);
469:     }
470: 
471: 
472:     public static function renderSnippets(Nette\Application\UI\Control $control, \stdClass $local, array $params)
473:     {
474:         $control->snippetMode = FALSE;
475:         $payload = $control->getPresenter()->getPayload();
476:         if (isset($local->blocks)) {
477:             foreach ($local->blocks as $name => $function) {
478:                 if ($name[0] !== '_' || !$control->isControlInvalid((string) substr($name, 1))) {
479:                     continue;
480:                 }
481:                 ob_start();
482:                 $function = reset($function);
483:                 $snippets = $function($local, $params + array('_snippetMode' => TRUE));
484:                 $payload->snippets[$id = $control->getSnippetId((string) substr($name, 1))] = ob_get_clean();
485:                 if ($snippets !== NULL) { // pass FALSE from snippetArea
486:                     if ($snippets) {
487:                         $payload->snippets += $snippets;
488:                     }
489:                     unset($payload->snippets[$id]);
490:                 }
491:             }
492:         }
493:         $control->snippetMode = TRUE;
494:         if ($control instanceof Nette\Application\UI\IRenderable) {
495:             $queue = array($control);
496:             do {
497:                 foreach (array_shift($queue)->getComponents() as $child) {
498:                     if ($child instanceof Nette\Application\UI\IRenderable) {
499:                         if ($child->isControlInvalid()) {
500:                             $child->snippetMode = TRUE;
501:                             $child->render();
502:                             $child->snippetMode = FALSE;
503:                         }
504:                     } elseif ($child instanceof Nette\ComponentModel\IContainer) {
505:                         $queue[] = $child;
506:                     }
507:                 }
508:             } while ($queue);
509:         }
510:     }
511: 
512: }
513: 
Nette 2.1 API documentation generated by ApiGen 2.8.0