1: <?php
2:
3: 4: 5: 6:
7:
8: namespace Latte\Macros;
9:
10: use Latte;
11: use Latte\MacroNode;
12: use Latte\PhpWriter;
13: use Latte\CompileException;
14: use Latte\RuntimeException;
15:
16:
17: 18: 19: 20: 21:
22: class BlockMacros extends MacroSet
23: {
24:
25: private $namedBlocks = array();
26:
27:
28: private $extends;
29:
30:
31: public static function install(Latte\Compiler $compiler)
32: {
33: $me = new static($compiler);
34: $me->addMacro('include', array($me, 'macroInclude'));
35: $me->addMacro('includeblock', array($me, 'macroIncludeBlock'));
36: $me->addMacro('extends', array($me, 'macroExtends'));
37: $me->addMacro('layout', array($me, 'macroExtends'));
38: $me->addMacro('block', array($me, 'macroBlock'), array($me, 'macroBlockEnd'));
39: $me->addMacro('define', array($me, 'macroBlock'), array($me, 'macroBlockEnd'));
40: $me->addMacro('snippet', array($me, 'macroBlock'), array($me, 'macroBlockEnd'));
41: $me->addMacro('snippetArea', array($me, 'macroBlock'), array($me, 'macroBlockEnd'));
42: $me->addMacro('ifset', array($me, 'macroIfset'), '}');
43: $me->addMacro('elseifset', array($me, 'macroIfset'), '}');
44: }
45:
46:
47: 48: 49: 50:
51: public function initialize()
52: {
53: $this->namedBlocks = array();
54: $this->extends = NULL;
55: }
56:
57:
58: 59: 60: 61:
62: public function finalize()
63: {
64:
65: $last = $this->getCompiler()->getMacroNode();
66: if ($last && $last->name === 'block') {
67: $this->getCompiler()->closeMacro($last->name);
68: }
69:
70: $epilog = $prolog = array();
71:
72: if ($this->namedBlocks) {
73: foreach ($this->namedBlocks as $name => $code) {
74: $func = '_lb' . substr(md5($this->getCompiler()->getTemplateId() . $name), 0, 10) . '_' . preg_replace('#[^a-z0-9_]#i', '_', $name);
75: $snippet = $name[0] === '_';
76: $prolog[] = "//\n// block $name\n//\n"
77: . "if (!function_exists(\$_b->blocks[" . var_export($name, TRUE) . "][] = '$func')) { "
78: . "function $func(\$_b, \$_args) { foreach (\$_args as \$__k => \$__v) \$\$__k = \$__v"
79: . ($snippet ? '; $_control->redrawControl(' . var_export((string) substr($name, 1), TRUE) . ', FALSE)' : '')
80: . "\n?>$code<?php\n}}";
81: }
82: $prolog[] = "//\n// end of blocks\n//";
83: }
84:
85: if ($this->namedBlocks || $this->extends) {
86: $prolog[] = '// template extending';
87:
88: $prolog[] = '$_l->extends = '
89: . ($this->extends ? $this->extends : 'empty($_g->extended) && isset($_control) && $_control instanceof Nette\Application\UI\Presenter ? $_control->findLayoutTemplateFile() : NULL')
90: . '; $_g->extended = TRUE;';
91:
92: $prolog[] = 'if ($_l->extends) { ob_start();}';
93: if (!$this->namedBlocks) {
94: $epilog[] = 'if ($_l->extends) { ob_end_clean(); return $template->renderChildTemplate($_l->extends, get_defined_vars());}';
95: }
96: }
97:
98: return array(implode("\n\n", $prolog), implode("\n", $epilog));
99: }
100:
101:
102:
103:
104:
105: 106: 107:
108: public function macroInclude(MacroNode $node, PhpWriter $writer)
109: {
110: $destination = $node->tokenizer->fetchWord();
111: if (!preg_match('~#|[\w-]+\z~A', $destination)) {
112: return FALSE;
113: }
114:
115: $destination = ltrim($destination, '#');
116: $parent = $destination === 'parent';
117: if ($destination === 'parent' || $destination === 'this') {
118: for ($item = $node->parentNode; $item && $item->name !== 'block' && !isset($item->data->name); $item = $item->parentNode);
119: if (!$item) {
120: throw new CompileException("Cannot include $destination block outside of any block.");
121: }
122: $destination = $item->data->name;
123: }
124:
125: $name = strpos($destination, '$') === FALSE ? var_export($destination, TRUE) : $destination;
126: if (isset($this->namedBlocks[$destination]) && !$parent) {
127: $cmd = "call_user_func(reset(\$_b->blocks[$name]), \$_b, %node.array? + get_defined_vars())";
128: } else {
129: $cmd = 'Latte\Macros\BlockMacros::callBlock' . ($parent ? 'Parent' : '') . "(\$_b, $name, %node.array? + " . ($parent ? 'get_defined_vars' : '$template->getParameters') . '())';
130: }
131:
132: if ($node->modifiers) {
133: return $writer->write("ob_start(); $cmd; echo %modify(ob_get_clean())");
134: } else {
135: return $writer->write($cmd);
136: }
137: }
138:
139:
140: 141: 142:
143: public function macroIncludeBlock(MacroNode $node, PhpWriter $writer)
144: {
145: return $writer->write('ob_start(); $_b->templates[%var]->renderChildTemplate(%node.word, %node.array? + get_defined_vars()); echo rtrim(ob_get_clean())',
146: $this->getCompiler()->getTemplateId());
147: }
148:
149:
150: 151: 152:
153: public function macroExtends(MacroNode $node, PhpWriter $writer)
154: {
155: if (!$node->args) {
156: throw new CompileException("Missing destination in {{$node->name}}");
157: }
158: if (!empty($node->parentNode)) {
159: throw new CompileException("{{$node->name}} must be placed outside any macro.");
160: }
161: if ($this->extends !== NULL) {
162: throw new CompileException("Multiple {{$node->name}} declarations are not allowed.");
163: }
164: if ($node->args === 'none') {
165: $this->extends = 'FALSE';
166: } elseif ($node->args === 'auto') {
167: $this->extends = '$_presenter->findLayoutTemplateFile()';
168: } else {
169: $this->extends = $writer->write('%node.word%node.args');
170: }
171: return;
172: }
173:
174:
175: 176: 177: 178: 179: 180:
181: public function macroBlock(MacroNode $node, PhpWriter $writer)
182: {
183: $name = $node->tokenizer->fetchWord();
184:
185: if ($node->name === '#') {
186: trigger_error('Shortcut {#block} is deprecated.', E_USER_DEPRECATED);
187:
188: } elseif ($node->name === 'block' && $name === FALSE) {
189: return $node->modifiers === '' ? '' : 'ob_start()';
190: }
191:
192: $node->data->name = $name = ltrim($name, '#');
193: if ($name == NULL) {
194: if ($node->name === 'define') {
195: throw new CompileException('Missing block name.');
196: }
197:
198: } elseif (strpos($name, '$') !== FALSE) {
199: if ($node->name === 'snippet') {
200: for ($parent = $node->parentNode; $parent && !($parent->name === 'snippet' || $parent->name === 'snippetArea'); $parent = $parent->parentNode);
201: if (!$parent) {
202: throw new CompileException('Dynamic snippets are allowed only inside static snippet/snippetArea.');
203: }
204: $parent->data->dynamic = TRUE;
205: $node->data->leave = TRUE;
206: $node->closingCode = "<?php \$_l->dynSnippets[\$_l->dynSnippetId] = ob_get_flush() ?>";
207:
208: if ($node->prefix) {
209: $node->attrCode = $writer->write("<?php echo ' id=\"' . (\$_l->dynSnippetId = \$_control->getSnippetId({$writer->formatWord($name)})) . '\"' ?>");
210: return $writer->write('ob_start()');
211: }
212: $tag = trim($node->tokenizer->fetchWord(), '<>');
213: $tag = $tag ? $tag : 'div';
214: $node->closingCode .= "\n</$tag>";
215: return $writer->write("?>\n<$tag id=\"<?php echo \$_l->dynSnippetId = \$_control->getSnippetId({$writer->formatWord($name)}) ?>\"><?php ob_start()");
216:
217: } else {
218: $node->data->leave = TRUE;
219: $fname = $writer->formatWord($name);
220: $node->closingCode = '<?php }} ' . ($node->name === 'define' ? '' : "call_user_func(reset(\$_b->blocks[$fname]), \$_b, get_defined_vars())") . ' ?>';
221: $func = '_lb' . substr(md5($this->getCompiler()->getTemplateId() . $name), 0, 10) . '_' . preg_replace('#[^a-z0-9_]#i', '_', $name);
222: return "\n\n//\n// block $name\n//\n"
223: . "if (!function_exists(\$_b->blocks[$fname]['{$this->getCompiler()->getTemplateId()}'] = '$func')) { "
224: . "function $func(\$_b, \$_args) { foreach (\$_args as \$__k => \$__v) \$\$__k = \$__v";
225: }
226: }
227:
228:
229: if ($node->name === 'snippet' || $node->name === 'snippetArea') {
230: if ($node->prefix && isset($node->htmlNode->attrs['id'])) {
231: throw new CompileException('Cannot combine HTML attribute id with n:snippet.');
232: }
233: $node->data->name = $name = '_' . $name;
234: }
235:
236: if (isset($this->namedBlocks[$name])) {
237: throw new CompileException("Cannot redeclare static {$node->name} '$name'");
238: }
239:
240: $prolog = $this->namedBlocks ? '' : "if (\$_l->extends) { ob_end_clean(); return \$template->renderChildTemplate(\$_l->extends, get_defined_vars()); }\n";
241: $this->namedBlocks[$name] = TRUE;
242:
243: $include = 'call_user_func(reset($_b->blocks[%var]), $_b, ' . (($node->name === 'snippet' || $node->name === 'snippetArea') ? '$template->getParameters()' : 'get_defined_vars()') . ')';
244: if ($node->modifiers) {
245: $include = "ob_start(); $include; echo %modify(ob_get_clean())";
246: }
247:
248: if ($node->name === 'snippet') {
249: if ($node->prefix) {
250: $node->attrCode = $writer->write('<?php echo \' id="\' . $_control->getSnippetId(%var) . \'"\' ?>', (string) substr($name, 1));
251: return $writer->write($prolog . $include, $name);
252: }
253: $tag = trim($node->tokenizer->fetchWord(), '<>');
254: $tag = $tag ? $tag : 'div';
255: return $writer->write("$prolog ?>\n<$tag id=\"<?php echo \$_control->getSnippetId(%var) ?>\"><?php $include ?>\n</$tag><?php ",
256: (string) substr($name, 1), $name
257: );
258:
259: } elseif ($node->name === 'define') {
260: return $prolog;
261:
262: } else {
263: return $writer->write($prolog . $include, $name);
264: }
265: }
266:
267:
268: 269: 270: 271: 272: 273:
274: public function macroBlockEnd(MacroNode $node, PhpWriter $writer)
275: {
276: if (isset($node->data->name)) {
277: if ($node->name === 'snippet' && $node->prefix === MacroNode::PREFIX_NONE
278: && preg_match('#^.*? n:\w+>\n?#s', $node->content, $m1) && preg_match('#[ \t]*<[^<]+\z#s', $node->content, $m2)
279: ) {
280: $node->openingCode = $m1[0] . $node->openingCode;
281: $node->content = substr($node->content, strlen($m1[0]), -strlen($m2[0]));
282: $node->closingCode .= $m2[0];
283: }
284:
285: if (empty($node->data->leave)) {
286: if ($node->name === 'snippetArea') {
287: $node->content = "<?php \$_control->snippetMode = isset(\$_snippetMode) && \$_snippetMode; ?>{$node->content}<?php \$_control->snippetMode = FALSE; ?>";
288: }
289: if (!empty($node->data->dynamic)) {
290: $node->content .= '<?php if (isset($_l->dynSnippets)) return $_l->dynSnippets; ?>';
291: }
292: if ($node->name === 'snippetArea') {
293: $node->content .= '<?php return FALSE; ?>';
294: }
295: $this->namedBlocks[$node->data->name] = $tmp = preg_replace('#^\n+|(?<=\n)[ \t]+\z#', '', $node->content);
296: $node->content = substr_replace($node->content, $node->openingCode . "\n", strspn($node->content, "\n"), strlen($tmp));
297: $node->openingCode = '<?php ?>';
298: }
299:
300: } elseif ($node->modifiers) {
301: return $writer->write('echo %modify(ob_get_clean())');
302: }
303: }
304:
305:
306: 307: 308: 309:
310: public function macroIfset(MacroNode $node, PhpWriter $writer)
311: {
312: if (strpos($node->args, '#') === FALSE) {
313: return FALSE;
314: }
315: $list = array();
316: while (($name = $node->tokenizer->fetchWord()) !== FALSE) {
317: $list[] = $name[0] === '#' ? '$_b->blocks["' . substr($name, 1) . '"]' : $name;
318: }
319: return ($node->name === 'elseifset' ? '} else' : '')
320: . 'if (isset(' . implode(', ', $list) . ')) {';
321: }
322:
323:
324:
325:
326:
327: 328: 329: 330:
331: public static function callBlock(\stdClass $context, $name, array $params)
332: {
333: if (empty($context->blocks[$name])) {
334: throw new RuntimeException("Cannot include undefined block '$name'.");
335: }
336: $block = reset($context->blocks[$name]);
337: $block($context, $params);
338: }
339:
340:
341: 342: 343: 344:
345: public static function callBlockParent(\stdClass $context, $name, array $params)
346: {
347: if (empty($context->blocks[$name]) || ($block = next($context->blocks[$name])) === FALSE) {
348: throw new RuntimeException("Cannot include undefined parent block '$name'.");
349: }
350: $block($context, $params);
351: prev($context->blocks[$name]);
352: }
353:
354: }
355: