Namespaces

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

Classes

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