1: <?php
2:
3: 4: 5: 6: 7: 8: 9: 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: 25: 26: 27: 28: 29: 30: 31: 32: 33:
34: class UIMacros extends MacroSet
35: {
36:
37: const RE_IDENTIFIER = '[_a-zA-Z\x7F-\xFF][_a-zA-Z0-9\x7F-\xFF]*';
38:
39:
40: private $namedBlocks = array();
41:
42:
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'));
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');
68:
69: $me->addMacro('contentType', array($me, 'macroContentType'));
70: $me->addMacro('status', array($me, 'macroStatus'));
71: }
72:
73:
74:
75: 76: 77: 78:
79: public function initialize()
80: {
81: $this->namedBlocks = array();
82: $this->extends = NULL;
83: }
84:
85:
86:
87: 88: 89: 90:
91: public function finalize()
92: {
93:
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')
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:
143:
144:
145:
146: 147: 148:
149: public function macroInclude(MacroNode $node, PhpWriter $writer)
150: {
151: $destination = $node->tokenizer->fetchWord();
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: 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: 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: 225: 226: 227:
228: public function macroBlock(MacroNode $node, PhpWriter $writer)
229: {
230: $name = $node->tokenizer->fetchWord();
231:
232: if ($node->name === 'block' && $name === FALSE) {
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 . '$#')) {
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');
270: }
271: }
272:
273:
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: 312: 313: 314:
315: public function macroBlockEnd(MacroNode $node, PhpWriter $writer)
316: {
317: if (isset($node->data->name)) {
318: if ($node->name === 'snippet' && $node->htmlNode && !$node->prefix
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) {
336: return $writer->write('echo %modify(ob_get_clean())');
337: }
338: }
339:
340:
341:
342: 343: 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: 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);
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: 386: 387: 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: 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: 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:
435: if (Strings::contains($node->args, '/')) {
436: return $writer->write('$netteHttpResponse->setHeader("Content-Type", %var)', $node->args);
437: }
438: }
439:
440:
441:
442: 443: 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:
455:
456:
457:
458: 459: 460: 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: 475: 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: $control->snippetMode = TRUE;
507: if ($control instanceof Nette\Application\UI\IRenderable) {
508: $queue = array($control);
509: do {
510: foreach (array_shift($queue)->getComponents() as $child) {
511: if ($child instanceof Nette\Application\UI\IRenderable) {
512: if ($child->isControlInvalid()) {
513: $child->snippetMode = TRUE;
514: $child->render();
515: $child->snippetMode = FALSE;
516: }
517: } elseif ($child instanceof Nette\ComponentModel\IContainer) {
518: $queue[] = $child;
519: }
520: }
521: } while ($queue);
522: }
523: }
524:
525: }
526: