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