These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * Copyright (c) 2013-2017 |
||
4 | * |
||
5 | * @category Library |
||
6 | * @package Dwoo |
||
7 | * @author Jordi Boggiano <[email protected]> |
||
8 | * @author David Sanchez <[email protected]> |
||
9 | * @copyright 2008-2013 Jordi Boggiano |
||
10 | * @copyright 2013-2017 David Sanchez |
||
11 | * @license http://dwoo.org/LICENSE Modified BSD License |
||
12 | * @version 1.3.2 |
||
13 | * @date 2017-01-04 |
||
14 | * @link http://dwoo.org/ |
||
15 | */ |
||
16 | |||
17 | namespace Dwoo; |
||
18 | |||
19 | use Closure; |
||
20 | use Dwoo\Plugins\Blocks\PluginIf; |
||
21 | use Dwoo\Security\Exception as SecurityException; |
||
22 | use Dwoo\Security\Policy as SecurityPolicy; |
||
23 | use Dwoo\Compilation\Exception as CompilationException; |
||
24 | use ReflectionFunction; |
||
25 | use ReflectionMethod; |
||
26 | |||
27 | /** |
||
28 | * Default dwoo compiler class, compiles dwoo templates into php. |
||
29 | * This software is provided 'as-is', without any express or implied warranty. |
||
30 | * In no event will the authors be held liable for any damages arising from the use of this software. |
||
31 | */ |
||
32 | class Compiler implements ICompiler |
||
33 | { |
||
34 | /** |
||
35 | * Constant that represents a php opening tag. |
||
36 | * use it in case it needs to be adjusted |
||
37 | * |
||
38 | * @var string |
||
39 | */ |
||
40 | const PHP_OPEN = '<?php '; |
||
41 | |||
42 | /** |
||
43 | * Constant that represents a php closing tag. |
||
44 | * use it in case it needs to be adjusted |
||
45 | * |
||
46 | * @var string |
||
47 | */ |
||
48 | const PHP_CLOSE = '?>'; |
||
49 | |||
50 | /** |
||
51 | * Boolean flag to enable or disable debugging output. |
||
52 | * |
||
53 | * @var bool |
||
54 | */ |
||
55 | public $debug = false; |
||
56 | |||
57 | /** |
||
58 | * Left script delimiter. |
||
59 | * |
||
60 | * @var string |
||
61 | */ |
||
62 | protected $ld = '{'; |
||
63 | |||
64 | /** |
||
65 | * Left script delimiter with escaped regex meta characters. |
||
66 | * |
||
67 | * @var string |
||
68 | */ |
||
69 | protected $ldr = '\\{'; |
||
70 | |||
71 | /** |
||
72 | * Right script delimiter. |
||
73 | * |
||
74 | * @var string |
||
75 | */ |
||
76 | protected $rd = '}'; |
||
77 | |||
78 | /** |
||
79 | * Right script delimiter with escaped regex meta characters. |
||
80 | * |
||
81 | * @var string |
||
82 | */ |
||
83 | protected $rdr = '\\}'; |
||
84 | |||
85 | /** |
||
86 | * Defines whether the nested comments should be parsed as nested or not. |
||
87 | * defaults to false (classic block comment parsing as in all languages) |
||
88 | * |
||
89 | * @var bool |
||
90 | */ |
||
91 | protected $allowNestedComments = false; |
||
92 | |||
93 | /** |
||
94 | * Defines whether opening and closing tags can contain spaces before valid data or not. |
||
95 | * turn to true if you want to be sloppy with the syntax, but when set to false it allows |
||
96 | * to skip javascript and css tags as long as they are in the form "{ something", which is |
||
97 | * nice. default is false. |
||
98 | * |
||
99 | * @var bool |
||
100 | */ |
||
101 | protected $allowLooseOpenings = false; |
||
102 | |||
103 | /** |
||
104 | * Defines whether the compiler will automatically html-escape variables or not. |
||
105 | * default is false |
||
106 | * |
||
107 | * @var bool |
||
108 | */ |
||
109 | protected $autoEscape = false; |
||
110 | |||
111 | /** |
||
112 | * Security policy object. |
||
113 | * |
||
114 | * @var SecurityPolicy |
||
115 | */ |
||
116 | protected $securityPolicy; |
||
117 | |||
118 | /** |
||
119 | * Stores the custom plugins registered with this compiler. |
||
120 | * |
||
121 | * @var array |
||
122 | */ |
||
123 | protected $customPlugins = array(); |
||
124 | |||
125 | /** |
||
126 | * Stores the template plugins registered with this compiler. |
||
127 | * |
||
128 | * @var array |
||
129 | */ |
||
130 | protected $templatePlugins = array(); |
||
131 | |||
132 | /** |
||
133 | * Stores the pre- and post-processors callbacks. |
||
134 | * |
||
135 | * @var array |
||
136 | */ |
||
137 | protected $processors = array('pre' => array(), 'post' => array()); |
||
138 | |||
139 | /** |
||
140 | * Stores a list of plugins that are used in the currently compiled |
||
141 | * template, and that are not compilable. these plugins will be loaded |
||
142 | * during the template's runtime if required. |
||
143 | * it is a 1D array formatted as key:pluginName value:pluginType |
||
144 | * |
||
145 | * @var array |
||
146 | */ |
||
147 | protected $usedPlugins; |
||
148 | |||
149 | /** |
||
150 | * Stores the template undergoing compilation. |
||
151 | * |
||
152 | * @var string |
||
153 | */ |
||
154 | protected $template; |
||
155 | |||
156 | /** |
||
157 | * Stores the current pointer position inside the template. |
||
158 | * |
||
159 | * @var int |
||
160 | */ |
||
161 | protected $pointer; |
||
162 | |||
163 | /** |
||
164 | * Stores the current line count inside the template for debugging purposes. |
||
165 | * |
||
166 | * @var int |
||
167 | */ |
||
168 | protected $line; |
||
169 | |||
170 | /** |
||
171 | * Stores the current template source while compiling it. |
||
172 | * |
||
173 | * @var string |
||
174 | */ |
||
175 | protected $templateSource; |
||
176 | |||
177 | /** |
||
178 | * Stores the data within which the scope moves. |
||
179 | * |
||
180 | * @var array |
||
181 | */ |
||
182 | protected $data; |
||
183 | |||
184 | /** |
||
185 | * Variable scope of the compiler, set to null if |
||
186 | * it can not be resolved to a static string (i.e. if some |
||
187 | * plugin defines a new scope based on a variable array key). |
||
188 | * |
||
189 | * @var mixed |
||
190 | */ |
||
191 | protected $scope; |
||
192 | |||
193 | /** |
||
194 | * Variable scope tree, that allows to rebuild the current |
||
195 | * scope if required, i.e. when going to a parent level. |
||
196 | * |
||
197 | * @var array |
||
198 | */ |
||
199 | protected $scopeTree; |
||
200 | |||
201 | /** |
||
202 | * Block plugins stack, accessible through some methods. |
||
203 | * |
||
204 | * @see findBlock |
||
205 | * @see getCurrentBlock |
||
206 | * @see addBlock |
||
207 | * @see addCustomBlock |
||
208 | * @see injectBlock |
||
209 | * @see removeBlock |
||
210 | * @see removeTopBlock |
||
211 | * @var array |
||
212 | */ |
||
213 | protected $stack = array(); |
||
214 | |||
215 | /** |
||
216 | * Current block at the top of the block plugins stack, |
||
217 | * accessible through getCurrentBlock. |
||
218 | * |
||
219 | * @see getCurrentBlock |
||
220 | * @var array |
||
221 | */ |
||
222 | protected $curBlock; |
||
223 | |||
224 | /** |
||
225 | * Current dwoo object that uses this compiler, or null. |
||
226 | * |
||
227 | * @var Core |
||
228 | */ |
||
229 | public $dwoo; |
||
230 | |||
231 | /** |
||
232 | * Holds an instance of this class, used by getInstance when you don't |
||
233 | * provide a custom compiler in order to save resources. |
||
234 | * |
||
235 | * @var Compiler |
||
236 | */ |
||
237 | protected static $instance; |
||
238 | |||
239 | /** |
||
240 | * Token types. |
||
241 | * |
||
242 | * @var int |
||
243 | */ |
||
244 | const T_UNQUOTED_STRING = 1; |
||
245 | const T_NUMERIC = 2; |
||
246 | const T_NULL = 4; |
||
247 | const T_BOOL = 8; |
||
248 | const T_MATH = 16; |
||
249 | const T_BREAKCHAR = 32; |
||
250 | |||
251 | /** |
||
252 | * Compiler constructor. |
||
253 | * saves the created instance so that child templates get the same one |
||
254 | */ |
||
255 | public function __construct() |
||
256 | { |
||
257 | self::$instance = $this; |
||
258 | } |
||
259 | |||
260 | /** |
||
261 | * Sets the delimiters to use in the templates. |
||
262 | * delimiters can be multi-character strings but should not be one of those as they will |
||
263 | * make it very hard to work with templates or might even break the compiler entirely : "\", "$", "|", ":" and |
||
264 | * finally "#" only if you intend to use config-vars with the #var# syntax. |
||
265 | * |
||
266 | * @param string $left left delimiter |
||
267 | * @param string $right right delimiter |
||
268 | */ |
||
269 | public function setDelimiters($left, $right) |
||
270 | { |
||
271 | $this->ld = $left; |
||
272 | $this->rd = $right; |
||
273 | $this->ldr = preg_quote($left, '/'); |
||
274 | $this->rdr = preg_quote($right, '/'); |
||
275 | } |
||
276 | |||
277 | /** |
||
278 | * Returns the left and right template delimiters. |
||
279 | * |
||
280 | * @return array containing the left and the right delimiters |
||
281 | */ |
||
282 | public function getDelimiters() |
||
283 | { |
||
284 | return array($this->ld, $this->rd); |
||
285 | } |
||
286 | |||
287 | /** |
||
288 | * Sets the way to handle nested comments, if set to true |
||
289 | * {* foo {* some other *} comment *} will be stripped correctly. |
||
290 | * if false it will remove {* foo {* some other *} and leave "comment *}" alone, |
||
291 | * this is the default behavior |
||
292 | * |
||
293 | * @param bool $allow allow nested comments or not, defaults to true (but the default internal value is false) |
||
294 | */ |
||
295 | public function setNestedCommentsHandling($allow = true) |
||
296 | { |
||
297 | $this->allowNestedComments = (bool)$allow; |
||
298 | } |
||
299 | |||
300 | /** |
||
301 | * Returns the nested comments handling setting. |
||
302 | * |
||
303 | * @see setNestedCommentsHandling |
||
304 | * @return bool true if nested comments are allowed |
||
305 | */ |
||
306 | public function getNestedCommentsHandling() |
||
307 | { |
||
308 | return $this->allowNestedComments; |
||
309 | } |
||
310 | |||
311 | /** |
||
312 | * Sets the tag openings handling strictness, if set to true, template tags can |
||
313 | * contain spaces before the first function/string/variable such as { $foo} is valid. |
||
314 | * if set to false (default setting), { $foo} is invalid but that is however a good thing |
||
315 | * as it allows css (i.e. #foo { color:red; }) to be parsed silently without triggering |
||
316 | * an error, same goes for javascript. |
||
317 | * |
||
318 | * @param bool $allow true to allow loose handling, false to restore default setting |
||
319 | */ |
||
320 | public function setLooseOpeningHandling($allow = false) |
||
321 | { |
||
322 | $this->allowLooseOpenings = (bool)$allow; |
||
323 | } |
||
324 | |||
325 | /** |
||
326 | * Returns the tag openings handling strictness setting. |
||
327 | * |
||
328 | * @see setLooseOpeningHandling |
||
329 | * @return bool true if loose tags are allowed |
||
330 | */ |
||
331 | public function getLooseOpeningHandling() |
||
332 | { |
||
333 | return $this->allowLooseOpenings; |
||
334 | } |
||
335 | |||
336 | /** |
||
337 | * Changes the auto escape setting. |
||
338 | * if enabled, the compiler will automatically html-escape variables, |
||
339 | * unless they are passed through the safe function such as {$var|safe} |
||
340 | * or {safe $var} |
||
341 | * default setting is disabled/false |
||
342 | * |
||
343 | * @param bool $enabled set to true to enable, false to disable |
||
344 | */ |
||
345 | public function setAutoEscape($enabled) |
||
346 | { |
||
347 | $this->autoEscape = (bool)$enabled; |
||
348 | } |
||
349 | |||
350 | /** |
||
351 | * Returns the auto escape setting. |
||
352 | * default setting is disabled/false |
||
353 | * |
||
354 | * @return bool |
||
355 | */ |
||
356 | public function getAutoEscape() |
||
357 | { |
||
358 | return $this->autoEscape; |
||
359 | } |
||
360 | |||
361 | /** |
||
362 | * Adds a preprocessor to the compiler, it will be called |
||
363 | * before the template is compiled. |
||
364 | * |
||
365 | * @param mixed $callback either a valid callback to the preprocessor or a simple name if the autoload is set to |
||
366 | * true |
||
367 | * @param bool $autoload if set to true, the preprocessor is auto-loaded from one of the plugin directories, else |
||
368 | * you must provide a valid callback |
||
369 | */ |
||
370 | View Code Duplication | public function addPreProcessor($callback, $autoload = false) |
|
371 | { |
||
372 | if ($autoload) { |
||
373 | $name = str_replace(Core::NAMESPACE_PLUGINS_PROCESSORS, '', Core::toCamelCase($callback)); |
||
374 | $class = Core::NAMESPACE_PLUGINS_PROCESSORS . $name; |
||
375 | |||
376 | if (class_exists($class)) { |
||
377 | $callback = array(new $class($this), 'process'); |
||
378 | } elseif (function_exists($class)) { |
||
379 | $callback = $class; |
||
380 | } else { |
||
381 | $callback = array('autoload' => true, 'class' => $class, 'name' => $name); |
||
382 | } |
||
383 | |||
384 | $this->processors['pre'][] = $callback; |
||
385 | } else { |
||
386 | $this->processors['pre'][] = $callback; |
||
387 | } |
||
388 | } |
||
389 | |||
390 | /** |
||
391 | * Removes a preprocessor from the compiler. |
||
392 | * |
||
393 | * @param mixed $callback either a valid callback to the preprocessor or a simple name if it was autoloaded |
||
394 | */ |
||
395 | View Code Duplication | public function removePreProcessor($callback) |
|
396 | { |
||
397 | if (($index = array_search($callback, $this->processors['pre'], true)) !== false) { |
||
398 | unset($this->processors['pre'][$index]); |
||
399 | } elseif (($index = array_search(Core::NAMESPACE_PLUGINS_PROCESSORS . str_replace(Core::NAMESPACE_PLUGINS_PROCESSORS, '', |
||
400 | $callback), |
||
401 | $this->processors['pre'], true)) !== false) { |
||
402 | unset($this->processors['pre'][$index]); |
||
403 | } else { |
||
404 | $class = Core::NAMESPACE_PLUGINS_PROCESSORS . str_replace(Core::NAMESPACE_PLUGINS_PROCESSORS, '', $callback); |
||
405 | foreach ($this->processors['pre'] as $index => $proc) { |
||
406 | if (is_array($proc) && ($proc[0] instanceof $class) || (isset($proc['class']) && $proc['class'] == $class)) { |
||
407 | unset($this->processors['pre'][$index]); |
||
408 | break; |
||
409 | } |
||
410 | } |
||
411 | } |
||
412 | } |
||
413 | |||
414 | /** |
||
415 | * Adds a postprocessor to the compiler, it will be called |
||
416 | * before the template is compiled. |
||
417 | * |
||
418 | * @param mixed $callback either a valid callback to the postprocessor or a simple name if the autoload is set to |
||
419 | * true |
||
420 | * @param bool $autoload if set to true, the postprocessor is auto-loaded from one of the plugin directories, else |
||
421 | * you must provide a valid callback |
||
422 | */ |
||
423 | View Code Duplication | public function addPostProcessor($callback, $autoload = false) |
|
424 | { |
||
425 | if ($autoload) { |
||
426 | $name = str_replace(Core::NAMESPACE_PLUGINS_PROCESSORS, '', $callback); |
||
427 | $class = Core::NAMESPACE_PLUGINS_PROCESSORS . Core::toCamelCase($name); |
||
428 | |||
429 | if (class_exists($class)) { |
||
430 | $callback = array(new $class($this), 'process'); |
||
431 | } elseif (function_exists($class)) { |
||
432 | $callback = $class; |
||
433 | } else { |
||
434 | $callback = array('autoload' => true, 'class' => $class, 'name' => $name); |
||
435 | } |
||
436 | |||
437 | $this->processors['post'][] = $callback; |
||
438 | } else { |
||
439 | $this->processors['post'][] = $callback; |
||
440 | } |
||
441 | } |
||
442 | |||
443 | /** |
||
444 | * Removes a postprocessor from the compiler. |
||
445 | * |
||
446 | * @param mixed $callback either a valid callback to the postprocessor or a simple name if it was autoloaded |
||
447 | */ |
||
448 | View Code Duplication | public function removePostProcessor($callback) |
|
449 | { |
||
450 | if (($index = array_search($callback, $this->processors['post'], true)) !== false) { |
||
451 | unset($this->processors['post'][$index]); |
||
452 | } elseif (($index = array_search(Core::NAMESPACE_PLUGINS_PROCESSORS . str_replace(Core::NAMESPACE_PLUGINS_PROCESSORS, '', |
||
453 | $callback), |
||
454 | $this->processors['post'], true)) !== false) { |
||
455 | unset($this->processors['post'][$index]); |
||
456 | } else { |
||
457 | $class = Core::NAMESPACE_PLUGINS_PROCESSORS . str_replace(Core::NAMESPACE_PLUGINS_PROCESSORS, '', $callback); |
||
458 | foreach ($this->processors['post'] as $index => $proc) { |
||
459 | if (is_array($proc) && ($proc[0] instanceof $class) || (isset($proc['class']) && $proc['class'] == $class)) { |
||
460 | unset($this->processors['post'][$index]); |
||
461 | break; |
||
462 | } |
||
463 | } |
||
464 | } |
||
465 | } |
||
466 | |||
467 | /** |
||
468 | * Internal function to autoload processors at runtime if required. |
||
469 | * |
||
470 | * @param string $class the class/function name |
||
471 | * @param string $name the plugin name (without Dwoo_Plugin_ prefix) |
||
472 | * |
||
473 | * @return array|string |
||
474 | * @throws Exception |
||
475 | */ |
||
476 | protected function loadProcessor($class, $name) |
||
477 | { |
||
478 | if (!class_exists($class) && !function_exists($class)) { |
||
479 | try { |
||
480 | $this->getDwoo()->getLoader()->loadPlugin($name); |
||
481 | } |
||
482 | catch (Exception $e) { |
||
483 | throw new Exception('Processor ' . $name . ' could not be found in your plugin directories, please ensure it is in a file named ' . $name . '.php in the plugin directory'); |
||
484 | } |
||
485 | } |
||
486 | |||
487 | if (class_exists($class)) { |
||
488 | return array(new $class($this), 'process'); |
||
489 | } |
||
490 | |||
491 | if (function_exists($class)) { |
||
492 | return $class; |
||
493 | } |
||
494 | |||
495 | throw new Exception('Wrong processor name, when using autoload the processor must be in one of your plugin dir as "name.php" containg a class or function named "Dwoo_Processor_name"'); |
||
496 | } |
||
497 | |||
498 | /** |
||
499 | * Adds an used plugin, this is reserved for use by the {template} plugin. |
||
500 | * this is required so that plugin loading bubbles up from loaded |
||
501 | * template files to the current one |
||
502 | * |
||
503 | * @private |
||
504 | * |
||
505 | * @param string $name function name |
||
506 | * @param int $type plugin type (Core::*_PLUGIN) |
||
507 | */ |
||
508 | public function addUsedPlugin($name, $type) |
||
509 | { |
||
510 | $this->usedPlugins[$name] = $type; |
||
511 | } |
||
512 | |||
513 | /** |
||
514 | * Returns all the plugins this template uses. |
||
515 | * |
||
516 | * @private |
||
517 | * @return array the list of used plugins in the parsed template |
||
518 | */ |
||
519 | public function getUsedPlugins() |
||
520 | { |
||
521 | return $this->usedPlugins; |
||
522 | } |
||
523 | |||
524 | /** |
||
525 | * Adds a template plugin, this is reserved for use by the {template} plugin. |
||
526 | * this is required because the template functions are not declared yet |
||
527 | * during compilation, so we must have a way of validating their argument |
||
528 | * signature without using the reflection api |
||
529 | * |
||
530 | * @private |
||
531 | * |
||
532 | * @param string $name function name |
||
533 | * @param array $params parameter array to help validate the function call |
||
534 | * @param string $uuid unique id of the function |
||
535 | * @param string $body function php code |
||
536 | */ |
||
537 | public function addTemplatePlugin($name, array $params, $uuid, $body = null) |
||
538 | { |
||
539 | $this->templatePlugins[$name] = array('params' => $params, 'body' => $body, 'uuid' => $uuid); |
||
540 | } |
||
541 | |||
542 | /** |
||
543 | * Returns all the parsed sub-templates. |
||
544 | * |
||
545 | * @private |
||
546 | * @return array the parsed sub-templates |
||
547 | */ |
||
548 | public function getTemplatePlugins() |
||
549 | { |
||
550 | return $this->templatePlugins; |
||
551 | } |
||
552 | |||
553 | /** |
||
554 | * Marks a template plugin as being called, which means its source must be included in the compiled template. |
||
555 | * |
||
556 | * @param string $name function name |
||
557 | */ |
||
558 | public function useTemplatePlugin($name) |
||
559 | { |
||
560 | $this->templatePlugins[$name]['called'] = true; |
||
561 | } |
||
562 | |||
563 | /** |
||
564 | * Adds the custom plugins loaded into Dwoo to the compiler so it can load them. |
||
565 | * |
||
566 | * @see Core::addPlugin |
||
567 | * |
||
568 | * @param array $customPlugins an array of custom plugins |
||
569 | */ |
||
570 | public function setCustomPlugins(array $customPlugins) |
||
571 | { |
||
572 | $this->customPlugins = $customPlugins; |
||
573 | } |
||
574 | |||
575 | /** |
||
576 | * Sets the security policy object to enforce some php security settings. |
||
577 | * use this if untrusted persons can modify templates, |
||
578 | * set it on the Dwoo object as it will be passed onto the compiler automatically |
||
579 | * |
||
580 | * @param SecurityPolicy $policy the security policy object |
||
581 | */ |
||
582 | public function setSecurityPolicy(SecurityPolicy $policy = null) |
||
583 | { |
||
584 | $this->securityPolicy = $policy; |
||
585 | } |
||
586 | |||
587 | /** |
||
588 | * Returns the current security policy object or null by default. |
||
589 | * |
||
590 | * @return SecurityPolicy|null the security policy object if any |
||
591 | */ |
||
592 | public function getSecurityPolicy() |
||
593 | { |
||
594 | return $this->securityPolicy; |
||
595 | } |
||
596 | |||
597 | /** |
||
598 | * Sets the pointer position. |
||
599 | * |
||
600 | * @param int $position the new pointer position |
||
601 | * @param bool $isOffset if set to true, the position acts as an offset and not an absolute position |
||
602 | */ |
||
603 | public function setPointer($position, $isOffset = false) |
||
604 | { |
||
605 | if ($isOffset) { |
||
606 | $this->pointer += $position; |
||
607 | } else { |
||
608 | $this->pointer = $position; |
||
609 | } |
||
610 | } |
||
611 | |||
612 | /** |
||
613 | * Returns the current pointer position, only available during compilation of a template. |
||
614 | * |
||
615 | * @return int |
||
616 | */ |
||
617 | public function getPointer() |
||
618 | { |
||
619 | return $this->pointer; |
||
620 | } |
||
621 | |||
622 | /** |
||
623 | * Sets the line number. |
||
624 | * |
||
625 | * @param int $number the new line number |
||
626 | * @param bool $isOffset if set to true, the position acts as an offset and not an absolute position |
||
627 | */ |
||
628 | public function setLine($number, $isOffset = false) |
||
629 | { |
||
630 | if ($isOffset) { |
||
631 | $this->line += $number; |
||
632 | } else { |
||
633 | $this->line = $number; |
||
634 | } |
||
635 | } |
||
636 | |||
637 | /** |
||
638 | * Returns the current line number, only available during compilation of a template. |
||
639 | * |
||
640 | * @return int |
||
641 | */ |
||
642 | public function getLine() |
||
643 | { |
||
644 | return $this->line; |
||
645 | } |
||
646 | |||
647 | /** |
||
648 | * Returns the dwoo object that initiated this template compilation, only available during compilation of a |
||
649 | * template. |
||
650 | * |
||
651 | * @return Core |
||
652 | */ |
||
653 | public function getDwoo() |
||
654 | { |
||
655 | return $this->dwoo; |
||
656 | } |
||
657 | |||
658 | /** |
||
659 | * Overwrites the template that is being compiled. |
||
660 | * |
||
661 | * @param string $newSource the template source that must replace the current one |
||
662 | * @param bool $fromPointer if set to true, only the source from the current pointer position is replaced |
||
663 | * |
||
664 | * @return void |
||
665 | */ |
||
666 | public function setTemplateSource($newSource, $fromPointer = false) |
||
667 | { |
||
668 | if ($fromPointer === true) { |
||
669 | $this->templateSource = substr($this->templateSource, 0, $this->pointer) . $newSource; |
||
670 | } else { |
||
671 | $this->templateSource = $newSource; |
||
672 | } |
||
673 | } |
||
674 | |||
675 | /** |
||
676 | * Returns the template that is being compiled. |
||
677 | * |
||
678 | * @param mixed $fromPointer if set to true, only the source from the current pointer |
||
679 | * position is returned, if a number is given it overrides the current pointer |
||
680 | * |
||
681 | * @return string the template or partial template |
||
682 | */ |
||
683 | public function getTemplateSource($fromPointer = false) |
||
684 | { |
||
685 | if ($fromPointer === true) { |
||
686 | return substr($this->templateSource, $this->pointer); |
||
687 | } elseif (is_numeric($fromPointer)) { |
||
688 | return substr($this->templateSource, $fromPointer); |
||
689 | } else { |
||
690 | return $this->templateSource; |
||
691 | } |
||
692 | } |
||
693 | |||
694 | /** |
||
695 | * Resets the compilation pointer, effectively restarting the compilation process. |
||
696 | * this is useful if a plugin modifies the template source since it might need to be recompiled |
||
697 | */ |
||
698 | public function recompile() |
||
699 | { |
||
700 | $this->setPointer(0); |
||
701 | } |
||
702 | |||
703 | /** |
||
704 | * Compiles the provided string down to php code. |
||
705 | * |
||
706 | * @param Core $dwoo |
||
707 | * @param ITemplate $template the template to compile |
||
708 | * |
||
709 | * @return string a compiled php string |
||
710 | * @throws CompilationException |
||
711 | */ |
||
712 | public function compile(Core $dwoo, ITemplate $template) |
||
713 | { |
||
714 | // init vars |
||
715 | // $compiled = ''; |
||
716 | $tpl = $template->getSource(); |
||
717 | $ptr = 0; |
||
718 | $this->dwoo = $dwoo; |
||
719 | $this->template = $template; |
||
720 | $this->templateSource = &$tpl; |
||
721 | $this->pointer = &$ptr; |
||
722 | |||
723 | while (true) { |
||
724 | // if pointer is at the beginning, reset everything, that allows a plugin to externally reset the compiler if everything must be reparsed |
||
725 | if ($ptr === 0) { |
||
726 | // resets variables |
||
727 | $this->usedPlugins = array(); |
||
728 | $this->data = array(); |
||
729 | $this->scope = &$this->data; |
||
730 | $this->scopeTree = array(); |
||
731 | $this->stack = array(); |
||
732 | $this->line = 1; |
||
733 | $this->templatePlugins = array(); |
||
734 | // add top level block |
||
735 | $compiled = $this->addBlock('TopLevelBlock', array(), 0); |
||
736 | $this->stack[0]['buffer'] = ''; |
||
737 | |||
738 | if ($this->debug) { |
||
739 | echo "\n"; |
||
740 | echo 'COMPILER INIT' . "\n"; |
||
741 | } |
||
742 | |||
743 | if ($this->debug) { |
||
744 | echo 'PROCESSING PREPROCESSORS (' . count($this->processors['pre']) . ')' . "\n"; |
||
745 | } |
||
746 | |||
747 | // runs preprocessors |
||
748 | View Code Duplication | foreach ($this->processors['pre'] as $preProc) { |
|
749 | if (is_array($preProc) && isset($preProc['autoload'])) { |
||
750 | $preProc = $this->loadProcessor($preProc['class'], $preProc['name']); |
||
751 | } |
||
752 | if (is_array($preProc) && $preProc[0] instanceof Processor) { |
||
753 | $tpl = call_user_func($preProc, $tpl); |
||
754 | } else { |
||
755 | $tpl = call_user_func($preProc, $this, $tpl); |
||
756 | } |
||
757 | } |
||
758 | unset($preProc); |
||
759 | |||
760 | // show template source if debug |
||
761 | if ($this->debug) { |
||
762 | echo '<pre>'.print_r(htmlentities($tpl), true).'</pre>'."\n"; |
||
763 | } |
||
764 | |||
765 | // strips php tags if required by the security policy |
||
766 | if ($this->securityPolicy !== null) { |
||
767 | $search = array('{<\?php.*?\?>}'); |
||
768 | if (ini_get('short_open_tags')) { |
||
769 | $search = array('{<\?.*?\?>}', '{<%.*?%>}'); |
||
770 | } |
||
771 | switch ($this->securityPolicy->getPhpHandling()) { |
||
772 | case SecurityPolicy::PHP_ALLOW: |
||
773 | break; |
||
774 | case SecurityPolicy::PHP_ENCODE: |
||
775 | $tpl = preg_replace_callback($search, array($this, 'phpTagEncodingHelper'), $tpl); |
||
776 | break; |
||
777 | case SecurityPolicy::PHP_REMOVE: |
||
778 | $tpl = preg_replace($search, '', $tpl); |
||
779 | } |
||
780 | } |
||
781 | } |
||
782 | |||
783 | $pos = strpos($tpl, $this->ld, $ptr); |
||
784 | |||
785 | if ($pos === false) { |
||
786 | $this->push(substr($tpl, $ptr), 0); |
||
787 | break; |
||
788 | } elseif (substr($tpl, $pos - 1, 1) === '\\' && substr($tpl, $pos - 2, 1) !== '\\') { |
||
789 | $this->push(substr($tpl, $ptr, $pos - $ptr - 1) . $this->ld); |
||
790 | $ptr = $pos + strlen($this->ld); |
||
791 | } elseif (preg_match('/^' . $this->ldr . ($this->allowLooseOpenings ? '\s*' : '') . 'literal' . ($this->allowLooseOpenings ? '\s*' : '') . $this->rdr . '/s', substr($tpl, $pos), $litOpen)) { |
||
792 | if (!preg_match('/' . $this->ldr . ($this->allowLooseOpenings ? '\s*' : '') . '\/literal' . ($this->allowLooseOpenings ? '\s*' : '') . $this->rdr . '/s', $tpl, $litClose, PREG_OFFSET_CAPTURE, $pos)) { |
||
793 | throw new CompilationException($this, 'The {literal} blocks must be closed explicitly with {/literal}'); |
||
794 | } |
||
795 | $endpos = $litClose[0][1]; |
||
796 | $this->push(substr($tpl, $ptr, $pos - $ptr) . substr($tpl, $pos + strlen($litOpen[0]), $endpos - $pos - strlen($litOpen[0]))); |
||
797 | $ptr = $endpos + strlen($litClose[0][0]); |
||
798 | } else { |
||
799 | if (substr($tpl, $pos - 2, 1) === '\\' && substr($tpl, $pos - 1, 1) === '\\') { |
||
800 | $this->push(substr($tpl, $ptr, $pos - $ptr - 1)); |
||
801 | $ptr = $pos; |
||
802 | } |
||
803 | |||
804 | $this->push(substr($tpl, $ptr, $pos - $ptr)); |
||
805 | $ptr = $pos; |
||
806 | |||
807 | $pos += strlen($this->ld); |
||
808 | if ($this->allowLooseOpenings) { |
||
809 | while (substr($tpl, $pos, 1) === ' ') { |
||
810 | $pos += 1; |
||
811 | } |
||
812 | } else { |
||
813 | if (substr($tpl, $pos, 1) === ' ' || substr($tpl, $pos, 1) === "\r" || substr($tpl, $pos, 1) === "\n" || substr($tpl, $pos, 1) === "\t") { |
||
814 | $ptr = $pos; |
||
815 | $this->push($this->ld); |
||
816 | continue; |
||
817 | } |
||
818 | } |
||
819 | |||
820 | // check that there is an end tag present |
||
821 | View Code Duplication | if (strpos($tpl, $this->rd, $pos) === false) { |
|
822 | throw new CompilationException($this, 'A template tag was not closed, started with "' . substr($tpl, $ptr, 30) . '"'); |
||
823 | } |
||
824 | |||
825 | $ptr += strlen($this->ld); |
||
826 | $subptr = $ptr; |
||
827 | |||
828 | while (true) { |
||
829 | $parsed = $this->parse($tpl, $subptr, null, false, 'root', $subptr); |
||
830 | |||
831 | // reload loop if the compiler was reset |
||
832 | if ($ptr === 0) { |
||
833 | continue 2; |
||
834 | } |
||
835 | |||
836 | $len = $subptr - $ptr; |
||
837 | $this->push($parsed, substr_count(substr($tpl, $ptr, $len), "\n")); |
||
838 | $ptr += $len; |
||
839 | |||
840 | if ($parsed === false) { |
||
841 | break; |
||
842 | } |
||
843 | } |
||
844 | } |
||
845 | } |
||
846 | |||
847 | $compiled .= $this->removeBlock('TopLevelBlock'); |
||
848 | |||
849 | if ($this->debug) { |
||
850 | echo 'PROCESSING POSTPROCESSORS' . "\n"; |
||
851 | } |
||
852 | |||
853 | View Code Duplication | foreach ($this->processors['post'] as $postProc) { |
|
854 | if (is_array($postProc) && isset($postProc['autoload'])) { |
||
855 | $postProc = $this->loadProcessor($postProc['class'], $postProc['name']); |
||
856 | } |
||
857 | if (is_array($postProc) && $postProc[0] instanceof Processor) { |
||
858 | $compiled = call_user_func($postProc, $compiled); |
||
859 | } else { |
||
860 | $compiled = call_user_func($postProc, $this, $compiled); |
||
861 | } |
||
862 | } |
||
863 | unset($postProc); |
||
864 | |||
865 | if ($this->debug) { |
||
866 | echo 'COMPILATION COMPLETE : MEM USAGE : ' . memory_get_usage() . "\n"; |
||
867 | } |
||
868 | |||
869 | $output = "<?php\n/* template head */\n"; |
||
870 | |||
871 | // build plugin preloader |
||
872 | foreach ($this->getUsedPlugins() as $plugin => $type) { |
||
873 | if ($type & Core::CUSTOM_PLUGIN) { |
||
874 | continue; |
||
875 | } |
||
876 | |||
877 | switch ($type) { |
||
878 | case Core::BLOCK_PLUGIN: |
||
879 | View Code Duplication | case Core::CLASS_PLUGIN: |
|
880 | if (class_exists('Plugin' . $plugin) !== false) { |
||
881 | $output .= "if (class_exists('" . "Plugin" . $plugin . "')===false)". |
||
882 | "\n\t\$this->getLoader()->loadPlugin('Plugin$plugin');\n"; |
||
883 | } else { |
||
884 | $output .= "if (class_exists('" . Core::NAMESPACE_PLUGINS_BLOCKS . "Plugin" . $plugin . "')===false)". |
||
885 | "\n\t\$this->getLoader()->loadPlugin('Plugin$plugin');\n"; |
||
886 | } |
||
887 | break; |
||
888 | View Code Duplication | case Core::FUNC_PLUGIN: |
|
889 | if (function_exists('Plugin' . $plugin) !== false) { |
||
890 | $output .= "if (function_exists('" . "Plugin" . $plugin . "')===false)". |
||
891 | "\n\t\$this->getLoader()->loadPlugin('Plugin$plugin');\n"; |
||
892 | } else { |
||
893 | $output .= "if (function_exists('" . Core::NAMESPACE_PLUGINS_FUNCTIONS . "Plugin" . $plugin . "')===false)". |
||
894 | "\n\t\$this->getLoader()->loadPlugin('Plugin$plugin');\n"; |
||
895 | } |
||
896 | break; |
||
897 | case Core::SMARTY_MODIFIER: |
||
898 | $output .= "if (function_exists('smarty_modifier_$plugin')===false)". |
||
899 | "\n\t\$this->getLoader()->loadPlugin('$plugin');\n"; |
||
900 | break; |
||
901 | case Core::SMARTY_FUNCTION: |
||
902 | $output .= "if (function_exists('smarty_function_$plugin')===false)". |
||
903 | "\n\t\$this->getLoader()->loadPlugin('$plugin');\n"; |
||
904 | break; |
||
905 | case Core::SMARTY_BLOCK: |
||
906 | $output .= "if (function_exists('smarty_block_$plugin')===false)". |
||
907 | "\n\t\$this->getLoader()->loadPlugin('$plugin');\n"; |
||
908 | break; |
||
909 | case Core::PROXY_PLUGIN: |
||
910 | $output .= $this->getDwoo()->getPluginProxy()->getLoader($plugin); |
||
911 | break; |
||
912 | default: |
||
913 | throw new CompilationException($this, 'Type error for ' . $plugin . ' with type' . $type); |
||
914 | } |
||
915 | } |
||
916 | |||
917 | foreach ($this->templatePlugins as $function => $attr) { |
||
918 | if (isset($attr['called']) && $attr['called'] === true && !isset($attr['checked'])) { |
||
919 | $this->resolveSubTemplateDependencies($function); |
||
920 | } |
||
921 | } |
||
922 | foreach ($this->templatePlugins as $function) { |
||
923 | if (isset($function['called']) && $function['called'] === true) { |
||
924 | $output .= $function['body'] . PHP_EOL; |
||
925 | } |
||
926 | } |
||
927 | |||
928 | $output .= $compiled . "\n?>"; |
||
929 | |||
930 | $output = preg_replace('/(?<!;|\}|\*\/|\n|\{)(\s*' . preg_quote(self::PHP_CLOSE, '/') . preg_quote(self::PHP_OPEN, '/') . ')/', ";\n", $output); |
||
931 | $output = str_replace(self::PHP_CLOSE . self::PHP_OPEN, "\n", $output); |
||
932 | |||
933 | // handle <?xml tag at the beginning |
||
934 | $output = preg_replace('#(/\* template body \*/ \?>\s*)<\?xml#is', '$1<?php echo \'<?xml\'; ?>', $output); |
||
935 | |||
936 | // add another line break after PHP closing tags that have a line break following, |
||
937 | // as we do not know whether it's intended, and PHP will strip it otherwise |
||
938 | $output = preg_replace('/(?<!"|<\?xml)\s*\?>\n/', '$0' . "\n", $output); |
||
939 | |||
940 | if ($this->debug) { |
||
941 | echo '=============================================================================================' . "\n"; |
||
942 | $lines = preg_split('{\r\n|\n|<br />}', $output); |
||
943 | array_shift($lines); |
||
944 | foreach ($lines as $i => $line) { |
||
945 | echo ($i + 1) . '. ' . $line . "\r\n"; |
||
946 | } |
||
947 | echo '=============================================================================================' . "\n"; |
||
948 | } |
||
949 | |||
950 | $this->template = $this->dwoo = null; |
||
951 | $tpl = null; |
||
952 | |||
953 | return $output; |
||
954 | } |
||
955 | |||
956 | /** |
||
957 | * Checks what sub-templates are used in every sub-template so that we're sure they are all compiled. |
||
958 | * |
||
959 | * @param string $function the sub-template name |
||
960 | */ |
||
961 | protected function resolveSubTemplateDependencies($function) |
||
962 | { |
||
963 | if ($this->debug) { |
||
964 | echo 'Compiler::' . __FUNCTION__ . "\n"; |
||
965 | } |
||
966 | |||
967 | $body = $this->templatePlugins[$function]['body']; |
||
968 | foreach ($this->templatePlugins as $func => $attr) { |
||
969 | if ($func !== $function && !isset($attr['called']) && strpos($body, Core::NAMESPACE_PLUGINS_FUNCTIONS . |
||
970 | 'Plugin' . Core::toCamelCase($func)) !== false) { |
||
971 | $this->templatePlugins[$func]['called'] = true; |
||
972 | $this->resolveSubTemplateDependencies($func); |
||
973 | } |
||
974 | } |
||
975 | $this->templatePlugins[$function]['checked'] = true; |
||
976 | } |
||
977 | |||
978 | /** |
||
979 | * Adds compiled content to the current block. |
||
980 | * |
||
981 | * @param string $content the content to push |
||
982 | * @param int $lineCount newlines count in content, optional |
||
983 | * |
||
984 | * @throws CompilationException |
||
985 | */ |
||
986 | public function push($content, $lineCount = null) |
||
987 | { |
||
988 | if ($lineCount === null) { |
||
989 | $lineCount = substr_count($content, "\n"); |
||
990 | } |
||
991 | |||
992 | if ($this->curBlock['buffer'] === null && count($this->stack) > 1) { |
||
993 | // buffer is not initialized yet (the block has just been created) |
||
994 | $this->stack[count($this->stack) - 2]['buffer'] .= (string)$content; |
||
995 | $this->curBlock['buffer'] = ''; |
||
996 | } else { |
||
997 | if (!isset($this->curBlock['buffer'])) { |
||
998 | throw new CompilationException($this, 'The template has been closed too early, you probably have an extra block-closing tag somewhere'); |
||
999 | } |
||
1000 | // append current content to current block's buffer |
||
1001 | $this->curBlock['buffer'] .= (string)$content; |
||
1002 | } |
||
1003 | $this->line += $lineCount; |
||
1004 | } |
||
1005 | |||
1006 | /** |
||
1007 | * Sets the scope. |
||
1008 | * set to null if the scope becomes "unstable" (i.e. too variable or unknown) so that |
||
1009 | * variables are compiled in a more evaluative way than just $this->scope['key'] |
||
1010 | * |
||
1011 | * @param mixed $scope a string i.e. "level1.level2" or an array i.e. array("level1", "level2") |
||
1012 | * @param bool $absolute if true, the scope is set from the top level scope and not from the current scope |
||
1013 | * |
||
1014 | * @return array the current scope tree |
||
1015 | */ |
||
1016 | public function setScope($scope, $absolute = false) |
||
1017 | { |
||
1018 | $old = $this->scopeTree; |
||
1019 | |||
1020 | if ($scope === null) { |
||
1021 | unset($this->scope); |
||
1022 | $this->scope = null; |
||
1023 | } |
||
1024 | |||
1025 | if (is_array($scope) === false) { |
||
1026 | $scope = explode('.', $scope); |
||
1027 | } |
||
1028 | |||
1029 | if ($absolute === true) { |
||
1030 | $this->scope = &$this->data; |
||
1031 | $this->scopeTree = array(); |
||
1032 | } |
||
1033 | |||
1034 | while (($bit = array_shift($scope)) !== null) { |
||
1035 | if ($bit === '_parent' || $bit === '_') { |
||
1036 | array_pop($this->scopeTree); |
||
1037 | reset($this->scopeTree); |
||
1038 | $this->scope = &$this->data; |
||
1039 | $cnt = count($this->scopeTree); |
||
1040 | View Code Duplication | for ($i = 0; $i < $cnt; ++ $i) { |
|
1041 | $this->scope = &$this->scope[$this->scopeTree[$i]]; |
||
1042 | } |
||
1043 | View Code Duplication | } elseif ($bit === '_root' || $bit === '__') { |
|
1044 | $this->scope = &$this->data; |
||
1045 | $this->scopeTree = array(); |
||
1046 | } elseif (isset($this->scope[$bit])) { |
||
1047 | $this->scope = &$this->scope[$bit]; |
||
1048 | $this->scopeTree[] = $bit; |
||
1049 | } else { |
||
1050 | $this->scope[$bit] = array(); |
||
1051 | $this->scope = &$this->scope[$bit]; |
||
1052 | $this->scopeTree[] = $bit; |
||
1053 | } |
||
1054 | } |
||
1055 | |||
1056 | return $old; |
||
1057 | } |
||
1058 | |||
1059 | /** |
||
1060 | * Adds a block to the top of the block stack. |
||
1061 | * |
||
1062 | * @param string $type block type (name) |
||
1063 | * @param array $params the parameters array |
||
1064 | * @param int $paramtype the parameters type (see mapParams), 0, 1 or 2 |
||
1065 | * |
||
1066 | * @return string the preProcessing() method's output |
||
1067 | */ |
||
1068 | public function addBlock($type, array $params, $paramtype) |
||
1069 | { |
||
1070 | if ($this->debug) { |
||
1071 | echo 'Compiler::' . __FUNCTION__ . "\n"; |
||
1072 | } |
||
1073 | |||
1074 | $class = Core::NAMESPACE_PLUGINS_BLOCKS . 'Plugin' . Core::toCamelCase($type); |
||
1075 | if (class_exists($class) === false) { |
||
1076 | $this->getDwoo()->getLoader()->loadPlugin($type); |
||
1077 | } |
||
1078 | $params = $this->mapParams($params, array($class, 'init'), $paramtype); |
||
1079 | |||
1080 | $this->stack[] = array( |
||
1081 | 'type' => $type, |
||
1082 | 'params' => $params, |
||
1083 | 'custom' => false, |
||
1084 | 'class' => $class, |
||
1085 | 'buffer' => null |
||
1086 | ); |
||
1087 | $this->curBlock = &$this->stack[count($this->stack) - 1]; |
||
1088 | |||
1089 | return call_user_func(array($class, 'preProcessing'), $this, $params, '', '', $type); |
||
1090 | } |
||
1091 | |||
1092 | /** |
||
1093 | * Adds a custom block to the top of the block stack. |
||
1094 | * |
||
1095 | * @param string $type block type (name) |
||
1096 | * @param array $params the parameters array |
||
1097 | * @param int $paramtype the parameters type (see mapParams), 0, 1 or 2 |
||
1098 | * |
||
1099 | * @return string the preProcessing() method's output |
||
1100 | */ |
||
1101 | public function addCustomBlock($type, array $params, $paramtype) |
||
1102 | { |
||
1103 | $callback = $this->customPlugins[$type]['callback']; |
||
1104 | if (is_array($callback)) { |
||
1105 | $class = is_object($callback[0]) ? get_class($callback[0]) : $callback[0]; |
||
1106 | } else { |
||
1107 | $class = $callback; |
||
1108 | } |
||
1109 | |||
1110 | $params = $this->mapParams($params, array($class, 'init'), $paramtype); |
||
1111 | |||
1112 | $this->stack[] = array( |
||
1113 | 'type' => $type, |
||
1114 | 'params' => $params, |
||
1115 | 'custom' => true, |
||
1116 | 'class' => $class, |
||
1117 | 'buffer' => null |
||
1118 | ); |
||
1119 | $this->curBlock = &$this->stack[count($this->stack) - 1]; |
||
1120 | |||
1121 | return call_user_func(array($class, 'preProcessing'), $this, $params, '', '', $type); |
||
1122 | } |
||
1123 | |||
1124 | /** |
||
1125 | * Injects a block at the top of the plugin stack without calling its preProcessing method. |
||
1126 | * used by {else} blocks to re-add themselves after having closed everything up to their parent |
||
1127 | * |
||
1128 | * @param string $type block type (name) |
||
1129 | * @param array $params parameters array |
||
1130 | */ |
||
1131 | public function injectBlock($type, array $params) |
||
1132 | { |
||
1133 | if ($this->debug) { |
||
1134 | echo 'Compiler::' . __FUNCTION__ . "\n"; |
||
1135 | } |
||
1136 | |||
1137 | $class = Core::NAMESPACE_PLUGINS_BLOCKS . 'Plugin' . Core::toCamelCase($type); |
||
1138 | if (class_exists($class) === false) { |
||
1139 | $this->getDwoo()->getLoader()->loadPlugin($type); |
||
1140 | } |
||
1141 | $this->stack[] = array( |
||
1142 | 'type' => $type, |
||
1143 | 'params' => $params, |
||
1144 | 'custom' => false, |
||
1145 | 'class' => $class, |
||
1146 | 'buffer' => null |
||
1147 | ); |
||
1148 | $this->curBlock = &$this->stack[count($this->stack) - 1]; |
||
1149 | } |
||
1150 | |||
1151 | /** |
||
1152 | * Removes the closest-to-top block of the given type and all other |
||
1153 | * blocks encountered while going down the block stack. |
||
1154 | * |
||
1155 | * @param string $type block type (name) |
||
1156 | * |
||
1157 | * @return string the output of all postProcessing() method's return values of the closed blocks |
||
1158 | * @throws CompilationException |
||
1159 | */ |
||
1160 | public function removeBlock($type) |
||
1161 | { |
||
1162 | if ($this->debug) { |
||
1163 | echo 'Compiler::' . __FUNCTION__ . "\n"; |
||
1164 | } |
||
1165 | |||
1166 | $output = ''; |
||
1167 | |||
1168 | $pluginType = $this->getPluginType($type); |
||
1169 | if ($pluginType & Core::SMARTY_BLOCK) { |
||
1170 | $type = 'Smartyinterface'; |
||
1171 | } |
||
1172 | while (true) { |
||
1173 | while ($top = array_pop($this->stack)) { |
||
1174 | View Code Duplication | if ($top['custom']) { |
|
1175 | $class = $top['class']; |
||
1176 | } else { |
||
1177 | $class = Core::NAMESPACE_PLUGINS_BLOCKS . 'Plugin' . Core::toCamelCase($top['type']); |
||
1178 | } |
||
1179 | if (count($this->stack)) { |
||
1180 | $this->curBlock = &$this->stack[count($this->stack) - 1]; |
||
1181 | $this->push(call_user_func(array( |
||
1182 | $class, |
||
1183 | 'postProcessing' |
||
1184 | ), $this, $top['params'], '', '', $top['buffer']), 0); |
||
1185 | } else { |
||
1186 | $null = null; |
||
1187 | $this->curBlock = &$null; |
||
1188 | $output = call_user_func( |
||
1189 | array( |
||
1190 | $class, |
||
1191 | 'postProcessing' |
||
1192 | ), $this, $top['params'], '', '', $top['buffer'] |
||
1193 | ); |
||
1194 | } |
||
1195 | |||
1196 | if ($top['type'] === $type) { |
||
1197 | break 2; |
||
1198 | } |
||
1199 | } |
||
1200 | |||
1201 | throw new CompilationException($this, 'Syntax malformation, a block of type "' . $type . '" was closed but was not opened'); |
||
1202 | break; |
||
1203 | } |
||
1204 | |||
1205 | return $output; |
||
1206 | } |
||
1207 | |||
1208 | /** |
||
1209 | * Returns a reference to the first block of the given type encountered and |
||
1210 | * optionally closes all blocks until it finds it |
||
1211 | * this is mainly used by {else} plugins to close everything that was opened |
||
1212 | * between their parent and themselves. |
||
1213 | * |
||
1214 | * @param string $type the block type (name) |
||
1215 | * @param bool $closeAlong whether to close all blocks encountered while going down the block stack or not |
||
1216 | * |
||
1217 | * @return mixed &array the array is as such: array('type'=>pluginName, 'params'=>parameter array, |
||
1218 | * 'custom'=>bool defining whether it's a custom plugin or not, for internal use) |
||
1219 | * @throws CompilationException |
||
1220 | */ |
||
1221 | public function &findBlock($type, $closeAlong = false) |
||
1222 | { |
||
1223 | if ($closeAlong === true) { |
||
1224 | View Code Duplication | while ($b = end($this->stack)) { |
|
1225 | if ($b['type'] === $type) { |
||
1226 | return $this->stack[key($this->stack)]; |
||
1227 | } |
||
1228 | $this->push($this->removeTopBlock(), 0); |
||
1229 | } |
||
1230 | } else { |
||
1231 | end($this->stack); |
||
1232 | View Code Duplication | while ($b = current($this->stack)) { |
|
1233 | if ($b['type'] === $type) { |
||
1234 | return $this->stack[key($this->stack)]; |
||
1235 | } |
||
1236 | prev($this->stack); |
||
1237 | } |
||
1238 | } |
||
1239 | |||
1240 | throw new CompilationException($this, 'A parent block of type "' . $type . '" is required and can not be found'); |
||
1241 | } |
||
1242 | |||
1243 | /** |
||
1244 | * Returns a reference to the current block array. |
||
1245 | * |
||
1246 | * @return array the array is as such: array('type'=>pluginName, 'params'=>parameter array, |
||
1247 | * 'custom'=>bool defining whether it's a custom plugin or not, for internal use) |
||
1248 | */ |
||
1249 | public function &getCurrentBlock() |
||
1250 | { |
||
1251 | return $this->curBlock; |
||
1252 | } |
||
1253 | |||
1254 | /** |
||
1255 | * Removes the block at the top of the stack and calls its postProcessing() method. |
||
1256 | * |
||
1257 | * @return string the postProcessing() method's output |
||
1258 | * @throws CompilationException |
||
1259 | */ |
||
1260 | public function removeTopBlock() |
||
1261 | { |
||
1262 | if ($this->debug) { |
||
1263 | echo 'Compiler::' . __FUNCTION__ . "\n"; |
||
1264 | } |
||
1265 | |||
1266 | $o = array_pop($this->stack); |
||
1267 | if ($o === null) { |
||
1268 | throw new CompilationException($this, 'Syntax malformation, a block of unknown type was closed but was not opened.'); |
||
1269 | } |
||
1270 | View Code Duplication | if ($o['custom']) { |
|
1271 | $class = $o['class']; |
||
1272 | } else { |
||
1273 | $class = Core::NAMESPACE_PLUGINS_BLOCKS . 'Plugin' . Core::toCamelCase($o['type']); |
||
1274 | } |
||
1275 | |||
1276 | $this->curBlock = &$this->stack[count($this->stack) - 1]; |
||
1277 | |||
1278 | return call_user_func(array($class, 'postProcessing'), $this, $o['params'], '', '', $o['buffer']); |
||
1279 | } |
||
1280 | |||
1281 | /** |
||
1282 | * Returns the compiled parameters (for example a variable's compiled parameter will be "$this->scope['key']") out |
||
1283 | * of the given parameter array. |
||
1284 | * |
||
1285 | * @param array $params parameter array |
||
1286 | * |
||
1287 | * @return array filtered parameters |
||
1288 | */ |
||
1289 | View Code Duplication | public function getCompiledParams(array $params) |
|
1290 | { |
||
1291 | foreach ($params as $k => $p) { |
||
1292 | if (is_array($p)) { |
||
1293 | $params[$k] = $p[0]; |
||
1294 | } |
||
1295 | } |
||
1296 | |||
1297 | return $params; |
||
1298 | } |
||
1299 | |||
1300 | /** |
||
1301 | * Returns the real parameters (for example a variable's real parameter will be its key, etc) out of the given |
||
1302 | * parameter array. |
||
1303 | * |
||
1304 | * @param array $params parameter array |
||
1305 | * |
||
1306 | * @return array filtered parameters |
||
1307 | */ |
||
1308 | View Code Duplication | public function getRealParams(array $params) |
|
1309 | { |
||
1310 | foreach ($params as $k => $p) { |
||
1311 | if (is_array($p)) { |
||
1312 | $params[$k] = $p[1]; |
||
1313 | } |
||
1314 | } |
||
1315 | |||
1316 | return $params; |
||
1317 | } |
||
1318 | |||
1319 | /** |
||
1320 | * Returns the token of each parameter out of the given parameter array. |
||
1321 | * |
||
1322 | * @param array $params parameter array |
||
1323 | * |
||
1324 | * @return array tokens |
||
1325 | */ |
||
1326 | View Code Duplication | public function getParamTokens(array $params) |
|
1327 | { |
||
1328 | foreach ($params as $k => $p) { |
||
1329 | if (is_array($p)) { |
||
1330 | $params[$k] = isset($p[2]) ? $p[2] : 0; |
||
1331 | } |
||
1332 | } |
||
1333 | |||
1334 | return $params; |
||
1335 | } |
||
1336 | |||
1337 | /** |
||
1338 | * Entry point of the parser, it redirects calls to other parse* functions. |
||
1339 | * |
||
1340 | * @param string $in the string within which we must parse something |
||
1341 | * @param int $from the starting offset of the parsed area |
||
1342 | * @param int $to the ending offset of the parsed area |
||
1343 | * @param mixed $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by |
||
1344 | * default |
||
1345 | * @param string $curBlock the current parser-block being processed |
||
1346 | * @param mixed $pointer a reference to a pointer that will be increased by the amount of characters parsed, |
||
1347 | * or null by default |
||
1348 | * |
||
1349 | * @return string parsed values |
||
1350 | * @throws CompilationException |
||
1351 | */ |
||
1352 | protected function parse($in, $from, $to, $parsingParams = false, $curBlock = '', &$pointer = null) |
||
1353 | { |
||
1354 | if ($this->debug) { |
||
1355 | echo 'Compiler::' . __FUNCTION__ . "\n"; |
||
1356 | } |
||
1357 | |||
1358 | if ($to === null) { |
||
1359 | $to = strlen($in); |
||
1360 | } |
||
1361 | $first = substr($in, $from, 1); |
||
1362 | |||
1363 | if ($first === false) { |
||
1364 | throw new CompilationException($this, 'Unexpected EOF, a template tag was not closed'); |
||
1365 | } |
||
1366 | |||
1367 | while ($first === ' ' || $first === "\n" || $first === "\t" || $first === "\r") { |
||
1368 | View Code Duplication | if ($curBlock === 'root' && substr($in, $from, strlen($this->rd)) === $this->rd) { |
|
1369 | // end template tag |
||
1370 | $pointer += strlen($this->rd); |
||
1371 | if ($this->debug) { |
||
1372 | echo 'TEMPLATE PARSING ENDED' . "\n"; |
||
1373 | } |
||
1374 | |||
1375 | return false; |
||
1376 | } |
||
1377 | ++ $from; |
||
1378 | if ($pointer !== null) { |
||
1379 | ++ $pointer; |
||
1380 | } |
||
1381 | if ($from >= $to) { |
||
1382 | if (is_array($parsingParams)) { |
||
1383 | return $parsingParams; |
||
1384 | } else { |
||
1385 | return ''; |
||
1386 | } |
||
1387 | } |
||
1388 | $first = $in[$from]; |
||
1389 | } |
||
1390 | |||
1391 | $substr = substr($in, $from, $to - $from); |
||
1392 | |||
1393 | if ($this->debug) { |
||
1394 | echo 'PARSE CALL : PARSING "' . htmlentities(substr($in, $from, min($to - $from, 50))) . (($to - $from) > 50 ? '...' : '') . '" @ ' . $from . ':' . $to . ' in ' . $curBlock . ' : pointer=' . $pointer . "\n"; |
||
1395 | } |
||
1396 | $parsed = ''; |
||
1397 | |||
1398 | if ($curBlock === 'root' && $first === '*') { |
||
1399 | $src = $this->getTemplateSource(); |
||
1400 | $startpos = $this->getPointer() - strlen($this->ld); |
||
1401 | if (substr($src, $startpos, strlen($this->ld)) === $this->ld) { |
||
1402 | if ($startpos > 0) { |
||
1403 | do { |
||
1404 | $char = substr($src, -- $startpos, 1); |
||
1405 | if ($char == "\n") { |
||
1406 | ++ $startpos; |
||
1407 | $whitespaceStart = true; |
||
1408 | break; |
||
1409 | } |
||
1410 | } |
||
1411 | while ($startpos > 0 && ($char == ' ' || $char == "\t")); |
||
1412 | } |
||
1413 | |||
1414 | if (!isset($whitespaceStart)) { |
||
1415 | $startpos = $this->getPointer(); |
||
1416 | } else { |
||
1417 | $pointer -= $this->getPointer() - $startpos; |
||
1418 | } |
||
1419 | |||
1420 | if ($this->allowNestedComments && strpos($src, $this->ld . '*', $this->getPointer()) !== false) { |
||
1421 | $comOpen = $this->ld . '*'; |
||
1422 | $comClose = '*' . $this->rd; |
||
1423 | $level = 1; |
||
1424 | $ptr = $this->getPointer(); |
||
1425 | |||
1426 | while ($level > 0 && $ptr < strlen($src)) { |
||
1427 | $open = strpos($src, $comOpen, $ptr); |
||
1428 | $close = strpos($src, $comClose, $ptr); |
||
1429 | |||
1430 | if ($open !== false && $close !== false) { |
||
1431 | if ($open < $close) { |
||
1432 | $ptr = $open + strlen($comOpen); |
||
1433 | ++ $level; |
||
1434 | } else { |
||
1435 | $ptr = $close + strlen($comClose); |
||
1436 | -- $level; |
||
1437 | } |
||
1438 | } elseif ($open !== false) { |
||
1439 | $ptr = $open + strlen($comOpen); |
||
1440 | ++ $level; |
||
1441 | } elseif ($close !== false) { |
||
1442 | $ptr = $close + strlen($comClose); |
||
1443 | -- $level; |
||
1444 | } else { |
||
1445 | $ptr = strlen($src); |
||
1446 | } |
||
1447 | } |
||
1448 | $endpos = $ptr - strlen('*' . $this->rd); |
||
1449 | View Code Duplication | } else { |
|
1450 | $endpos = strpos($src, '*' . $this->rd, $startpos); |
||
1451 | if ($endpos == false) { |
||
1452 | throw new CompilationException($this, 'Un-ended comment'); |
||
1453 | } |
||
1454 | } |
||
1455 | $pointer += $endpos - $startpos + strlen('*' . $this->rd); |
||
1456 | if (isset($whitespaceStart) && preg_match('#^[\t ]*\r?\n#', substr($src, $endpos + strlen('*' . $this->rd)), $m)) { |
||
1457 | $pointer += strlen($m[0]); |
||
1458 | $this->curBlock['buffer'] = substr($this->curBlock['buffer'], 0, strlen($this->curBlock['buffer']) - ($this->getPointer() - $startpos - strlen($this->ld))); |
||
1459 | } |
||
1460 | |||
1461 | return false; |
||
1462 | } |
||
1463 | } |
||
1464 | |||
1465 | if ($first === '$') { |
||
1466 | // var |
||
1467 | $out = $this->parseVar($in, $from, $to, $parsingParams, $curBlock, $pointer); |
||
1468 | $parsed = 'var'; |
||
1469 | } elseif ($first === '%' && preg_match('#^%[a-z_\\\\]#i', $substr)) { |
||
1470 | // Short constant |
||
1471 | $out = $this->parseConst($in, $from, $to, $parsingParams, $curBlock, $pointer); |
||
1472 | } elseif (($first === '"' || $first === "'") && !(is_array($parsingParams) && preg_match('#^([\'"])[a-z0-9_]+\1\s*=>?(?:\s+|[^=])#i', $substr))) { |
||
1473 | // string |
||
1474 | $out = $this->parseString($in, $from, $to, $parsingParams, $curBlock, $pointer); |
||
1475 | } elseif (preg_match('/^\\\\?[a-z_](?:\\\\?[a-z0-9_]+)*(?:::[a-z_][a-z0-9_]*)?(' . (is_array($parsingParams) || $curBlock != 'root' ? '' : '\s+[^(]|') . '\s*\(|\s*' . $this->rdr . '|\s*;)/i', $substr)) { |
||
1476 | // func |
||
1477 | $out = $this->parseFunction($in, $from, $to, $parsingParams, $curBlock, $pointer); |
||
1478 | $parsed = 'func'; |
||
1479 | } elseif ($first === ';') { |
||
1480 | // instruction end |
||
1481 | if ($this->debug) { |
||
1482 | echo 'END OF INSTRUCTION' . "\n"; |
||
1483 | } |
||
1484 | if ($pointer !== null) { |
||
1485 | ++ $pointer; |
||
1486 | } |
||
1487 | |||
1488 | return $this->parse($in, $from + 1, $to, false, 'root', $pointer); |
||
1489 | } elseif ($curBlock === 'root' && preg_match('#^/([a-z_][a-z0-9_]*)?#i', $substr, $match)) { |
||
1490 | // close block |
||
1491 | View Code Duplication | if (!empty($match[1]) && $match[1] == 'else') { |
|
1492 | throw new CompilationException($this, 'Else blocks must not be closed explicitly, they are automatically closed when their parent block is closed'); |
||
1493 | } |
||
1494 | View Code Duplication | if (!empty($match[1]) && $match[1] == 'elseif') { |
|
1495 | throw new CompilationException($this, 'Elseif blocks must not be closed explicitly, they are automatically closed when their parent block is closed or a new else/elseif block is declared after them'); |
||
1496 | } |
||
1497 | if ($pointer !== null) { |
||
1498 | $pointer += strlen($match[0]); |
||
1499 | } |
||
1500 | if (empty($match[1])) { |
||
1501 | if ($this->curBlock['type'] == 'else' || $this->curBlock['type'] == 'elseif') { |
||
1502 | $pointer -= strlen($match[0]); |
||
1503 | } |
||
1504 | if ($this->debug) { |
||
1505 | echo 'TOP BLOCK CLOSED' . "\n"; |
||
1506 | } |
||
1507 | |||
1508 | return $this->removeTopBlock(); |
||
1509 | } else { |
||
1510 | if ($this->debug) { |
||
1511 | echo 'BLOCK OF TYPE ' . $match[1] . ' CLOSED' . "\n"; |
||
1512 | } |
||
1513 | |||
1514 | return $this->removeBlock($match[1]); |
||
1515 | } |
||
1516 | View Code Duplication | } elseif ($curBlock === 'root' && substr($substr, 0, strlen($this->rd)) === $this->rd) { |
|
1517 | // end template tag |
||
1518 | if ($this->debug) { |
||
1519 | echo 'TAG PARSING ENDED' . "\n"; |
||
1520 | } |
||
1521 | $pointer += strlen($this->rd); |
||
1522 | |||
1523 | return false; |
||
1524 | } elseif (is_array($parsingParams) && preg_match('#^(([\'"]?)[a-z0-9_]+\2\s*=' . ($curBlock === 'array' ? '>?' : '') . ')(?:\s+|[^=]).*#i', $substr, $match)) { |
||
1525 | // named parameter |
||
1526 | if ($this->debug) { |
||
1527 | echo 'NAMED PARAM FOUND' . "\n"; |
||
1528 | } |
||
1529 | $len = strlen($match[1]); |
||
1530 | while (substr($in, $from + $len, 1) === ' ') { |
||
1531 | ++ $len; |
||
1532 | } |
||
1533 | if ($pointer !== null) { |
||
1534 | $pointer += $len; |
||
1535 | } |
||
1536 | |||
1537 | $output = array( |
||
1538 | trim($match[1], " \t\r\n=>'\""), |
||
1539 | $this->parse($in, $from + $len, $to, false, 'namedparam', $pointer) |
||
1540 | ); |
||
1541 | |||
1542 | $parsingParams[] = $output; |
||
1543 | |||
1544 | return $parsingParams; |
||
1545 | } elseif (preg_match('#^(\\\\?[a-z_](?:\\\\?[a-z0-9_]+)*::\$[a-z0-9_]+)#i', $substr, $match)) { |
||
1546 | // static member access |
||
1547 | $parsed = 'var'; |
||
1548 | if (is_array($parsingParams)) { |
||
1549 | $parsingParams[] = array($match[1], $match[1]); |
||
1550 | $out = $parsingParams; |
||
1551 | } else { |
||
1552 | $out = $match[1]; |
||
1553 | } |
||
1554 | $pointer += strlen($match[1]); |
||
1555 | } elseif ($substr !== '' && (is_array($parsingParams) || $curBlock === 'namedparam' || $curBlock === 'condition' || $curBlock === 'expression')) { |
||
1556 | // unquoted string, bool or number |
||
1557 | $out = $this->parseOthers($in, $from, $to, $parsingParams, $curBlock, $pointer); |
||
1558 | View Code Duplication | } else { |
|
1559 | // parse error |
||
1560 | throw new CompilationException($this, 'Parse error in "' . substr($in, $from, $to - $from) . '"'); |
||
1561 | } |
||
1562 | |||
1563 | if (empty($out)) { |
||
1564 | return ''; |
||
1565 | } |
||
1566 | |||
1567 | $substr = substr($in, $pointer, $to - $pointer); |
||
1568 | |||
1569 | // var parsed, check if any var-extension applies |
||
1570 | if ($parsed === 'var') { |
||
1571 | if (preg_match('#^\s*([/%+*-])\s*([a-z0-9]|\$)#i', $substr, $match)) { |
||
1572 | if ($this->debug) { |
||
1573 | echo 'PARSING POST-VAR EXPRESSION ' . $substr . "\n"; |
||
1574 | } |
||
1575 | // parse expressions |
||
1576 | $pointer += strlen($match[0]) - 1; |
||
1577 | if (is_array($parsingParams)) { |
||
1578 | if ($match[2] == '$') { |
||
1579 | $expr = $this->parseVar($in, $pointer, $to, array(), $curBlock, $pointer); |
||
1580 | } else { |
||
1581 | $expr = $this->parse($in, $pointer, $to, array(), 'expression', $pointer); |
||
1582 | } |
||
1583 | $out[count($out) - 1][0] .= $match[1] . $expr[0][0]; |
||
1584 | $out[count($out) - 1][1] .= $match[1] . $expr[0][1]; |
||
1585 | } else { |
||
1586 | if ($match[2] == '$') { |
||
1587 | $expr = $this->parseVar($in, $pointer, $to, false, $curBlock, $pointer); |
||
1588 | } else { |
||
1589 | $expr = $this->parse($in, $pointer, $to, false, 'expression', $pointer); |
||
1590 | } |
||
1591 | if (is_array($out) && is_array($expr)) { |
||
1592 | $out[0] .= $match[1] . $expr[0]; |
||
1593 | $out[1] .= $match[1] . $expr[1]; |
||
1594 | } elseif (is_array($out)) { |
||
1595 | $out[0] .= $match[1] . $expr; |
||
1596 | $out[1] .= $match[1] . $expr; |
||
1597 | } elseif (is_array($expr)) { |
||
1598 | $out .= $match[1] . $expr[0]; |
||
1599 | } else { |
||
1600 | $out .= $match[1] . $expr; |
||
1601 | } |
||
1602 | } |
||
1603 | } elseif ($curBlock === 'root' && preg_match('#^(\s*(?:[+/*%-.]=|=|\+\+|--)\s*)(.*)#s', $substr, $match)) { |
||
1604 | if ($this->debug) { |
||
1605 | echo 'PARSING POST-VAR ASSIGNMENT ' . $substr . "\n"; |
||
1606 | } |
||
1607 | // parse assignment |
||
1608 | $value = $match[2]; |
||
1609 | $operator = trim($match[1]); |
||
1610 | if (substr($value, 0, 1) == '=') { |
||
1611 | throw new CompilationException($this, 'Unexpected "=" in <em>' . $substr . '</em>'); |
||
1612 | } |
||
1613 | |||
1614 | if ($pointer !== null) { |
||
1615 | $pointer += strlen($match[1]); |
||
1616 | } |
||
1617 | |||
1618 | if ($operator !== '++' && $operator !== '--') { |
||
1619 | $parts = array(); |
||
1620 | $ptr = 0; |
||
1621 | $parts = $this->parse($value, 0, strlen($value), $parts, 'condition', $ptr); |
||
1622 | $pointer += $ptr; |
||
1623 | |||
1624 | // load if plugin |
||
1625 | try { |
||
1626 | $this->getPluginType('if'); |
||
1627 | } |
||
1628 | catch (Exception $e) { |
||
1629 | throw new CompilationException($this, 'Assignments require the "if" plugin to be accessible'); |
||
1630 | } |
||
1631 | |||
1632 | $parts = $this->mapParams($parts, array(Core::NAMESPACE_PLUGINS_BLOCKS . 'PluginIf', 'init'), 1); |
||
1633 | $tokens = $this->getParamTokens($parts); |
||
1634 | $parts = $this->getCompiledParams($parts); |
||
1635 | |||
1636 | $value = PluginIf::replaceKeywords($parts['*'], $tokens['*'], $this); |
||
1637 | $echo = ''; |
||
1638 | } else { |
||
1639 | $value = array(); |
||
1640 | $echo = 'echo '; |
||
1641 | } |
||
1642 | |||
1643 | if ($this->autoEscape) { |
||
1644 | $out = preg_replace('#\(is_string\(\$tmp=(.+?)\) \? htmlspecialchars\(\$tmp, ENT_QUOTES, \$this->charset\) : \$tmp\)#', '$1', $out); |
||
1645 | } |
||
1646 | $out = self::PHP_OPEN . $echo . $out . $operator . implode(' ', $value) . self::PHP_CLOSE; |
||
1647 | } elseif ($curBlock === 'array' && is_array($parsingParams) && preg_match('#^(\s*=>?\s*)#', $substr, $match)) { |
||
1648 | // parse namedparam with var as name (only for array) |
||
1649 | if ($this->debug) { |
||
1650 | echo 'VARIABLE NAMED PARAM (FOR ARRAY) FOUND' . "\n"; |
||
1651 | } |
||
1652 | $len = strlen($match[1]); |
||
1653 | $var = $out[count($out) - 1]; |
||
1654 | $pointer += $len; |
||
1655 | |||
1656 | $output = array($var[0], $this->parse($substr, $len, null, false, 'namedparam', $pointer)); |
||
1657 | |||
1658 | $parsingParams[] = $output; |
||
1659 | |||
1660 | return $parsingParams; |
||
1661 | } |
||
1662 | } |
||
1663 | |||
1664 | if ($curBlock !== 'modifier' && ($parsed === 'func' || $parsed === 'var') && preg_match('#^(\|@?[a-z0-9_]+(:.*)?)+#i', $substr, $match)) { |
||
1665 | // parse modifier on funcs or vars |
||
1666 | $srcPointer = $pointer; |
||
1667 | if (is_array($parsingParams)) { |
||
1668 | $tmp = $this->replaceModifiers( |
||
1669 | array( |
||
1670 | null, |
||
1671 | null, |
||
1672 | $out[count($out) - 1][0], |
||
1673 | $match[0] |
||
1674 | ), $curBlock, $pointer |
||
1675 | ); |
||
1676 | $out[count($out) - 1][0] = $tmp; |
||
1677 | $out[count($out) - 1][1] .= substr($substr, $srcPointer, $srcPointer - $pointer); |
||
1678 | } else { |
||
1679 | $out = $this->replaceModifiers(array(null, null, $out, $match[0]), $curBlock, $pointer); |
||
1680 | } |
||
1681 | } |
||
1682 | |||
1683 | // func parsed, check if any func-extension applies |
||
1684 | if ($parsed === 'func' && preg_match('#^->[a-z0-9_]+(\s*\(.+|->[a-z_].*)?#is', $substr, $match)) { |
||
1685 | // parse method call or property read |
||
1686 | $ptr = 0; |
||
1687 | |||
1688 | if (is_array($parsingParams)) { |
||
1689 | $output = $this->parseMethodCall($out[count($out) - 1][1], $match[0], $curBlock, $ptr); |
||
1690 | |||
1691 | $out[count($out) - 1][0] = $output; |
||
1692 | $out[count($out) - 1][1] .= substr($match[0], 0, $ptr); |
||
1693 | } else { |
||
1694 | $out = $this->parseMethodCall($out, $match[0], $curBlock, $ptr); |
||
1695 | } |
||
1696 | |||
1697 | $pointer += $ptr; |
||
1698 | } |
||
1699 | |||
1700 | if ($curBlock === 'root' && substr($out, 0, strlen(self::PHP_OPEN)) !== self::PHP_OPEN) { |
||
1701 | return self::PHP_OPEN . 'echo ' . $out . ';' . self::PHP_CLOSE; |
||
1702 | } else { |
||
1703 | return $out; |
||
1704 | } |
||
1705 | } |
||
1706 | |||
1707 | /** |
||
1708 | * Parses a function call. |
||
1709 | * |
||
1710 | * @param string $in the string within which we must parse something |
||
1711 | * @param int $from the starting offset of the parsed area |
||
1712 | * @param int $to the ending offset of the parsed area |
||
1713 | * @param mixed $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by |
||
1714 | * default |
||
1715 | * @param string $curBlock the current parser-block being processed |
||
1716 | * @param mixed $pointer a reference to a pointer that will be increased by the amount of characters parsed, |
||
1717 | * or null by default |
||
1718 | * |
||
1719 | * @return string parsed values |
||
1720 | * @throws CompilationException |
||
1721 | * @throws Exception |
||
1722 | * @throws SecurityException |
||
1723 | */ |
||
1724 | protected function parseFunction($in, $from, $to, $parsingParams = false, $curBlock = '', &$pointer = null) |
||
1725 | { |
||
1726 | $output = ''; |
||
1727 | $cmdstr = substr($in, $from, $to - $from); |
||
1728 | preg_match('/^(\\\\?[a-z_](?:\\\\?[a-z0-9_]+)*(?:::[a-z_][a-z0-9_]*)?)(\s*' . $this->rdr . '|\s*;)?/i', $cmdstr, $match); |
||
1729 | |||
1730 | View Code Duplication | if (empty($match[1])) { |
|
1731 | throw new CompilationException($this, 'Parse error, invalid function name : ' . substr($cmdstr, 0, 15)); |
||
1732 | } |
||
1733 | |||
1734 | $func = $match[1]; |
||
1735 | |||
1736 | if (!empty($match[2])) { |
||
1737 | $cmdstr = $match[1]; |
||
1738 | } |
||
1739 | |||
1740 | if ($this->debug) { |
||
1741 | echo 'FUNC FOUND (' . $func . ')' . "\n"; |
||
1742 | } |
||
1743 | |||
1744 | $paramsep = ''; |
||
1745 | |||
1746 | if (is_array($parsingParams) || $curBlock != 'root') { |
||
1747 | $paramspos = strpos($cmdstr, '('); |
||
1748 | $paramsep = ')'; |
||
1749 | } elseif (preg_match_all('#^\s*[\\\\:a-z0-9_]+(\s*\(|\s+[^(])#i', $cmdstr, $match, PREG_OFFSET_CAPTURE)) { |
||
1750 | $paramspos = $match[1][0][1]; |
||
1751 | $paramsep = substr($match[1][0][0], - 1) === '(' ? ')' : ''; |
||
1752 | if ($paramsep === ')') { |
||
1753 | $paramspos += strlen($match[1][0][0]) - 1; |
||
1754 | if (substr($cmdstr, 0, 2) === 'if' || substr($cmdstr, 0, 6) === 'elseif') { |
||
1755 | $paramsep = ''; |
||
1756 | if (strlen($match[1][0][0]) > 1) { |
||
1757 | -- $paramspos; |
||
1758 | } |
||
1759 | } |
||
1760 | } |
||
1761 | } else { |
||
1762 | $paramspos = false; |
||
1763 | } |
||
1764 | |||
1765 | $state = 0; |
||
1766 | |||
1767 | if ($paramspos === false) { |
||
1768 | $params = array(); |
||
1769 | |||
1770 | if ($curBlock !== 'root') { |
||
1771 | return $this->parseOthers($in, $from, $to, $parsingParams, $curBlock, $pointer); |
||
1772 | } |
||
1773 | } else { |
||
1774 | if ($curBlock === 'condition') { |
||
1775 | // load if plugin |
||
1776 | $this->getPluginType('if'); |
||
1777 | |||
1778 | if (PluginIf::replaceKeywords(array($func), array(self::T_UNQUOTED_STRING), $this) !== array($func)) { |
||
1779 | return $this->parseOthers($in, $from, $to, $parsingParams, $curBlock, $pointer); |
||
1780 | } |
||
1781 | } |
||
1782 | $whitespace = strlen(substr($cmdstr, strlen($func), $paramspos - strlen($func))); |
||
1783 | $paramstr = substr($cmdstr, $paramspos + 1); |
||
1784 | View Code Duplication | if (substr($paramstr, - 1, 1) === $paramsep) { |
|
1785 | $paramstr = substr($paramstr, 0, - 1); |
||
1786 | } |
||
1787 | |||
1788 | if (strlen($paramstr) === 0) { |
||
1789 | $params = array(); |
||
1790 | $paramstr = ''; |
||
1791 | } else { |
||
1792 | $ptr = 0; |
||
1793 | $params = array(); |
||
1794 | if ($func === 'empty') { |
||
1795 | $params = $this->parseVar($paramstr, $ptr, strlen($paramstr), $params, 'root', $ptr); |
||
1796 | } else { |
||
1797 | while ($ptr < strlen($paramstr)) { |
||
1798 | while (true) { |
||
1799 | if ($ptr >= strlen($paramstr)) { |
||
1800 | break 2; |
||
1801 | } |
||
1802 | |||
1803 | if ($func !== 'if' && $func !== 'elseif' && $paramstr[$ptr] === ')') { |
||
1804 | if ($this->debug) { |
||
1805 | echo 'PARAM PARSING ENDED, ")" FOUND, POINTER AT ' . $ptr . "\n"; |
||
1806 | } |
||
1807 | break 2; |
||
1808 | } elseif ($paramstr[$ptr] === ';') { |
||
1809 | ++ $ptr; |
||
1810 | if ($this->debug) { |
||
1811 | echo 'PARAM PARSING ENDED, ";" FOUND, POINTER AT ' . $ptr . "\n"; |
||
1812 | } |
||
1813 | break 2; |
||
1814 | } elseif ($func !== 'if' && $func !== 'elseif' && $paramstr[$ptr] === '/') { |
||
1815 | if ($this->debug) { |
||
1816 | echo 'PARAM PARSING ENDED, "/" FOUND, POINTER AT ' . $ptr . "\n"; |
||
1817 | } |
||
1818 | break 2; |
||
1819 | } elseif (substr($paramstr, $ptr, strlen($this->rd)) === $this->rd) { |
||
1820 | if ($this->debug) { |
||
1821 | echo 'PARAM PARSING ENDED, RIGHT DELIMITER FOUND, POINTER AT ' . $ptr . "\n"; |
||
1822 | } |
||
1823 | break 2; |
||
1824 | } |
||
1825 | |||
1826 | if ($paramstr[$ptr] === ' ' || $paramstr[$ptr] === ',' || $paramstr[$ptr] === "\r" || $paramstr[$ptr] === "\n" || $paramstr[$ptr] === "\t") { |
||
1827 | ++ $ptr; |
||
1828 | } else { |
||
1829 | break; |
||
1830 | } |
||
1831 | } |
||
1832 | |||
1833 | if ($this->debug) { |
||
1834 | echo 'FUNC START PARAM PARSING WITH POINTER AT ' . $ptr . "\n"; |
||
1835 | } |
||
1836 | |||
1837 | if ($func === 'if' || $func === 'elseif' || $func === 'tif') { |
||
1838 | $params = $this->parse($paramstr, $ptr, strlen($paramstr), $params, 'condition', $ptr); |
||
1839 | } elseif ($func === 'array') { |
||
1840 | $params = $this->parse($paramstr, $ptr, strlen($paramstr), $params, 'array', $ptr); |
||
1841 | } else { |
||
1842 | $params = $this->parse($paramstr, $ptr, strlen($paramstr), $params, 'function', $ptr); |
||
1843 | } |
||
1844 | |||
1845 | View Code Duplication | if ($this->debug) { |
|
1846 | echo 'PARAM PARSED, POINTER AT ' . $ptr . ' (' . substr($paramstr, $ptr - 1, 3) . ')' . "\n"; |
||
1847 | } |
||
1848 | } |
||
1849 | } |
||
1850 | $paramstr = substr($paramstr, 0, $ptr); |
||
1851 | $state = 0; |
||
1852 | foreach ($params as $k => $p) { |
||
1853 | if (is_array($p) && is_array($p[1])) { |
||
1854 | $state |= 2; |
||
1855 | } else { |
||
1856 | if (($state & 2) && preg_match('#^(["\'])(.+?)\1$#', $p[0], $m) && $func !== 'array') { |
||
1857 | $params[$k] = array($m[2], array('true', 'true')); |
||
1858 | } else { |
||
1859 | if ($state & 2 && $func !== 'array') { |
||
1860 | throw new CompilationException($this, 'You can not use an unnamed parameter after a named one'); |
||
1861 | } |
||
1862 | $state |= 1; |
||
1863 | } |
||
1864 | } |
||
1865 | } |
||
1866 | } |
||
1867 | } |
||
1868 | |||
1869 | if ($pointer !== null) { |
||
1870 | $pointer += (isset($paramstr) ? strlen($paramstr) : 0) + (')' === $paramsep ? 2 : ($paramspos === false ? 0 : 1)) + strlen($func) + (isset($whitespace) ? $whitespace : 0); |
||
1871 | if ($this->debug) { |
||
1872 | echo 'FUNC ADDS ' . ((isset($paramstr) ? strlen($paramstr) : 0) + (')' === $paramsep ? 2 : ($paramspos === false ? 0 : 1)) + strlen($func)) . ' TO POINTER' . "\n"; |
||
1873 | } |
||
1874 | } |
||
1875 | |||
1876 | if ($curBlock === 'method' || $func === 'do' || strstr($func, '::') !== false) { |
||
1877 | // handle static method calls with security policy |
||
1878 | if (strstr($func, '::') !== false && $this->securityPolicy !== null && $this->securityPolicy->isMethodAllowed(explode('::', strtolower($func))) !== true) { |
||
1879 | throw new SecurityException('Call to a disallowed php function : ' . $func); |
||
1880 | } |
||
1881 | $pluginType = Core::NATIVE_PLUGIN; |
||
1882 | } else { |
||
1883 | $pluginType = $this->getPluginType($func); |
||
1884 | } |
||
1885 | |||
1886 | // Blocks plugin |
||
1887 | if ($pluginType & Core::BLOCK_PLUGIN) { |
||
1888 | if ($curBlock !== 'root' || is_array($parsingParams)) { |
||
1889 | throw new CompilationException($this, 'Block plugins can not be used as other plugin\'s arguments'); |
||
1890 | } |
||
1891 | if ($pluginType & Core::CUSTOM_PLUGIN) { |
||
1892 | return $this->addCustomBlock($func, $params, $state); |
||
1893 | } else { |
||
1894 | return $this->addBlock($func, $params, $state); |
||
1895 | } |
||
1896 | } elseif ($pluginType & Core::SMARTY_BLOCK) { |
||
1897 | if ($curBlock !== 'root' || is_array($parsingParams)) { |
||
1898 | throw new CompilationException($this, 'Block plugins can not be used as other plugin\'s arguments'); |
||
1899 | } |
||
1900 | |||
1901 | if ($state & 2) { |
||
1902 | array_unshift($params, array('__functype', array($pluginType, $pluginType))); |
||
1903 | array_unshift($params, array('__funcname', array($func, $func))); |
||
1904 | } else { |
||
1905 | array_unshift($params, array($pluginType, $pluginType)); |
||
1906 | array_unshift($params, array($func, $func)); |
||
1907 | } |
||
1908 | |||
1909 | return $this->addBlock('smartyinterface', $params, $state); |
||
1910 | } |
||
1911 | |||
1912 | // Functions plugin |
||
1913 | if ($pluginType & Core::NATIVE_PLUGIN || $pluginType & Core::SMARTY_FUNCTION || $pluginType & Core::SMARTY_BLOCK) { |
||
1914 | $params = $this->mapParams($params, null, $state); |
||
1915 | } elseif ($pluginType & Core::CLASS_PLUGIN) { |
||
1916 | if ($pluginType & Core::CUSTOM_PLUGIN) { |
||
1917 | $params = $this->mapParams( |
||
1918 | $params, array( |
||
1919 | $this->customPlugins[$func]['class'], |
||
1920 | $this->customPlugins[$func]['function'] |
||
1921 | ), $state); |
||
1922 | } else { |
||
1923 | if (class_exists('Plugin' . Core::toCamelCase($func)) !== false) { |
||
1924 | $params = $this->mapParams($params, array( |
||
1925 | 'Plugin' . Core::toCamelCase($func), |
||
1926 | ($pluginType & Core::COMPILABLE_PLUGIN) ? 'compile' : 'process' |
||
1927 | ), $state); |
||
1928 | View Code Duplication | } else { |
|
1929 | $params = $this->mapParams($params, array( |
||
1930 | Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . Core::toCamelCase($func), |
||
1931 | ($pluginType & Core::COMPILABLE_PLUGIN) ? 'compile' : 'process' |
||
1932 | ), $state); |
||
1933 | } |
||
1934 | } |
||
1935 | } elseif ($pluginType & Core::FUNC_PLUGIN) { |
||
1936 | if ($pluginType & Core::CUSTOM_PLUGIN) { |
||
1937 | $params = $this->mapParams($params, $this->customPlugins[$func]['callback'], $state); |
||
1938 | } else { |
||
1939 | // Custom plugin |
||
1940 | if (function_exists('Plugin' . Core::toCamelCase($func) . (($pluginType & Core::COMPILABLE_PLUGIN) ? |
||
1941 | 'Compile' : '')) !== false) { |
||
1942 | $params = $this->mapParams($params, 'Plugin' . Core::toCamelCase($func) . (($pluginType & |
||
1943 | Core::COMPILABLE_PLUGIN) ? 'Compile' : ''), $state); |
||
1944 | } // Builtin helper plugin |
||
1945 | elseif(function_exists(Core::NAMESPACE_PLUGINS_HELPERS . 'Plugin' . Core::toCamelCase($func) . ( |
||
1946 | ($pluginType & Core::COMPILABLE_PLUGIN) ? 'Compile' : '')) !== false) { |
||
1947 | $params = $this->mapParams($params, Core::NAMESPACE_PLUGINS_HELPERS . 'Plugin' . Core::toCamelCase |
||
1948 | ($func) . (($pluginType & Core::COMPILABLE_PLUGIN) ? 'Compile' : ''), $state); |
||
1949 | } // Builtin function plugin |
||
1950 | View Code Duplication | else { |
|
1951 | $params = $this->mapParams($params, Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . Core::toCamelCase |
||
1952 | ($func) . (($pluginType & Core::COMPILABLE_PLUGIN) ? 'Compile' : ''), $state); |
||
1953 | } |
||
1954 | } |
||
1955 | } elseif ($pluginType & Core::SMARTY_MODIFIER) { |
||
1956 | $output = 'smarty_modifier_' . $func . '(' . implode(', ', $params) . ')'; |
||
1957 | } elseif ($pluginType & Core::PROXY_PLUGIN) { |
||
1958 | $params = $this->mapParams($params, $this->getDwoo()->getPluginProxy()->getCallback($func), $state); |
||
1959 | } elseif ($pluginType & Core::TEMPLATE_PLUGIN) { |
||
1960 | // transforms the parameter array from (x=>array('paramname'=>array(values))) to (paramname=>array(values)) |
||
1961 | $map = array(); |
||
1962 | foreach ($this->templatePlugins[$func]['params'] as $param => $defValue) { |
||
1963 | if ($param == 'rest') { |
||
1964 | $param = '*'; |
||
1965 | } |
||
1966 | $hasDefault = $defValue !== null; |
||
1967 | if ($defValue === 'null') { |
||
1968 | $defValue = null; |
||
1969 | } elseif ($defValue === 'false') { |
||
1970 | $defValue = false; |
||
1971 | } elseif ($defValue === 'true') { |
||
1972 | $defValue = true; |
||
1973 | } elseif (preg_match('#^([\'"]).*?\1$#', $defValue)) { |
||
1974 | $defValue = substr($defValue, 1, - 1); |
||
1975 | } |
||
1976 | $map[] = array($param, $hasDefault, $defValue); |
||
1977 | } |
||
1978 | |||
1979 | $params = $this->mapParams($params, null, $state, $map); |
||
1980 | } |
||
1981 | |||
1982 | // only keep php-syntax-safe values for non-block plugins |
||
1983 | $tokens = array(); |
||
1984 | foreach ($params as $k => $p) { |
||
1985 | $tokens[$k] = isset($p[2]) ? $p[2] : 0; |
||
1986 | $params[$k] = $p[0]; |
||
1987 | } |
||
1988 | if ($pluginType & Core::NATIVE_PLUGIN) { |
||
1989 | if ($func === 'do') { |
||
1990 | if (isset($params['*'])) { |
||
1991 | $output = implode(';', $params['*']) . ';'; |
||
1992 | } else { |
||
1993 | $output = ''; |
||
1994 | } |
||
1995 | |||
1996 | if (is_array($parsingParams) || $curBlock !== 'root') { |
||
1997 | throw new CompilationException($this, 'Do can not be used inside another function or block'); |
||
1998 | } else { |
||
1999 | return self::PHP_OPEN . $output . self::PHP_CLOSE; |
||
2000 | } |
||
2001 | } else { |
||
2002 | if (isset($params['*'])) { |
||
2003 | $output = $func . '(' . implode(', ', $params['*']) . ')'; |
||
2004 | } else { |
||
2005 | $output = $func . '()'; |
||
2006 | } |
||
2007 | } |
||
2008 | } elseif ($pluginType & Core::FUNC_PLUGIN) { |
||
2009 | if ($pluginType & Core::COMPILABLE_PLUGIN) { |
||
2010 | if ($pluginType & Core::CUSTOM_PLUGIN) { |
||
2011 | $funcCompiler = $this->customPlugins[$func]['callback']; |
||
2012 | } else { |
||
2013 | // Custom plugin |
||
2014 | if (function_exists('Plugin' . Core::toCamelCase($func) . 'Compile') !== false) { |
||
2015 | $funcCompiler = 'Plugin' . Core::toCamelCase($func) . 'Compile'; |
||
2016 | } // Builtin helper plugin |
||
2017 | View Code Duplication | elseif(function_exists(Core::NAMESPACE_PLUGINS_HELPERS . 'Plugin' . Core::toCamelCase($func) . |
|
2018 | 'Compile') !== false) { |
||
2019 | $funcCompiler = Core::NAMESPACE_PLUGINS_HELPERS . 'Plugin' . Core::toCamelCase($func) . |
||
2020 | 'Compile'; |
||
2021 | } // Builtin function plugin |
||
2022 | else { |
||
2023 | $funcCompiler = Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . Core::toCamelCase($func) . |
||
2024 | 'Compile'; |
||
2025 | } |
||
2026 | } |
||
2027 | array_unshift($params, $this); |
||
2028 | if ($func === 'tif') { |
||
2029 | $params[] = $tokens; |
||
2030 | } |
||
2031 | $output = call_user_func_array($funcCompiler, $params); |
||
2032 | } else { |
||
2033 | array_unshift($params, '$this'); |
||
2034 | $params = self::implode_r($params); |
||
2035 | if ($pluginType & Core::CUSTOM_PLUGIN) { |
||
2036 | $callback = $this->customPlugins[$func]['callback']; |
||
2037 | if ($callback instanceof Closure) { |
||
2038 | $output = 'call_user_func($this->getCustomPlugin(\'' . $func . '\'), ' . $params . ')'; |
||
2039 | } else { |
||
2040 | $output = 'call_user_func(\'' . $callback . '\', ' . $params . ')'; |
||
2041 | } |
||
2042 | } else { |
||
2043 | // Custom plugin |
||
2044 | if (function_exists('Plugin' . Core::toCamelCase($func)) !== false) { |
||
2045 | $output = 'Plugin' . Core::toCamelCase($func) . '(' . $params . |
||
2046 | ')'; |
||
2047 | } // Builtin helper plugin |
||
2048 | View Code Duplication | elseif(function_exists(Core::NAMESPACE_PLUGINS_HELPERS . 'Plugin' . Core::toCamelCase($func)) !== |
|
2049 | false) { |
||
2050 | $output = Core::NAMESPACE_PLUGINS_HELPERS . 'Plugin' . Core::toCamelCase($func) . '(' . |
||
2051 | $params . ')'; |
||
2052 | } // Builtin function plugin |
||
2053 | View Code Duplication | else { |
|
2054 | $output = Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . Core::toCamelCase($func) . '(' . |
||
2055 | $params . ')'; |
||
2056 | } |
||
2057 | } |
||
2058 | } |
||
2059 | } elseif ($pluginType & Core::CLASS_PLUGIN) { |
||
2060 | if ($pluginType & Core::COMPILABLE_PLUGIN) { |
||
2061 | View Code Duplication | if ($pluginType & Core::CUSTOM_PLUGIN) { |
|
2062 | $callback = $this->customPlugins[$func]['callback']; |
||
2063 | if (!is_array($callback)) { |
||
2064 | if (!method_exists($callback, 'compile')) { |
||
2065 | throw new Exception('Custom plugin ' . $func . ' must implement the "compile" method to be compilable, or you should provide a full callback to the method to use'); |
||
2066 | } |
||
2067 | if (($ref = new ReflectionMethod($callback, 'compile')) && $ref->isStatic()) { |
||
2068 | $funcCompiler = array($callback, 'compile'); |
||
2069 | } else { |
||
2070 | $funcCompiler = array(new $callback(), 'compile'); |
||
2071 | } |
||
2072 | } else { |
||
2073 | $funcCompiler = $callback; |
||
2074 | } |
||
2075 | } else { |
||
2076 | if (class_exists('Plugin' . Core::toCamelCase($func)) !== false) { |
||
2077 | $funcCompiler = array('Plugin' . Core::toCamelCase($func), 'compile'); |
||
2078 | } else { |
||
2079 | $funcCompiler = array( |
||
2080 | Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . Core::toCamelCase($func), |
||
2081 | 'compile' |
||
2082 | ); |
||
2083 | } |
||
2084 | array_unshift($params, $this); |
||
2085 | } |
||
2086 | $output = call_user_func_array($funcCompiler, $params); |
||
2087 | } else { |
||
2088 | $params = self::implode_r($params); |
||
2089 | if ($pluginType & Core::CUSTOM_PLUGIN) { |
||
2090 | $callback = $this->customPlugins[$func]['callback']; |
||
2091 | if (!is_array($callback)) { |
||
2092 | if (!method_exists($callback, 'process')) { |
||
2093 | throw new Exception('Custom plugin ' . $func . ' must implement the "process" method to be usable, or you should provide a full callback to the method to use'); |
||
2094 | } |
||
2095 | if (($ref = new ReflectionMethod($callback, 'process')) && $ref->isStatic()) { |
||
2096 | $output = 'call_user_func(array(\'' . $callback . '\', \'process\'), ' . $params . ')'; |
||
2097 | } else { |
||
2098 | $output = 'call_user_func(array($this->getObjectPlugin(\'' . $callback . '\'), \'process\'), ' . $params . ')'; |
||
2099 | } |
||
2100 | View Code Duplication | } elseif (is_object($callback[0])) { |
|
2101 | $output = 'call_user_func(array($this->plugins[\'' . $func . '\'][\'callback\'][0], \'' . $callback[1] . '\'), ' . $params . ')'; |
||
2102 | } elseif (($ref = new ReflectionMethod($callback[0], $callback[1])) && $ref->isStatic()) { |
||
2103 | $output = 'call_user_func(array(\'' . $callback[0] . '\', \'' . $callback[1] . '\'), ' . $params . ')'; |
||
2104 | View Code Duplication | } else { |
|
2105 | $output = 'call_user_func(array($this->getObjectPlugin(\'' . $callback[0] . '\'), \'' . $callback[1] . '\'), ' . $params . ')'; |
||
2106 | } |
||
2107 | if (empty($params)) { |
||
2108 | $output = substr($output, 0, - 3) . ')'; |
||
2109 | } |
||
2110 | } else { |
||
2111 | if (class_exists('Plugin' . Core::toCamelCase($func)) !== false) { |
||
2112 | $output = '$this->classCall(\'Plugin' . $func . '\', array(' . $params . '))'; |
||
2113 | View Code Duplication | } elseif (class_exists(Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . Core::toCamelCase($func)) !== |
|
2114 | false) { |
||
2115 | $output = '$this->classCall(\'' . Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . $func . '\', |
||
2116 | array(' . $params . '))'; |
||
2117 | } else{ |
||
2118 | $output = '$this->classCall(\'' . $func . '\', array(' . $params . '))'; |
||
2119 | } |
||
2120 | } |
||
2121 | } |
||
2122 | } elseif ($pluginType & Core::PROXY_PLUGIN) { |
||
2123 | $output = call_user_func(array($this->getDwoo()->getPluginProxy(), 'getCode'), $func, $params); |
||
2124 | } elseif ($pluginType & Core::SMARTY_FUNCTION) { |
||
2125 | if (isset($params['*'])) { |
||
2126 | $params = self::implode_r($params['*'], true); |
||
2127 | } else { |
||
2128 | $params = ''; |
||
2129 | } |
||
2130 | |||
2131 | if ($pluginType & Core::CUSTOM_PLUGIN) { |
||
2132 | $callback = $this->customPlugins[$func]['callback']; |
||
2133 | if (is_array($callback)) { |
||
2134 | if (is_object($callback[0])) { |
||
2135 | $output = 'call_user_func_array(array($this->plugins[\'' . $func . '\'][\'callback\'][0], \'' . $callback[1] . '\'), array(array(' . $params . '), $this))'; |
||
2136 | View Code Duplication | } else { |
|
2137 | $output = 'call_user_func_array(array(\'' . $callback[0] . '\', \'' . $callback[1] . '\'), array(array(' . $params . '), $this))'; |
||
2138 | } |
||
2139 | } else { |
||
2140 | $output = $callback . '(array(' . $params . '), $this)'; |
||
2141 | } |
||
2142 | } else { |
||
2143 | $output = 'smarty_function_' . $func . '(array(' . $params . '), $this)'; |
||
2144 | } |
||
2145 | } elseif ($pluginType & Core::TEMPLATE_PLUGIN) { |
||
2146 | array_unshift($params, '$this'); |
||
2147 | $params = self::implode_r($params); |
||
2148 | $output = 'Plugin' . Core::toCamelCase($func) . |
||
2149 | $this->templatePlugins[$func]['uuid'] . '(' . $params . ')'; |
||
2150 | $this->templatePlugins[$func]['called'] = true; |
||
2151 | } |
||
2152 | |||
2153 | View Code Duplication | if (is_array($parsingParams)) { |
|
2154 | $parsingParams[] = array($output, $output); |
||
2155 | |||
2156 | return $parsingParams; |
||
2157 | } elseif ($curBlock === 'namedparam') { |
||
2158 | return array($output, $output); |
||
2159 | } else { |
||
2160 | return $output; |
||
2161 | } |
||
2162 | } |
||
2163 | |||
2164 | /** |
||
2165 | * Parses a string. |
||
2166 | * |
||
2167 | * @param string $in the string within which we must parse something |
||
2168 | * @param int $from the starting offset of the parsed area |
||
2169 | * @param int $to the ending offset of the parsed area |
||
2170 | * @param mixed $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by |
||
2171 | * default |
||
2172 | * @param string $curBlock the current parser-block being processed |
||
2173 | * @param mixed $pointer a reference to a pointer that will be increased by the amount of characters parsed, |
||
2174 | * or null by default |
||
2175 | * |
||
2176 | * @return string parsed values |
||
2177 | * @throws CompilationException |
||
2178 | */ |
||
2179 | protected function parseString($in, $from, $to, $parsingParams = false, $curBlock = '', &$pointer = null) |
||
2180 | { |
||
2181 | $substr = substr($in, $from, $to - $from); |
||
2182 | $first = $substr[0]; |
||
2183 | |||
2184 | if ($this->debug) { |
||
2185 | echo 'STRING FOUND (in ' . htmlentities(substr($in, $from, min($to - $from, 50))) . (($to - $from) > 50 ? '...' : '') . ')' . "\n"; |
||
2186 | } |
||
2187 | $strend = false; |
||
2188 | $o = $from + 1; |
||
2189 | while ($strend === false) { |
||
2190 | $strend = strpos($in, $first, $o); |
||
2191 | View Code Duplication | if ($strend === false) { |
|
2192 | throw new CompilationException($this, 'Unfinished string, started with ' . substr($in, $from, $to - $from)); |
||
2193 | } |
||
2194 | if (substr($in, $strend - 1, 1) === '\\') { |
||
2195 | $o = $strend + 1; |
||
2196 | $strend = false; |
||
2197 | } |
||
2198 | } |
||
2199 | if ($this->debug) { |
||
2200 | echo 'STRING DELIMITED: ' . substr($in, $from, $strend + 1 - $from) . "\n"; |
||
2201 | } |
||
2202 | |||
2203 | $srcOutput = substr($in, $from, $strend + 1 - $from); |
||
2204 | |||
2205 | if ($pointer !== null) { |
||
2206 | $pointer += strlen($srcOutput); |
||
2207 | } |
||
2208 | |||
2209 | $output = $this->replaceStringVars($srcOutput, $first); |
||
2210 | |||
2211 | // handle modifiers |
||
2212 | if ($curBlock !== 'modifier' && preg_match('#^((?:\|(?:@?[a-z0-9_]+(?::.*)*))+)#i', substr($substr, $strend + 1 - $from), $match)) { |
||
2213 | $modstr = $match[1]; |
||
2214 | |||
2215 | if ($curBlock === 'root' && substr($modstr, - 1) === '}') { |
||
2216 | $modstr = substr($modstr, 0, - 1); |
||
2217 | } |
||
2218 | $modstr = str_replace('\\' . $first, $first, $modstr); |
||
2219 | $ptr = 0; |
||
2220 | $output = $this->replaceModifiers(array(null, null, $output, $modstr), 'string', $ptr); |
||
2221 | |||
2222 | $strend += $ptr; |
||
2223 | if ($pointer !== null) { |
||
2224 | $pointer += $ptr; |
||
2225 | } |
||
2226 | $srcOutput .= substr($substr, $strend + 1 - $from, $ptr); |
||
2227 | } |
||
2228 | |||
2229 | if (is_array($parsingParams)) { |
||
2230 | $parsingParams[] = array($output, substr($srcOutput, 1, - 1)); |
||
2231 | |||
2232 | return $parsingParams; |
||
2233 | } elseif ($curBlock === 'namedparam') { |
||
2234 | return array($output, substr($srcOutput, 1, - 1)); |
||
2235 | } else { |
||
2236 | return $output; |
||
2237 | } |
||
2238 | } |
||
2239 | |||
2240 | /** |
||
2241 | * Parses a constant. |
||
2242 | * |
||
2243 | * @param string $in the string within which we must parse something |
||
2244 | * @param int $from the starting offset of the parsed area |
||
2245 | * @param int $to the ending offset of the parsed area |
||
2246 | * @param mixed $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by |
||
2247 | * default |
||
2248 | * @param string $curBlock the current parser-block being processed |
||
2249 | * @param mixed $pointer a reference to a pointer that will be increased by the amount of characters parsed, |
||
2250 | * or null by default |
||
2251 | * |
||
2252 | * @return string parsed values |
||
2253 | * @throws CompilationException |
||
2254 | */ |
||
2255 | protected function parseConst($in, $from, $to, $parsingParams = false, $curBlock = '', &$pointer = null) |
||
2256 | { |
||
2257 | $substr = substr($in, $from, $to - $from); |
||
2258 | |||
2259 | if ($this->debug) { |
||
2260 | echo 'CONST FOUND : ' . $substr . "\n"; |
||
2261 | } |
||
2262 | |||
2263 | if (!preg_match('#^%([\\\\a-z0-9_:]+)#i', $substr, $m)) { |
||
2264 | throw new CompilationException($this, 'Invalid constant'); |
||
2265 | } |
||
2266 | |||
2267 | if ($pointer !== null) { |
||
2268 | $pointer += strlen($m[0]); |
||
2269 | } |
||
2270 | |||
2271 | $output = $this->parseConstKey($m[1], $curBlock); |
||
2272 | |||
2273 | View Code Duplication | if (is_array($parsingParams)) { |
|
2274 | $parsingParams[] = array($output, $m[1]); |
||
2275 | |||
2276 | return $parsingParams; |
||
2277 | } elseif ($curBlock === 'namedparam') { |
||
2278 | return array($output, $m[1]); |
||
2279 | } else { |
||
2280 | return $output; |
||
2281 | } |
||
2282 | } |
||
2283 | |||
2284 | /** |
||
2285 | * Parses a constant. |
||
2286 | * |
||
2287 | * @param string $key the constant to parse |
||
2288 | * @param string $curBlock the current parser-block being processed |
||
2289 | * |
||
2290 | * @return string parsed constant |
||
2291 | */ |
||
2292 | protected function parseConstKey($key, $curBlock) |
||
2293 | { |
||
2294 | if ($this->securityPolicy !== null && $this->securityPolicy->getConstantHandling() === SecurityPolicy::CONST_DISALLOW) { |
||
2295 | return 'null'; |
||
2296 | } |
||
2297 | |||
2298 | View Code Duplication | if ($curBlock !== 'root') { |
|
2299 | $output = '(defined("' . $key . '") ? ' . $key . ' : null)'; |
||
2300 | } else { |
||
2301 | $output = $key; |
||
2302 | } |
||
2303 | |||
2304 | return $output; |
||
2305 | } |
||
2306 | |||
2307 | /** |
||
2308 | * Parses a variable. |
||
2309 | * |
||
2310 | * @param string $in the string within which we must parse something |
||
2311 | * @param int $from the starting offset of the parsed area |
||
2312 | * @param int $to the ending offset of the parsed area |
||
2313 | * @param mixed $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by |
||
2314 | * default |
||
2315 | * @param string $curBlock the current parser-block being processed |
||
2316 | * @param mixed $pointer a reference to a pointer that will be increased by the amount of characters parsed, |
||
2317 | * or null by default |
||
2318 | * |
||
2319 | * @return string parsed values |
||
2320 | * @throws CompilationException |
||
2321 | */ |
||
2322 | protected function parseVar($in, $from, $to, $parsingParams = false, $curBlock = '', &$pointer = null) |
||
2323 | { |
||
2324 | $substr = substr($in, $from, $to - $from); |
||
2325 | |||
2326 | // var key |
||
2327 | $varRegex = '(\\$?\\.?[a-z0-9\\\\_:]*(?:(?:(?:\\.|->)(?:[a-z0-9\\\\_:]+|(?R))|\\[(?:[a-z0-9\\\\_:]+|(?R)|(["\'])[^\\2]*?\\2)\\]))*)'; |
||
2328 | // method call |
||
2329 | $methodCall = ($curBlock === 'root' || $curBlock === 'function' || $curBlock === 'namedparam' || $curBlock === 'condition' || $curBlock === 'variable' || $curBlock === 'expression' || $curBlock === 'delimited_string' ? '(\(.*)?' : '()'); |
||
2330 | // simple math expressions |
||
2331 | $simpleMathExpressions = ($curBlock === 'root' || $curBlock === 'function' || $curBlock === 'namedparam' || $curBlock === 'condition' || $curBlock === 'variable' || $curBlock === 'delimited_string' ? '((?:(?:[+\/*%=-])(?:(?<!=)=?-?[$%][a-z0-9\\\\.[\]>_:-]+(?:\([^)]*\))?|(?<!=)=?-?[0-9\.,]*|[+-]))*)' : '()'); |
||
2332 | // modifiers |
||
2333 | $modifiers = $curBlock !== 'modifier' ? '((?:\|(?:@?[a-z0-9\\\\_]+(?:(?::("|\').*?\5|:[^`]*))*))+)?' : '(())'; |
||
2334 | |||
2335 | $regex = '#'; |
||
2336 | $regex .= $varRegex; |
||
2337 | $regex .= $methodCall; |
||
2338 | $regex .= $simpleMathExpressions; |
||
2339 | $regex .= $modifiers; |
||
2340 | $regex .= '#i'; |
||
2341 | |||
2342 | if (preg_match($regex, $substr, $match)) { |
||
2343 | $key = substr($match[1], 1); |
||
2344 | |||
2345 | $matchedLength = strlen($match[0]); |
||
2346 | $hasModifiers = !empty($match[5]); |
||
2347 | $hasExpression = !empty($match[4]); |
||
2348 | $hasMethodCall = !empty($match[3]); |
||
2349 | |||
2350 | if (substr($key, - 1) == '.') { |
||
2351 | $key = substr($key, 0, - 1); |
||
2352 | -- $matchedLength; |
||
2353 | } |
||
2354 | |||
2355 | if ($hasMethodCall) { |
||
2356 | $matchedLength -= strlen($match[3]) + strlen(substr($match[1], strrpos($match[1], '->'))); |
||
2357 | $key = substr($match[1], 1, strrpos($match[1], '->') - 1); |
||
2358 | $methodCall = substr($match[1], strrpos($match[1], '->')) . $match[3]; |
||
2359 | } |
||
2360 | |||
2361 | if ($hasModifiers) { |
||
2362 | $matchedLength -= strlen($match[5]); |
||
2363 | } |
||
2364 | |||
2365 | if ($pointer !== null) { |
||
2366 | $pointer += $matchedLength; |
||
2367 | } |
||
2368 | |||
2369 | // replace useless brackets by dot accessed vars and strip enclosing quotes if present |
||
2370 | $key = preg_replace('#\[(["\']?)([^$%\[.>-]+)\1\]#', '.$2', $key); |
||
2371 | |||
2372 | View Code Duplication | if ($this->debug) { |
|
2373 | if ($hasMethodCall) { |
||
2374 | echo 'METHOD CALL FOUND : $' . $key . substr($methodCall, 0, 30) . "\n"; |
||
2375 | } else { |
||
2376 | echo 'VAR FOUND : $' . $key . "\n"; |
||
2377 | } |
||
2378 | } |
||
2379 | |||
2380 | $key = str_replace('"', '\\"', $key); |
||
2381 | |||
2382 | $cnt = substr_count($key, '$'); |
||
2383 | if ($cnt > 0) { |
||
2384 | $uid = 0; |
||
2385 | $parsed = array($uid => ''); |
||
2386 | $current = &$parsed; |
||
2387 | $curTxt = &$parsed[$uid ++]; |
||
2388 | $tree = array(); |
||
2389 | $chars = str_split($key, 1); |
||
2390 | $inSplittedVar = false; |
||
2391 | $bracketCount = 0; |
||
2392 | |||
2393 | while (($char = array_shift($chars)) !== null) { |
||
2394 | if ($char === '[') { |
||
2395 | if (count($tree) > 0) { |
||
2396 | ++ $bracketCount; |
||
2397 | } else { |
||
2398 | $tree[] = &$current; |
||
2399 | $current[$uid] = array($uid + 1 => ''); |
||
2400 | $current = &$current[$uid ++]; |
||
2401 | $curTxt = &$current[$uid ++]; |
||
2402 | continue; |
||
2403 | } |
||
2404 | } elseif ($char === ']') { |
||
2405 | if ($bracketCount > 0) { |
||
2406 | -- $bracketCount; |
||
2407 | } else { |
||
2408 | $current = &$tree[count($tree) - 1]; |
||
2409 | array_pop($tree); |
||
2410 | if (current($chars) !== '[' && current($chars) !== false && current($chars) !== ']') { |
||
2411 | $current[$uid] = ''; |
||
2412 | $curTxt = &$current[$uid ++]; |
||
2413 | } |
||
2414 | continue; |
||
2415 | } |
||
2416 | } elseif ($char === '$') { |
||
2417 | if (count($tree) == 0) { |
||
2418 | $curTxt = &$current[$uid ++]; |
||
2419 | $inSplittedVar = true; |
||
2420 | } |
||
2421 | } elseif (($char === '.' || $char === '-') && count($tree) == 0 && $inSplittedVar) { |
||
2422 | $curTxt = &$current[$uid ++]; |
||
2423 | $inSplittedVar = false; |
||
2424 | } |
||
2425 | |||
2426 | $curTxt .= $char; |
||
2427 | } |
||
2428 | unset($uid, $current, $curTxt, $tree, $chars); |
||
2429 | |||
2430 | if ($this->debug) { |
||
2431 | echo 'RECURSIVE VAR REPLACEMENT : ' . $key . "\n"; |
||
2432 | } |
||
2433 | |||
2434 | $key = $this->flattenVarTree($parsed); |
||
2435 | |||
2436 | if ($this->debug) { |
||
2437 | echo 'RECURSIVE VAR REPLACEMENT DONE : ' . $key . "\n"; |
||
2438 | } |
||
2439 | |||
2440 | $output = preg_replace('#(^""\.|""\.|\.""$|(\()""\.|\.""(\)))#', '$2$3', '$this->readVar("' . $key . '")'); |
||
2441 | } else { |
||
2442 | $output = $this->parseVarKey($key, $hasModifiers ? 'modifier' : $curBlock); |
||
2443 | } |
||
2444 | |||
2445 | |||
2446 | // methods |
||
2447 | if ($hasMethodCall) { |
||
2448 | $ptr = 0; |
||
2449 | |||
2450 | $output = $this->parseMethodCall($output, $methodCall, $curBlock, $ptr); |
||
2451 | |||
2452 | if ($pointer !== null) { |
||
2453 | $pointer += $ptr; |
||
2454 | } |
||
2455 | $matchedLength += $ptr; |
||
2456 | } |
||
2457 | |||
2458 | if ($hasExpression) { |
||
2459 | // expressions |
||
2460 | preg_match_all('#(?:([+/*%=-])(=?-?[%$][a-z0-9\\\\.[\]>_:-]+(?:\([^)]*\))?|=?-?[0-9.,]+|\1))#i', $match[4], $expMatch); |
||
2461 | foreach ($expMatch[1] as $k => $operator) { |
||
2462 | if (substr($expMatch[2][$k], 0, 1) === '=') { |
||
2463 | $assign = true; |
||
2464 | if ($operator === '=') { |
||
2465 | throw new CompilationException($this, 'Invalid expression <em>' . $substr . '</em>, can not use "==" in expressions'); |
||
2466 | } |
||
2467 | if ($curBlock !== 'root') { |
||
2468 | throw new CompilationException($this, 'Invalid expression <em>' . $substr . '</em>, assignments can only be used in top level expressions like {$foo+=3} or {$foo="bar"}'); |
||
2469 | } |
||
2470 | $operator .= '='; |
||
2471 | $expMatch[2][$k] = substr($expMatch[2][$k], 1); |
||
2472 | } |
||
2473 | |||
2474 | if (substr($expMatch[2][$k], 0, 1) === '-' && strlen($expMatch[2][$k]) > 1) { |
||
2475 | $operator .= '-'; |
||
2476 | $expMatch[2][$k] = substr($expMatch[2][$k], 1); |
||
2477 | } |
||
2478 | if (($operator === '+' || $operator === '-') && $expMatch[2][$k] === $operator) { |
||
2479 | $output = '(' . $output . $operator . $operator . ')'; |
||
2480 | break; |
||
2481 | } elseif (substr($expMatch[2][$k], 0, 1) === '$') { |
||
2482 | $output = '(' . $output . ' ' . $operator . ' ' . $this->parseVar($expMatch[2][$k], 0, strlen($expMatch[2][$k]), false, 'expression') . ')'; |
||
2483 | } elseif (substr($expMatch[2][$k], 0, 1) === '%') { |
||
2484 | $output = '(' . $output . ' ' . $operator . ' ' . $this->parseConst($expMatch[2][$k], 0, strlen($expMatch[2][$k]), false, 'expression') . ')'; |
||
2485 | } elseif (!empty($expMatch[2][$k])) { |
||
2486 | $output = '(' . $output . ' ' . $operator . ' ' . str_replace(',', '.', $expMatch[2][$k]) . ')'; |
||
2487 | } else { |
||
2488 | throw new CompilationException($this, 'Unfinished expression <em>' . $substr . '</em>, missing var or number after math operator'); |
||
2489 | } |
||
2490 | } |
||
2491 | } |
||
2492 | |||
2493 | if ($this->autoEscape === true && $curBlock !== 'condition') { |
||
2494 | $output = '(is_string($tmp=' . $output . ') ? htmlspecialchars($tmp, ENT_QUOTES, $this->charset) : $tmp)'; |
||
2495 | } |
||
2496 | |||
2497 | // handle modifiers |
||
2498 | if ($curBlock !== 'modifier' && $hasModifiers) { |
||
2499 | $ptr = 0; |
||
2500 | $output = $this->replaceModifiers(array(null, null, $output, $match[5]), 'var', $ptr); |
||
2501 | if ($pointer !== null) { |
||
2502 | $pointer += $ptr; |
||
2503 | } |
||
2504 | $matchedLength += $ptr; |
||
2505 | } |
||
2506 | |||
2507 | if (is_array($parsingParams)) { |
||
2508 | $parsingParams[] = array($output, $key); |
||
2509 | |||
2510 | return $parsingParams; |
||
2511 | } elseif ($curBlock === 'namedparam') { |
||
2512 | return array($output, $key); |
||
2513 | } elseif ($curBlock === 'string' || $curBlock === 'delimited_string') { |
||
2514 | return array($matchedLength, $output); |
||
2515 | } elseif ($curBlock === 'expression' || $curBlock === 'variable') { |
||
2516 | return $output; |
||
2517 | } elseif (isset($assign)) { |
||
2518 | return self::PHP_OPEN . $output . ';' . self::PHP_CLOSE; |
||
2519 | } else { |
||
2520 | return $output; |
||
2521 | } |
||
2522 | } else { |
||
2523 | if ($curBlock === 'string' || $curBlock === 'delimited_string') { |
||
2524 | return array(0, ''); |
||
2525 | } else { |
||
2526 | throw new CompilationException($this, 'Invalid variable name <em>' . $substr . '</em>'); |
||
2527 | } |
||
2528 | } |
||
2529 | } |
||
2530 | |||
2531 | /** |
||
2532 | * Parses any number of chained method calls/property reads. |
||
2533 | * |
||
2534 | * @param string $output the variable or whatever upon which the method are called |
||
2535 | * @param string $methodCall method call source, starting at "->" |
||
2536 | * @param string $curBlock the current parser-block being processed |
||
2537 | * @param int $pointer a reference to a pointer that will be increased by the amount of characters parsed |
||
2538 | * |
||
2539 | * @return string parsed call(s)/read(s) |
||
2540 | */ |
||
2541 | protected function parseMethodCall($output, $methodCall, $curBlock, &$pointer) |
||
2542 | { |
||
2543 | $ptr = 0; |
||
2544 | $len = strlen($methodCall); |
||
2545 | |||
2546 | while ($ptr < $len) { |
||
2547 | if (strpos($methodCall, '->', $ptr) === $ptr) { |
||
2548 | $ptr += 2; |
||
2549 | } |
||
2550 | |||
2551 | if (in_array( |
||
2552 | $methodCall[$ptr], array( |
||
2553 | ';', |
||
2554 | ',', |
||
2555 | '/', |
||
2556 | ' ', |
||
2557 | "\t", |
||
2558 | "\r", |
||
2559 | "\n", |
||
2560 | ')', |
||
2561 | '+', |
||
2562 | '*', |
||
2563 | '%', |
||
2564 | '=', |
||
2565 | '-', |
||
2566 | '|' |
||
2567 | ) |
||
2568 | ) || substr($methodCall, $ptr, strlen($this->rd)) === $this->rd |
||
2569 | ) { |
||
2570 | // break char found |
||
2571 | break; |
||
2572 | } |
||
2573 | |||
2574 | if (!preg_match('/^([a-z0-9_]+)(\(.*?\))?/i', substr($methodCall, $ptr), $methMatch)) { |
||
2575 | break; |
||
2576 | } |
||
2577 | |||
2578 | if (empty($methMatch[2])) { |
||
2579 | // property |
||
2580 | if ($curBlock === 'root') { |
||
2581 | $output .= '->' . $methMatch[1]; |
||
2582 | } else { |
||
2583 | $output = '(($tmp = ' . $output . ') ? $tmp->' . $methMatch[1] . ' : null)'; |
||
2584 | } |
||
2585 | $ptr += strlen($methMatch[1]); |
||
2586 | } else { |
||
2587 | // method |
||
2588 | if (substr($methMatch[2], 0, 2) === '()') { |
||
2589 | $parsedCall = $methMatch[1] . '()'; |
||
2590 | $ptr += strlen($methMatch[1]) + 2; |
||
2591 | } else { |
||
2592 | $parsedCall = $this->parseFunction($methodCall, $ptr, strlen($methodCall), false, 'method', $ptr); |
||
2593 | } |
||
2594 | if ($this->securityPolicy !== null) { |
||
2595 | $argPos = strpos($parsedCall, '('); |
||
2596 | $method = strtolower(substr($parsedCall, 0, $argPos)); |
||
2597 | $args = substr($parsedCall, $argPos); |
||
2598 | if ($curBlock === 'root') { |
||
2599 | $output = '$this->getSecurityPolicy()->callMethod($this, ' . $output . ', ' . var_export($method, true) . ', array' . $args . ')'; |
||
2600 | } else { |
||
2601 | $output = '(($tmp = ' . $output . ') ? $this->getSecurityPolicy()->callMethod($this, $tmp, ' . var_export($method, true) . ', array' . $args . ') : null)'; |
||
2602 | } |
||
2603 | View Code Duplication | } else { |
|
0 ignored issues
–
show
|
|||
2604 | if ($curBlock === 'root') { |
||
2605 | $output .= '->' . $parsedCall; |
||
2606 | } else { |
||
2607 | $output = '(($tmp = ' . $output . ') ? $tmp->' . $parsedCall . ' : null)'; |
||
2608 | } |
||
2609 | } |
||
2610 | } |
||
2611 | } |
||
2612 | |||
2613 | $pointer += $ptr; |
||
2614 | |||
2615 | return $output; |
||
2616 | } |
||
2617 | |||
2618 | /** |
||
2619 | * Parses a constant variable (a variable that doesn't contain another variable) and preprocesses it to save |
||
2620 | * runtime processing time. |
||
2621 | * |
||
2622 | * @param string $key the variable to parse |
||
2623 | * @param string $curBlock the current parser-block being processed |
||
2624 | * |
||
2625 | * @return string parsed variable |
||
2626 | */ |
||
2627 | protected function parseVarKey($key, $curBlock) |
||
2628 | { |
||
2629 | if ($key === '') { |
||
2630 | return '$this->scope'; |
||
2631 | } |
||
2632 | if (substr($key, 0, 1) === '.') { |
||
2633 | $key = 'dwoo' . $key; |
||
2634 | } |
||
2635 | if (preg_match('#dwoo\.(get|post|server|cookies|session|env|request)((?:\.[a-z0-9_-]+)+)#i', $key, $m)) { |
||
2636 | $global = strtoupper($m[1]); |
||
2637 | if ($global === 'COOKIES') { |
||
2638 | $global = 'COOKIE'; |
||
2639 | } |
||
2640 | $key = '$_' . $global; |
||
2641 | foreach (explode('.', ltrim($m[2], '.')) as $part) { |
||
2642 | $key .= '[' . var_export($part, true) . ']'; |
||
2643 | } |
||
2644 | View Code Duplication | if ($curBlock === 'root') { |
|
2645 | $output = $key; |
||
2646 | } else { |
||
2647 | $output = '(isset(' . $key . ')?' . $key . ':null)'; |
||
2648 | } |
||
2649 | } elseif (preg_match('#dwoo\\.const\\.([a-z0-9\\\\_:]+)#i', $key, $m)) { |
||
2650 | return $this->parseConstKey($m[1], $curBlock); |
||
2651 | } elseif ($this->scope !== null) { |
||
2652 | if (strstr($key, '.') === false && strstr($key, '[') === false && strstr($key, '->') === false) { |
||
2653 | if ($key === 'dwoo') { |
||
2654 | $output = '$this->globals'; |
||
2655 | } elseif ($key === '_root' || $key === '__') { |
||
2656 | $output = '$this->data'; |
||
2657 | } elseif ($key === '_parent' || $key === '_') { |
||
2658 | $output = '$this->readParentVar(1)'; |
||
2659 | } elseif ($key === '_key') { |
||
2660 | $output = '$tmp_key'; |
||
2661 | View Code Duplication | } else { |
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository.
Loading history...
|
|||
2662 | if ($curBlock === 'root') { |
||
2663 | $output = '$this->scope["' . $key . '"]'; |
||
2664 | } else { |
||
2665 | $output = '(isset($this->scope["' . $key . '"]) ? $this->scope["' . $key . '"] : null)'; |
||
2666 | } |
||
2667 | } |
||
2668 | } else { |
||
2669 | preg_match_all('#(\[|->|\.)?((?:[a-z0-9_]|-(?!>))+|(\\\?[\'"])[^\3]*?\3)\]?#i', $key, $m); |
||
2670 | |||
2671 | $i = $m[2][0]; |
||
2672 | if ($i === '_parent' || $i === '_') { |
||
2673 | $parentCnt = 0; |
||
2674 | |||
2675 | while (true) { |
||
2676 | ++ $parentCnt; |
||
2677 | array_shift($m[2]); |
||
2678 | array_shift($m[1]); |
||
2679 | if (current($m[2]) === '_parent') { |
||
2680 | continue; |
||
2681 | } |
||
2682 | break; |
||
2683 | } |
||
2684 | |||
2685 | $output = '$this->readParentVar(' . $parentCnt . ')'; |
||
2686 | } else { |
||
2687 | if ($i === 'dwoo') { |
||
2688 | $output = '$this->globals'; |
||
2689 | array_shift($m[2]); |
||
2690 | array_shift($m[1]); |
||
2691 | View Code Duplication | } elseif ($i === '_root' || $i === '__') { |
|
2692 | $output = '$this->data'; |
||
2693 | array_shift($m[2]); |
||
2694 | array_shift($m[1]); |
||
2695 | } elseif ($i === '_key') { |
||
2696 | $output = '$tmp_key'; |
||
2697 | } else { |
||
2698 | $output = '$this->scope'; |
||
2699 | } |
||
2700 | |||
2701 | while (count($m[1]) && $m[1][0] !== '->') { |
||
2702 | $m[2][0] = preg_replace('/(^\\\([\'"])|\\\([\'"])$)/x', '$2$3', $m[2][0]); |
||
2703 | if (substr($m[2][0], 0, 1) == '"' || substr($m[2][0], 0, 1) == "'") { |
||
2704 | $output .= '[' . $m[2][0] . ']'; |
||
2705 | } else { |
||
2706 | $output .= '["' . $m[2][0] . '"]'; |
||
2707 | } |
||
2708 | array_shift($m[2]); |
||
2709 | array_shift($m[1]); |
||
2710 | } |
||
2711 | |||
2712 | if ($curBlock !== 'root') { |
||
2713 | $output = '(isset(' . $output . ') ? ' . $output . ':null)'; |
||
2714 | } |
||
2715 | } |
||
2716 | |||
2717 | if (count($m[2])) { |
||
2718 | unset($m[0]); |
||
2719 | $output = '$this->readVarInto(' . str_replace("\n", '', var_export($m, true)) . ', ' . $output . ', ' . ($curBlock == 'root' ? 'false' : 'true') . ')'; |
||
2720 | } |
||
2721 | } |
||
2722 | } else { |
||
2723 | preg_match_all('#(\[|->|\.)?((?:[a-z0-9_]|-(?!>))+)\]?#i', $key, $m); |
||
2724 | unset($m[0]); |
||
2725 | $output = '$this->readVar(' . str_replace("\n", '', var_export($m, true)) . ')'; |
||
2726 | } |
||
2727 | |||
2728 | return $output; |
||
2729 | } |
||
2730 | |||
2731 | /** |
||
2732 | * Flattens a variable tree, this helps in parsing very complex variables such as $var.foo[$foo.bar->baz].baz, |
||
2733 | * it computes the contents of the brackets first and works out from there. |
||
2734 | * |
||
2735 | * @param array $tree the variable tree parsed by he parseVar() method that must be flattened |
||
2736 | * @param bool $recursed leave that to false by default, it is only for internal use |
||
2737 | * |
||
2738 | * @return string flattened tree |
||
2739 | */ |
||
2740 | protected function flattenVarTree(array $tree, $recursed = false) |
||
2741 | { |
||
2742 | $out = $recursed ? '".$this->readVarInto(' : ''; |
||
2743 | foreach ($tree as $bit) { |
||
2744 | if (is_array($bit)) { |
||
2745 | $out .= '.' . $this->flattenVarTree($bit, false); |
||
2746 | } else { |
||
2747 | $key = str_replace('"', '\\"', $bit); |
||
2748 | |||
2749 | if (substr($key, 0, 1) === '$') { |
||
2750 | $out .= '".' . $this->parseVar($key, 0, strlen($key), false, 'variable') . '."'; |
||
2751 | } else { |
||
2752 | $cnt = substr_count($key, '$'); |
||
2753 | |||
2754 | if ($this->debug) { |
||
2755 | echo 'PARSING SUBVARS IN : ' . $key . "\n"; |
||
2756 | } |
||
2757 | if ($cnt > 0) { |
||
2758 | while (-- $cnt >= 0) { |
||
2759 | if (isset($last)) { |
||
2760 | $last = strrpos($key, '$', - (strlen($key) - $last + 1)); |
||
2761 | } else { |
||
2762 | $last = strrpos($key, '$'); |
||
2763 | } |
||
2764 | preg_match('#\$[a-z0-9_]+((?:(?:\.|->)(?:[a-z0-9_]+|(?R))|\[(?:[a-z0-9_]+|(?R))\]))*' . '((?:(?:[+/*%-])(?:\$[a-z0-9.[\]>_:-]+(?:\([^)]*\))?|[0-9.,]*))*)#i', substr($key, $last), $submatch); |
||
2765 | |||
2766 | $len = strlen($submatch[0]); |
||
2767 | $key = substr_replace( |
||
2768 | $key, preg_replace_callback( |
||
2769 | '#(\$[a-z0-9_]+((?:(?:\.|->)(?:[a-z0-9_]+|(?R))|\[(?:[a-z0-9_]+|(?R))\]))*)' . '((?:(?:[+/*%-])(?:\$[a-z0-9.[\]>_:-]+(?:\([^)]*\))?|[0-9.,]*))*)#i', array( |
||
2770 | $this, |
||
2771 | 'replaceVarKeyHelper' |
||
2772 | ), substr($key, $last, $len) |
||
2773 | ), $last, $len |
||
2774 | ); |
||
2775 | if ($this->debug) { |
||
2776 | echo 'RECURSIVE VAR REPLACEMENT DONE : ' . $key . "\n"; |
||
2777 | } |
||
2778 | } |
||
2779 | unset($last); |
||
2780 | |||
2781 | $out .= $key; |
||
2782 | } else { |
||
2783 | $out .= $key; |
||
2784 | } |
||
2785 | } |
||
2786 | } |
||
2787 | } |
||
2788 | $out .= $recursed ? ', true)."' : ''; |
||
2789 | |||
2790 | return $out; |
||
2791 | } |
||
2792 | |||
2793 | /** |
||
2794 | * Helper function that parses a variable. |
||
2795 | * |
||
2796 | * @param array $match the matched variable, array(1=>"string match") |
||
2797 | * |
||
2798 | * @return string parsed variable |
||
2799 | */ |
||
2800 | protected function replaceVarKeyHelper($match) |
||
2801 | { |
||
2802 | return '".' . $this->parseVar($match[0], 0, strlen($match[0]), false, 'variable') . '."'; |
||
2803 | } |
||
2804 | |||
2805 | /** |
||
2806 | * Parses various constants, operators or non-quoted strings. |
||
2807 | * |
||
2808 | * @param string $in the string within which we must parse something |
||
2809 | * @param int $from the starting offset of the parsed area |
||
2810 | * @param int $to the ending offset of the parsed area |
||
2811 | * @param mixed $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by |
||
2812 | * default |
||
2813 | * @param string $curBlock the current parser-block being processed |
||
2814 | * @param mixed $pointer a reference to a pointer that will be increased by the amount of characters parsed, |
||
2815 | * or null by default |
||
2816 | * |
||
2817 | * @return string parsed values |
||
2818 | * @throws Exception |
||
2819 | */ |
||
2820 | protected function parseOthers($in, $from, $to, $parsingParams = false, $curBlock = '', &$pointer = null) |
||
2821 | { |
||
2822 | $substr = substr($in, $from, $to - $from); |
||
2823 | |||
2824 | $end = strlen($substr); |
||
2825 | |||
2826 | if ($curBlock === 'condition') { |
||
2827 | $breakChars = array( |
||
2828 | '(', |
||
2829 | ')', |
||
2830 | ' ', |
||
2831 | '||', |
||
2832 | '&&', |
||
2833 | '|', |
||
2834 | '&', |
||
2835 | '>=', |
||
2836 | '<=', |
||
2837 | '===', |
||
2838 | '==', |
||
2839 | '=', |
||
2840 | '!==', |
||
2841 | '!=', |
||
2842 | '<<', |
||
2843 | '<', |
||
2844 | '>>', |
||
2845 | '>', |
||
2846 | '^', |
||
2847 | '~', |
||
2848 | ',', |
||
2849 | '+', |
||
2850 | '-', |
||
2851 | '*', |
||
2852 | '/', |
||
2853 | '%', |
||
2854 | '!', |
||
2855 | '?', |
||
2856 | ':', |
||
2857 | $this->rd, |
||
2858 | ';' |
||
2859 | ); |
||
2860 | } elseif ($curBlock === 'modifier') { |
||
2861 | $breakChars = array(' ', ',', ')', ':', '|', "\r", "\n", "\t", ';', $this->rd); |
||
2862 | } elseif ($curBlock === 'expression') { |
||
2863 | $breakChars = array('/', '%', '+', '-', '*', ' ', ',', ')', "\r", "\n", "\t", ';', $this->rd); |
||
2864 | } else { |
||
2865 | $breakChars = array(' ', ',', ')', "\r", "\n", "\t", ';', $this->rd); |
||
2866 | } |
||
2867 | |||
2868 | $breaker = false; |
||
2869 | while (list($k, $char) = each($breakChars)) { |
||
2870 | $test = strpos($substr, $char); |
||
2871 | if ($test !== false && $test < $end) { |
||
2872 | $end = $test; |
||
2873 | $breaker = $k; |
||
2874 | } |
||
2875 | } |
||
2876 | |||
2877 | if ($curBlock === 'condition') { |
||
2878 | if ($end === 0 && $breaker !== false) { |
||
2879 | $end = strlen($breakChars[$breaker]); |
||
2880 | } |
||
2881 | } |
||
2882 | |||
2883 | if ($end !== false) { |
||
2884 | $substr = substr($substr, 0, $end); |
||
2885 | } |
||
2886 | |||
2887 | if ($pointer !== null) { |
||
2888 | $pointer += strlen($substr); |
||
2889 | } |
||
2890 | |||
2891 | $src = $substr; |
||
2892 | $substr = trim($substr); |
||
2893 | |||
2894 | if (strtolower($substr) === 'false' || strtolower($substr) === 'no' || strtolower($substr) === 'off') { |
||
2895 | if ($this->debug) { |
||
2896 | echo 'BOOLEAN(FALSE) PARSED' . "\n"; |
||
2897 | } |
||
2898 | $substr = 'false'; |
||
2899 | $type = self::T_BOOL; |
||
2900 | } elseif (strtolower($substr) === 'true' || strtolower($substr) === 'yes' || strtolower($substr) === 'on') { |
||
2901 | if ($this->debug) { |
||
2902 | echo 'BOOLEAN(TRUE) PARSED' . "\n"; |
||
2903 | } |
||
2904 | $substr = 'true'; |
||
2905 | $type = self::T_BOOL; |
||
2906 | View Code Duplication | } elseif ($substr === 'null' || $substr === 'NULL') { |
|
2907 | if ($this->debug) { |
||
2908 | echo 'NULL PARSED' . "\n"; |
||
2909 | } |
||
2910 | $substr = 'null'; |
||
2911 | $type = self::T_NULL; |
||
2912 | } elseif (is_numeric($substr)) { |
||
2913 | $substr = (float)$substr; |
||
2914 | if ((int)$substr == $substr) { |
||
2915 | $substr = (int)$substr; |
||
2916 | } |
||
2917 | $type = self::T_NUMERIC; |
||
2918 | if ($this->debug) { |
||
2919 | echo 'NUMBER (' . $substr . ') PARSED' . "\n"; |
||
2920 | } |
||
2921 | View Code Duplication | } elseif (preg_match('{^-?(\d+|\d*(\.\d+))\s*([/*%+-]\s*-?(\d+|\d*(\.\d+)))+$}', $substr)) { |
|
2922 | if ($this->debug) { |
||
2923 | echo 'SIMPLE MATH PARSED . "\n"'; |
||
2924 | } |
||
2925 | $type = self::T_MATH; |
||
2926 | $substr = '(' . $substr . ')'; |
||
2927 | } elseif ($curBlock === 'condition' && array_search($substr, $breakChars, true) !== false) { |
||
2928 | if ($this->debug) { |
||
2929 | echo 'BREAKCHAR (' . $substr . ') PARSED' . "\n"; |
||
2930 | } |
||
2931 | $type = self::T_BREAKCHAR; |
||
2932 | //$substr = '"'.$substr.'"'; |
||
2933 | } else { |
||
2934 | $substr = $this->replaceStringVars('\'' . str_replace('\'', '\\\'', $substr) . '\'', '\'', $curBlock); |
||
2935 | $type = self::T_UNQUOTED_STRING; |
||
2936 | if ($this->debug) { |
||
2937 | echo 'BLABBER (' . $substr . ') CASTED AS STRING' . "\n"; |
||
2938 | } |
||
2939 | } |
||
2940 | |||
2941 | if (is_array($parsingParams)) { |
||
2942 | $parsingParams[] = array($substr, $src, $type); |
||
2943 | |||
2944 | return $parsingParams; |
||
2945 | } elseif ($curBlock === 'namedparam') { |
||
2946 | return array($substr, $src, $type); |
||
2947 | } elseif ($curBlock === 'expression') { |
||
2948 | return $substr; |
||
2949 | } else { |
||
2950 | throw new Exception('Something went wrong'); |
||
2951 | } |
||
2952 | } |
||
2953 | |||
2954 | /** |
||
2955 | * Replaces variables within a parsed string. |
||
2956 | * |
||
2957 | * @param string $string the parsed string |
||
2958 | * @param string $first the first character parsed in the string, which is the string delimiter (' or ") |
||
2959 | * @param string $curBlock the current parser-block being processed |
||
2960 | * |
||
2961 | * @return string the original string with variables replaced |
||
2962 | */ |
||
2963 | protected function replaceStringVars($string, $first, $curBlock = '') |
||
2964 | { |
||
2965 | $pos = 0; |
||
2966 | if ($this->debug) { |
||
2967 | echo 'STRING VAR REPLACEMENT : ' . $string . "\n"; |
||
2968 | } |
||
2969 | // replace vars |
||
2970 | while (($pos = strpos($string, '$', $pos)) !== false) { |
||
2971 | $prev = substr($string, $pos - 1, 1); |
||
2972 | if ($prev === '\\') { |
||
2973 | ++ $pos; |
||
2974 | continue; |
||
2975 | } |
||
2976 | |||
2977 | $var = $this->parse($string, $pos, null, false, ($curBlock === 'modifier' ? 'modifier' : ($prev === '`' ? 'delimited_string' : 'string'))); |
||
2978 | $len = $var[0]; |
||
2979 | $var = $this->parse(str_replace('\\' . $first, $first, $string), $pos, null, false, ($curBlock === 'modifier' ? 'modifier' : ($prev === '`' ? 'delimited_string' : 'string'))); |
||
2980 | |||
2981 | if ($prev === '`' && substr($string, $pos + $len, 1) === '`') { |
||
2982 | $string = substr_replace($string, $first . '.' . $var[1] . '.' . $first, $pos - 1, $len + 2); |
||
2983 | } else { |
||
2984 | $string = substr_replace($string, $first . '.' . $var[1] . '.' . $first, $pos, $len); |
||
2985 | } |
||
2986 | $pos += strlen($var[1]) + 2; |
||
2987 | if ($this->debug) { |
||
2988 | echo 'STRING VAR REPLACEMENT DONE : ' . $string . "\n"; |
||
2989 | } |
||
2990 | } |
||
2991 | |||
2992 | // handle modifiers |
||
2993 | // TODO Obsolete? |
||
2994 | $string = preg_replace_callback( |
||
2995 | '#("|\')\.(.+?)\.\1((?:\|(?:@?[a-z0-9_]+(?:(?::("|\').+?\4|:[^`]*))*))+)#i', array( |
||
2996 | $this, |
||
2997 | 'replaceModifiers' |
||
2998 | ), $string |
||
2999 | ); |
||
3000 | |||
3001 | // replace escaped dollar operators by unescaped ones if required |
||
3002 | if ($first === "'") { |
||
3003 | $string = str_replace('\\$', '$', $string); |
||
3004 | } |
||
3005 | |||
3006 | return $string; |
||
3007 | } |
||
3008 | |||
3009 | /** |
||
3010 | * Replaces the modifiers applied to a string or a variable. |
||
3011 | * |
||
3012 | * @param array $m the regex matches that must be array(1=>"double or single quotes enclosing a string, |
||
3013 | * when applicable", 2=>"the string or var", 3=>"the modifiers matched") |
||
3014 | * @param string $curBlock the current parser-block being processed |
||
3015 | * @param null $pointer |
||
3016 | * |
||
3017 | * @return string the input enclosed with various function calls according to the modifiers found |
||
3018 | * @throws CompilationException |
||
3019 | * @throws Exception |
||
3020 | */ |
||
3021 | protected function replaceModifiers(array $m, $curBlock = null, &$pointer = null) |
||
3022 | { |
||
3023 | if ($this->debug) { |
||
3024 | echo 'PARSING MODIFIERS : ' . $m[3] . "\n"; |
||
3025 | } |
||
3026 | |||
3027 | if ($pointer !== null) { |
||
3028 | $pointer += strlen($m[3]); |
||
3029 | } |
||
3030 | // remove first pipe |
||
3031 | $cmdstrsrc = substr($m[3], 1); |
||
3032 | // remove last quote if present |
||
3033 | if (substr($cmdstrsrc, - 1, 1) === $m[1]) { |
||
3034 | $cmdstrsrc = substr($cmdstrsrc, 0, - 1); |
||
3035 | $add = $m[1]; |
||
3036 | } |
||
3037 | |||
3038 | $output = $m[2]; |
||
3039 | |||
3040 | $continue = true; |
||
3041 | while (strlen($cmdstrsrc) > 0 && $continue) { |
||
3042 | if ($cmdstrsrc[0] === '|') { |
||
3043 | $cmdstrsrc = substr($cmdstrsrc, 1); |
||
3044 | continue; |
||
3045 | } |
||
3046 | if ($cmdstrsrc[0] === ' ' || $cmdstrsrc[0] === ';' || substr($cmdstrsrc, 0, strlen($this->rd)) === $this->rd) { |
||
3047 | if ($this->debug) { |
||
3048 | echo 'MODIFIER PARSING ENDED, RIGHT DELIMITER or ";" FOUND' . "\n"; |
||
3049 | } |
||
3050 | $continue = false; |
||
3051 | if ($pointer !== null) { |
||
3052 | $pointer -= strlen($cmdstrsrc); |
||
3053 | } |
||
3054 | break; |
||
3055 | } |
||
3056 | $cmdstr = $cmdstrsrc; |
||
3057 | $paramsep = ':'; |
||
3058 | View Code Duplication | if (!preg_match('/^(@{0,2}[a-z_][a-z0-9_]*)(:)?/i', $cmdstr, $match)) { |
|
3059 | throw new CompilationException($this, 'Invalid modifier name, started with : ' . substr($cmdstr, 0, 10)); |
||
3060 | } |
||
3061 | $paramspos = !empty($match[2]) ? strlen($match[1]) : false; |
||
3062 | $func = $match[1]; |
||
3063 | |||
3064 | $state = 0; |
||
3065 | if ($paramspos === false) { |
||
3066 | $cmdstrsrc = substr($cmdstrsrc, strlen($func)); |
||
3067 | $params = array(); |
||
3068 | if ($this->debug) { |
||
3069 | echo 'MODIFIER (' . $func . ') CALLED WITH NO PARAMS' . "\n"; |
||
3070 | } |
||
3071 | } else { |
||
3072 | $paramstr = substr($cmdstr, $paramspos + 1); |
||
3073 | View Code Duplication | if (substr($paramstr, - 1, 1) === $paramsep) { |
|
3074 | $paramstr = substr($paramstr, 0, - 1); |
||
3075 | } |
||
3076 | |||
3077 | $ptr = 0; |
||
3078 | $params = array(); |
||
3079 | while ($ptr < strlen($paramstr)) { |
||
3080 | if ($this->debug) { |
||
3081 | echo 'MODIFIER (' . $func . ') START PARAM PARSING WITH POINTER AT ' . $ptr . "\n"; |
||
3082 | } |
||
3083 | if ($this->debug) { |
||
3084 | echo $paramstr . '--' . $ptr . '--' . strlen($paramstr) . '--modifier' . "\n"; |
||
3085 | } |
||
3086 | $params = $this->parse($paramstr, $ptr, strlen($paramstr), $params, 'modifier', $ptr); |
||
3087 | if ($this->debug) { |
||
3088 | echo 'PARAM PARSED, POINTER AT ' . $ptr . "\n"; |
||
3089 | } |
||
3090 | |||
3091 | if ($ptr >= strlen($paramstr)) { |
||
3092 | if ($this->debug) { |
||
3093 | echo 'PARAM PARSING ENDED, PARAM STRING CONSUMED' . "\n"; |
||
3094 | } |
||
3095 | break; |
||
3096 | } |
||
3097 | |||
3098 | if ($paramstr[$ptr] === ' ' || $paramstr[$ptr] === '|' || $paramstr[$ptr] === ';' || substr($paramstr, $ptr, strlen($this->rd)) === $this->rd) { |
||
3099 | if ($this->debug) { |
||
3100 | echo 'PARAM PARSING ENDED, " ", "|", RIGHT DELIMITER or ";" FOUND, POINTER AT ' . $ptr . "\n"; |
||
3101 | } |
||
3102 | if ($paramstr[$ptr] !== '|') { |
||
3103 | $continue = false; |
||
3104 | if ($pointer !== null) { |
||
3105 | $pointer -= strlen($paramstr) - $ptr; |
||
3106 | } |
||
3107 | } |
||
3108 | ++ $ptr; |
||
3109 | break; |
||
3110 | } |
||
3111 | if ($ptr < strlen($paramstr) && $paramstr[$ptr] === ':') { |
||
3112 | ++ $ptr; |
||
3113 | } |
||
3114 | } |
||
3115 | $cmdstrsrc = substr($cmdstrsrc, strlen($func) + 1 + $ptr); |
||
3116 | foreach ($params as $k => $p) { |
||
3117 | if (is_array($p) && is_array($p[1])) { |
||
3118 | $state |= 2; |
||
3119 | } else { |
||
3120 | if (($state & 2) && preg_match('#^(["\'])(.+?)\1$#', $p[0], $m)) { |
||
3121 | $params[$k] = array($m[2], array('true', 'true')); |
||
3122 | } else { |
||
3123 | if ($state & 2) { |
||
3124 | throw new CompilationException($this, 'You can not use an unnamed parameter after a named one'); |
||
3125 | } |
||
3126 | $state |= 1; |
||
3127 | } |
||
3128 | } |
||
3129 | } |
||
3130 | } |
||
3131 | |||
3132 | // check if we must use array_map with this plugin or not |
||
3133 | $mapped = false; |
||
3134 | if (substr($func, 0, 1) === '@') { |
||
3135 | $func = substr($func, 1); |
||
3136 | $mapped = true; |
||
3137 | } |
||
3138 | |||
3139 | $pluginType = $this->getPluginType($func); |
||
3140 | |||
3141 | if ($state & 2) { |
||
3142 | array_unshift($params, array('value', is_array($output) ? $output : array($output, $output))); |
||
3143 | } else { |
||
3144 | array_unshift($params, is_array($output) ? $output : array($output, $output)); |
||
3145 | } |
||
3146 | |||
3147 | if ($pluginType & Core::NATIVE_PLUGIN) { |
||
3148 | $params = $this->mapParams($params, null, $state); |
||
3149 | |||
3150 | $params = $params['*'][0]; |
||
3151 | |||
3152 | $params = self::implode_r($params); |
||
3153 | |||
3154 | View Code Duplication | if ($mapped) { |
|
3155 | $output = '$this->arrayMap(\'' . $func . '\', array(' . $params . '))'; |
||
3156 | } else { |
||
3157 | $output = $func . '(' . $params . ')'; |
||
3158 | } |
||
3159 | } elseif ($pluginType & Core::PROXY_PLUGIN) { |
||
3160 | $params = $this->mapParams($params, $this->getDwoo()->getPluginProxy()->getCallback($func), $state); |
||
3161 | foreach ($params as &$p) { |
||
3162 | $p = $p[0]; |
||
3163 | } |
||
3164 | $output = call_user_func(array($this->getDwoo()->getPluginProxy(), 'getCode'), $func, $params); |
||
3165 | } elseif ($pluginType & Core::SMARTY_MODIFIER) { |
||
3166 | $params = $this->mapParams($params, null, $state); |
||
3167 | $params = $params['*'][0]; |
||
3168 | |||
3169 | $params = self::implode_r($params); |
||
3170 | |||
3171 | if ($pluginType & Core::CUSTOM_PLUGIN) { |
||
3172 | $callback = $this->customPlugins[$func]['callback']; |
||
3173 | if (is_array($callback)) { |
||
3174 | if (is_object($callback[0])) { |
||
3175 | $output = ($mapped ? '$this->arrayMap' : 'call_user_func_array') . '(array($this->plugins[\'' . $func . '\'][\'callback\'][0], \'' . $callback[1] . '\'), array(' . $params . '))'; |
||
3176 | } else { |
||
3177 | $output = ($mapped ? '$this->arrayMap' : 'call_user_func_array') . '(array(\'' . $callback[0] . '\', \'' . $callback[1] . '\'), array(' . $params . '))'; |
||
3178 | } |
||
3179 | } elseif ($mapped) { |
||
3180 | $output = '$this->arrayMap(\'' . $callback . '\', array(' . $params . '))'; |
||
3181 | } else { |
||
3182 | $output = $callback . '(' . $params . ')'; |
||
3183 | } |
||
3184 | } elseif ($mapped) { |
||
3185 | $output = '$this->arrayMap(\'smarty_modifier_' . $func . '\', array(' . $params . '))'; |
||
3186 | } else { |
||
3187 | $output = 'smarty_modifier_' . $func . '(' . $params . ')'; |
||
3188 | } |
||
3189 | } else { |
||
3190 | if ($pluginType & Core::CUSTOM_PLUGIN) { |
||
3191 | $callback = $this->customPlugins[$func]['callback']; |
||
3192 | $pluginName = $callback; |
||
3193 | } else { |
||
3194 | if (class_exists('Plugin' . Core::toCamelCase($func)) !== false || function_exists('Plugin' . |
||
3195 | Core::toCamelCase($func) . (($pluginType & Core::COMPILABLE_PLUGIN) ? 'Compile' : '')) |
||
3196 | !== false) { |
||
3197 | $pluginName = 'Plugin' . Core::toCamelCase($func); |
||
3198 | } else { |
||
3199 | $pluginName = Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . Core::toCamelCase($func); |
||
3200 | } |
||
3201 | if ($pluginType & Core::CLASS_PLUGIN) { |
||
3202 | $callback = array($pluginName, ($pluginType & Core::COMPILABLE_PLUGIN) ? 'compile' : 'process'); |
||
3203 | } else { |
||
3204 | $callback = $pluginName . (($pluginType & Core::COMPILABLE_PLUGIN) ? 'Compile' : ''); |
||
3205 | } |
||
3206 | } |
||
3207 | $params = $this->mapParams($params, $callback, $state); |
||
3208 | |||
3209 | foreach ($params as &$p) { |
||
3210 | $p = $p[0]; |
||
3211 | } |
||
3212 | |||
3213 | if ($pluginType & Core::FUNC_PLUGIN) { |
||
3214 | if ($pluginType & Core::COMPILABLE_PLUGIN) { |
||
3215 | if ($mapped) { |
||
3216 | throw new CompilationException($this, 'The @ operator can not be used on compiled plugins.'); |
||
3217 | } |
||
3218 | if ($pluginType & Core::CUSTOM_PLUGIN) { |
||
3219 | $funcCompiler = $this->customPlugins[$func]['callback']; |
||
3220 | } else { |
||
3221 | if (function_exists('Plugin' . Core::toCamelCase($func) . 'Compile') !== false) { |
||
3222 | $funcCompiler = 'Plugin' . Core::toCamelCase($func) . 'Compile'; |
||
3223 | } else { |
||
3224 | $funcCompiler = Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . Core::toCamelCase($func) . |
||
3225 | 'Compile'; |
||
3226 | } |
||
3227 | } |
||
3228 | array_unshift($params, $this); |
||
3229 | $output = call_user_func_array($funcCompiler, $params); |
||
3230 | } else { |
||
3231 | array_unshift($params, '$this'); |
||
3232 | |||
3233 | $params = self::implode_r($params); |
||
3234 | View Code Duplication | if ($mapped) { |
|
3235 | $output = '$this->arrayMap(\'' . $pluginName . '\', array(' . $params . '))'; |
||
3236 | } else { |
||
3237 | $output = $pluginName . '(' . $params . ')'; |
||
3238 | } |
||
3239 | } |
||
3240 | } else { |
||
3241 | if ($pluginType & Core::COMPILABLE_PLUGIN) { |
||
3242 | if ($mapped) { |
||
3243 | throw new CompilationException($this, 'The @ operator can not be used on compiled plugins.'); |
||
3244 | } |
||
3245 | View Code Duplication | if ($pluginType & Core::CUSTOM_PLUGIN) { |
|
3246 | $callback = $this->customPlugins[$func]['callback']; |
||
3247 | if (!is_array($callback)) { |
||
3248 | if (!method_exists($callback, 'compile')) { |
||
3249 | throw new Exception('Custom plugin ' . $func . ' must implement the "compile" method to be compilable, or you should provide a full callback to the method to use'); |
||
3250 | } |
||
3251 | if (($ref = new ReflectionMethod($callback, 'compile')) && $ref->isStatic()) { |
||
3252 | $funcCompiler = array($callback, 'compile'); |
||
3253 | } else { |
||
3254 | $funcCompiler = array(new $callback(), 'compile'); |
||
3255 | } |
||
3256 | } else { |
||
3257 | $funcCompiler = $callback; |
||
3258 | } |
||
3259 | } else { |
||
3260 | if (class_exists('Plugin' . Core::toCamelCase($func)) !== false) { |
||
3261 | $funcCompiler = array('Plugin' . Core::toCamelCase($func), 'compile'); |
||
3262 | } else { |
||
3263 | $funcCompiler = array( |
||
3264 | Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . Core::toCamelCase($func), |
||
3265 | 'compile' |
||
3266 | ); |
||
3267 | } |
||
3268 | array_unshift($params, $this); |
||
3269 | } |
||
3270 | $output = call_user_func_array($funcCompiler, $params); |
||
3271 | } else { |
||
3272 | $params = self::implode_r($params); |
||
3273 | |||
3274 | if ($pluginType & Core::CUSTOM_PLUGIN) { |
||
3275 | if (is_object($callback[0])) { |
||
3276 | $output = ($mapped ? '$this->arrayMap' : 'call_user_func_array') . '(array($this->plugins[\'' . $func . '\'][\'callback\'][0], \'' . $callback[1] . '\'), array(' . $params . '))'; |
||
3277 | } else { |
||
3278 | $output = ($mapped ? '$this->arrayMap' : 'call_user_func_array') . '(array(\'' . $callback[0] . '\', \'' . $callback[1] . '\'), array(' . $params . '))'; |
||
3279 | } |
||
3280 | View Code Duplication | } elseif ($mapped) { |
|
3281 | $output = '$this->arrayMap(array($this->getObjectPlugin(\''. |
||
3282 | Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . Core::toCamelCase($func) . '\'), |
||
3283 | \'process\'), array(' . $params . '))'; |
||
3284 | } else { |
||
3285 | if (class_exists('Plugin' . Core::toCamelCase($func)) !== false) { |
||
3286 | $output = '$this->classCall(\'Plugin' . Core::toCamelCase($func) . '\', array(' . $params . '))'; |
||
3287 | } else { |
||
3288 | $output = '$this->classCall(\'' . $func . '\', array(' . $params . '))'; |
||
3289 | } |
||
3290 | } |
||
3291 | } |
||
3292 | } |
||
3293 | } |
||
3294 | } |
||
3295 | |||
3296 | if ($curBlock === 'namedparam') { |
||
3297 | return array($output, $output); |
||
3298 | } elseif ($curBlock === 'var' || $m[1] === null) { |
||
3299 | return $output; |
||
3300 | } elseif ($curBlock === 'string' || $curBlock === 'root') { |
||
3301 | return $m[1] . '.' . $output . '.' . $m[1] . (isset($add) ? $add : null); |
||
3302 | } |
||
3303 | |||
3304 | return ''; |
||
3305 | } |
||
3306 | |||
3307 | /** |
||
3308 | * Recursively implodes an array in a similar manner as var_export() does but with some tweaks |
||
3309 | * to handle pre-compiled values and the fact that we do not need to enclose everything with |
||
3310 | * "array" and do not require top-level keys to be displayed. |
||
3311 | * |
||
3312 | * @param array $params the array to implode |
||
3313 | * @param bool $recursiveCall if set to true, the function outputs key names for the top level |
||
3314 | * |
||
3315 | * @return string the imploded array |
||
3316 | */ |
||
3317 | public static function implode_r(array $params, $recursiveCall = false) |
||
3318 | { |
||
3319 | $out = ''; |
||
3320 | foreach ($params as $k => $p) { |
||
3321 | if (is_array($p)) { |
||
3322 | $out2 = 'array('; |
||
3323 | foreach ($p as $k2 => $v) { |
||
3324 | $out2 .= var_export($k2, true) . ' => ' . (is_array($v) ? 'array(' . self::implode_r($v, true) . ')' : $v) . ', '; |
||
3325 | } |
||
3326 | $p = rtrim($out2, ', ') . ')'; |
||
3327 | } |
||
3328 | if ($recursiveCall) { |
||
3329 | $out .= var_export($k, true) . ' => ' . $p . ', '; |
||
3330 | } else { |
||
3331 | $out .= $p . ', '; |
||
3332 | } |
||
3333 | } |
||
3334 | |||
3335 | return rtrim($out, ', '); |
||
3336 | } |
||
3337 | |||
3338 | /** |
||
3339 | * Returns the plugin type of a plugin and adds it to the used plugins array if required. |
||
3340 | * |
||
3341 | * @param string $name plugin name, as found in the template |
||
3342 | * |
||
3343 | * @return int type as a multi bit flag composed of the Dwoo plugin types constants |
||
3344 | * @throws Exception |
||
3345 | * @throws SecurityException |
||
3346 | * @throws Exception |
||
3347 | */ |
||
3348 | protected function getPluginType($name) |
||
3349 | { |
||
3350 | $pluginType = - 1; |
||
3351 | |||
3352 | if (($this->securityPolicy === null && (function_exists($name) || strtolower($name) === 'isset' || strtolower($name) === 'empty')) || ($this->securityPolicy !== null && array_key_exists(strtolower($name), $this->securityPolicy->getAllowedPhpFunctions()) !== false)) { |
||
3353 | $phpFunc = true; |
||
3354 | } elseif ($this->securityPolicy !== null && function_exists($name) && array_key_exists(strtolower($name), $this->securityPolicy->getAllowedPhpFunctions()) === false) { |
||
3355 | throw new SecurityException('Call to a disallowed php function : ' . $name); |
||
3356 | } |
||
3357 | |||
3358 | while ($pluginType <= 0) { |
||
3359 | // Template plugin compilable |
||
3360 | if (isset($this->templatePlugins[$name])) { |
||
3361 | $pluginType = Core::TEMPLATE_PLUGIN | Core::COMPILABLE_PLUGIN; |
||
3362 | } // Custom plugin |
||
3363 | elseif (isset($this->customPlugins[$name])) { |
||
3364 | $pluginType = $this->customPlugins[$name]['type'] | Core::CUSTOM_PLUGIN; |
||
3365 | } // Class blocks plugin |
||
3366 | elseif (class_exists(Core::NAMESPACE_PLUGINS_BLOCKS . 'Plugin' . Core::toCamelCase($name), false) !== |
||
3367 | false) { |
||
3368 | if (is_subclass_of(Core::NAMESPACE_PLUGINS_BLOCKS . 'Plugin' . Core::toCamelCase($name), 'Dwoo\Block\Plugin')) { |
||
3369 | $pluginType = Core::BLOCK_PLUGIN; |
||
3370 | } else { |
||
3371 | $pluginType = Core::CLASS_PLUGIN; |
||
3372 | } |
||
3373 | $interfaces = class_implements(Core::NAMESPACE_PLUGINS_BLOCKS . 'Plugin' . Core::toCamelCase($name)); |
||
3374 | View Code Duplication | if (in_array('Dwoo\ICompilable', $interfaces) !== false || in_array('Dwoo\ICompilable\Block', $interfaces) !== false) { |
|
3375 | $pluginType |= Core::COMPILABLE_PLUGIN; |
||
3376 | } |
||
3377 | } // Class functions plugin |
||
3378 | elseif(class_exists(Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . Core::toCamelCase($name), false) !== |
||
3379 | false) { |
||
3380 | $pluginType = Core::CLASS_PLUGIN; |
||
3381 | $interfaces = class_implements(Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . Core::toCamelCase($name)); |
||
3382 | View Code Duplication | if (in_array('Dwoo\ICompilable', $interfaces) !== false || in_array('Dwoo\ICompilable\Block', $interfaces) !== false) { |
|
3383 | $pluginType |= Core::COMPILABLE_PLUGIN; |
||
3384 | } |
||
3385 | } // Class without namespace |
||
3386 | elseif(class_exists('Plugin' . Core::toCamelCase($name), false) !== false) { |
||
3387 | $pluginType = Core::CLASS_PLUGIN; |
||
3388 | $interfaces = class_implements('Plugin' . Core::toCamelCase($name)); |
||
3389 | View Code Duplication | if (in_array('Dwoo\ICompilable', $interfaces) !== false || in_array('Dwoo\ICompilable\Block', $interfaces) !== false) { |
|
3390 | $pluginType |= Core::COMPILABLE_PLUGIN; |
||
3391 | } |
||
3392 | } // Function plugin (with/without namespaces) |
||
3393 | elseif (function_exists(Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . Core::toCamelCase |
||
3394 | ($name)) !== |
||
3395 | false || function_exists('Plugin' . Core::toCamelCase($name)) !== false) { |
||
3396 | $pluginType = Core::FUNC_PLUGIN; |
||
3397 | } // Function plugin compile (with/without namespaces) |
||
3398 | elseif (function_exists(Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . Core::toCamelCase($name) . |
||
3399 | 'Compile') !== false || function_exists('Plugin' . Core::toCamelCase($name) . 'Compile') !== |
||
3400 | false) { |
||
3401 | $pluginType = Core::FUNC_PLUGIN | Core::COMPILABLE_PLUGIN; |
||
3402 | } // Helper plugin compile |
||
3403 | elseif(function_exists(Core::NAMESPACE_PLUGINS_HELPERS . 'Plugin' . Core::toCamelCase($name) . 'Compile') |
||
3404 | !== false) { |
||
3405 | $pluginType = Core::FUNC_PLUGIN | Core::COMPILABLE_PLUGIN; |
||
3406 | } // Smarty modifier |
||
3407 | elseif (function_exists('smarty_modifier_' . $name) !== false) { |
||
3408 | $pluginType = Core::SMARTY_MODIFIER; |
||
3409 | } // Smarty function |
||
3410 | elseif (function_exists('smarty_function_' . $name) !== false) { |
||
3411 | $pluginType = Core::SMARTY_FUNCTION; |
||
3412 | } // Smarty block |
||
3413 | elseif (function_exists('smarty_block_' . $name) !== false) { |
||
3414 | $pluginType = Core::SMARTY_BLOCK; |
||
3415 | } // Everything else |
||
3416 | else { |
||
3417 | if ($pluginType === - 1) { |
||
3418 | try { |
||
3419 | $this->getDwoo()->getLoader()->loadPlugin( |
||
3420 | 'Plugin' . Core::toCamelCase($name)); |
||
3421 | } |
||
3422 | catch (Exception $e) { |
||
3423 | if (isset($phpFunc)) { |
||
3424 | $pluginType = Core::NATIVE_PLUGIN; |
||
3425 | } elseif (is_object($this->getDwoo()->getPluginProxy()) && $this->getDwoo()->getPluginProxy()->handles($name)) { |
||
3426 | $pluginType = Core::PROXY_PLUGIN; |
||
3427 | break; |
||
3428 | } else { |
||
3429 | throw $e; |
||
3430 | } |
||
3431 | } |
||
3432 | } else { |
||
3433 | throw new Exception('Plugin "' . $name . '" could not be found, type:' . $pluginType); |
||
3434 | } |
||
3435 | ++ $pluginType; |
||
3436 | } |
||
3437 | } |
||
3438 | |||
3439 | if (($pluginType & Core::COMPILABLE_PLUGIN) === 0 && ($pluginType & Core::NATIVE_PLUGIN) === 0 && ($pluginType & Core::PROXY_PLUGIN) === 0) { |
||
3440 | $this->addUsedPlugin(Core::toCamelCase($name), $pluginType); |
||
3441 | } |
||
3442 | |||
3443 | return $pluginType; |
||
3444 | } |
||
3445 | |||
3446 | /** |
||
3447 | * Allows a plugin to load another one at compile time, this will also mark |
||
3448 | * it as used by this template so it will be loaded at runtime (which can be |
||
3449 | * useful for compiled plugins that rely on another plugin when their compiled |
||
3450 | * code runs). |
||
3451 | * |
||
3452 | * @param string $name the plugin name |
||
3453 | * |
||
3454 | * @return void |
||
3455 | */ |
||
3456 | public function loadPlugin($name) |
||
3457 | { |
||
3458 | $this->getPluginType($name); |
||
3459 | } |
||
3460 | |||
3461 | /** |
||
3462 | * Runs htmlentities over the matched <?php ?> blocks when the security policy enforces that. |
||
3463 | * |
||
3464 | * @param array $match matched php block |
||
3465 | * |
||
3466 | * @return string the htmlentities-converted string |
||
3467 | */ |
||
3468 | protected function phpTagEncodingHelper($match) |
||
3469 | { |
||
3470 | return htmlspecialchars($match[0]); |
||
3471 | } |
||
3472 | |||
3473 | /** |
||
3474 | * Maps the parameters received from the template onto the parameters required by the given callback. |
||
3475 | * |
||
3476 | * @param array $params the array of parameters |
||
3477 | * @param callback $callback the function or method to reflect on to find out the required parameters |
||
3478 | * @param int $callType the type of call in the template, 0 = no params, 1 = php-style call, 2 = named |
||
3479 | * parameters call |
||
3480 | * @param array $map the parameter map to use, if not provided it will be built from the callback |
||
3481 | * |
||
3482 | * @return array parameters sorted in the correct order with missing optional parameters filled |
||
3483 | * @throws CompilationException |
||
3484 | */ |
||
3485 | protected function mapParams(array $params, $callback, $callType = 2, $map = null) |
||
3486 | { |
||
3487 | if (!$map) { |
||
3488 | $map = $this->getParamMap($callback); |
||
3489 | } |
||
3490 | |||
3491 | $paramlist = array(); |
||
3492 | |||
3493 | // transforms the parameter array from (x=>array('paramname'=>array(values))) to (paramname=>array(values)) |
||
3494 | $ps = array(); |
||
3495 | foreach ($params as $p) { |
||
3496 | if (is_array($p[1])) { |
||
3497 | $ps[$p[0]] = $p[1]; |
||
3498 | } else { |
||
3499 | $ps[] = $p; |
||
3500 | } |
||
3501 | } |
||
3502 | |||
3503 | // loops over the param map and assigns values from the template or default value for unset optional params |
||
3504 | while (list($k, $v) = each($map)) { |
||
3505 | if ($v[0] === '*') { |
||
3506 | // "rest" array parameter, fill every remaining params in it and then break |
||
3507 | if (count($ps) === 0) { |
||
3508 | if ($v[1] === false) { |
||
3509 | throw new CompilationException( |
||
3510 | $this, 'Rest argument missing for ' . str_replace( |
||
3511 | array( |
||
3512 | Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin', |
||
3513 | 'Compile' |
||
3514 | ), '', (is_array($callback) ? $callback[0] : $callback) |
||
3515 | ) |
||
3516 | ); |
||
3517 | } else { |
||
3518 | break; |
||
3519 | } |
||
3520 | } |
||
3521 | $tmp = array(); |
||
3522 | $tmp2 = array(); |
||
3523 | $tmp3 = array(); |
||
3524 | foreach ($ps as $i => $p) { |
||
3525 | $tmp[$i] = $p[0]; |
||
3526 | $tmp2[$i] = $p[1]; |
||
3527 | $tmp3[$i] = isset($p[2]) ? $p[2] : 0; |
||
3528 | unset($ps[$i]); |
||
3529 | } |
||
3530 | $paramlist[$v[0]] = array($tmp, $tmp2, $tmp3); |
||
3531 | unset($tmp, $tmp2, $i, $p); |
||
3532 | break; |
||
3533 | } elseif (isset($ps[$v[0]])) { |
||
3534 | // parameter is defined as named param |
||
3535 | $paramlist[$v[0]] = $ps[$v[0]]; |
||
3536 | unset($ps[$v[0]]); |
||
3537 | } elseif (isset($ps[$k])) { |
||
3538 | // parameter is defined as ordered param |
||
3539 | $paramlist[$v[0]] = $ps[$k]; |
||
3540 | unset($ps[$k]); |
||
3541 | } elseif ($v[1] === false) { |
||
3542 | // parameter is not defined and not optional, throw error |
||
3543 | if (is_array($callback)) { |
||
3544 | if (is_object($callback[0])) { |
||
3545 | $name = get_class($callback[0]) . '::' . $callback[1]; |
||
3546 | } else { |
||
3547 | $name = $callback[0]; |
||
3548 | } |
||
3549 | } else { |
||
3550 | $name = $callback; |
||
3551 | } |
||
3552 | |||
3553 | throw new CompilationException( |
||
3554 | $this, 'Argument ' . $k . '/' . $v[0] . ' missing for ' . str_replace( |
||
3555 | array( |
||
3556 | Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin', |
||
3557 | 'Compile' |
||
3558 | ), '', $name |
||
3559 | ) |
||
3560 | ); |
||
3561 | } elseif ($v[2] === null) { |
||
3562 | // enforce lowercased null if default value is null (php outputs NULL with var export) |
||
3563 | $paramlist[$v[0]] = array('null', null, self::T_NULL); |
||
3564 | } else { |
||
3565 | // outputs default value with var_export |
||
3566 | $paramlist[$v[0]] = array(var_export($v[2], true), $v[2]); |
||
3567 | } |
||
3568 | } |
||
3569 | |||
3570 | if (count($ps)) { |
||
3571 | foreach ($ps as $i => $p) { |
||
3572 | array_push($paramlist, $p); |
||
3573 | } |
||
3574 | } |
||
3575 | |||
3576 | return $paramlist; |
||
3577 | } |
||
3578 | |||
3579 | /** |
||
3580 | * Returns the parameter map of the given callback, it filters out entries typed as Dwoo and Compiler and turns the |
||
3581 | * rest parameter into a "*". |
||
3582 | * |
||
3583 | * @param callback $callback the function/method to reflect on |
||
3584 | * |
||
3585 | * @return array processed parameter map |
||
3586 | */ |
||
3587 | protected function getParamMap($callback) |
||
3588 | { |
||
3589 | if (is_null($callback)) { |
||
3590 | return array(array('*', true)); |
||
3591 | } |
||
3592 | if (is_array($callback)) { |
||
3593 | $ref = new ReflectionMethod($callback[0], $callback[1]); |
||
3594 | } else { |
||
3595 | $ref = new ReflectionFunction($callback); |
||
3596 | } |
||
3597 | |||
3598 | $out = array(); |
||
3599 | foreach ($ref->getParameters() as $param) { |
||
3600 | if (($class = $param->getClass()) !== null && $class->name === 'Dwoo\Core') { |
||
3601 | continue; |
||
3602 | } |
||
3603 | if (($class = $param->getClass()) !== null && $class->name === 'Dwoo\Compiler') { |
||
3604 | continue; |
||
3605 | } |
||
3606 | if ($param->getName() === 'rest' && $param->isArray() === true) { |
||
3607 | $out[] = array('*', $param->isOptional(), null); |
||
3608 | continue; |
||
3609 | } |
||
3610 | $out[] = array( |
||
3611 | $param->getName(), |
||
3612 | $param->isOptional(), |
||
3613 | $param->isOptional() ? $param->getDefaultValue() : null |
||
3614 | ); |
||
3615 | } |
||
3616 | |||
3617 | return $out; |
||
3618 | } |
||
3619 | |||
3620 | /** |
||
3621 | * Returns a default instance of this compiler, used by default by all Dwoo templates that do not have a |
||
3622 | * specific compiler assigned and when you do not override the default compiler factory function. |
||
3623 | * |
||
3624 | * @see Core::setDefaultCompilerFactory() |
||
3625 | * @return Compiler |
||
3626 | */ |
||
3627 | public static function compilerFactory() |
||
3628 | { |
||
3629 | if (self::$instance === null) { |
||
3630 | self::$instance = new self(); |
||
3631 | } |
||
3632 | |||
3633 | return self::$instance; |
||
3634 | } |
||
3635 | } |
||
3636 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.