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