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