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