Namespaces

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