1: <?php
2:
3: 4: 5: 6:
7:
8: namespace Latte\Macros;
9:
10: use Latte;
11: use Latte\CompileException;
12: use Latte\Engine;
13: use Latte\Helpers;
14: use Latte\MacroNode;
15: use Latte\PhpWriter;
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: class CoreMacros extends MacroSet
41: {
42:
43: private $overwrittenVars;
44:
45:
46: public static function install(Latte\Compiler $compiler)
47: {
48: $me = new static($compiler);
49:
50: $me->addMacro('if', [$me, 'macroIf'], [$me, 'macroEndIf']);
51: $me->addMacro('elseif', '} elseif (%node.args) {');
52: $me->addMacro('else', [$me, 'macroElse']);
53: $me->addMacro('ifset', 'if (isset(%node.args)) {', '}');
54: $me->addMacro('elseifset', '} elseif (isset(%node.args)) {');
55: $me->addMacro('ifcontent', [$me, 'macroIfContent'], [$me, 'macroEndIfContent']);
56:
57: $me->addMacro('switch', '$this->global->switch[] = (%node.args); if (FALSE) {', '} array_pop($this->global->switch)');
58: $me->addMacro('case', '} elseif (end($this->global->switch) === (%node.args)) {');
59:
60: $me->addMacro('foreach', '', [$me, 'macroEndForeach']);
61: $me->addMacro('for', 'for (%node.args) {', '}');
62: $me->addMacro('while', [$me, 'macroWhile'], [$me, 'macroEndWhile']);
63: $me->addMacro('continueIf', [$me, 'macroBreakContinueIf']);
64: $me->addMacro('breakIf', [$me, 'macroBreakContinueIf']);
65: $me->addMacro('first', 'if ($iterator->isFirst(%node.args)) {', '}');
66: $me->addMacro('last', 'if ($iterator->isLast(%node.args)) {', '}');
67: $me->addMacro('sep', 'if (!$iterator->isLast(%node.args)) {', '}');
68:
69: $me->addMacro('var', [$me, 'macroVar']);
70: $me->addMacro('default', [$me, 'macroVar']);
71: $me->addMacro('dump', [$me, 'macroDump']);
72: $me->addMacro('debugbreak', [$me, 'macroDebugbreak']);
73: $me->addMacro('l', '?>{<?php');
74: $me->addMacro('r', '?>}<?php');
75:
76: $me->addMacro('_', [$me, 'macroTranslate'], [$me, 'macroTranslate']);
77: $me->addMacro('=', [$me, 'macroExpr']);
78: $me->addMacro('?', [$me, 'macroExpr']);
79:
80: $me->addMacro('capture', [$me, 'macroCapture'], [$me, 'macroCaptureEnd']);
81: $me->addMacro('spaceless', [$me, 'macroSpaceless'], [$me, 'macroSpaceless']);
82: $me->addMacro('include', [$me, 'macroInclude']);
83: $me->addMacro('use', [$me, 'macroUse']);
84: $me->addMacro('contentType', [$me, 'macroContentType'], null, null, self::ALLOWED_IN_HEAD);
85: $me->addMacro('status', [$me, 'macroStatus']);
86: $me->addMacro('php', [$me, 'macroExpr']);
87:
88: $me->addMacro('class', null, null, [$me, 'macroClass']);
89: $me->addMacro('attr', null, null, [$me, 'macroAttr']);
90: }
91:
92:
93: 94: 95: 96:
97: public function initialize()
98: {
99: $this->overwrittenVars = [];
100: }
101:
102:
103: 104: 105: 106:
107: public function finalize()
108: {
109: $code = '';
110: foreach ($this->overwrittenVars as $var => $lines) {
111: $s = var_export($var, true);
112: $code .= 'if (isset($this->params[' . var_export($var, true)
113: . "])) trigger_error('Variable $" . addcslashes($var, "'") . ' overwritten in foreach on line ' . implode(', ', $lines) . "'); ";
114: }
115: return [$code];
116: }
117:
118:
119:
120:
121:
122: 123: 124:
125: public function macroIf(MacroNode $node, PhpWriter $writer)
126: {
127: if ($node->modifiers) {
128: throw new CompileException('Modifiers are not allowed in ' . $node->getNotation());
129: }
130: if ($node->data->capture = ($node->args === '')) {
131: return 'ob_start(function () {})';
132: }
133: if ($node->prefix === $node::PREFIX_TAG) {
134: return $writer->write($node->htmlNode->closing ? 'if (array_pop($this->global->ifs)) {' : 'if ($this->global->ifs[] = (%node.args)) {');
135: }
136: return $writer->write('if (%node.args) {');
137: }
138:
139:
140: 141: 142:
143: public function macroEndIf(MacroNode $node, PhpWriter $writer)
144: {
145: if ($node->data->capture) {
146: if ($node->args === '') {
147: throw new CompileException('Missing condition in {if} macro.');
148: }
149: return $writer->write('if (%node.args) '
150: . (isset($node->data->else) ? '{ ob_end_clean(); echo ob_get_clean(); }' : 'echo ob_get_clean();')
151: . ' else '
152: . (isset($node->data->else) ? '{ $this->global->else = ob_get_clean(); ob_end_clean(); echo $this->global->else; }' : 'ob_end_clean();')
153: );
154: }
155: return '}';
156: }
157:
158:
159: 160: 161:
162: public function macroElse(MacroNode $node, PhpWriter $writer)
163: {
164: if ($node->modifiers) {
165: throw new CompileException('Modifiers are not allowed in ' . $node->getNotation());
166: } elseif ($node->args) {
167: $hint = Helpers::startsWith($node->args, 'if') ? ', did you mean {elseif}?' : '';
168: throw new CompileException('Arguments are not allowed in ' . $node->getNotation() . $hint);
169: }
170: $ifNode = $node->parentNode;
171: if ($ifNode && $ifNode->name === 'if' && $ifNode->data->capture) {
172: if (isset($ifNode->data->else)) {
173: throw new CompileException('Macro {if} supports only one {else}.');
174: }
175: $ifNode->data->else = true;
176: return 'ob_start(function () {})';
177: }
178: return '} else {';
179: }
180:
181:
182: 183: 184:
185: public function macroIfContent(MacroNode $node, PhpWriter $writer)
186: {
187: if (!$node->prefix || $node->prefix !== MacroNode::PREFIX_NONE) {
188: throw new CompileException('Unknown ' . $node->getNotation() . ", use n:{$node->name} attribute.");
189: }
190: }
191:
192:
193: 194: 195:
196: public function macroEndIfContent(MacroNode $node, PhpWriter $writer)
197: {
198: $node->openingCode = '<?php ob_start(function () {}); ?>';
199: $node->innerContent = '<?php ob_start(); ?>' . $node->innerContent . '<?php $this->global->ifcontent = ob_get_flush(); ?>';
200: $node->closingCode = '<?php if (rtrim($this->global->ifcontent) === "") ob_end_clean(); else echo ob_get_clean(); ?>';
201: }
202:
203:
204: 205: 206:
207: public function macroTranslate(MacroNode $node, PhpWriter $writer)
208: {
209: if ($node->closing) {
210: if (strpos($node->content, '<?php') === false) {
211: $value = var_export($node->content, true);
212: $node->content = '';
213: } else {
214: $node->openingCode = '<?php ob_start(function () {}) ?>' . $node->openingCode;
215: $value = 'ob_get_clean()';
216: }
217:
218: return $writer->write('$_fi = new LR\FilterInfo(%var); echo %modifyContent($this->filters->filterContent("translate", $_fi, %raw))', $node->context[0], $value);
219:
220: } elseif ($node->empty = ($node->args !== '')) {
221: return $writer->write('echo %modify(call_user_func($this->filters->translate, %node.args))');
222: }
223: }
224:
225:
226: 227: 228:
229: public function macroInclude(MacroNode $node, PhpWriter $writer)
230: {
231: $node->replaced = false;
232: $noEscape = Helpers::removeFilter($node->modifiers, 'noescape');
233: if (!$noEscape && Helpers::removeFilter($node->modifiers, 'escape')) {
234: trigger_error('Macro {include} provides auto-escaping, remove |escape.');
235: }
236: if ($node->modifiers && !$noEscape) {
237: $node->modifiers .= '|escape';
238: }
239: return $writer->write(
240: '/* line ' . $node->startLine . ' */
241: $this->createTemplate(%node.word, %node.array? + $this->params, "include")->renderToContentType(%raw);',
242: $node->modifiers
243: ? $writer->write('function ($s, $type) { $_fi = new LR\FilterInfo($type); return %modifyContent($s); }')
244: : var_export($noEscape ? null : implode($node->context), true)
245: );
246: }
247:
248:
249: 250: 251:
252: public function macroUse(MacroNode $node, PhpWriter $writer)
253: {
254: trigger_error('Macro {use} is deprecated.', E_USER_DEPRECATED);
255: if ($node->modifiers) {
256: throw new CompileException('Modifiers are not allowed in ' . $node->getNotation());
257: }
258: call_user_func(Helpers::checkCallback([$node->tokenizer->fetchWord(), 'install']), $this->getCompiler())
259: ->initialize();
260: }
261:
262:
263: 264: 265:
266: public function macroCapture(MacroNode $node, PhpWriter $writer)
267: {
268: $variable = $node->tokenizer->fetchWord();
269: if (!Helpers::startsWith($variable, '$')) {
270: throw new CompileException("Invalid capture block variable '$variable'");
271: }
272: $this->checkExtraArgs($node);
273: $node->data->variable = $variable;
274: return 'ob_start(function () {})';
275: }
276:
277:
278: 279: 280:
281: public function macroCaptureEnd(MacroNode $node, PhpWriter $writer)
282: {
283: $body = in_array($node->context[0], [Engine::CONTENT_HTML, Engine::CONTENT_XHTML], true)
284: ? 'ob_get_length() ? new LR\\Html(ob_get_clean()) : ob_get_clean()'
285: : 'ob_get_clean()';
286: return $writer->write("\$_fi = new LR\\FilterInfo(%var); %raw = %modifyContent($body);", $node->context[0], $node->data->variable);
287: }
288:
289:
290: 291: 292:
293: public function macroSpaceless(MacroNode $node, PhpWriter $writer)
294: {
295: if ($node->modifiers || $node->args) {
296: throw new CompileException('Modifiers and arguments are not allowed in ' . $node->getNotation());
297: }
298: $node->openingCode = in_array($node->context[0], [Engine::CONTENT_HTML, Engine::CONTENT_XHTML], true)
299: ? '<?php ob_start(function ($s, $phase) { static $strip = true; return LR\Filters::spacelessHtml($s, $phase, $strip); }, 4096); ?>'
300: : "<?php ob_start('Latte\\Runtime\\Filters::spacelessText', 4096); ?>";
301: $node->closingCode = '<?php ob_end_flush(); ?>';
302: }
303:
304:
305: 306: 307:
308: public function macroWhile(MacroNode $node, PhpWriter $writer)
309: {
310: if ($node->modifiers) {
311: throw new CompileException('Modifiers are not allowed in ' . $node->getNotation());
312: }
313: if ($node->data->do = ($node->args === '')) {
314: return 'do {';
315: }
316: return $writer->write('while (%node.args) {');
317: }
318:
319:
320: 321: 322:
323: public function macroEndWhile(MacroNode $node, PhpWriter $writer)
324: {
325: if ($node->data->do) {
326: if ($node->args === '') {
327: throw new CompileException('Missing condition in {while} macro.');
328: }
329: return $writer->write('} while (%node.args);');
330: }
331: return '}';
332: }
333:
334:
335: 336: 337:
338: public function macroEndForeach(MacroNode $node, PhpWriter $writer)
339: {
340: $noCheck = Helpers::removeFilter($node->modifiers, 'nocheck');
341: $noIterator = Helpers::removeFilter($node->modifiers, 'noiterator');
342: if ($node->modifiers) {
343: throw new CompileException('Only modifiers |noiterator and |nocheck are allowed here.');
344: }
345: $node->openingCode = '<?php $iterations = 0; ';
346: $args = $writer->formatArgs();
347: if (!$noCheck) {
348: preg_match('#.+\s+as\s*\$(\w+)(?:\s*=>\s*\$(\w+))?#i', $args, $m);
349: for ($i = 1; $i < count($m); $i++) {
350: $this->overwrittenVars[$m[$i]][] = $node->startLine;
351: }
352: }
353: if (!$noIterator && preg_match('#\W(\$iterator|include|require|get_defined_vars)\W#', $this->getCompiler()->expandTokens($node->content))) {
354: $node->openingCode .= 'foreach ($iterator = $this->global->its[] = new LR\CachingIterator('
355: . preg_replace('#(.*)\s+as\s+#i', '$1) as ', $args, 1) . ') { ?>';
356: $node->closingCode = '<?php $iterations++; } array_pop($this->global->its); $iterator = end($this->global->its); ?>';
357: } else {
358: $node->openingCode .= 'foreach (' . $args . ') { ?>';
359: $node->closingCode = '<?php $iterations++; } ?>';
360: }
361: }
362:
363:
364: 365: 366: 367:
368: public function macroBreakContinueIf(MacroNode $node, PhpWriter $writer)
369: {
370: if ($node->modifiers) {
371: throw new CompileException('Modifiers are not allowed in ' . $node->getNotation());
372: }
373: $cmd = str_replace('If', '', $node->name);
374: if ($node->parentNode && $node->parentNode->prefix === $node::PREFIX_NONE) {
375: return $writer->write("if (%node.args) { echo \"</{$node->parentNode->htmlNode->name}>\\n\"; $cmd; }");
376: }
377: return $writer->write("if (%node.args) $cmd;");
378: }
379:
380:
381: 382: 383:
384: public function macroClass(MacroNode $node, PhpWriter $writer)
385: {
386: if (isset($node->htmlNode->attrs['class'])) {
387: throw new CompileException('It is not possible to combine class with n:class.');
388: }
389: return $writer->write('if ($_tmp = array_filter(%node.array)) echo \' class="\', %escape(implode(" ", array_unique($_tmp))), \'"\'');
390: }
391:
392:
393: 394: 395:
396: public function macroAttr(MacroNode $node, PhpWriter $writer)
397: {
398: return $writer->write('$_tmp = %node.array; echo LR\Filters::htmlAttributes(isset($_tmp[0]) && is_array($_tmp[0]) ? $_tmp[0] : $_tmp);');
399: }
400:
401:
402: 403: 404:
405: public function macroDump(MacroNode $node, PhpWriter $writer)
406: {
407: if ($node->modifiers) {
408: throw new CompileException('Modifiers are not allowed in ' . $node->getNotation());
409: }
410: $args = $writer->formatArgs();
411: return $writer->write(
412: 'Tracy\Debugger::barDump(' . ($args ? "($args)" : 'get_defined_vars()') . ', %var);',
413: $args ?: 'variables'
414: );
415: }
416:
417:
418: 419: 420:
421: public function macroDebugbreak(MacroNode $node, PhpWriter $writer)
422: {
423: if ($node->modifiers) {
424: throw new CompileException('Modifiers are not allowed in ' . $node->getNotation());
425: }
426: if (function_exists($func = 'debugbreak') || function_exists($func = 'xdebug_break')) {
427: return $writer->write($node->args == null ? "$func()" : "if (%node.args) $func();");
428: }
429: }
430:
431:
432: 433: 434: 435:
436: public function macroVar(MacroNode $node, PhpWriter $writer)
437: {
438: if ($node->modifiers) {
439: throw new CompileException('Modifiers are not allowed in ' . $node->getNotation());
440: }
441: if ($node->args === '' && $node->parentNode && $node->parentNode->name === 'switch') {
442: return '} else {';
443: }
444:
445: $var = true;
446: $tokens = $writer->preprocess();
447: $res = new Latte\MacroTokens;
448: while ($tokens->nextToken()) {
449: if ($var && $tokens->isCurrent($tokens::T_SYMBOL, $tokens::T_VARIABLE)) {
450: if ($node->name === 'default') {
451: $res->append("'" . ltrim($tokens->currentValue(), '$') . "'");
452: } else {
453: $res->append('$' . ltrim($tokens->currentValue(), '$'));
454: }
455: $var = null;
456:
457: } elseif ($tokens->isCurrent('=', '=>') && $tokens->depth === 0) {
458: $res->append($node->name === 'default' ? '=>' : '=');
459: $var = false;
460:
461: } elseif ($tokens->isCurrent(',') && $tokens->depth === 0) {
462: if ($var === null) {
463: $res->append($node->name === 'default' ? '=>NULL' : '=NULL');
464: }
465: $res->append($node->name === 'default' ? ',' : ';');
466: $var = true;
467:
468: } elseif ($var === null && $node->name === 'default' && !$tokens->isCurrent($tokens::T_WHITESPACE)) {
469: throw new CompileException("Unexpected '{$tokens->currentValue()}' in {default $node->args}");
470:
471: } else {
472: $res->append($tokens->currentToken());
473: }
474: }
475: if ($var === null) {
476: $res->append($node->name === 'default' ? '=>NULL' : '=NULL');
477: }
478: $out = $writer->quotingPass($res)->joinAll();
479: return $node->name === 'default' ? "extract([$out], EXTR_SKIP)" : "$out;";
480: }
481:
482:
483: 484: 485: 486:
487: public function macroExpr(MacroNode $node, PhpWriter $writer)
488: {
489: if ($node->name === '?') {
490: trigger_error('Macro {? ...} is deprecated, use {php ...}.', E_USER_DEPRECATED);
491: }
492: return $writer->write($node->name === '='
493: ? "echo %modify(%node.args) /* line $node->startLine */"
494: : '%modify(%node.args);'
495: );
496: }
497:
498:
499: 500: 501:
502: public function macroContentType(MacroNode $node, PhpWriter $writer)
503: {
504: if (
505: !$this->getCompiler()->isInHead()
506: && !($node->htmlNode && strtolower($node->htmlNode->name) === 'script' && strpos($node->args, 'html') !== false)
507: ) {
508: throw new CompileException($node->getNotation() . ' is allowed only in template header.');
509: }
510: $compiler = $this->getCompiler();
511: if ($node->modifiers) {
512: throw new CompileException('Modifiers are not allowed in ' . $node->getNotation());
513: } elseif (strpos($node->args, 'xhtml') !== false) {
514: $type = $compiler::CONTENT_XHTML;
515: } elseif (strpos($node->args, 'html') !== false) {
516: $type = $compiler::CONTENT_HTML;
517: } elseif (strpos($node->args, 'xml') !== false) {
518: $type = $compiler::CONTENT_XML;
519: } elseif (strpos($node->args, 'javascript') !== false) {
520: $type = $compiler::CONTENT_JS;
521: } elseif (strpos($node->args, 'css') !== false) {
522: $type = $compiler::CONTENT_CSS;
523: } elseif (strpos($node->args, 'calendar') !== false) {
524: $type = $compiler::CONTENT_ICAL;
525: } else {
526: $type = $compiler::CONTENT_TEXT;
527: }
528: $compiler->setContentType($type);
529:
530: if (strpos($node->args, '/') && !$node->htmlNode) {
531: return $writer->write('if (empty($this->global->coreCaptured) && in_array($this->getReferenceType(), ["extends", NULL], TRUE)) header(%var);', "Content-Type: $node->args");
532: }
533: }
534:
535:
536: 537: 538:
539: public function macroStatus(MacroNode $node, PhpWriter $writer)
540: {
541: trigger_error('Macro {status} is deprecated.', E_USER_DEPRECATED);
542: if ($node->modifiers) {
543: throw new CompileException('Modifiers are not allowed in ' . $node->getNotation());
544: }
545: return $writer->write((substr($node->args, -1) === '?' ? 'if (!headers_sent()) ' : '') .
546: 'http_response_code(%0.var);', (int) $node->args
547: );
548: }
549: }
550: