PluginProbe
User Profile Builder – Beautiful User Registration Forms, User Profiles & User Role Editor / 4.0.2
User Profile Builder – Beautiful User Registration Forms, User Profiles & User Role Editor v4.0.2
4.0.2 4.0.1 4.0.0 3.16.6 3.16.5 3.16.4 3.16.3 3.16.2 3.16.1 3.16.0 3.15.9 3.9.9 3.9.5 3.9.6 3.9.7 3.9.8 1.1.7 1.1.8 1.1.9 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 All 340 releases
profile-builder / assets / lib / Mustache / Compiler.php

Compiler.php in User Profile Builder – Beautiful User Registration Forms, User Profiles & User Role Editor 4.0.2, at assets/lib/Mustache/Compiler.php

690 lines 22.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /*
4 * This file is part of Mustache.php.
5 *
6 * (c) 2010-2017 Justin Hileman
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12 /**
13 * Mustache Compiler class.
14 *
15 * This class is responsible for turning a Mustache token parse tree into normal PHP source code.
16 */
17 class Mustache_Compiler
18 {
19 private $pragmas;
20 private $defaultPragmas = array();
21 private $sections;
22 private $blocks;
23 private $source;
24 private $indentNextLine;
25 private $customEscape;
26 private $entityFlags;
27 private $charset;
28 private $strictCallables;
29
30 /**
31 * Compile a Mustache token parse tree into PHP source code.
32 *
33 * @param string $source Mustache Template source code
34 * @param string $tree Parse tree of Mustache tokens
35 * @param string $name Mustache Template class name
36 * @param bool $customEscape (default: false)
37 * @param string $charset (default: 'UTF-8')
38 * @param bool $strictCallables (default: false)
39 * @param int $entityFlags (default: ENT_COMPAT)
40 *
41 * @return string Generated PHP source code
42 */
43 public function compile($source, array $tree, $name, $customEscape = false, $charset = 'UTF-8', $strictCallables = false, $entityFlags = ENT_COMPAT)
44 {
45 $this->pragmas = $this->defaultPragmas;
46 $this->sections = array();
47 $this->blocks = array();
48 $this->source = $source;
49 $this->indentNextLine = true;
50 $this->customEscape = $customEscape;
51 $this->entityFlags = $entityFlags;
52 $this->charset = $charset;
53 $this->strictCallables = $strictCallables;
54
55 return $this->writeCode($tree, $name);
56 }
57
58 /**
59 * Enable pragmas across all templates, regardless of the presence of pragma
60 * tags in the individual templates.
61 *
62 * @internal Users should set global pragmas in Mustache_Engine, not here :)
63 *
64 * @param string[] $pragmas
65 */
66 public function setPragmas(array $pragmas)
67 {
68 $this->pragmas = array();
69 foreach ($pragmas as $pragma) {
70 $this->pragmas[$pragma] = true;
71 }
72 $this->defaultPragmas = $this->pragmas;
73 }
74
75 /**
76 * Helper function for walking the Mustache token parse tree.
77 *
78 * @throws Mustache_Exception_SyntaxException upon encountering unknown token types
79 *
80 * @param array $tree Parse tree of Mustache tokens
81 * @param int $level (default: 0)
82 *
83 * @return string Generated PHP source code
84 */
85 private function walk(array $tree, $level = 0)
86 {
87 $code = '';
88 $level++;
89 foreach ($tree as $node) {
90 switch ($node[Mustache_Tokenizer::TYPE]) {
91 case Mustache_Tokenizer::T_PRAGMA:
92 $this->pragmas[$node[Mustache_Tokenizer::NAME]] = true;
93 break;
94
95 case Mustache_Tokenizer::T_SECTION:
96 $code .= $this->section(
97 $node[Mustache_Tokenizer::NODES],
98 $node[Mustache_Tokenizer::NAME],
99 isset($node[Mustache_Tokenizer::FILTERS]) ? $node[Mustache_Tokenizer::FILTERS] : array(),
100 $node[Mustache_Tokenizer::INDEX],
101 $node[Mustache_Tokenizer::END],
102 $node[Mustache_Tokenizer::OTAG],
103 $node[Mustache_Tokenizer::CTAG],
104 $level
105 );
106 break;
107
108 case Mustache_Tokenizer::T_INVERTED:
109 $code .= $this->invertedSection(
110 $node[Mustache_Tokenizer::NODES],
111 $node[Mustache_Tokenizer::NAME],
112 isset($node[Mustache_Tokenizer::FILTERS]) ? $node[Mustache_Tokenizer::FILTERS] : array(),
113 $level
114 );
115 break;
116
117 case Mustache_Tokenizer::T_PARTIAL:
118 $code .= $this->partial(
119 $node[Mustache_Tokenizer::NAME],
120 isset($node[Mustache_Tokenizer::INDENT]) ? $node[Mustache_Tokenizer::INDENT] : '',
121 $level
122 );
123 break;
124
125 case Mustache_Tokenizer::T_PARENT:
126 $code .= $this->parent(
127 $node[Mustache_Tokenizer::NAME],
128 isset($node[Mustache_Tokenizer::INDENT]) ? $node[Mustache_Tokenizer::INDENT] : '',
129 $node[Mustache_Tokenizer::NODES],
130 $level
131 );
132 break;
133
134 case Mustache_Tokenizer::T_BLOCK_ARG:
135 $code .= $this->blockArg(
136 $node[Mustache_Tokenizer::NODES],
137 $node[Mustache_Tokenizer::NAME],
138 $node[Mustache_Tokenizer::INDEX],
139 $node[Mustache_Tokenizer::END],
140 $node[Mustache_Tokenizer::OTAG],
141 $node[Mustache_Tokenizer::CTAG],
142 $level
143 );
144 break;
145
146 case Mustache_Tokenizer::T_BLOCK_VAR:
147 $code .= $this->blockVar(
148 $node[Mustache_Tokenizer::NODES],
149 $node[Mustache_Tokenizer::NAME],
150 $node[Mustache_Tokenizer::INDEX],
151 $node[Mustache_Tokenizer::END],
152 $node[Mustache_Tokenizer::OTAG],
153 $node[Mustache_Tokenizer::CTAG],
154 $level
155 );
156 break;
157
158 case Mustache_Tokenizer::T_COMMENT:
159 break;
160
161 case Mustache_Tokenizer::T_ESCAPED:
162 case Mustache_Tokenizer::T_UNESCAPED:
163 case Mustache_Tokenizer::T_UNESCAPED_2:
164 $code .= $this->variable(
165 $node[Mustache_Tokenizer::NAME],
166 isset($node[Mustache_Tokenizer::FILTERS]) ? $node[Mustache_Tokenizer::FILTERS] : array(),
167 $node[Mustache_Tokenizer::TYPE] === Mustache_Tokenizer::T_ESCAPED,
168 $level
169 );
170 break;
171
172 case Mustache_Tokenizer::T_TEXT:
173 $code .= $this->text($node[Mustache_Tokenizer::VALUE], $level);
174 break;
175
176 default:
177 throw new Mustache_Exception_SyntaxException(sprintf('Unknown token type: %s', $node[Mustache_Tokenizer::TYPE]), $node);
178 }
179 }
180
181 return $code;
182 }
183
184 const KLASS = '<?php
185
186 class %s extends Mustache_Template
187 {
188 private $lambdaHelper;%s
189
190 public function renderInternal(Mustache_Context $context, $indent = \'\')
191 {
192 $this->lambdaHelper = new Mustache_LambdaHelper($this->mustache, $context);
193 $buffer = \'\';
194 %s
195
196 return $buffer;
197 }
198 %s
199 %s
200 }';
201
202 const KLASS_NO_LAMBDAS = '<?php
203
204 class %s extends Mustache_Template
205 {%s
206 public function renderInternal(Mustache_Context $context, $indent = \'\')
207 {
208 $buffer = \'\';
209 %s
210
211 return $buffer;
212 }
213 }';
214
215 const STRICT_CALLABLE = 'protected $strictCallables = true;';
216
217 /**
218 * Generate Mustache Template class PHP source.
219 *
220 * @param array $tree Parse tree of Mustache tokens
221 * @param string $name Mustache Template class name
222 *
223 * @return string Generated PHP source code
224 */
225 private function writeCode($tree, $name)
226 {
227 $code = $this->walk($tree);
228 $sections = implode("\n", $this->sections);
229 $blocks = implode("\n", $this->blocks);
230 $klass = empty($this->sections) && empty($this->blocks) ? self::KLASS_NO_LAMBDAS : self::KLASS;
231
232 $callable = $this->strictCallables ? $this->prepare(self::STRICT_CALLABLE) : '';
233
234 return sprintf($this->prepare($klass, 0, false, true), $name, $callable, $code, $sections, $blocks);
235 }
236
237 const BLOCK_VAR = '
238 $blockFunction = $context->findInBlock(%s);
239 if (is_callable($blockFunction)) {
240 $buffer .= call_user_func($blockFunction, $context);
241 %s}
242 ';
243
244 const BLOCK_VAR_ELSE = '} else {%s';
245
246 /**
247 * Generate Mustache Template inheritance block variable PHP source.
248 *
249 * @param array $nodes Array of child tokens
250 * @param string $id Section name
251 * @param int $start Section start offset
252 * @param int $end Section end offset
253 * @param string $otag Current Mustache opening tag
254 * @param string $ctag Current Mustache closing tag
255 * @param int $level
256 *
257 * @return string Generated PHP source code
258 */
259 private function blockVar($nodes, $id, $start, $end, $otag, $ctag, $level)
260 {
261 $id = var_export($id, true);
262
263 $else = $this->walk($nodes, $level);
264 if ($else !== '') {
265 $else = sprintf($this->prepare(self::BLOCK_VAR_ELSE, $level + 1, false, true), $else);
266 }
267
268 return sprintf($this->prepare(self::BLOCK_VAR, $level), $id, $else);
269 }
270
271 const BLOCK_ARG = '%s => array($this, \'block%s\'),';
272
273 /**
274 * Generate Mustache Template inheritance block argument PHP source.
275 *
276 * @param array $nodes Array of child tokens
277 * @param string $id Section name
278 * @param int $start Section start offset
279 * @param int $end Section end offset
280 * @param string $otag Current Mustache opening tag
281 * @param string $ctag Current Mustache closing tag
282 * @param int $level
283 *
284 * @return string Generated PHP source code
285 */
286 private function blockArg($nodes, $id, $start, $end, $otag, $ctag, $level)
287 {
288 $key = $this->block($nodes);
289 $id = var_export($id, true);
290
291 return sprintf($this->prepare(self::BLOCK_ARG, $level), $id, $key);
292 }
293
294 const BLOCK_FUNCTION = '
295 public function block%s($context)
296 {
297 $indent = $buffer = \'\';%s
298
299 return $buffer;
300 }
301 ';
302
303 /**
304 * Generate Mustache Template inheritance block function PHP source.
305 *
306 * @param array $nodes Array of child tokens
307 *
308 * @return string key of new block function
309 */
310 private function block($nodes)
311 {
312 $code = $this->walk($nodes, 0);
313 $key = ucfirst(md5($code));
314
315 if (!isset($this->blocks[$key])) {
316 $this->blocks[$key] = sprintf($this->prepare(self::BLOCK_FUNCTION, 0), $key, $code);
317 }
318
319 return $key;
320 }
321
322 const SECTION_CALL = '
323 $value = $context->%s(%s);%s
324 $buffer .= $this->section%s($context, $indent, $value);
325 ';
326
327 const SECTION = '
328 private function section%s(Mustache_Context $context, $indent, $value)
329 {
330 $buffer = \'\';
331
332 if (%s) {
333 $source = %s;
334 $result = (string) call_user_func($value, $source, %s);
335 if (strpos($result, \'{{\') === false) {
336 $buffer .= $result;
337 } else {
338 $buffer .= $this->mustache
339 ->loadLambda($result%s)
340 ->renderInternal($context);
341 }
342 } elseif (!empty($value)) {
343 $values = $this->isIterable($value) ? $value : array($value);
344 foreach ($values as $value) {
345 $context->push($value);
346 %s
347 $context->pop();
348 }
349 }
350
351 return $buffer;
352 }
353 ';
354
355 /**
356 * Generate Mustache Template section PHP source.
357 *
358 * @param array $nodes Array of child tokens
359 * @param string $id Section name
360 * @param string[] $filters Array of filters
361 * @param int $start Section start offset
362 * @param int $end Section end offset
363 * @param string $otag Current Mustache opening tag
364 * @param string $ctag Current Mustache closing tag
365 * @param int $level
366 *
367 * @return string Generated section PHP source code
368 */
369 private function section($nodes, $id, $filters, $start, $end, $otag, $ctag, $level)
370 {
371 $source = var_export(substr($this->source, $start, $end - $start), true);
372 $callable = $this->getCallable();
373
374 if ($otag !== '{{' || $ctag !== '}}') {
375 $delimTag = var_export(sprintf('{{= %s %s =}}', $otag, $ctag), true);
376 $helper = sprintf('$this->lambdaHelper->withDelimiters(%s)', $delimTag);
377 $delims = ', ' . $delimTag;
378 } else {
379 $helper = '$this->lambdaHelper';
380 $delims = '';
381 }
382
383 $key = ucfirst(md5($delims . "\n" . $source));
384
385 if (!isset($this->sections[$key])) {
386 $this->sections[$key] = sprintf($this->prepare(self::SECTION), $key, $callable, $source, $helper, $delims, $this->walk($nodes, 2));
387 }
388
389 $method = $this->getFindMethod($id);
390 $id = var_export($id, true);
391 $filters = $this->getFilters($filters, $level);
392
393 return sprintf($this->prepare(self::SECTION_CALL, $level), $method, $id, $filters, $key);
394 }
395
396 const INVERTED_SECTION = '
397 $value = $context->%s(%s);%s
398 if (empty($value)) {
399 %s
400 }
401 ';
402
403 /**
404 * Generate Mustache Template inverted section PHP source.
405 *
406 * @param array $nodes Array of child tokens
407 * @param string $id Section name
408 * @param string[] $filters Array of filters
409 * @param int $level
410 *
411 * @return string Generated inverted section PHP source code
412 */
413 private function invertedSection($nodes, $id, $filters, $level)
414 {
415 $method = $this->getFindMethod($id);
416 $id = var_export($id, true);
417 $filters = $this->getFilters($filters, $level);
418
419 return sprintf($this->prepare(self::INVERTED_SECTION, $level), $method, $id, $filters, $this->walk($nodes, $level));
420 }
421
422 const PARTIAL_INDENT = ', $indent . %s';
423 const PARTIAL = '
424 if ($partial = $this->mustache->loadPartial(%s)) {
425 $buffer .= $partial->renderInternal($context%s);
426 }
427 ';
428
429 /**
430 * Generate Mustache Template partial call PHP source.
431 *
432 * @param string $id Partial name
433 * @param string $indent Whitespace indent to apply to partial
434 * @param int $level
435 *
436 * @return string Generated partial call PHP source code
437 */
438 private function partial($id, $indent, $level)
439 {
440 if ($indent !== '') {
441 $indentParam = sprintf(self::PARTIAL_INDENT, var_export($indent, true));
442 } else {
443 $indentParam = '';
444 }
445
446 return sprintf(
447 $this->prepare(self::PARTIAL, $level),
448 var_export($id, true),
449 $indentParam
450 );
451 }
452
453 const PARENT = '
454 if ($parent = $this->mustache->loadPartial(%s)) {
455 $context->pushBlockContext(array(%s
456 ));
457 $buffer .= $parent->renderInternal($context, $indent);
458 $context->popBlockContext();
459 }
460 ';
461
462 const PARENT_NO_CONTEXT = '
463 if ($parent = $this->mustache->loadPartial(%s)) {
464 $buffer .= $parent->renderInternal($context, $indent);
465 }
466 ';
467
468 /**
469 * Generate Mustache Template inheritance parent call PHP source.
470 *
471 * @param string $id Parent tag name
472 * @param string $indent Whitespace indent to apply to parent
473 * @param array $children Child nodes
474 * @param int $level
475 *
476 * @return string Generated PHP source code
477 */
478 private function parent($id, $indent, array $children, $level)
479 {
480 $realChildren = array_filter($children, array(__CLASS__, 'onlyBlockArgs'));
481
482 if (empty($realChildren)) {
483 return sprintf($this->prepare(self::PARENT_NO_CONTEXT, $level), var_export($id, true));
484 }
485
486 return sprintf(
487 $this->prepare(self::PARENT, $level),
488 var_export($id, true),
489 $this->walk($realChildren, $level + 1)
490 );
491 }
492
493 /**
494 * Helper method for filtering out non-block-arg tokens.
495 *
496 * @param array $node
497 *
498 * @return bool True if $node is a block arg token
499 */
500 private static function onlyBlockArgs(array $node)
501 {
502 return $node[Mustache_Tokenizer::TYPE] === Mustache_Tokenizer::T_BLOCK_ARG;
503 }
504
505 const VARIABLE = '
506 $value = $this->resolveValue($context->%s(%s), $context);%s
507 $buffer .= %s($value === null ? \'\' : %s);
508 ';
509
510 /**
511 * Generate Mustache Template variable interpolation PHP source.
512 *
513 * @param string $id Variable name
514 * @param string[] $filters Array of filters
515 * @param bool $escape Escape the variable value for output?
516 * @param int $level
517 *
518 * @return string Generated variable interpolation PHP source
519 */
520 private function variable($id, $filters, $escape, $level)
521 {
522 $method = $this->getFindMethod($id);
523 $id = ($method !== 'last') ? var_export($id, true) : '';
524 $filters = $this->getFilters($filters, $level);
525 $value = $escape ? $this->getEscape() : '$value';
526
527 return sprintf($this->prepare(self::VARIABLE, $level), $method, $id, $filters, $this->flushIndent(), $value);
528 }
529
530 const FILTER = '
531 $filter = $context->%s(%s);
532 if (!(%s)) {
533 throw new Mustache_Exception_UnknownFilterException(%s);
534 }
535 $value = call_user_func($filter, $value);%s
536 ';
537
538 /**
539 * Generate Mustache Template variable filtering PHP source.
540 *
541 * @param string[] $filters Array of filters
542 * @param int $level
543 *
544 * @return string Generated filter PHP source
545 */
546 private function getFilters(array $filters, $level)
547 {
548 if (empty($filters)) {
549 return '';
550 }
551
552 $name = array_shift($filters);
553 $method = $this->getFindMethod($name);
554 $filter = ($method !== 'last') ? var_export($name, true) : '';
555 $callable = $this->getCallable('$filter');
556 $msg = var_export($name, true);
557
558 return sprintf($this->prepare(self::FILTER, $level), $method, $filter, $callable, $msg, $this->getFilters($filters, $level));
559 }
560
561 const LINE = '$buffer .= "\n";';
562 const TEXT = '$buffer .= %s%s;';
563
564 /**
565 * Generate Mustache Template output Buffer call PHP source.
566 *
567 * @param string $text
568 * @param int $level
569 *
570 * @return string Generated output Buffer call PHP source
571 */
572 private function text($text, $level)
573 {
574 $indentNextLine = (substr($text, -1) === "\n");
575 $code = sprintf($this->prepare(self::TEXT, $level), $this->flushIndent(), var_export($text, true));
576 $this->indentNextLine = $indentNextLine;
577
578 return $code;
579 }
580
581 /**
582 * Prepare PHP source code snippet for output.
583 *
584 * @param string $text
585 * @param int $bonus Additional indent level (default: 0)
586 * @param bool $prependNewline Prepend a newline to the snippet? (default: true)
587 * @param bool $appendNewline Append a newline to the snippet? (default: false)
588 *
589 * @return string PHP source code snippet
590 */
591 private function prepare($text, $bonus = 0, $prependNewline = true, $appendNewline = false)
592 {
593 $text = ($prependNewline ? "\n" : '') . trim($text);
594 if ($prependNewline) {
595 $bonus++;
596 }
597 if ($appendNewline) {
598 $text .= "\n";
599 }
600
601 return preg_replace("/\n( {8})?/", "\n" . str_repeat(' ', $bonus * 4), $text);
602 }
603
604 const DEFAULT_ESCAPE = 'htmlspecialchars(%s, %s, %s)';
605 const CUSTOM_ESCAPE = 'call_user_func($this->mustache->getEscape(), %s)';
606
607 /**
608 * Get the current escaper.
609 *
610 * @param string $value (default: '$value')
611 *
612 * @return string Either a custom callback, or an inline call to `htmlspecialchars`
613 */
614 private function getEscape($value = '$value')
615 {
616 if ($this->customEscape) {
617 return sprintf(self::CUSTOM_ESCAPE, $value);
618 }
619
620 return sprintf(self::DEFAULT_ESCAPE, $value, var_export($this->entityFlags, true), var_export($this->charset, true));
621 }
622
623 /**
624 * Select the appropriate Context `find` method for a given $id.
625 *
626 * The return value will be one of `find`, `findDot`, `findAnchoredDot` or `last`.
627 *
628 * @see Mustache_Context::find
629 * @see Mustache_Context::findDot
630 * @see Mustache_Context::last
631 *
632 * @param string $id Variable name
633 *
634 * @return string `find` method name
635 */
636 private function getFindMethod($id)
637 {
638 if ($id === '.') {
639 return 'last';
640 }
641
642 if (isset($this->pragmas[Mustache_Engine::PRAGMA_ANCHORED_DOT]) && $this->pragmas[Mustache_Engine::PRAGMA_ANCHORED_DOT]) {
643 if (substr($id, 0, 1) === '.') {
644 return 'findAnchoredDot';
645 }
646 }
647
648 if (strpos($id, '.') === false) {
649 return 'find';
650 }
651
652 return 'findDot';
653 }
654
655 const IS_CALLABLE = '!is_string(%s) && is_callable(%s)';
656 const STRICT_IS_CALLABLE = 'is_object(%s) && is_callable(%s)';
657
658 /**
659 * Helper function to compile strict vs lax "is callable" logic.
660 *
661 * @param string $variable (default: '$value')
662 *
663 * @return string "is callable" logic
664 */
665 private function getCallable($variable = '$value')
666 {
667 $tpl = $this->strictCallables ? self::STRICT_IS_CALLABLE : self::IS_CALLABLE;
668
669 return sprintf($tpl, $variable, $variable);
670 }
671
672 const LINE_INDENT = '$indent . ';
673
674 /**
675 * Get the current $indent prefix to write to the buffer.
676 *
677 * @return string "$indent . " or ""
678 */
679 private function flushIndent()
680 {
681 if (!$this->indentNextLine) {
682 return '';
683 }
684
685 $this->indentNextLine = false;
686
687 return self::LINE_INDENT;
688 }
689 }
690