1: <?php
2:
3: 4: 5: 6:
7:
8: namespace Nette\Latte\Macros;
9:
10: use Nette;
11: use Nette\Latte;
12: use Nette\Latte\CompileException;
13: use Nette\Latte\MacroNode;
14: use Nette\Latte\PhpWriter;
15:
16:
17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40:
41: class CoreMacros extends MacroSet
42: {
43:
44:
45: public static function install(Latte\Compiler $compiler)
46: {
47: $me = new static($compiler);
48:
49: $me->addMacro('if', array($me, 'macroIf'), array($me, 'macroEndIf'));
50: $me->addMacro('elseif', '} elseif (%node.args) {');
51: $me->addMacro('else', array($me, 'macroElse'));
52: $me->addMacro('ifset', 'if (isset(%node.args)) {', '}');
53: $me->addMacro('elseifset', '} elseif (isset(%node.args)) {');
54: $me->addMacro('ifcontent', array($me, 'macroIfContent'), array($me, 'macroEndIfContent'));
55:
56: $me->addMacro('switch', '$_l->switch[] = (%node.args); if (FALSE) {', '} array_pop($_l->switch)');
57: $me->addMacro('case', '} elseif (end($_l->switch) === (%node.args)) {');
58:
59: $me->addMacro('foreach', '', array($me, 'macroEndForeach'));
60: $me->addMacro('for', 'for (%node.args) {', '}');
61: $me->addMacro('while', 'while (%node.args) {', '}');
62: $me->addMacro('continueIf', array($me, 'macroBreakContinueIf'));
63: $me->addMacro('breakIf', array($me, 'macroBreakContinueIf'));
64: $me->addMacro('first', 'if ($iterator->isFirst(%node.args)) {', '}');
65: $me->addMacro('last', 'if ($iterator->isLast(%node.args)) {', '}');
66: $me->addMacro('sep', 'if (!$iterator->isLast(%node.args)) {', '}');
67:
68: $me->addMacro('var', array($me, 'macroVar'));
69: $me->addMacro('assign', array($me, 'macroVar'));
70: $me->addMacro('default', array($me, 'macroVar'));
71: $me->addMacro('dump', array($me, 'macroDump'));
72: $me->addMacro('debugbreak', array($me, 'macroDebugbreak'));
73: $me->addMacro('l', '?>{<?php');
74: $me->addMacro('r', '?>}<?php');
75:
76: $me->addMacro('_', array($me, 'macroTranslate'), array($me, 'macroTranslate'));
77: $me->addMacro('=', array($me, 'macroExpr'));
78: $me->addMacro('?', array($me, 'macroExpr'));
79:
80: $me->addMacro('capture', array($me, 'macroCapture'), array($me, 'macroCaptureEnd'));
81: $me->addMacro('include', array($me, 'macroInclude'));
82: $me->addMacro('use', array($me, 'macroUse'));
83:
84: $me->addMacro('class', NULL, NULL, array($me, 'macroClass'));
85: $me->addMacro('attr', array($me, 'macroOldAttr'), '', array($me, 'macroAttr'));
86: $me->addMacro('href', NULL);
87: }
88:
89:
90: 91: 92: 93:
94: public function finalize()
95: {
96: return array('list($_l, $_g) = Nette\Latte\Macros\CoreMacros::initRuntime($template, '
97: . var_export($this->getCompiler()->getTemplateId(), TRUE) . ')');
98: }
99:
100:
101:
102:
103:
104: 105: 106:
107: public function macroIf(MacroNode $node, PhpWriter $writer)
108: {
109: if ($node->data->capture = ($node->args === '')) {
110: return 'ob_start()';
111: }
112: if ($node->prefix === $node::PREFIX_TAG) {
113: return $writer->write($node->htmlNode->closing ? 'if (array_pop($_l->ifs)) {' : 'if ($_l->ifs[] = (%node.args)) {');
114: }
115: return $writer->write('if (%node.args) {');
116: }
117:
118:
119: 120: 121:
122: public function macroEndIf(MacroNode $node, PhpWriter $writer)
123: {
124: if ($node->data->capture) {
125: if ($node->args === '') {
126: throw new CompileException('Missing condition in {if} macro.');
127: }
128: return $writer->write('if (%node.args) '
129: . (isset($node->data->else) ? '{ ob_end_clean(); ob_end_flush(); }' : 'ob_end_flush();')
130: . ' else '
131: . (isset($node->data->else) ? '{ $_else = ob_get_contents(); ob_end_clean(); ob_end_clean(); echo $_else; }' : 'ob_end_clean();')
132: );
133: }
134: return '}';
135: }
136:
137:
138: 139: 140:
141: public function macroElse(MacroNode $node, PhpWriter $writer)
142: {
143: $ifNode = $node->parentNode;
144: if ($ifNode && $ifNode->name === 'if' && $ifNode->data->capture) {
145: if (isset($ifNode->data->else)) {
146: throw new CompileException('Macro {if} supports only one {else}.');
147: }
148: $ifNode->data->else = TRUE;
149: return 'ob_start()';
150: }
151: return '} else {';
152: }
153:
154:
155: 156: 157:
158: public function macroIfContent(MacroNode $node, PhpWriter $writer)
159: {
160: if (!$node->prefix) {
161: throw new CompileException("Unknown macro {{$node->name}}, use n:{$node->name} attribute.");
162: } elseif ($node->prefix !== MacroNode::PREFIX_NONE) {
163: throw new CompileException("Unknown attribute n:{$node->prefix}-{$node->name}, use n:{$node->name} attribute.");
164: }
165:
166: return $writer->write('ob_start()');
167: }
168:
169:
170: 171: 172:
173: public function macroEndIfContent(MacroNode $node, PhpWriter $writer)
174: {
175: preg_match('#(^.*?>)(.*)(<.*\z)#s', $node->content, $parts);
176: $node->content = $parts[1]
177: . '<?php ob_start() ?>'
178: . $parts[2]
179: . '<?php $_ifcontent = ob_get_contents(); ob_end_flush() ?>'
180: . $parts[3];
181: return 'rtrim($_ifcontent) === "" ? ob_end_clean() : ob_end_flush()';
182: }
183:
184:
185: 186: 187:
188: public function macroTranslate(MacroNode $node, PhpWriter $writer)
189: {
190: if ($node->closing) {
191: return $writer->write('echo %modify($template->translate(ob_get_clean()))');
192:
193: } elseif ($node->isEmpty = ($node->args !== '')) {
194: return $writer->write('echo %modify($template->translate(%node.args))');
195:
196: } else {
197: return 'ob_start()';
198: }
199: }
200:
201:
202: 203: 204:
205: public function macroInclude(MacroNode $node, PhpWriter $writer)
206: {
207: $code = $writer->write('Nette\Latte\Macros\CoreMacros::includeTemplate(%node.word, %node.array? + $template->getParameters(), $_l->templates[%var])',
208: $this->getCompiler()->getTemplateId());
209:
210: if ($node->modifiers) {
211: return $writer->write('echo %modify(%raw->__toString(TRUE))', $code);
212: } else {
213: return $code . '->render()';
214: }
215: }
216:
217:
218: 219: 220:
221: public function macroUse(MacroNode $node, PhpWriter $writer)
222: {
223: Nette\Utils\Callback::invoke(array($node->tokenizer->fetchWord(), 'install'), $this->getCompiler())
224: ->initialize();
225: }
226:
227:
228: 229: 230:
231: public function macroCapture(MacroNode $node, PhpWriter $writer)
232: {
233: $variable = $node->tokenizer->fetchWord();
234: if (substr($variable, 0, 1) !== '$') {
235: throw new CompileException("Invalid capture block variable '$variable'");
236: }
237: $node->data->variable = $variable;
238: return 'ob_start()';
239: }
240:
241:
242: 243: 244:
245: public function macroCaptureEnd(MacroNode $node, PhpWriter $writer)
246: {
247: return $node->data->variable . $writer->write(' = %modify(ob_get_clean())');
248: }
249:
250:
251: 252: 253:
254: public function macroEndForeach(MacroNode $node, PhpWriter $writer)
255: {
256: if ($node->modifiers !== '|noiterator' && preg_match('#\W(\$iterator|include|require|get_defined_vars)\W#', $this->getCompiler()->expandTokens($node->content))) {
257: $node->openingCode = '<?php $iterations = 0; foreach ($iterator = $_l->its[] = new Nette\Iterators\CachingIterator('
258: . preg_replace('#(.*)\s+as\s+#i', '$1) as ', $writer->formatArgs(), 1) . ') { ?>';
259: $node->closingCode = '<?php $iterations++; } array_pop($_l->its); $iterator = end($_l->its) ?>';
260: } else {
261: $node->openingCode = '<?php $iterations = 0; foreach (' . $writer->formatArgs() . ') { ?>';
262: $node->closingCode = '<?php $iterations++; } ?>';
263: }
264: }
265:
266:
267: 268: 269: 270:
271: public function macroBreakContinueIf(MacroNode $node, PhpWriter $writer)
272: {
273: $cmd = str_replace('If', '', $node->name);
274: if ($node->parentNode && $node->parentNode->prefix === $node::PREFIX_NONE) {
275: return $writer->write("if (%node.args) { echo \"</{$node->parentNode->htmlNode->name}>\\n\"; $cmd; }");
276: }
277: return $writer->write("if (%node.args) $cmd");
278: }
279:
280:
281: 282: 283:
284: public function macroClass(MacroNode $node, PhpWriter $writer)
285: {
286: return $writer->write('if ($_l->tmp = array_filter(%node.array)) echo \' class="\' . %escape(implode(" ", array_unique($_l->tmp))) . \'"\'');
287: }
288:
289:
290: 291: 292:
293: public function macroAttr(MacroNode $node, PhpWriter $writer)
294: {
295: return $writer->write('echo Nette\Utils\Html::el(NULL, %node.array)->attributes()');
296: }
297:
298:
299: 300: 301: 302:
303: public function macroOldAttr(MacroNode $node)
304: {
305: trigger_error('Macro {attr} is deprecated; use n:attr="..." instead.', E_USER_DEPRECATED);
306: return Nette\Utils\Strings::replace($node->args . ' ', '#\)\s+#', ')->');
307: }
308:
309:
310: 311: 312:
313: public function macroDump(MacroNode $node, PhpWriter $writer)
314: {
315: $args = $writer->formatArgs();
316: return 'Nette\Diagnostics\Debugger::barDump(' . ($node->args ? "array(" . $writer->write('%var', $args) . " => $args)" : 'get_defined_vars()')
317: . ', "Template " . str_replace(dirname(dirname($template->getFile())), "\xE2\x80\xA6", $template->getFile()))';
318: }
319:
320:
321: 322: 323:
324: public function macroDebugbreak(MacroNode $node, PhpWriter $writer)
325: {
326: return $writer->write(($node->args == NULL ? '' : 'if (!(%node.args)); else')
327: . 'if (function_exists("debugbreak")) debugbreak(); elseif (function_exists("xdebug_break")) xdebug_break()');
328: }
329:
330:
331: 332: 333: 334:
335: public function macroVar(MacroNode $node, PhpWriter $writer)
336: {
337: if ($node->args === '' && $node->parentNode && $node->parentNode->name === 'switch') {
338: return '} else {';
339:
340: } elseif ($node->name === 'assign') {
341: trigger_error('Macro {assign} is deprecated; use {var} instead.', E_USER_DEPRECATED);
342: }
343:
344: $var = TRUE;
345: $tokens = $writer->preprocess();
346: $res = new Latte\MacroTokens;
347: while ($tokens->nextToken()) {
348: if ($var && $tokens->isCurrent(Latte\MacroTokens::T_SYMBOL, Latte\MacroTokens::T_VARIABLE)) {
349: if ($node->name === 'default') {
350: $res->append("'" . ltrim($tokens->currentValue(), '$') . "'");
351: } else {
352: $res->append('$' . ltrim($tokens->currentValue(), '$'));
353: }
354: $var = NULL;
355:
356: } elseif ($tokens->isCurrent('=', '=>') && $tokens->depth === 0) {
357: $res->append($node->name === 'default' ? '=>' : '=');
358: $var = FALSE;
359:
360: } elseif ($tokens->isCurrent(',') && $tokens->depth === 0) {
361: $res->append($node->name === 'default' ? ',' : ';');
362: $var = TRUE;
363:
364: } elseif ($var === NULL && $node->name === 'default' && !$tokens->isCurrent(Latte\MacroTokens::T_WHITESPACE)) {
365: throw new CompileException("Unexpected '{$tokens->currentValue()}' in {default $node->args}");
366:
367: } else {
368: $res->append($tokens->currentToken());
369: }
370: }
371: $out = $writer->quoteFilter($res)->joinAll();
372: return $node->name === 'default' ? "extract(array($out), EXTR_SKIP)" : $out;
373: }
374:
375:
376: 377: 378: 379:
380: public function macroExpr(MacroNode $node, PhpWriter $writer)
381: {
382: return $writer->write(($node->name === '?' ? '' : 'echo ') . '%modify(%node.args)');
383: }
384:
385:
386:
387:
388:
389: 390: 391: 392: 393: 394: 395:
396: public static function includeTemplate($destination, array $params, Nette\Templating\ITemplate $template)
397: {
398: if ($destination instanceof Nette\Templating\ITemplate) {
399: $tpl = $destination;
400:
401: } elseif ($destination == NULL) {
402: throw new Nette\InvalidArgumentException("Template file name was not specified.");
403:
404: } elseif ($template instanceof Nette\Templating\IFileTemplate) {
405: if (!preg_match('#/|\\\\|[a-z]:#iA', $destination)) {
406: $destination = dirname($template->getFile()) . '/' . $destination;
407: }
408: $tpl = clone $template;
409: $tpl->setFile($destination);
410:
411: } else {
412: throw new Nette\NotSupportedException('Macro {include "filename"} is supported only with Nette\Templating\IFileTemplate.');
413: }
414:
415: $tpl->setParameters($params);
416: return $tpl;
417: }
418:
419:
420: 421: 422: 423:
424: public static function initRuntime(Nette\Templating\ITemplate $template, $templateId)
425: {
426:
427: if (isset($template->_l)) {
428: $local = $template->_l;
429: unset($template->_l);
430: } else {
431: $local = new \stdClass;
432: }
433: $local->templates[$templateId] = $template;
434:
435:
436: if (!isset($template->_g)) {
437: $template->_g = new \stdClass;
438: }
439:
440: return array($local, $template->_g);
441: }
442:
443: }
444: