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