Completed
Push — master ( a400a4...020752 )
by David
07:41 queued 04:43
created

lib/Dwoo/Compiler.php (3 issues)

Upgrade to new PHP Analysis Engine

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.4
13
 * @date      2017-03-01
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::CLASS_PLUGIN:
879 View Code Duplication
                case Core::CLASS_PLUGIN + Core::BLOCK_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::CLASS_PLUGIN + Core::FUNC_PLUGIN:
889
                    if (class_exists('Plugin' . $plugin) !== false) {
890
                        $output .= "if (class_exists('" . "Plugin" . $plugin . "')===false)".
891
                            "\n\t\$this->getLoader()->loadPlugin('Plugin$plugin');\n";
892
                    } else {
893
                        $output .= "if (class_exists('" . Core::NAMESPACE_PLUGINS_FUNCTIONS . "Plugin" . $plugin . "')===false)".
894
                            "\n\t\$this->getLoader()->loadPlugin('Plugin$plugin');\n";
895
                    }
896
                    break;
897 View Code Duplication
                case Core::FUNC_PLUGIN:
898
                    if (function_exists('Plugin' . $plugin) !== false) {
899
                        $output .= "if (function_exists('" . "Plugin" . $plugin . "')===false)".
900
                        "\n\t\$this->getLoader()->loadPlugin('Plugin$plugin');\n";
901
                    } else {
902
                        $output .= "if (function_exists('" . Core::NAMESPACE_PLUGINS_FUNCTIONS . "Plugin" . $plugin . "')===false)".
903
                        "\n\t\$this->getLoader()->loadPlugin('Plugin$plugin');\n";
904
                    }
905
                    break;
906
                case Core::SMARTY_MODIFIER:
907
                    $output .= "if (function_exists('smarty_modifier_$plugin')===false)".
908
                    "\n\t\$this->getLoader()->loadPlugin('$plugin');\n";
909
                    break;
910
                case Core::SMARTY_FUNCTION:
911
                    $output .= "if (function_exists('smarty_function_$plugin')===false)".
912
                    "\n\t\$this->getLoader()->loadPlugin('$plugin');\n";
913
                    break;
914
                case Core::SMARTY_BLOCK:
915
                    $output .= "if (function_exists('smarty_block_$plugin')===false)".
916
                    "\n\t\$this->getLoader()->loadPlugin('$plugin');\n";
917
                    break;
918
                case Core::PROXY_PLUGIN:
919
                    $output .= $this->getDwoo()->getPluginProxy()->getLoader($plugin);
920
                    break;
921
                default:
922
                    throw new CompilationException($this, 'Type error for ' . $plugin . ' with type' . $type);
923
            }
924
        }
925
926
        foreach ($this->templatePlugins as $function => $attr) {
927
            if (isset($attr['called']) && $attr['called'] === true && !isset($attr['checked'])) {
928
                $this->resolveSubTemplateDependencies($function);
929
            }
930
        }
931
        foreach ($this->templatePlugins as $function) {
932
            if (isset($function['called']) && $function['called'] === true) {
933
                $output .= $function['body'] . PHP_EOL;
934
            }
935
        }
936
937
        $output .= $compiled . "\n?>";
938
939
        $output = preg_replace('/(?<!;|\}|\*\/|\n|\{)(\s*' . preg_quote(self::PHP_CLOSE, '/') . preg_quote(self::PHP_OPEN, '/') . ')/', ";\n", $output);
940
        $output = str_replace(self::PHP_CLOSE . self::PHP_OPEN, "\n", $output);
941
942
        // handle <?xml tag at the beginning
943
        $output = preg_replace('#(/\* template body \*/ \?>\s*)<\?xml#is', '$1<?php echo \'<?xml\'; ?>', $output);
944
945
        // add another line break after PHP closing tags that have a line break following,
946
        // as we do not know whether it's intended, and PHP will strip it otherwise
947
        $output = preg_replace('/(?<!"|<\?xml)\s*\?>\n/', '$0' . "\n", $output);
948
949
        if ($this->debug) {
950
            echo '=============================================================================================' . "\n";
951
            $lines = preg_split('{\r\n|\n|<br />}', $output);
952
            array_shift($lines);
953
            foreach ($lines as $i => $line) {
954
                echo ($i + 1) . '. ' . $line . "\r\n";
955
            }
956
            echo '=============================================================================================' . "\n";
957
        }
958
959
        $this->template = $this->dwoo = null;
960
        $tpl            = null;
961
962
        return $output;
963
    }
964
965
    /**
966
     * Checks what sub-templates are used in every sub-template so that we're sure they are all compiled.
967
     *
968
     * @param string $function the sub-template name
969
     */
970
    protected function resolveSubTemplateDependencies($function)
971
    {
972
        if ($this->debug) {
973
            echo 'Compiler::' . __FUNCTION__ . "\n";
974
        }
975
976
        $body = $this->templatePlugins[$function]['body'];
977
        foreach ($this->templatePlugins as $func => $attr) {
978
            if ($func !== $function && !isset($attr['called']) && strpos($body, Core::NAMESPACE_PLUGINS_FUNCTIONS .
979
            'Plugin' . Core::toCamelCase($func)) !== false) {
980
                $this->templatePlugins[$func]['called'] = true;
981
                $this->resolveSubTemplateDependencies($func);
982
            }
983
        }
984
        $this->templatePlugins[$function]['checked'] = true;
985
    }
986
987
    /**
988
     * Adds compiled content to the current block.
989
     *
990
     * @param string $content   the content to push
991
     * @param int    $lineCount newlines count in content, optional
992
     *
993
     * @throws CompilationException
994
     */
995
    public function push($content, $lineCount = null)
996
    {
997
        if ($lineCount === null) {
998
            $lineCount = substr_count($content, "\n");
999
        }
1000
1001
        if ($this->curBlock['buffer'] === null && count($this->stack) > 1) {
1002
            // buffer is not initialized yet (the block has just been created)
1003
            $this->stack[count($this->stack) - 2]['buffer'] .= (string)$content;
1004
            $this->curBlock['buffer'] = '';
1005
        } else {
1006
            if (!isset($this->curBlock['buffer'])) {
1007
                throw new CompilationException($this, 'The template has been closed too early, you probably have an extra block-closing tag somewhere');
1008
            }
1009
            // append current content to current block's buffer
1010
            $this->curBlock['buffer'] .= (string)$content;
1011
        }
1012
        $this->line += $lineCount;
1013
    }
1014
1015
    /**
1016
     * Sets the scope.
1017
     * set to null if the scope becomes "unstable" (i.e. too variable or unknown) so that
1018
     * variables are compiled in a more evaluative way than just $this->scope['key']
1019
     *
1020
     * @param mixed $scope    a string i.e. "level1.level2" or an array i.e. array("level1", "level2")
1021
     * @param bool  $absolute if true, the scope is set from the top level scope and not from the current scope
1022
     *
1023
     * @return array the current scope tree
1024
     */
1025
    public function setScope($scope, $absolute = false)
1026
    {
1027
        $old = $this->scopeTree;
1028
1029
        if ($scope === null) {
1030
            unset($this->scope);
1031
            $this->scope = null;
1032
        }
1033
1034
        if (is_array($scope) === false) {
1035
            $scope = explode('.', $scope);
1036
        }
1037
1038
        if ($absolute === true) {
1039
            $this->scope     = &$this->data;
1040
            $this->scopeTree = array();
1041
        }
1042
1043
        while (($bit = array_shift($scope)) !== null) {
1044
            if ($bit === '_parent' || $bit === '_') {
1045
                array_pop($this->scopeTree);
1046
                reset($this->scopeTree);
1047
                $this->scope = &$this->data;
1048
                $cnt         = count($this->scopeTree);
1049 View Code Duplication
                for ($i = 0; $i < $cnt; ++ $i) {
1050
                    $this->scope = &$this->scope[$this->scopeTree[$i]];
1051
                }
1052 View Code Duplication
            } elseif ($bit === '_root' || $bit === '__') {
1053
                $this->scope     = &$this->data;
1054
                $this->scopeTree = array();
1055
            } elseif (isset($this->scope[$bit])) {
1056
                $this->scope       = &$this->scope[$bit];
1057
                $this->scopeTree[] = $bit;
1058
            } else {
1059
                $this->scope[$bit] = array();
1060
                $this->scope       = &$this->scope[$bit];
1061
                $this->scopeTree[] = $bit;
1062
            }
1063
        }
1064
1065
        return $old;
1066
    }
1067
1068
    /**
1069
     * Adds a block to the top of the block stack.
1070
     *
1071
     * @param string $type      block type (name)
1072
     * @param array  $params    the parameters array
1073
     * @param int    $paramtype the parameters type (see mapParams), 0, 1 or 2
1074
     *
1075
     * @return string the preProcessing() method's output
1076
     */
1077
    public function addBlock($type, array $params, $paramtype)
1078
    {
1079
        if ($this->debug) {
1080
            echo 'Compiler::' . __FUNCTION__ . "\n";
1081
        }
1082
1083
        $class = Core::NAMESPACE_PLUGINS_BLOCKS . 'Plugin' . Core::toCamelCase($type);
1084
        if (class_exists($class) === false) {
1085
            $this->getDwoo()->getLoader()->loadPlugin($type);
1086
        }
1087
        $params = $this->mapParams($params, array($class, 'init'), $paramtype);
1088
1089
        $this->stack[]  = array(
1090
            'type'   => $type,
1091
            'params' => $params,
1092
            'custom' => false,
1093
            'class'  => $class,
1094
            'buffer' => null
1095
        );
1096
        $this->curBlock = &$this->stack[count($this->stack) - 1];
1097
1098
        return call_user_func(array($class, 'preProcessing'), $this, $params, '', '', $type);
1099
    }
1100
1101
    /**
1102
     * Adds a custom block to the top of the block stack.
1103
     *
1104
     * @param string $type      block type (name)
1105
     * @param array  $params    the parameters array
1106
     * @param int    $paramtype the parameters type (see mapParams), 0, 1 or 2
1107
     *
1108
     * @return string the preProcessing() method's output
1109
     */
1110
    public function addCustomBlock($type, array $params, $paramtype)
1111
    {
1112
        $callback = $this->customPlugins[$type]['callback'];
1113
        if (is_array($callback)) {
1114
            $class = is_object($callback[0]) ? get_class($callback[0]) : $callback[0];
1115
        } else {
1116
            $class = $callback;
1117
        }
1118
1119
        $params = $this->mapParams($params, array($class, 'init'), $paramtype);
1120
1121
        $this->stack[]  = array(
1122
            'type'   => $type,
1123
            'params' => $params,
1124
            'custom' => true,
1125
            'class'  => $class,
1126
            'buffer' => null
1127
        );
1128
        $this->curBlock = &$this->stack[count($this->stack) - 1];
1129
1130
        return call_user_func(array($class, 'preProcessing'), $this, $params, '', '', $type);
1131
    }
1132
1133
    /**
1134
     * Injects a block at the top of the plugin stack without calling its preProcessing method.
1135
     * used by {else} blocks to re-add themselves after having closed everything up to their parent
1136
     *
1137
     * @param string $type   block type (name)
1138
     * @param array  $params parameters array
1139
     */
1140
    public function injectBlock($type, array $params)
1141
    {
1142
        if ($this->debug) {
1143
            echo 'Compiler::' . __FUNCTION__ . "\n";
1144
        }
1145
1146
        $class = Core::NAMESPACE_PLUGINS_BLOCKS . 'Plugin' . Core::toCamelCase($type);
1147
        if (class_exists($class) === false) {
1148
            $this->getDwoo()->getLoader()->loadPlugin($type);
1149
        }
1150
        $this->stack[]  = array(
1151
            'type'   => $type,
1152
            'params' => $params,
1153
            'custom' => false,
1154
            'class'  => $class,
1155
            'buffer' => null
1156
        );
1157
        $this->curBlock = &$this->stack[count($this->stack) - 1];
1158
    }
1159
1160
    /**
1161
     * Removes the closest-to-top block of the given type and all other
1162
     * blocks encountered while going down the block stack.
1163
     *
1164
     * @param string $type block type (name)
1165
     *
1166
     * @return string the output of all postProcessing() method's return values of the closed blocks
1167
     * @throws CompilationException
1168
     */
1169
    public function removeBlock($type)
1170
    {
1171
        if ($this->debug) {
1172
            echo 'Compiler::' . __FUNCTION__ . "\n";
1173
        }
1174
1175
        $output = '';
1176
1177
        $pluginType = $this->getPluginType($type);
1178
        if ($pluginType & Core::SMARTY_BLOCK) {
1179
            $type = 'Smartyinterface';
1180
        }
1181
        while (true) {
1182
            while ($top = array_pop($this->stack)) {
1183 View Code Duplication
                if ($top['custom']) {
1184
                    $class = $top['class'];
1185
                } else {
1186
                    $class = Core::NAMESPACE_PLUGINS_BLOCKS . 'Plugin' . Core::toCamelCase($top['type']);
1187
                }
1188
                if (count($this->stack)) {
1189
                    $this->curBlock = &$this->stack[count($this->stack) - 1];
1190
                    $this->push(call_user_func(array(
1191
                        $class,
1192
                        'postProcessing'
1193
                    ), $this, $top['params'], '', '', $top['buffer']), 0);
1194
                } else {
1195
                    $null           = null;
1196
                    $this->curBlock = &$null;
1197
                    $output         = call_user_func(
1198
                        array(
1199
                        $class,
1200
                        'postProcessing'
1201
                        ), $this, $top['params'], '', '', $top['buffer']
1202
                    );
1203
                }
1204
1205
                if ($top['type'] === $type) {
1206
                    break 2;
1207
                }
1208
            }
1209
1210
            throw new CompilationException($this, 'Syntax malformation, a block of type "' . $type . '" was closed but was not opened');
1211
            break;
0 ignored issues
show
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
1212
        }
1213
1214
        return $output;
1215
    }
1216
1217
    /**
1218
     * Returns a reference to the first block of the given type encountered and
1219
     * optionally closes all blocks until it finds it
1220
     * this is mainly used by {else} plugins to close everything that was opened
1221
     * between their parent and themselves.
1222
     *
1223
     * @param string $type       the block type (name)
1224
     * @param bool   $closeAlong whether to close all blocks encountered while going down the block stack or not
1225
     *
1226
     * @return mixed &array the array is as such: array('type'=>pluginName, 'params'=>parameter array,
1227
     *               'custom'=>bool defining whether it's a custom plugin or not, for internal use)
1228
     * @throws CompilationException
1229
     */
1230
    public function &findBlock($type, $closeAlong = false)
1231
    {
1232
        if ($closeAlong === true) {
1233 View Code Duplication
            while ($b = end($this->stack)) {
1234
                if ($b['type'] === $type) {
1235
                    return $this->stack[key($this->stack)];
1236
                }
1237
                $this->push($this->removeTopBlock(), 0);
1238
            }
1239
        } else {
1240
            end($this->stack);
1241 View Code Duplication
            while ($b = current($this->stack)) {
1242
                if ($b['type'] === $type) {
1243
                    return $this->stack[key($this->stack)];
1244
                }
1245
                prev($this->stack);
1246
            }
1247
        }
1248
1249
        throw new CompilationException($this, 'A parent block of type "' . $type . '" is required and can not be found');
1250
    }
1251
1252
    /**
1253
     * Returns a reference to the current block array.
1254
     *
1255
     * @return array the array is as such: array('type'=>pluginName, 'params'=>parameter array,
1256
     *                'custom'=>bool defining whether it's a custom plugin or not, for internal use)
1257
     */
1258
    public function &getCurrentBlock()
1259
    {
1260
        return $this->curBlock;
1261
    }
1262
1263
    /**
1264
     * Removes the block at the top of the stack and calls its postProcessing() method.
1265
     *
1266
     * @return string the postProcessing() method's output
1267
     * @throws CompilationException
1268
     */
1269
    public function removeTopBlock()
1270
    {
1271
        if ($this->debug) {
1272
            echo 'Compiler::' . __FUNCTION__ . "\n";
1273
        }
1274
1275
        $o = array_pop($this->stack);
1276
        if ($o === null) {
1277
            throw new CompilationException($this, 'Syntax malformation, a block of unknown type was closed but was not opened.');
1278
        }
1279 View Code Duplication
        if ($o['custom']) {
1280
            $class = $o['class'];
1281
        } else {
1282
            $class = Core::NAMESPACE_PLUGINS_BLOCKS . 'Plugin' . Core::toCamelCase($o['type']);
1283
        }
1284
1285
        $this->curBlock = &$this->stack[count($this->stack) - 1];
1286
1287
        return call_user_func(array($class, 'postProcessing'), $this, $o['params'], '', '', $o['buffer']);
1288
    }
1289
1290
    /**
1291
     * Returns the compiled parameters (for example a variable's compiled parameter will be "$this->scope['key']") out
1292
     * of the given parameter array.
1293
     *
1294
     * @param array $params parameter array
1295
     *
1296
     * @return array filtered parameters
1297
     */
1298 View Code Duplication
    public function getCompiledParams(array $params)
1299
    {
1300
        foreach ($params as $k => $p) {
1301
            if (is_array($p)) {
1302
                $params[$k] = $p[0];
1303
            }
1304
        }
1305
1306
        return $params;
1307
    }
1308
1309
    /**
1310
     * Returns the real parameters (for example a variable's real parameter will be its key, etc) out of the given
1311
     * parameter array.
1312
     *
1313
     * @param array $params parameter array
1314
     *
1315
     * @return array filtered parameters
1316
     */
1317 View Code Duplication
    public function getRealParams(array $params)
1318
    {
1319
        foreach ($params as $k => $p) {
1320
            if (is_array($p)) {
1321
                $params[$k] = $p[1];
1322
            }
1323
        }
1324
1325
        return $params;
1326
    }
1327
1328
    /**
1329
     * Returns the token of each parameter out of the given parameter array.
1330
     *
1331
     * @param array $params parameter array
1332
     *
1333
     * @return array tokens
1334
     */
1335 View Code Duplication
    public function getParamTokens(array $params)
1336
    {
1337
        foreach ($params as $k => $p) {
1338
            if (is_array($p)) {
1339
                $params[$k] = isset($p[2]) ? $p[2] : 0;
1340
            }
1341
        }
1342
1343
        return $params;
1344
    }
1345
1346
    /**
1347
     * Entry point of the parser, it redirects calls to other parse* functions.
1348
     *
1349
     * @param string $in            the string within which we must parse something
1350
     * @param int    $from          the starting offset of the parsed area
1351
     * @param int    $to            the ending offset of the parsed area
1352
     * @param mixed  $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by
1353
     *                              default
1354
     * @param string $curBlock      the current parser-block being processed
1355
     * @param mixed  $pointer       a reference to a pointer that will be increased by the amount of characters parsed,
1356
     *                              or null by default
1357
     *
1358
     * @return string parsed values
1359
     * @throws CompilationException
1360
     */
1361
    protected function parse($in, $from, $to, $parsingParams = false, $curBlock = '', &$pointer = null)
1362
    {
1363
        if ($this->debug) {
1364
            echo 'Compiler::' . __FUNCTION__ . "\n";
1365
        }
1366
1367
        if ($to === null) {
1368
            $to = strlen($in);
1369
        }
1370
        $first = substr($in, $from, 1);
1371
1372
        if ($first === false) {
1373
            throw new CompilationException($this, 'Unexpected EOF, a template tag was not closed');
1374
        }
1375
1376
        while ($first === ' ' || $first === "\n" || $first === "\t" || $first === "\r") {
1377 View Code Duplication
            if ($curBlock === 'root' && substr($in, $from, strlen($this->rd)) === $this->rd) {
1378
                // end template tag
1379
                $pointer += strlen($this->rd);
1380
                if ($this->debug) {
1381
                    echo 'TEMPLATE PARSING ENDED' . "\n";
1382
                }
1383
1384
                return false;
1385
            }
1386
            ++ $from;
1387
            if ($pointer !== null) {
1388
                ++ $pointer;
1389
            }
1390
            if ($from >= $to) {
1391
                if (is_array($parsingParams)) {
1392
                    return $parsingParams;
1393
                } else {
1394
                    return '';
1395
                }
1396
            }
1397
            $first = $in[$from];
1398
        }
1399
1400
        $substr = substr($in, $from, $to - $from);
1401
1402
        if ($this->debug) {
1403
            echo 'PARSE CALL : PARSING "' . htmlentities(substr($in, $from, min($to - $from, 50))) . (($to - $from) > 50 ? '...' : '') . '" @ ' . $from . ':' . $to . ' in ' . $curBlock . ' : pointer=' . $pointer . "\n";
1404
        }
1405
        $parsed = '';
1406
1407
        if ($curBlock === 'root' && $first === '*') {
1408
            $src      = $this->getTemplateSource();
1409
            $startpos = $this->getPointer() - strlen($this->ld);
1410
            if (substr($src, $startpos, strlen($this->ld)) === $this->ld) {
1411
                if ($startpos > 0) {
1412
                    do {
1413
                        $char = substr($src, -- $startpos, 1);
1414
                        if ($char == "\n") {
1415
                            ++ $startpos;
1416
                            $whitespaceStart = true;
1417
                            break;
1418
                        }
1419
                    }
1420
                    while ($startpos > 0 && ($char == ' ' || $char == "\t"));
1421
                }
1422
1423
                if (!isset($whitespaceStart)) {
1424
                    $startpos = $this->getPointer();
1425
                } else {
1426
                    $pointer -= $this->getPointer() - $startpos;
1427
                }
1428
1429
                if ($this->allowNestedComments && strpos($src, $this->ld . '*', $this->getPointer()) !== false) {
1430
                    $comOpen  = $this->ld . '*';
1431
                    $comClose = '*' . $this->rd;
1432
                    $level    = 1;
1433
                    $ptr      = $this->getPointer();
1434
1435
                    while ($level > 0 && $ptr < strlen($src)) {
1436
                        $open  = strpos($src, $comOpen, $ptr);
1437
                        $close = strpos($src, $comClose, $ptr);
1438
1439
                        if ($open !== false && $close !== false) {
1440
                            if ($open < $close) {
1441
                                $ptr = $open + strlen($comOpen);
1442
                                ++ $level;
1443
                            } else {
1444
                                $ptr = $close + strlen($comClose);
1445
                                -- $level;
1446
                            }
1447
                        } elseif ($open !== false) {
1448
                            $ptr = $open + strlen($comOpen);
1449
                            ++ $level;
1450
                        } elseif ($close !== false) {
1451
                            $ptr = $close + strlen($comClose);
1452
                            -- $level;
1453
                        } else {
1454
                            $ptr = strlen($src);
1455
                        }
1456
                    }
1457
                    $endpos = $ptr - strlen('*' . $this->rd);
1458 View Code Duplication
                } else {
1459
                    $endpos = strpos($src, '*' . $this->rd, $startpos);
1460
                    if ($endpos == false) {
1461
                        throw new CompilationException($this, 'Un-ended comment');
1462
                    }
1463
                }
1464
                $pointer += $endpos - $startpos + strlen('*' . $this->rd);
1465
                if (isset($whitespaceStart) && preg_match('#^[\t ]*\r?\n#', substr($src, $endpos + strlen('*' . $this->rd)), $m)) {
1466
                    $pointer += strlen($m[0]);
1467
                    $this->curBlock['buffer'] = substr($this->curBlock['buffer'], 0, strlen($this->curBlock['buffer']) - ($this->getPointer() - $startpos - strlen($this->ld)));
1468
                }
1469
1470
                return false;
1471
            }
1472
        }
1473
1474
        if ($first === '$') {
1475
            // var
1476
            $out    = $this->parseVar($in, $from, $to, $parsingParams, $curBlock, $pointer);
1477
            $parsed = 'var';
1478
        } elseif ($first === '%' && preg_match('#^%[a-z_\\\\]#i', $substr)) {
1479
            // Short constant
1480
            $out = $this->parseConst($in, $from, $to, $parsingParams, $curBlock, $pointer);
1481
        } elseif (($first === '"' || $first === "'") && !(is_array($parsingParams) && preg_match('#^([\'"])[a-z0-9_]+\1\s*=>?(?:\s+|[^=])#i', $substr))) {
1482
            // string
1483
            $out = $this->parseString($in, $from, $to, $parsingParams, $curBlock, $pointer);
1484
        } 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)) {
1485
            // func
1486
            $out    = $this->parseFunction($in, $from, $to, $parsingParams, $curBlock, $pointer);
1487
            $parsed = 'func';
1488
        } elseif ($first === ';') {
1489
            // instruction end
1490
            if ($this->debug) {
1491
                echo 'END OF INSTRUCTION' . "\n";
1492
            }
1493
            if ($pointer !== null) {
1494
                ++ $pointer;
1495
            }
1496
1497
            return $this->parse($in, $from + 1, $to, false, 'root', $pointer);
1498
        } elseif ($curBlock === 'root' && preg_match('#^/([a-z_][a-z0-9_]*)?#i', $substr, $match)) {
1499
            // close block
1500 View Code Duplication
            if (!empty($match[1]) && $match[1] == 'else') {
1501
                throw new CompilationException($this, 'Else blocks must not be closed explicitly, they are automatically closed when their parent block is closed');
1502
            }
1503 View Code Duplication
            if (!empty($match[1]) && $match[1] == 'elseif') {
1504
                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');
1505
            }
1506
            if ($pointer !== null) {
1507
                $pointer += strlen($match[0]);
1508
            }
1509
            if (empty($match[1])) {
1510
                if ($this->curBlock['type'] == 'else' || $this->curBlock['type'] == 'elseif') {
1511
                    $pointer -= strlen($match[0]);
1512
                }
1513
                if ($this->debug) {
1514
                    echo 'TOP BLOCK CLOSED' . "\n";
1515
                }
1516
1517
                return $this->removeTopBlock();
1518
            } else {
1519
                if ($this->debug) {
1520
                    echo 'BLOCK OF TYPE ' . $match[1] . ' CLOSED' . "\n";
1521
                }
1522
1523
                return $this->removeBlock($match[1]);
1524
            }
1525 View Code Duplication
        } elseif ($curBlock === 'root' && substr($substr, 0, strlen($this->rd)) === $this->rd) {
1526
            // end template tag
1527
            if ($this->debug) {
1528
                echo 'TAG PARSING ENDED' . "\n";
1529
            }
1530
            $pointer += strlen($this->rd);
1531
1532
            return false;
1533
        } elseif (is_array($parsingParams) && preg_match('#^(([\'"]?)[a-z0-9_]+\2\s*=' . ($curBlock === 'array' ? '>?' : '') . ')(?:\s+|[^=]).*#i', $substr, $match)) {
1534
            // named parameter
1535
            if ($this->debug) {
1536
                echo 'NAMED PARAM FOUND' . "\n";
1537
            }
1538
            $len = strlen($match[1]);
1539
            while (substr($in, $from + $len, 1) === ' ') {
1540
                ++ $len;
1541
            }
1542
            if ($pointer !== null) {
1543
                $pointer += $len;
1544
            }
1545
1546
            $output = array(
1547
                trim($match[1], " \t\r\n=>'\""),
1548
                $this->parse($in, $from + $len, $to, false, 'namedparam', $pointer)
1549
            );
1550
1551
            $parsingParams[] = $output;
1552
1553
            return $parsingParams;
1554
        } elseif (preg_match('#^(\\\\?[a-z_](?:\\\\?[a-z0-9_]+)*::\$[a-z0-9_]+)#i', $substr, $match)) {
1555
            // static member access
1556
            $parsed = 'var';
1557
            if (is_array($parsingParams)) {
1558
                $parsingParams[] = array($match[1], $match[1]);
1559
                $out             = $parsingParams;
1560
            } else {
1561
                $out = $match[1];
1562
            }
1563
            $pointer += strlen($match[1]);
1564
        } elseif ($substr !== '' && (is_array($parsingParams) || $curBlock === 'namedparam' || $curBlock === 'condition' || $curBlock === 'expression')) {
1565
            // unquoted string, bool or number
1566
            $out = $this->parseOthers($in, $from, $to, $parsingParams, $curBlock, $pointer);
1567 View Code Duplication
        } else {
1568
            // parse error
1569
            throw new CompilationException($this, 'Parse error in "' . substr($in, $from, $to - $from) . '"');
1570
        }
1571
1572
        if (empty($out)) {
1573
            return '';
1574
        }
1575
1576
        $substr = substr($in, $pointer, $to - $pointer);
1577
1578
        // var parsed, check if any var-extension applies
1579
        if ($parsed === 'var') {
1580
            if (preg_match('#^\s*([/%+*-])\s*([a-z0-9]|\$)#i', $substr, $match)) {
1581
                if ($this->debug) {
1582
                    echo 'PARSING POST-VAR EXPRESSION ' . $substr . "\n";
1583
                }
1584
                // parse expressions
1585
                $pointer += strlen($match[0]) - 1;
1586
                if (is_array($parsingParams)) {
1587
                    if ($match[2] == '$') {
1588
                        $expr = $this->parseVar($in, $pointer, $to, array(), $curBlock, $pointer);
1589
                    } else {
1590
                        $expr = $this->parse($in, $pointer, $to, array(), 'expression', $pointer);
1591
                    }
1592
                    $out[count($out) - 1][0] .= $match[1] . $expr[0][0];
1593
                    $out[count($out) - 1][1] .= $match[1] . $expr[0][1];
1594
                } else {
1595
                    if ($match[2] == '$') {
1596
                        $expr = $this->parseVar($in, $pointer, $to, false, $curBlock, $pointer);
1597
                    } else {
1598
                        $expr = $this->parse($in, $pointer, $to, false, 'expression', $pointer);
1599
                    }
1600
                    if (is_array($out) && is_array($expr)) {
1601
                        $out[0] .= $match[1] . $expr[0];
1602
                        $out[1] .= $match[1] . $expr[1];
1603
                    } elseif (is_array($out)) {
1604
                        $out[0] .= $match[1] . $expr;
1605
                        $out[1] .= $match[1] . $expr;
1606
                    } elseif (is_array($expr)) {
1607
                        $out .= $match[1] . $expr[0];
1608
                    } else {
1609
                        $out .= $match[1] . $expr;
1610
                    }
1611
                }
1612
            } elseif ($curBlock === 'root' && preg_match('#^(\s*(?:[+/*%-.]=|=|\+\+|--)\s*)(.*)#s', $substr, $match)) {
1613
                if ($this->debug) {
1614
                    echo 'PARSING POST-VAR ASSIGNMENT ' . $substr . "\n";
1615
                }
1616
                // parse assignment
1617
                $value    = $match[2];
1618
                $operator = trim($match[1]);
1619
                if (substr($value, 0, 1) == '=') {
1620
                    throw new CompilationException($this, 'Unexpected "=" in <em>' . $substr . '</em>');
1621
                }
1622
1623
                if ($pointer !== null) {
1624
                    $pointer += strlen($match[1]);
1625
                }
1626
1627
                if ($operator !== '++' && $operator !== '--') {
1628
                    $parts = array();
1629
                    $ptr   = 0;
1630
                    $parts = $this->parse($value, 0, strlen($value), $parts, 'condition', $ptr);
1631
                    $pointer += $ptr;
1632
1633
                    // load if plugin
1634
                    try {
1635
                        $this->getPluginType('if');
1636
                    }
1637
                    catch (Exception $e) {
1638
                        throw new CompilationException($this, 'Assignments require the "if" plugin to be accessible');
1639
                    }
1640
1641
                    $parts  = $this->mapParams($parts, array(Core::NAMESPACE_PLUGINS_BLOCKS . 'PluginIf', 'init'), 1);
1642
                    $tokens = $this->getParamTokens($parts);
1643
                    $parts  = $this->getCompiledParams($parts);
1644
1645
                    $value = PluginIf::replaceKeywords($parts['*'], $tokens['*'], $this);
1646
                    $echo  = '';
1647
                } else {
1648
                    $value = array();
1649
                    $echo  = 'echo ';
1650
                }
1651
1652
                if ($this->autoEscape) {
1653
                    $out = preg_replace('#\(is_string\(\$tmp=(.+?)\) \? htmlspecialchars\(\$tmp, ENT_QUOTES, \$this->charset\) : \$tmp\)#', '$1', $out);
1654
                }
1655
                $out = self::PHP_OPEN . $echo . $out . $operator . implode(' ', $value) . self::PHP_CLOSE;
1656
            } elseif ($curBlock === 'array' && is_array($parsingParams) && preg_match('#^(\s*=>?\s*)#', $substr, $match)) {
1657
                // parse namedparam with var as name (only for array)
1658
                if ($this->debug) {
1659
                    echo 'VARIABLE NAMED PARAM (FOR ARRAY) FOUND' . "\n";
1660
                }
1661
                $len = strlen($match[1]);
1662
                $var = $out[count($out) - 1];
1663
                $pointer += $len;
1664
1665
                $output = array($var[0], $this->parse($substr, $len, null, false, 'namedparam', $pointer));
1666
1667
                $parsingParams[] = $output;
1668
1669
                return $parsingParams;
1670
            }
1671
        }
1672
1673
        if ($curBlock !== 'modifier' && ($parsed === 'func' || $parsed === 'var') && preg_match('#^(\|@?[a-z0-9_]+(:.*)?)+#i', $substr, $match)) {
1674
            // parse modifier on funcs or vars
1675
            $srcPointer = $pointer;
1676
            if (is_array($parsingParams)) {
1677
                $tmp                     = $this->replaceModifiers(
1678
                    array(
1679
                    null,
1680
                    null,
1681
                    $out[count($out) - 1][0],
1682
                    $match[0]
1683
                    ), $curBlock, $pointer
1684
                );
1685
                $out[count($out) - 1][0] = $tmp;
1686
                $out[count($out) - 1][1] .= substr($substr, $srcPointer, $srcPointer - $pointer);
1687
            } else {
1688
                $out = $this->replaceModifiers(array(null, null, $out, $match[0]), $curBlock, $pointer);
1689
            }
1690
        }
1691
1692
        // func parsed, check if any func-extension applies
1693
        if ($parsed === 'func' && preg_match('#^->[a-z0-9_]+(\s*\(.+|->[a-z_].*)?#is', $substr, $match)) {
1694
            // parse method call or property read
1695
            $ptr = 0;
1696
1697
            if (is_array($parsingParams)) {
1698
                $output = $this->parseMethodCall($out[count($out) - 1][1], $match[0], $curBlock, $ptr);
1699
1700
                $out[count($out) - 1][0] = $output;
1701
                $out[count($out) - 1][1] .= substr($match[0], 0, $ptr);
1702
            } else {
1703
                $out = $this->parseMethodCall($out, $match[0], $curBlock, $ptr);
1704
            }
1705
1706
            $pointer += $ptr;
1707
        }
1708
1709
        if ($curBlock === 'root' && substr($out, 0, strlen(self::PHP_OPEN)) !== self::PHP_OPEN) {
1710
            return self::PHP_OPEN . 'echo ' . $out . ';' . self::PHP_CLOSE;
1711
        } else {
1712
            return $out;
1713
        }
1714
    }
1715
1716
    /**
1717
     * Parses a function call.
1718
     *
1719
     * @param string $in            the string within which we must parse something
1720
     * @param int    $from          the starting offset of the parsed area
1721
     * @param int    $to            the ending offset of the parsed area
1722
     * @param mixed  $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by
1723
     *                              default
1724
     * @param string $curBlock      the current parser-block being processed
1725
     * @param mixed  $pointer       a reference to a pointer that will be increased by the amount of characters parsed,
1726
     *                              or null by default
1727
     *
1728
     * @return string parsed values
1729
     * @throws CompilationException
1730
     * @throws Exception
1731
     * @throws SecurityException
1732
     */
1733
    protected function parseFunction($in, $from, $to, $parsingParams = false, $curBlock = '', &$pointer = null)
1734
    {
1735
        $output = '';
1736
        $cmdstr = substr($in, $from, $to - $from);
1737
        preg_match('/^(\\\\?[a-z_](?:\\\\?[a-z0-9_]+)*(?:::[a-z_][a-z0-9_]*)?)(\s*' . $this->rdr . '|\s*;)?/i', $cmdstr, $match);
1738
1739 View Code Duplication
        if (empty($match[1])) {
1740
            throw new CompilationException($this, 'Parse error, invalid function name : ' . substr($cmdstr, 0, 15));
1741
        }
1742
1743
        $func = $match[1];
1744
1745
        if (!empty($match[2])) {
1746
            $cmdstr = $match[1];
1747
        }
1748
1749
        if ($this->debug) {
1750
            echo 'FUNC FOUND (' . $func . ')' . "\n";
1751
        }
1752
1753
        $paramsep = '';
1754
1755
        if (is_array($parsingParams) || $curBlock != 'root') {
1756
            $paramspos = strpos($cmdstr, '(');
1757
            $paramsep  = ')';
1758
        } elseif (preg_match_all('#^\s*[\\\\:a-z0-9_]+(\s*\(|\s+[^(])#i', $cmdstr, $match, PREG_OFFSET_CAPTURE)) {
1759
            $paramspos = $match[1][0][1];
1760
            $paramsep  = substr($match[1][0][0], - 1) === '(' ? ')' : '';
1761
            if ($paramsep === ')') {
1762
                $paramspos += strlen($match[1][0][0]) - 1;
1763
                if (substr($cmdstr, 0, 2) === 'if' || substr($cmdstr, 0, 6) === 'elseif') {
1764
                    $paramsep = '';
1765
                    if (strlen($match[1][0][0]) > 1) {
1766
                        -- $paramspos;
1767
                    }
1768
                }
1769
            }
1770
        } else {
1771
            $paramspos = false;
1772
        }
1773
1774
        $state = 0;
1775
1776
        if ($paramspos === false) {
1777
            $params = array();
1778
1779
            if ($curBlock !== 'root') {
1780
                return $this->parseOthers($in, $from, $to, $parsingParams, $curBlock, $pointer);
1781
            }
1782
        } else {
1783
            if ($curBlock === 'condition') {
1784
                // load if plugin
1785
                $this->getPluginType('if');
1786
1787
                if (PluginIf::replaceKeywords(array($func), array(self::T_UNQUOTED_STRING), $this) !== array($func)) {
1788
                    return $this->parseOthers($in, $from, $to, $parsingParams, $curBlock, $pointer);
1789
                }
1790
            }
1791
            $whitespace = strlen(substr($cmdstr, strlen($func), $paramspos - strlen($func)));
1792
            $paramstr   = substr($cmdstr, $paramspos + 1);
1793 View Code Duplication
            if (substr($paramstr, - 1, 1) === $paramsep) {
1794
                $paramstr = substr($paramstr, 0, - 1);
1795
            }
1796
1797
            if (strlen($paramstr) === 0) {
1798
                $params   = array();
1799
                $paramstr = '';
1800
            } else {
1801
                $ptr    = 0;
1802
                $params = array();
1803
                if ($func === 'empty') {
1804
                    $params = $this->parseVar($paramstr, $ptr, strlen($paramstr), $params, 'root', $ptr);
1805
                } else {
1806
                    while ($ptr < strlen($paramstr)) {
1807
                        while (true) {
1808
                            if ($ptr >= strlen($paramstr)) {
1809
                                break 2;
1810
                            }
1811
1812
                            if ($func !== 'if' && $func !== 'elseif' && $paramstr[$ptr] === ')') {
1813
                                if ($this->debug) {
1814
                                    echo 'PARAM PARSING ENDED, ")" FOUND, POINTER AT ' . $ptr . "\n";
1815
                                }
1816
                                break 2;
1817
                            } elseif ($paramstr[$ptr] === ';') {
1818
                                ++ $ptr;
1819
                                if ($this->debug) {
1820
                                    echo 'PARAM PARSING ENDED, ";" FOUND, POINTER AT ' . $ptr . "\n";
1821
                                }
1822
                                break 2;
1823
                            } elseif ($func !== 'if' && $func !== 'elseif' && $paramstr[$ptr] === '/') {
1824
                                if ($this->debug) {
1825
                                    echo 'PARAM PARSING ENDED, "/" FOUND, POINTER AT ' . $ptr . "\n";
1826
                                }
1827
                                break 2;
1828
                            } elseif (substr($paramstr, $ptr, strlen($this->rd)) === $this->rd) {
1829
                                if ($this->debug) {
1830
                                    echo 'PARAM PARSING ENDED, RIGHT DELIMITER FOUND, POINTER AT ' . $ptr . "\n";
1831
                                }
1832
                                break 2;
1833
                            }
1834
1835
                            if ($paramstr[$ptr] === ' ' || $paramstr[$ptr] === ',' || $paramstr[$ptr] === "\r" || $paramstr[$ptr] === "\n" || $paramstr[$ptr] === "\t") {
1836
                                ++ $ptr;
1837
                            } else {
1838
                                break;
1839
                            }
1840
                        }
1841
1842
                        if ($this->debug) {
1843
                            echo 'FUNC START PARAM PARSING WITH POINTER AT ' . $ptr . "\n";
1844
                        }
1845
1846
                        if ($func === 'if' || $func === 'elseif' || $func === 'tif') {
1847
                            $params = $this->parse($paramstr, $ptr, strlen($paramstr), $params, 'condition', $ptr);
1848
                        } elseif ($func === 'array') {
1849
                            $params = $this->parse($paramstr, $ptr, strlen($paramstr), $params, 'array', $ptr);
1850
                        } else {
1851
                            $params = $this->parse($paramstr, $ptr, strlen($paramstr), $params, 'function', $ptr);
1852
                        }
1853
1854 View Code Duplication
                        if ($this->debug) {
1855
                            echo 'PARAM PARSED, POINTER AT ' . $ptr . ' (' . substr($paramstr, $ptr - 1, 3) . ')' . "\n";
1856
                        }
1857
                    }
1858
                }
1859
                $paramstr = substr($paramstr, 0, $ptr);
1860
                $state    = 0;
1861
                foreach ($params as $k => $p) {
1862
                    if (is_array($p) && is_array($p[1])) {
1863
                        $state |= 2;
1864
                    } else {
1865
                        if (($state & 2) && preg_match('#^(["\'])(.+?)\1$#', $p[0], $m) && $func !== 'array') {
1866
                            $params[$k] = array($m[2], array('true', 'true'));
1867
                        } else {
1868
                            if ($state & 2 && $func !== 'array') {
1869
                                throw new CompilationException($this, 'You can not use an unnamed parameter after a named one');
1870
                            }
1871
                            $state |= 1;
1872
                        }
1873
                    }
1874
                }
1875
            }
1876
        }
1877
1878
        if ($pointer !== null) {
1879
            $pointer += (isset($paramstr) ? strlen($paramstr) : 0) + (')' === $paramsep ? 2 : ($paramspos === false ? 0 : 1)) + strlen($func) + (isset($whitespace) ? $whitespace : 0);
1880
            if ($this->debug) {
1881
                echo 'FUNC ADDS ' . ((isset($paramstr) ? strlen($paramstr) : 0) + (')' === $paramsep ? 2 : ($paramspos === false ? 0 : 1)) + strlen($func)) . ' TO POINTER' . "\n";
1882
            }
1883
        }
1884
1885
        if ($curBlock === 'method' || $func === 'do' || strstr($func, '::') !== false) {
1886
            // handle static method calls with security policy
1887
            if (strstr($func, '::') !== false && $this->securityPolicy !== null && $this->securityPolicy->isMethodAllowed(explode('::', strtolower($func))) !== true) {
1888
                throw new SecurityException('Call to a disallowed php function : ' . $func);
1889
            }
1890
            $pluginType = Core::NATIVE_PLUGIN;
1891
        } else {
1892
            $pluginType = $this->getPluginType($func);
1893
        }
1894
1895
        // Blocks plugin
1896
        if ($pluginType & Core::BLOCK_PLUGIN) {
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
            if ($pluginType & Core::CUSTOM_PLUGIN) {
1901
                return $this->addCustomBlock($func, $params, $state);
1902
            } else {
1903
                return $this->addBlock($func, $params, $state);
1904
            }
1905
        } elseif ($pluginType & Core::SMARTY_BLOCK) {
1906
            if ($curBlock !== 'root' || is_array($parsingParams)) {
1907
                throw new CompilationException($this, 'Block plugins can not be used as other plugin\'s arguments');
1908
            }
1909
1910
            if ($state & 2) {
1911
                array_unshift($params, array('__functype', array($pluginType, $pluginType)));
1912
                array_unshift($params, array('__funcname', array($func, $func)));
1913
            } else {
1914
                array_unshift($params, array($pluginType, $pluginType));
1915
                array_unshift($params, array($func, $func));
1916
            }
1917
1918
            return $this->addBlock('smartyinterface', $params, $state);
1919
        }
1920
1921
        // Native & Smarty plugins
1922
        if ($pluginType & Core::NATIVE_PLUGIN || $pluginType & Core::SMARTY_FUNCTION || $pluginType & Core::SMARTY_BLOCK) {
1923
            $params = $this->mapParams($params, null, $state);
1924
        } // PHP class plugin
1925
        elseif ($pluginType & Core::CLASS_PLUGIN) {
1926
            if ($pluginType & Core::CUSTOM_PLUGIN) {
1927
                $params = $this->mapParams(
1928
                    $params, array(
1929
                    $this->customPlugins[$func]['class'],
1930
                    $this->customPlugins[$func]['function']
1931
                ), $state);
1932
            } else {
1933
                if (class_exists('Plugin' . Core::toCamelCase($func)) !== false) {
1934
                    $params = $this->mapParams($params, array(
1935
                        'Plugin' . Core::toCamelCase($func),
1936
                        ($pluginType & Core::COMPILABLE_PLUGIN) ? 'compile' : 'process'
1937
                    ), $state);
1938
                } elseif (class_exists(Core::NAMESPACE_PLUGINS_HELPERS . 'Plugin' . Core::toCamelCase($func)) !== false) {
1939
                    $params = $this->mapParams($params, array(
1940
                        Core::NAMESPACE_PLUGINS_HELPERS . 'Plugin' . Core::toCamelCase($func),
1941
                        ($pluginType & Core::COMPILABLE_PLUGIN) ? 'compile' : 'process'
1942
                    ), $state);
1943 View Code Duplication
                } else {
1944
                    $params = $this->mapParams($params, array(
1945
                        Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . Core::toCamelCase($func),
1946
                        ($pluginType & Core::COMPILABLE_PLUGIN) ? 'compile' : 'process'
1947
                    ), $state);
1948
                }
1949
            }
1950
        } // PHP function plugin
1951
        elseif ($pluginType & Core::FUNC_PLUGIN) {
1952
            if ($pluginType & Core::CUSTOM_PLUGIN) {
1953
                $params = $this->mapParams($params, $this->customPlugins[$func]['callback'], $state);
1954
            } else {
1955
                // Custom plugin
1956
                if (function_exists('Plugin' . Core::toCamelCase($func) . (($pluginType & Core::COMPILABLE_PLUGIN) ?
1957
                        'Compile' : '')) !== false) {
1958
                    $params = $this->mapParams($params, 'Plugin' . Core::toCamelCase($func) . (($pluginType &
1959
                            Core::COMPILABLE_PLUGIN) ? 'Compile' : ''), $state);
1960
                } // Builtin helper plugin
1961
                elseif (function_exists(Core::NAMESPACE_PLUGINS_HELPERS . 'Plugin' . Core::toCamelCase($func) . (
1962
                    ($pluginType & Core::COMPILABLE_PLUGIN) ? 'Compile' : '')) !== false) {
1963
                    $params = $this->mapParams($params, Core::NAMESPACE_PLUGINS_HELPERS . 'Plugin' . Core::toCamelCase
1964
                        ($func) . (($pluginType & Core::COMPILABLE_PLUGIN) ? 'Compile' : ''), $state);
1965
                } // Builtin function plugin
1966 View Code Duplication
                else {
1967
                    $params = $this->mapParams($params, Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . Core::toCamelCase
1968
                        ($func) . (($pluginType & Core::COMPILABLE_PLUGIN) ? 'Compile' : ''), $state);
1969
                }
1970
            }
1971
        } // Smarty modifier
1972
        elseif ($pluginType & Core::SMARTY_MODIFIER) {
1973
            $output = 'smarty_modifier_' . $func . '(' . implode(', ', $params) . ')';
1974
        } // Proxy plugin
1975
        elseif ($pluginType & Core::PROXY_PLUGIN) {
1976
            $params = $this->mapParams($params, $this->getDwoo()->getPluginProxy()->getCallback($func), $state);
1977
        } // Template plugin
1978
        elseif ($pluginType & Core::TEMPLATE_PLUGIN) {
1979
            // transforms the parameter array from (x=>array('paramname'=>array(values))) to (paramname=>array(values))
1980
            $map = array();
1981
            foreach ($this->templatePlugins[$func]['params'] as $param => $defValue) {
1982
                if ($param == 'rest') {
1983
                    $param = '*';
1984
                }
1985
                $hasDefault = $defValue !== null;
1986
                if ($defValue === 'null') {
1987
                    $defValue = null;
1988
                } elseif ($defValue === 'false') {
1989
                    $defValue = false;
1990
                } elseif ($defValue === 'true') {
1991
                    $defValue = true;
1992
                } elseif (preg_match('#^([\'"]).*?\1$#', $defValue)) {
1993
                    $defValue = substr($defValue, 1, - 1);
1994
                }
1995
                $map[] = array($param, $hasDefault, $defValue);
1996
            }
1997
1998
            $params = $this->mapParams($params, null, $state, $map);
1999
        }
2000
2001
        // only keep php-syntax-safe values for non-block plugins
2002
        $tokens = array();
2003
        foreach ($params as $k => $p) {
2004
            $tokens[$k] = isset($p[2]) ? $p[2] : 0;
2005
            $params[$k] = $p[0];
2006
        }
2007
2008
        // Native plugin
2009
        if ($pluginType & Core::NATIVE_PLUGIN) {
2010
            if ($func === 'do') {
2011
                $output = '';
2012
                if (isset($params['*'])) {
2013
                    $output = implode(';', $params['*']) . ';';
2014
                }
2015
2016
                if (is_array($parsingParams) || $curBlock !== 'root') {
2017
                    throw new CompilationException($this, 'Do can not be used inside another function or block');
2018
                }
2019
2020
                return self::PHP_OPEN . $output . self::PHP_CLOSE;
2021
            } else {
2022
                if (isset($params['*'])) {
2023
                    $output = $func . '(' . implode(', ', $params['*']) . ')';
2024
                } else {
2025
                    $output = $func . '()';
2026
                }
2027
            }
2028
        } // Block class OR Function class
2029
        elseif ($pluginType & Core::CLASS_PLUGIN || ($pluginType & Core::FUNC_PLUGIN && $pluginType & Core::CLASS_PLUGIN)) {
2030
            if ($pluginType & Core::COMPILABLE_PLUGIN) {
2031
                if ($pluginType & Core::CUSTOM_PLUGIN) {
2032
                    $callback = $this->customPlugins[$func]['callback'];
2033
                    if (!is_array($callback)) {
2034
                        if (!method_exists($callback, 'compile')) {
2035
                            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');
2036
                        }
2037
                        if (($ref = new ReflectionMethod($callback, 'compile')) && $ref->isStatic()) {
2038
                            $funcCompiler = array($callback, 'compile');
2039
                        } else {
2040
                            $funcCompiler = array(new $callback(), 'compile');
2041
                        }
2042
                    } else {
2043
                        $funcCompiler = $callback;
2044
                    }
2045
                } else {
2046
                    if (class_exists('Plugin' . Core::toCamelCase($func)) !== false) {
2047
                        $funcCompiler = array('Plugin' . Core::toCamelCase($func), 'compile');
2048
                    } elseif (class_exists(Core::NAMESPACE_PLUGINS_HELPERS . 'Plugin' . Core::toCamelCase($func)) !== false) {
2049
                        $funcCompiler = array(Core::NAMESPACE_PLUGINS_HELPERS . 'Plugin' . Core::toCamelCase($func), 'compile');
2050
                    } else {
2051
                        $funcCompiler = array(
2052
                            Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . Core::toCamelCase($func),
2053
                            'compile'
2054
                        );
2055
                    }
2056
                    array_unshift($params, $this);
2057
                }
2058
                // @TODO: Is it a real fix ?
2059
                if ($func === 'tif') {
2060
                    $params[] = $tokens;
2061
                }
2062
                $output = call_user_func_array($funcCompiler, $params);
2063
            } else {
2064
                $params = self::implode_r($params);
2065
                if ($pluginType & Core::CUSTOM_PLUGIN) {
2066
                    $callback = $this->customPlugins[$func]['callback'];
2067
                    if (!is_array($callback)) {
2068
                        if (!method_exists($callback, 'process')) {
2069
                            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');
2070
                        }
2071
                        if (($ref = new ReflectionMethod($callback, 'process')) && $ref->isStatic()) {
2072
                            $output = 'call_user_func(array(\'' . $callback . '\', \'process\'), ' . $params . ')';
2073
                        } else {
2074
                            $output = 'call_user_func(array($this->getObjectPlugin(\'' . $callback . '\'), \'process\'), ' . $params . ')';
2075
                        }
2076 View Code Duplication
                    } elseif (is_object($callback[0])) {
2077
                        $output = 'call_user_func(array($this->plugins[\'' . $func . '\'][\'callback\'][0], \'' . $callback[1] . '\'), ' . $params . ')';
2078
                    } elseif (($ref = new ReflectionMethod($callback[0], $callback[1])) && $ref->isStatic()) {
2079
                        $output = 'call_user_func(array(\'' . $callback[0] . '\', \'' . $callback[1] . '\'), ' . $params . ')';
2080 View Code Duplication
                    } else {
2081
                        $output = 'call_user_func(array($this->getObjectPlugin(\'' . $callback[0] . '\'), \'' . $callback[1] . '\'), ' . $params . ')';
2082
                    }
2083
                    if (empty($params)) {
2084
                        $output = substr($output, 0, - 3) . ')';
2085
                    }
2086
                } else {
2087
                    if (class_exists('Plugin' . Core::toCamelCase($func)) !== false) {
2088
                        $output = '$this->classCall(\'Plugin' . $func . '\', array(' . $params . '))';
2089 View Code Duplication
                    } elseif (class_exists(Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . Core::toCamelCase($func)) !== false) {
2090
                        $output = '$this->classCall(\'' . Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . $func . '\', 
2091
                        array(' . $params . '))';
2092
                    } else {
2093
                        $output = '$this->classCall(\'' . $func . '\', array(' . $params . '))';
2094
                    }
2095
                }
2096
            }
2097
        } // Function plugin only (cannot be a class)
2098
        elseif ($pluginType & Core::FUNC_PLUGIN) {
2099
            if ($pluginType & Core::COMPILABLE_PLUGIN) {
2100
                if ($pluginType & Core::CUSTOM_PLUGIN) {
2101
                    $funcCompiler = $this->customPlugins[$func]['callback'];
2102
                } else {
2103
                    // Custom plugin
2104
                    if (function_exists('Plugin' . Core::toCamelCase($func) . 'Compile') !== false) {
2105
                        $funcCompiler = 'Plugin' . Core::toCamelCase($func) . 'Compile';
2106
                    } // Builtin helper plugin
2107 View Code Duplication
                    elseif (function_exists(Core::NAMESPACE_PLUGINS_HELPERS . 'Plugin' . Core::toCamelCase($func) . 'Compile') !== false) {
2108
                        $funcCompiler = Core::NAMESPACE_PLUGINS_HELPERS . 'Plugin' . Core::toCamelCase($func) .
2109
                            'Compile';
2110
                    } // Builtin function plugin
2111
                    else {
2112
                        $funcCompiler = Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . Core::toCamelCase($func) .
2113
                            'Compile';
2114
                    }
2115
                }
2116
                array_unshift($params, $this);
2117
                // @TODO: Is it a real fix ?
2118
                if ($func === 'tif') {
2119
                    $params[] = $tokens;
2120
                }
2121
                $output = call_user_func_array($funcCompiler, $params);
2122
            } else {
2123
                array_unshift($params, '$this');
2124
                $params = self::implode_r($params);
2125
                if ($pluginType & Core::CUSTOM_PLUGIN) {
2126
                    $callback = $this->customPlugins[$func]['callback'];
2127
                    if ($callback instanceof Closure) {
2128
                        $output = 'call_user_func($this->getCustomPlugin(\'' . $func . '\'), ' . $params . ')';
2129
                    } else {
2130
                        $output = 'call_user_func(\'' . $callback . '\', ' . $params . ')';
2131
                    }
2132
                } else {
2133
                    // Custom plugin
2134
                    if (function_exists('Plugin' . Core::toCamelCase($func)) !== false) {
2135
                        $output = 'Plugin' . Core::toCamelCase($func) . '(' . $params .
2136
                            ')';
2137
                    } // Builtin helper plugin
2138 View Code Duplication
                    elseif(function_exists(Core::NAMESPACE_PLUGINS_HELPERS . 'Plugin' . Core::toCamelCase($func)) !==
2139
                        false) {
2140
                        $output = Core::NAMESPACE_PLUGINS_HELPERS . 'Plugin' . Core::toCamelCase($func) . '(' .
2141
                            $params . ')';
2142
                    } // Builtin function plugin
2143 View Code Duplication
                    else {
2144
                        $output = Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . Core::toCamelCase($func) . '(' .
2145
                            $params . ')';
2146
                    }
2147
                }
2148
            }
2149
        } // Proxy plugin
2150
        elseif ($pluginType & Core::PROXY_PLUGIN) {
2151
            $output = call_user_func(array($this->getDwoo()->getPluginProxy(), 'getCode'), $func, $params);
2152
        } // Smarty function (@deprecated)
2153
        elseif ($pluginType & Core::SMARTY_FUNCTION) {
2154
            $params = '';
2155
            if (isset($params['*'])) {
2156
                $params = self::implode_r($params['*'], true);
2157
            }
2158
2159
            if ($pluginType & Core::CUSTOM_PLUGIN) {
2160
                $callback = $this->customPlugins[$func]['callback'];
2161
                if (is_array($callback)) {
2162
                    if (is_object($callback[0])) {
2163
                        $output = 'call_user_func_array(array($this->plugins[\'' . $func . '\'][\'callback\'][0], \'' . $callback[1] . '\'), array(array(' . $params . '), $this))';
2164 View Code Duplication
                    } else {
2165
                        $output = 'call_user_func_array(array(\'' . $callback[0] . '\', \'' . $callback[1] . '\'), array(array(' . $params . '), $this))';
2166
                    }
2167
                } else {
2168
                    $output = $callback . '(array(' . $params . '), $this)';
2169
                }
2170
            } else {
2171
                $output = 'smarty_function_' . $func . '(array(' . $params . '), $this)';
2172
            }
2173
        } // Template plugin
2174
        elseif ($pluginType & Core::TEMPLATE_PLUGIN) {
2175
            array_unshift($params, '$this');
2176
            $params                                 = self::implode_r($params);
2177
            $output                                 = 'Plugin' . Core::toCamelCase($func) .
2178
                $this->templatePlugins[$func]['uuid'] . '(' . $params . ')';
2179
            $this->templatePlugins[$func]['called'] = true;
2180
        }
2181
2182
        if (is_array($parsingParams)) {
2183
            $parsingParams[] = array($output, $output);
2184
2185
            return $parsingParams;
2186
        } elseif ($curBlock === 'namedparam') {
2187
            return array($output, $output);
2188
        }
2189
2190
        return $output;
2191
    }
2192
2193
    /**
2194
     * Parses a string.
2195
     *
2196
     * @param string $in            the string within which we must parse something
2197
     * @param int    $from          the starting offset of the parsed area
2198
     * @param int    $to            the ending offset of the parsed area
2199
     * @param mixed  $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by
2200
     *                              default
2201
     * @param string $curBlock      the current parser-block being processed
2202
     * @param mixed  $pointer       a reference to a pointer that will be increased by the amount of characters parsed,
2203
     *                              or null by default
2204
     *
2205
     * @return string parsed values
2206
     * @throws CompilationException
2207
     */
2208
    protected function parseString($in, $from, $to, $parsingParams = false, $curBlock = '', &$pointer = null)
2209
    {
2210
        $substr = substr($in, $from, $to - $from);
2211
        $first  = $substr[0];
2212
2213
        if ($this->debug) {
2214
            echo 'STRING FOUND (in ' . htmlentities(substr($in, $from, min($to - $from, 50))) . (($to - $from) > 50 ? '...' : '') . ')' . "\n";
2215
        }
2216
        $strend = false;
2217
        $o      = $from + 1;
2218
        while ($strend === false) {
2219
            $strend = strpos($in, $first, $o);
2220 View Code Duplication
            if ($strend === false) {
2221
                throw new CompilationException($this, 'Unfinished string, started with ' . substr($in, $from, $to - $from));
2222
            }
2223
            if (substr($in, $strend - 1, 1) === '\\') {
2224
                $o      = $strend + 1;
2225
                $strend = false;
2226
            }
2227
        }
2228
        if ($this->debug) {
2229
            echo 'STRING DELIMITED: ' . substr($in, $from, $strend + 1 - $from) . "\n";
2230
        }
2231
2232
        $srcOutput = substr($in, $from, $strend + 1 - $from);
2233
2234
        if ($pointer !== null) {
2235
            $pointer += strlen($srcOutput);
2236
        }
2237
2238
        $output = $this->replaceStringVars($srcOutput, $first);
2239
2240
        // handle modifiers
2241
        if ($curBlock !== 'modifier' && preg_match('#^((?:\|(?:@?[a-z0-9_]+(?::.*)*))+)#i', substr($substr, $strend + 1 - $from), $match)) {
2242
            $modstr = $match[1];
2243
2244
            if ($curBlock === 'root' && substr($modstr, - 1) === '}') {
2245
                $modstr = substr($modstr, 0, - 1);
2246
            }
2247
            $modstr = str_replace('\\' . $first, $first, $modstr);
2248
            $ptr    = 0;
2249
            $output = $this->replaceModifiers(array(null, null, $output, $modstr), 'string', $ptr);
2250
2251
            $strend += $ptr;
2252
            if ($pointer !== null) {
2253
                $pointer += $ptr;
2254
            }
2255
            $srcOutput .= substr($substr, $strend + 1 - $from, $ptr);
2256
        }
2257
2258
        if (is_array($parsingParams)) {
2259
            $parsingParams[] = array($output, substr($srcOutput, 1, - 1));
2260
2261
            return $parsingParams;
2262
        } elseif ($curBlock === 'namedparam') {
2263
            return array($output, substr($srcOutput, 1, - 1));
2264
        } else {
2265
            return $output;
2266
        }
2267
    }
2268
2269
    /**
2270
     * Parses a constant.
2271
     *
2272
     * @param string $in            the string within which we must parse something
2273
     * @param int    $from          the starting offset of the parsed area
2274
     * @param int    $to            the ending offset of the parsed area
2275
     * @param mixed  $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by
2276
     *                              default
2277
     * @param string $curBlock      the current parser-block being processed
2278
     * @param mixed  $pointer       a reference to a pointer that will be increased by the amount of characters parsed,
2279
     *                              or null by default
2280
     *
2281
     * @return string parsed values
2282
     * @throws CompilationException
2283
     */
2284
    protected function parseConst($in, $from, $to, $parsingParams = false, $curBlock = '', &$pointer = null)
2285
    {
2286
        $substr = substr($in, $from, $to - $from);
2287
2288
        if ($this->debug) {
2289
            echo 'CONST FOUND : ' . $substr . "\n";
2290
        }
2291
2292
        if (!preg_match('#^%([\\\\a-z0-9_:]+)#i', $substr, $m)) {
2293
            throw new CompilationException($this, 'Invalid constant');
2294
        }
2295
2296
        if ($pointer !== null) {
2297
            $pointer += strlen($m[0]);
2298
        }
2299
2300
        $output = $this->parseConstKey($m[1], $curBlock);
2301
2302
        if (is_array($parsingParams)) {
2303
            $parsingParams[] = array($output, $m[1]);
2304
2305
            return $parsingParams;
2306
        } elseif ($curBlock === 'namedparam') {
2307
            return array($output, $m[1]);
2308
        } else {
2309
            return $output;
2310
        }
2311
    }
2312
2313
    /**
2314
     * Parses a constant.
2315
     *
2316
     * @param string $key      the constant to parse
2317
     * @param string $curBlock the current parser-block being processed
2318
     *
2319
     * @return string parsed constant
2320
     */
2321
    protected function parseConstKey($key, $curBlock)
2322
    {
2323
        if ($this->securityPolicy !== null && $this->securityPolicy->getConstantHandling() === SecurityPolicy::CONST_DISALLOW) {
2324
            return 'null';
2325
        }
2326
2327 View Code Duplication
        if ($curBlock !== 'root') {
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...
2328
            $output = '(defined("' . $key . '") ? ' . $key . ' : null)';
2329
        } else {
2330
            $output = $key;
2331
        }
2332
2333
        return $output;
2334
    }
2335
2336
    /**
2337
     * Parses a variable.
2338
     *
2339
     * @param string $in            the string within which we must parse something
2340
     * @param int    $from          the starting offset of the parsed area
2341
     * @param int    $to            the ending offset of the parsed area
2342
     * @param mixed  $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by
2343
     *                              default
2344
     * @param string $curBlock      the current parser-block being processed
2345
     * @param mixed  $pointer       a reference to a pointer that will be increased by the amount of characters parsed,
2346
     *                              or null by default
2347
     *
2348
     * @return string parsed values
2349
     * @throws CompilationException
2350
     */
2351
    protected function parseVar($in, $from, $to, $parsingParams = false, $curBlock = '', &$pointer = null)
2352
    {
2353
        $substr = substr($in, $from, $to - $from);
2354
2355
        // var key
2356
        $varRegex = '(\\$?\\.?[a-z0-9\\\\_:]*(?:(?:(?:\\.|->)(?:[a-z0-9\\\\_:]+|(?R))|\\[(?:[a-z0-9\\\\_:]+|(?R)|(["\'])[^\\2]*?\\2)\\]))*)';
2357
        // method call
2358
        $methodCall = ($curBlock === 'root' || $curBlock === 'function' || $curBlock === 'namedparam' || $curBlock === 'condition' || $curBlock === 'variable' || $curBlock === 'expression' || $curBlock === 'delimited_string' ? '(\(.*)?' : '()');
2359
        // simple math expressions
2360
        $simpleMathExpressions = ($curBlock === 'root' || $curBlock === 'function' || $curBlock === 'namedparam' || $curBlock === 'condition' || $curBlock === 'variable' || $curBlock === 'delimited_string' ? '((?:(?:[+\/*%=-])(?:(?<!=)=?-?[$%][a-z0-9\\\\.[\]>_:-]+(?:\([^)]*\))?|(?<!=)=?-?[0-9\.,]*|[+-]))*)' : '()');
2361
        // modifiers
2362
        $modifiers = $curBlock !== 'modifier' ? '((?:\|(?:@?[a-z0-9\\\\_]+(?:(?::("|\').*?\5|:[^`]*))*))+)?' : '(())';
2363
2364
        $regex = '#';
2365
        $regex .= $varRegex;
2366
        $regex .= $methodCall;
2367
        $regex .= $simpleMathExpressions;
2368
        $regex .= $modifiers;
2369
        $regex .= '#i';
2370
2371
        if (preg_match($regex, $substr, $match)) {
2372
            $key = substr($match[1], 1);
2373
2374
            $matchedLength = strlen($match[0]);
2375
            $hasModifiers  = !empty($match[5]);
2376
            $hasExpression = !empty($match[4]);
2377
            $hasMethodCall = !empty($match[3]);
2378
2379
            if (substr($key, - 1) == '.') {
2380
                $key = substr($key, 0, - 1);
2381
                -- $matchedLength;
2382
            }
2383
2384
            if ($hasMethodCall) {
2385
                $matchedLength -= strlen($match[3]) + strlen(substr($match[1], strrpos($match[1], '->')));
2386
                $key        = substr($match[1], 1, strrpos($match[1], '->') - 1);
2387
                $methodCall = substr($match[1], strrpos($match[1], '->')) . $match[3];
2388
            }
2389
2390
            if ($hasModifiers) {
2391
                $matchedLength -= strlen($match[5]);
2392
            }
2393
2394
            if ($pointer !== null) {
2395
                $pointer += $matchedLength;
2396
            }
2397
2398
            // replace useless brackets by dot accessed vars and strip enclosing quotes if present
2399
            $key = preg_replace('#\[(["\']?)([^$%\[.>-]+)\1\]#', '.$2', $key);
2400
2401 View Code Duplication
            if ($this->debug) {
2402
                if ($hasMethodCall) {
2403
                    echo 'METHOD CALL FOUND : $' . $key . substr($methodCall, 0, 30) . "\n";
2404
                } else {
2405
                    echo 'VAR FOUND : $' . $key . "\n";
2406
                }
2407
            }
2408
2409
            $key = str_replace('"', '\\"', $key);
2410
2411
            $cnt = substr_count($key, '$');
2412
            if ($cnt > 0) {
2413
                $uid           = 0;
2414
                $parsed        = array($uid => '');
2415
                $current       = &$parsed;
2416
                $curTxt        = &$parsed[$uid ++];
2417
                $tree          = array();
2418
                $chars         = str_split($key, 1);
2419
                $inSplittedVar = false;
2420
                $bracketCount  = 0;
2421
2422
                while (($char = array_shift($chars)) !== null) {
2423
                    if ($char === '[') {
2424
                        if (count($tree) > 0) {
2425
                            ++ $bracketCount;
2426
                        } else {
2427
                            $tree[]        = &$current;
2428
                            $current[$uid] = array($uid + 1 => '');
2429
                            $current       = &$current[$uid ++];
2430
                            $curTxt        = &$current[$uid ++];
2431
                            continue;
2432
                        }
2433
                    } elseif ($char === ']') {
2434
                        if ($bracketCount > 0) {
2435
                            -- $bracketCount;
2436
                        } else {
2437
                            $current = &$tree[count($tree) - 1];
2438
                            array_pop($tree);
2439
                            if (current($chars) !== '[' && current($chars) !== false && current($chars) !== ']') {
2440
                                $current[$uid] = '';
2441
                                $curTxt        = &$current[$uid ++];
2442
                            }
2443
                            continue;
2444
                        }
2445
                    } elseif ($char === '$') {
2446
                        if (count($tree) == 0) {
2447
                            $curTxt        = &$current[$uid ++];
2448
                            $inSplittedVar = true;
2449
                        }
2450
                    } elseif (($char === '.' || $char === '-') && count($tree) == 0 && $inSplittedVar) {
2451
                        $curTxt        = &$current[$uid ++];
2452
                        $inSplittedVar = false;
2453
                    }
2454
2455
                    $curTxt .= $char;
2456
                }
2457
                unset($uid, $current, $curTxt, $tree, $chars);
2458
2459
                if ($this->debug) {
2460
                    echo 'RECURSIVE VAR REPLACEMENT : ' . $key . "\n";
2461
                }
2462
2463
                $key = $this->flattenVarTree($parsed);
2464
2465
                if ($this->debug) {
2466
                    echo 'RECURSIVE VAR REPLACEMENT DONE : ' . $key . "\n";
2467
                }
2468
2469
                $output = preg_replace('#(^""\.|""\.|\.""$|(\()""\.|\.""(\)))#', '$2$3', '$this->readVar("' . $key . '")');
2470
            } else {
2471
                $output = $this->parseVarKey($key, $hasModifiers ? 'modifier' : $curBlock);
2472
            }
2473
2474
2475
            // methods
2476
            if ($hasMethodCall) {
2477
                $ptr = 0;
2478
2479
                $output = $this->parseMethodCall($output, $methodCall, $curBlock, $ptr);
2480
2481
                if ($pointer !== null) {
2482
                    $pointer += $ptr;
2483
                }
2484
                $matchedLength += $ptr;
2485
            }
2486
2487
            if ($hasExpression) {
2488
                // expressions
2489
                preg_match_all('#(?:([+/*%=-])(=?-?[%$][a-z0-9\\\\.[\]>_:-]+(?:\([^)]*\))?|=?-?[0-9.,]+|\1))#i', $match[4], $expMatch);
2490
                foreach ($expMatch[1] as $k => $operator) {
2491
                    if (substr($expMatch[2][$k], 0, 1) === '=') {
2492
                        $assign = true;
2493
                        if ($operator === '=') {
2494
                            throw new CompilationException($this, 'Invalid expression <em>' . $substr . '</em>, can not use "==" in expressions');
2495
                        }
2496
                        if ($curBlock !== 'root') {
2497
                            throw new CompilationException($this, 'Invalid expression <em>' . $substr . '</em>, assignments can only be used in top level expressions like {$foo+=3} or {$foo="bar"}');
2498
                        }
2499
                        $operator .= '=';
2500
                        $expMatch[2][$k] = substr($expMatch[2][$k], 1);
2501
                    }
2502
2503
                    if (substr($expMatch[2][$k], 0, 1) === '-' && strlen($expMatch[2][$k]) > 1) {
2504
                        $operator .= '-';
2505
                        $expMatch[2][$k] = substr($expMatch[2][$k], 1);
2506
                    }
2507
                    if (($operator === '+' || $operator === '-') && $expMatch[2][$k] === $operator) {
2508
                        $output = '(' . $output . $operator . $operator . ')';
2509
                        break;
2510
                    } elseif (substr($expMatch[2][$k], 0, 1) === '$') {
2511
                        $output = '(' . $output . ' ' . $operator . ' ' . $this->parseVar($expMatch[2][$k], 0, strlen($expMatch[2][$k]), false, 'expression') . ')';
2512
                    } elseif (substr($expMatch[2][$k], 0, 1) === '%') {
2513
                        $output = '(' . $output . ' ' . $operator . ' ' . $this->parseConst($expMatch[2][$k], 0, strlen($expMatch[2][$k]), false, 'expression') . ')';
2514
                    } elseif (!empty($expMatch[2][$k])) {
2515
                        $output = '(' . $output . ' ' . $operator . ' ' . str_replace(',', '.', $expMatch[2][$k]) . ')';
2516
                    } else {
2517
                        throw new CompilationException($this, 'Unfinished expression <em>' . $substr . '</em>, missing var or number after math operator');
2518
                    }
2519
                }
2520
            }
2521
2522
            if ($this->autoEscape === true && $curBlock !== 'condition') {
2523
                $output = '(is_string($tmp=' . $output . ') ? htmlspecialchars($tmp, ENT_QUOTES, $this->charset) : $tmp)';
2524
            }
2525
2526
            // handle modifiers
2527
            if ($curBlock !== 'modifier' && $hasModifiers) {
2528
                $ptr    = 0;
2529
                $output = $this->replaceModifiers(array(null, null, $output, $match[5]), 'var', $ptr);
2530
                if ($pointer !== null) {
2531
                    $pointer += $ptr;
2532
                }
2533
                $matchedLength += $ptr;
2534
            }
2535
2536
            if (is_array($parsingParams)) {
2537
                $parsingParams[] = array($output, $key);
2538
2539
                return $parsingParams;
2540
            } elseif ($curBlock === 'namedparam') {
2541
                return array($output, $key);
2542
            } elseif ($curBlock === 'string' || $curBlock === 'delimited_string') {
2543
                return array($matchedLength, $output);
2544
            } elseif ($curBlock === 'expression' || $curBlock === 'variable') {
2545
                return $output;
2546
            } elseif (isset($assign)) {
2547
                return self::PHP_OPEN . $output . ';' . self::PHP_CLOSE;
2548
            } else {
2549
                return $output;
2550
            }
2551
        } else {
2552
            if ($curBlock === 'string' || $curBlock === 'delimited_string') {
2553
                return array(0, '');
2554
            } else {
2555
                throw new CompilationException($this, 'Invalid variable name <em>' . $substr . '</em>');
2556
            }
2557
        }
2558
    }
2559
2560
    /**
2561
     * Parses any number of chained method calls/property reads.
2562
     *
2563
     * @param string $output     the variable or whatever upon which the method are called
2564
     * @param string $methodCall method call source, starting at "->"
2565
     * @param string $curBlock   the current parser-block being processed
2566
     * @param int    $pointer    a reference to a pointer that will be increased by the amount of characters parsed
2567
     *
2568
     * @return string parsed call(s)/read(s)
2569
     */
2570
    protected function parseMethodCall($output, $methodCall, $curBlock, &$pointer)
2571
    {
2572
        $ptr = 0;
2573
        $len = strlen($methodCall);
2574
2575
        while ($ptr < $len) {
2576
            if (strpos($methodCall, '->', $ptr) === $ptr) {
2577
                $ptr += 2;
2578
            }
2579
2580
            if (in_array(
2581
                $methodCall[$ptr], array(
2582
                    ';',
2583
                    ',',
2584
                    '/',
2585
                    ' ',
2586
                    "\t",
2587
                    "\r",
2588
                    "\n",
2589
                    ')',
2590
                    '+',
2591
                    '*',
2592
                    '%',
2593
                    '=',
2594
                    '-',
2595
                    '|'
2596
                )
2597
            ) || substr($methodCall, $ptr, strlen($this->rd)) === $this->rd
2598
            ) {
2599
                // break char found
2600
                break;
2601
            }
2602
2603
            if (!preg_match('/^([a-z0-9_]+)(\(.*?\))?/i', substr($methodCall, $ptr), $methMatch)) {
2604
                break;
2605
            }
2606
2607
            if (empty($methMatch[2])) {
2608
                // property
2609
                if ($curBlock === 'root') {
2610
                    $output .= '->' . $methMatch[1];
2611
                } else {
2612
                    $output = '(($tmp = ' . $output . ') ? $tmp->' . $methMatch[1] . ' : null)';
2613
                }
2614
                $ptr += strlen($methMatch[1]);
2615
            } else {
2616
                // method
2617
                if (substr($methMatch[2], 0, 2) === '()') {
2618
                    $parsedCall = $methMatch[1] . '()';
2619
                    $ptr += strlen($methMatch[1]) + 2;
2620
                } else {
2621
                    $parsedCall = $this->parseFunction($methodCall, $ptr, strlen($methodCall), false, 'method', $ptr);
2622
                }
2623
                if ($this->securityPolicy !== null) {
2624
                    $argPos = strpos($parsedCall, '(');
2625
                    $method = strtolower(substr($parsedCall, 0, $argPos));
2626
                    $args   = substr($parsedCall, $argPos);
2627
                    if ($curBlock === 'root') {
2628
                        $output = '$this->getSecurityPolicy()->callMethod($this, ' . $output . ', ' . var_export($method, true) . ', array' . $args . ')';
2629
                    } else {
2630
                        $output = '(($tmp = ' . $output . ') ? $this->getSecurityPolicy()->callMethod($this, $tmp, ' . var_export($method, true) . ', array' . $args . ') : null)';
2631
                    }
2632 View Code Duplication
                } else {
2633
                    if ($curBlock === 'root') {
2634
                        $output .= '->' . $parsedCall;
2635
                    } else {
2636
                        $output = '(($tmp = ' . $output . ') ? $tmp->' . $parsedCall . ' : null)';
2637
                    }
2638
                }
2639
            }
2640
        }
2641
2642
        $pointer += $ptr;
2643
2644
        return $output;
2645
    }
2646
2647
    /**
2648
     * Parses a constant variable (a variable that doesn't contain another variable) and preprocesses it to save
2649
     * runtime processing time.
2650
     *
2651
     * @param string $key      the variable to parse
2652
     * @param string $curBlock the current parser-block being processed
2653
     *
2654
     * @return string parsed variable
2655
     */
2656
    protected function parseVarKey($key, $curBlock)
2657
    {
2658
        if ($key === '') {
2659
            return '$this->scope';
2660
        }
2661
        if (substr($key, 0, 1) === '.') {
2662
            $key = 'dwoo' . $key;
2663
        }
2664
        if (preg_match('#dwoo\.(get|post|server|cookies|session|env|request)((?:\.[a-z0-9_-]+)+)#i', $key, $m)) {
2665
            $global = strtoupper($m[1]);
2666
            if ($global === 'COOKIES') {
2667
                $global = 'COOKIE';
2668
            }
2669
            $key = '$_' . $global;
2670
            foreach (explode('.', ltrim($m[2], '.')) as $part) {
2671
                $key .= '[' . var_export($part, true) . ']';
2672
            }
2673 View Code Duplication
            if ($curBlock === 'root') {
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...
2674
                $output = $key;
2675
            } else {
2676
                $output = '(isset(' . $key . ')?' . $key . ':null)';
2677
            }
2678
        } elseif (preg_match('#dwoo\\.const\\.([a-z0-9\\\\_:]+)#i', $key, $m)) {
2679
            return $this->parseConstKey($m[1], $curBlock);
2680
        } elseif ($this->scope !== null) {
2681
            if (strstr($key, '.') === false && strstr($key, '[') === false && strstr($key, '->') === false) {
2682
                if ($key === 'dwoo') {
2683
                    $output = '$this->globals';
2684
                } elseif ($key === '_root' || $key === '__') {
2685
                    $output = '$this->data';
2686
                } elseif ($key === '_parent' || $key === '_') {
2687
                    $output = '$this->readParentVar(1)';
2688
                } elseif ($key === '_key') {
2689
                    $output = '$tmp_key';
2690 View Code Duplication
                } else {
2691
                    if ($curBlock === 'root') {
2692
                        $output = '$this->scope["' . $key . '"]';
2693
                    } else {
2694
                        $output = '(isset($this->scope["' . $key . '"]) ? $this->scope["' . $key . '"] : null)';
2695
                    }
2696
                }
2697
            } else {
2698
                preg_match_all('#(\[|->|\.)?((?:[a-z0-9_]|-(?!>))+|(\\\?[\'"])[^\3]*?\3)\]?#i', $key, $m);
2699
2700
                $i = $m[2][0];
2701
                if ($i === '_parent' || $i === '_') {
2702
                    $parentCnt = 0;
2703
2704
                    while (true) {
2705
                        ++ $parentCnt;
2706
                        array_shift($m[2]);
2707
                        array_shift($m[1]);
2708
                        if (current($m[2]) === '_parent') {
2709
                            continue;
2710
                        }
2711
                        break;
2712
                    }
2713
2714
                    $output = '$this->readParentVar(' . $parentCnt . ')';
2715
                } else {
2716
                    if ($i === 'dwoo') {
2717
                        $output = '$this->globals';
2718
                        array_shift($m[2]);
2719
                        array_shift($m[1]);
2720 View Code Duplication
                    } elseif ($i === '_root' || $i === '__') {
2721
                        $output = '$this->data';
2722
                        array_shift($m[2]);
2723
                        array_shift($m[1]);
2724
                    } elseif ($i === '_key') {
2725
                        $output = '$tmp_key';
2726
                    } else {
2727
                        $output = '$this->scope';
2728
                    }
2729
2730
                    while (count($m[1]) && $m[1][0] !== '->') {
2731
                        $m[2][0] = preg_replace('/(^\\\([\'"])|\\\([\'"])$)/x', '$2$3', $m[2][0]);
2732
                        if (substr($m[2][0], 0, 1) == '"' || substr($m[2][0], 0, 1) == "'") {
2733
                            $output .= '[' . $m[2][0] . ']';
2734
                        } else {
2735
                            $output .= '["' . $m[2][0] . '"]';
2736
                        }
2737
                        array_shift($m[2]);
2738
                        array_shift($m[1]);
2739
                    }
2740
2741
                    if ($curBlock !== 'root') {
2742
                        $output = '(isset(' . $output . ') ? ' . $output . ':null)';
2743
                    }
2744
                }
2745
2746
                if (count($m[2])) {
2747
                    unset($m[0]);
2748
                    $output = '$this->readVarInto(' . str_replace("\n", '', var_export($m, true)) . ', ' . $output . ', ' . ($curBlock == 'root' ? 'false' : 'true') . ')';
2749
                }
2750
            }
2751
        } else {
2752
            preg_match_all('#(\[|->|\.)?((?:[a-z0-9_]|-(?!>))+)\]?#i', $key, $m);
2753
            unset($m[0]);
2754
            $output = '$this->readVar(' . str_replace("\n", '', var_export($m, true)) . ')';
2755
        }
2756
2757
        return $output;
2758
    }
2759
2760
    /**
2761
     * Flattens a variable tree, this helps in parsing very complex variables such as $var.foo[$foo.bar->baz].baz,
2762
     * it computes the contents of the brackets first and works out from there.
2763
     *
2764
     * @param array $tree     the variable tree parsed by he parseVar() method that must be flattened
2765
     * @param bool  $recursed leave that to false by default, it is only for internal use
2766
     *
2767
     * @return string flattened tree
2768
     */
2769
    protected function flattenVarTree(array $tree, $recursed = false)
2770
    {
2771
        $out = $recursed ? '".$this->readVarInto(' : '';
2772
        foreach ($tree as $bit) {
2773
            if (is_array($bit)) {
2774
                $out .= '.' . $this->flattenVarTree($bit, false);
2775
            } else {
2776
                $key = str_replace('"', '\\"', $bit);
2777
2778
                if (substr($key, 0, 1) === '$') {
2779
                    $out .= '".' . $this->parseVar($key, 0, strlen($key), false, 'variable') . '."';
2780
                } else {
2781
                    $cnt = substr_count($key, '$');
2782
2783
                    if ($this->debug) {
2784
                        echo 'PARSING SUBVARS IN : ' . $key . "\n";
2785
                    }
2786
                    if ($cnt > 0) {
2787
                        while (-- $cnt >= 0) {
2788
                            if (isset($last)) {
2789
                                $last = strrpos($key, '$', - (strlen($key) - $last + 1));
2790
                            } else {
2791
                                $last = strrpos($key, '$');
2792
                            }
2793
                            preg_match('#\$[a-z0-9_]+((?:(?:\.|->)(?:[a-z0-9_]+|(?R))|\[(?:[a-z0-9_]+|(?R))\]))*' . '((?:(?:[+/*%-])(?:\$[a-z0-9.[\]>_:-]+(?:\([^)]*\))?|[0-9.,]*))*)#i', substr($key, $last), $submatch);
2794
2795
                            $len = strlen($submatch[0]);
2796
                            $key = substr_replace(
2797
                                $key, preg_replace_callback(
2798
                                    '#(\$[a-z0-9_]+((?:(?:\.|->)(?:[a-z0-9_]+|(?R))|\[(?:[a-z0-9_]+|(?R))\]))*)' . '((?:(?:[+/*%-])(?:\$[a-z0-9.[\]>_:-]+(?:\([^)]*\))?|[0-9.,]*))*)#i', array(
2799
                                        $this,
2800
                                        'replaceVarKeyHelper'
2801
                                    ), substr($key, $last, $len)
2802
                                ), $last, $len
2803
                            );
2804
                            if ($this->debug) {
2805
                                echo 'RECURSIVE VAR REPLACEMENT DONE : ' . $key . "\n";
2806
                            }
2807
                        }
2808
                        unset($last);
2809
2810
                        $out .= $key;
2811
                    } else {
2812
                        $out .= $key;
2813
                    }
2814
                }
2815
            }
2816
        }
2817
        $out .= $recursed ? ', true)."' : '';
2818
2819
        return $out;
2820
    }
2821
2822
    /**
2823
     * Helper function that parses a variable.
2824
     *
2825
     * @param array $match the matched variable, array(1=>"string match")
2826
     *
2827
     * @return string parsed variable
2828
     */
2829
    protected function replaceVarKeyHelper($match)
2830
    {
2831
        return '".' . $this->parseVar($match[0], 0, strlen($match[0]), false, 'variable') . '."';
2832
    }
2833
2834
    /**
2835
     * Parses various constants, operators or non-quoted strings.
2836
     *
2837
     * @param string $in            the string within which we must parse something
2838
     * @param int    $from          the starting offset of the parsed area
2839
     * @param int    $to            the ending offset of the parsed area
2840
     * @param mixed  $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by
2841
     *                              default
2842
     * @param string $curBlock      the current parser-block being processed
2843
     * @param mixed  $pointer       a reference to a pointer that will be increased by the amount of characters parsed,
2844
     *                              or null by default
2845
     *
2846
     * @return string parsed values
2847
     * @throws Exception
2848
     */
2849
    protected function parseOthers($in, $from, $to, $parsingParams = false, $curBlock = '', &$pointer = null)
2850
    {
2851
        $substr = substr($in, $from, $to - $from);
2852
2853
        $end = strlen($substr);
2854
2855
        if ($curBlock === 'condition') {
2856
            $breakChars = array(
2857
                '(',
2858
                ')',
2859
                ' ',
2860
                '||',
2861
                '&&',
2862
                '|',
2863
                '&',
2864
                '>=',
2865
                '<=',
2866
                '===',
2867
                '==',
2868
                '=',
2869
                '!==',
2870
                '!=',
2871
                '<<',
2872
                '<',
2873
                '>>',
2874
                '>',
2875
                '^',
2876
                '~',
2877
                ',',
2878
                '+',
2879
                '-',
2880
                '*',
2881
                '/',
2882
                '%',
2883
                '!',
2884
                '?',
2885
                ':',
2886
                $this->rd,
2887
                ';'
2888
            );
2889
        } elseif ($curBlock === 'modifier') {
2890
            $breakChars = array(' ', ',', ')', ':', '|', "\r", "\n", "\t", ';', $this->rd);
2891
        } elseif ($curBlock === 'expression') {
2892
            $breakChars = array('/', '%', '+', '-', '*', ' ', ',', ')', "\r", "\n", "\t", ';', $this->rd);
2893
        } else {
2894
            $breakChars = array(' ', ',', ')', "\r", "\n", "\t", ';', $this->rd);
2895
        }
2896
2897
        $breaker = false;
2898
        foreach ($breakChars as $k => $char) {
2899
            $test = strpos($substr, $char);
2900
            if ($test !== false && $test < $end) {
2901
                $end     = $test;
2902
                $breaker = $k;
2903
            }
2904
        }
2905
2906
        if ($curBlock === 'condition') {
2907
            if ($end === 0 && $breaker !== false) {
2908
                $end = strlen($breakChars[$breaker]);
2909
            }
2910
        }
2911
2912
        if ($end !== false) {
2913
            $substr = substr($substr, 0, $end);
2914
        }
2915
2916
        if ($pointer !== null) {
2917
            $pointer += strlen($substr);
2918
        }
2919
2920
        $src    = $substr;
2921
        $substr = trim($substr);
2922
2923
        if (strtolower($substr) === 'false' || strtolower($substr) === 'no' || strtolower($substr) === 'off') {
2924
            if ($this->debug) {
2925
                echo 'BOOLEAN(FALSE) PARSED' . "\n";
2926
            }
2927
            $substr = 'false';
2928
            $type   = self::T_BOOL;
2929
        } elseif (strtolower($substr) === 'true' || strtolower($substr) === 'yes' || strtolower($substr) === 'on') {
2930
            if ($this->debug) {
2931
                echo 'BOOLEAN(TRUE) PARSED' . "\n";
2932
            }
2933
            $substr = 'true';
2934
            $type   = self::T_BOOL;
2935 View Code Duplication
        } elseif ($substr === 'null' || $substr === 'NULL') {
2936
            if ($this->debug) {
2937
                echo 'NULL PARSED' . "\n";
2938
            }
2939
            $substr = 'null';
2940
            $type   = self::T_NULL;
2941
        } elseif (is_numeric($substr)) {
2942
            $substr = (float)$substr;
2943
            if ((int)$substr == $substr) {
2944
                $substr = (int)$substr;
2945
            }
2946
            $type = self::T_NUMERIC;
2947
            if ($this->debug) {
2948
                echo 'NUMBER (' . $substr . ') PARSED' . "\n";
2949
            }
2950 View Code Duplication
        } elseif (preg_match('{^-?(\d+|\d*(\.\d+))\s*([/*%+-]\s*-?(\d+|\d*(\.\d+)))+$}', $substr)) {
2951
            if ($this->debug) {
2952
                echo 'SIMPLE MATH PARSED . "\n"';
2953
            }
2954
            $type   = self::T_MATH;
2955
            $substr = '(' . $substr . ')';
2956
        } elseif ($curBlock === 'condition' && array_search($substr, $breakChars, true) !== false) {
2957
            if ($this->debug) {
2958
                echo 'BREAKCHAR (' . $substr . ') PARSED' . "\n";
2959
            }
2960
            $type = self::T_BREAKCHAR;
2961
            //$substr = '"'.$substr.'"';
2962
        } else {
2963
            $substr = $this->replaceStringVars('\'' . str_replace('\'', '\\\'', $substr) . '\'', '\'', $curBlock);
2964
            $type   = self::T_UNQUOTED_STRING;
2965
            if ($this->debug) {
2966
                echo 'BLABBER (' . $substr . ') CASTED AS STRING' . "\n";
2967
            }
2968
        }
2969
2970
        if (is_array($parsingParams)) {
2971
            $parsingParams[] = array($substr, $src, $type);
2972
2973
            return $parsingParams;
2974
        } elseif ($curBlock === 'namedparam') {
2975
            return array($substr, $src, $type);
2976
        } elseif ($curBlock === 'expression') {
2977
            return $substr;
2978
        } else {
2979
            throw new Exception('Something went wrong');
2980
        }
2981
    }
2982
2983
    /**
2984
     * Replaces variables within a parsed string.
2985
     *
2986
     * @param string $string   the parsed string
2987
     * @param string $first    the first character parsed in the string, which is the string delimiter (' or ")
2988
     * @param string $curBlock the current parser-block being processed
2989
     *
2990
     * @return string the original string with variables replaced
2991
     */
2992
    protected function replaceStringVars($string, $first, $curBlock = '')
2993
    {
2994
        $pos = 0;
2995
        if ($this->debug) {
2996
            echo 'STRING VAR REPLACEMENT : ' . $string . "\n";
2997
        }
2998
        // replace vars
2999
        while (($pos = strpos($string, '$', $pos)) !== false) {
3000
            $prev = substr($string, $pos - 1, 1);
3001
            if ($prev === '\\') {
3002
                ++ $pos;
3003
                continue;
3004
            }
3005
3006
            $var = $this->parse($string, $pos, null, false, ($curBlock === 'modifier' ? 'modifier' : ($prev === '`' ? 'delimited_string' : 'string')));
3007
            $len = $var[0];
3008
            $var = $this->parse(str_replace('\\' . $first, $first, $string), $pos, null, false, ($curBlock === 'modifier' ? 'modifier' : ($prev === '`' ? 'delimited_string' : 'string')));
3009
3010
            if ($prev === '`' && substr($string, $pos + $len, 1) === '`') {
3011
                $string = substr_replace($string, $first . '.' . $var[1] . '.' . $first, $pos - 1, $len + 2);
3012
            } else {
3013
                $string = substr_replace($string, $first . '.' . $var[1] . '.' . $first, $pos, $len);
3014
            }
3015
            $pos += strlen($var[1]) + 2;
3016
            if ($this->debug) {
3017
                echo 'STRING VAR REPLACEMENT DONE : ' . $string . "\n";
3018
            }
3019
        }
3020
3021
        // handle modifiers
3022
        // TODO Obsolete?
3023
        $string = preg_replace_callback(
3024
            '#("|\')\.(.+?)\.\1((?:\|(?:@?[a-z0-9_]+(?:(?::("|\').+?\4|:[^`]*))*))+)#i', array(
3025
            $this,
3026
            'replaceModifiers'
3027
            ), $string
3028
        );
3029
3030
        // replace escaped dollar operators by unescaped ones if required
3031
        if ($first === "'") {
3032
            $string = str_replace('\\$', '$', $string);
3033
        }
3034
3035
        return $string;
3036
    }
3037
3038
    /**
3039
     * Replaces the modifiers applied to a string or a variable.
3040
     *
3041
     * @param array  $m        the regex matches that must be array(1=>"double or single quotes enclosing a string,
3042
     *                         when applicable", 2=>"the string or var", 3=>"the modifiers matched")
3043
     * @param string $curBlock the current parser-block being processed
3044
     * @param null   $pointer
3045
     *
3046
     * @return string the input enclosed with various function calls according to the modifiers found
3047
     * @throws CompilationException
3048
     * @throws Exception
3049
     */
3050
    protected function replaceModifiers(array $m, $curBlock = null, &$pointer = null)
3051
    {
3052
        if ($this->debug) {
3053
            echo 'PARSING MODIFIERS : ' . $m[3] . "\n";
3054
        }
3055
3056
        if ($pointer !== null) {
3057
            $pointer += strlen($m[3]);
3058
        }
3059
        // remove first pipe
3060
        $cmdstrsrc = substr($m[3], 1);
3061
        // remove last quote if present
3062
        if (substr($cmdstrsrc, - 1, 1) === $m[1]) {
3063
            $cmdstrsrc = substr($cmdstrsrc, 0, - 1);
3064
            $add       = $m[1];
3065
        }
3066
3067
        $output = $m[2];
3068
3069
        $continue = true;
3070
        while (strlen($cmdstrsrc) > 0 && $continue) {
3071
            if ($cmdstrsrc[0] === '|') {
3072
                $cmdstrsrc = substr($cmdstrsrc, 1);
3073
                continue;
3074
            }
3075
            if ($cmdstrsrc[0] === ' ' || $cmdstrsrc[0] === ';' || substr($cmdstrsrc, 0, strlen($this->rd)) === $this->rd) {
3076
                if ($this->debug) {
3077
                    echo 'MODIFIER PARSING ENDED, RIGHT DELIMITER or ";" FOUND' . "\n";
3078
                }
3079
                $continue = false;
3080
                if ($pointer !== null) {
3081
                    $pointer -= strlen($cmdstrsrc);
3082
                }
3083
                break;
3084
            }
3085
            $cmdstr   = $cmdstrsrc;
3086
            $paramsep = ':';
3087 View Code Duplication
            if (!preg_match('/^(@{0,2}[a-z_][a-z0-9_]*)(:)?/i', $cmdstr, $match)) {
3088
                throw new CompilationException($this, 'Invalid modifier name, started with : ' . substr($cmdstr, 0, 10));
3089
            }
3090
            $paramspos = !empty($match[2]) ? strlen($match[1]) : false;
3091
            $func      = $match[1];
3092
3093
            $state = 0;
3094
            if ($paramspos === false) {
3095
                $cmdstrsrc = substr($cmdstrsrc, strlen($func));
3096
                $params    = array();
3097
                if ($this->debug) {
3098
                    echo 'MODIFIER (' . $func . ') CALLED WITH NO PARAMS' . "\n";
3099
                }
3100
            } else {
3101
                $paramstr = substr($cmdstr, $paramspos + 1);
3102 View Code Duplication
                if (substr($paramstr, - 1, 1) === $paramsep) {
3103
                    $paramstr = substr($paramstr, 0, - 1);
3104
                }
3105
3106
                $ptr    = 0;
3107
                $params = array();
3108
                while ($ptr < strlen($paramstr)) {
3109
                    if ($this->debug) {
3110
                        echo 'MODIFIER (' . $func . ') START PARAM PARSING WITH POINTER AT ' . $ptr . "\n";
3111
                    }
3112
                    if ($this->debug) {
3113
                        echo $paramstr . '--' . $ptr . '--' . strlen($paramstr) . '--modifier' . "\n";
3114
                    }
3115
                    $params = $this->parse($paramstr, $ptr, strlen($paramstr), $params, 'modifier', $ptr);
3116
                    if ($this->debug) {
3117
                        echo 'PARAM PARSED, POINTER AT ' . $ptr . "\n";
3118
                    }
3119
3120
                    if ($ptr >= strlen($paramstr)) {
3121
                        if ($this->debug) {
3122
                            echo 'PARAM PARSING ENDED, PARAM STRING CONSUMED' . "\n";
3123
                        }
3124
                        break;
3125
                    }
3126
3127
                    if ($paramstr[$ptr] === ' ' || $paramstr[$ptr] === '|' || $paramstr[$ptr] === ';' || substr($paramstr, $ptr, strlen($this->rd)) === $this->rd) {
3128
                        if ($this->debug) {
3129
                            echo 'PARAM PARSING ENDED, " ", "|", RIGHT DELIMITER or ";" FOUND, POINTER AT ' . $ptr . "\n";
3130
                        }
3131
                        if ($paramstr[$ptr] !== '|') {
3132
                            $continue = false;
3133
                            if ($pointer !== null) {
3134
                                $pointer -= strlen($paramstr) - $ptr;
3135
                            }
3136
                        }
3137
                        ++ $ptr;
3138
                        break;
3139
                    }
3140
                    if ($ptr < strlen($paramstr) && $paramstr[$ptr] === ':') {
3141
                        ++ $ptr;
3142
                    }
3143
                }
3144
                $cmdstrsrc = substr($cmdstrsrc, strlen($func) + 1 + $ptr);
3145
                foreach ($params as $k => $p) {
3146
                    if (is_array($p) && is_array($p[1])) {
3147
                        $state |= 2;
3148
                    } else {
3149
                        if (($state & 2) && preg_match('#^(["\'])(.+?)\1$#', $p[0], $m)) {
3150
                            $params[$k] = array($m[2], array('true', 'true'));
3151
                        } else {
3152
                            if ($state & 2) {
3153
                                throw new CompilationException($this, 'You can not use an unnamed parameter after a named one');
3154
                            }
3155
                            $state |= 1;
3156
                        }
3157
                    }
3158
                }
3159
            }
3160
3161
            // check if we must use array_map with this plugin or not
3162
            $mapped = false;
3163
            if (substr($func, 0, 1) === '@') {
3164
                $func   = substr($func, 1);
3165
                $mapped = true;
3166
            }
3167
3168
            $pluginType = $this->getPluginType($func);
3169
3170
            if ($state & 2) {
3171
                array_unshift($params, array('value', is_array($output) ? $output : array($output, $output)));
3172
            } else {
3173
                array_unshift($params, is_array($output) ? $output : array($output, $output));
3174
            }
3175
3176
            if ($pluginType & Core::NATIVE_PLUGIN) {
3177
                $params = $this->mapParams($params, null, $state);
3178
3179
                $params = $params['*'][0];
3180
3181
                $params = self::implode_r($params);
3182
3183 View Code Duplication
                if ($mapped) {
3184
                    $output = '$this->arrayMap(\'' . $func . '\', array(' . $params . '))';
3185
                } else {
3186
                    $output = $func . '(' . $params . ')';
3187
                }
3188
            } elseif ($pluginType & Core::PROXY_PLUGIN) {
3189
                $params = $this->mapParams($params, $this->getDwoo()->getPluginProxy()->getCallback($func), $state);
3190
                foreach ($params as &$p) {
3191
                    $p = $p[0];
3192
                }
3193
                $output = call_user_func(array($this->getDwoo()->getPluginProxy(), 'getCode'), $func, $params);
3194
            } elseif ($pluginType & Core::SMARTY_MODIFIER) {
3195
                $params = $this->mapParams($params, null, $state);
3196
                $params = $params['*'][0];
3197
3198
                $params = self::implode_r($params);
3199
3200
                if ($pluginType & Core::CUSTOM_PLUGIN) {
3201
                    $callback = $this->customPlugins[$func]['callback'];
3202
                    if (is_array($callback)) {
3203
                        if (is_object($callback[0])) {
3204
                            $output = ($mapped ? '$this->arrayMap' : 'call_user_func_array') . '(array($this->plugins[\'' . $func . '\'][\'callback\'][0], \'' . $callback[1] . '\'), array(' . $params . '))';
3205
                        } else {
3206
                            $output = ($mapped ? '$this->arrayMap' : 'call_user_func_array') . '(array(\'' . $callback[0] . '\', \'' . $callback[1] . '\'), array(' . $params . '))';
3207
                        }
3208
                    } elseif ($mapped) {
3209
                        $output = '$this->arrayMap(\'' . $callback . '\', array(' . $params . '))';
3210
                    } else {
3211
                        $output = $callback . '(' . $params . ')';
3212
                    }
3213
                } elseif ($mapped) {
3214
                    $output = '$this->arrayMap(\'smarty_modifier_' . $func . '\', array(' . $params . '))';
3215
                } else {
3216
                    $output = 'smarty_modifier_' . $func . '(' . $params . ')';
3217
                }
3218
            } else {
3219
                if ($pluginType & Core::CUSTOM_PLUGIN) {
3220
                    $callback   = $this->customPlugins[$func]['callback'];
3221
                    $pluginName = $callback;
3222
                } else {
3223
                    if (class_exists('Plugin' . Core::toCamelCase($func)) !== false || function_exists('Plugin' .
3224
                            Core::toCamelCase($func) . (($pluginType & Core::COMPILABLE_PLUGIN) ? 'Compile' : ''))
3225
                        !== false) {
3226
                        $pluginName = 'Plugin' . Core::toCamelCase($func);
3227
                    } else {
3228
                        $pluginName = Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . Core::toCamelCase($func);
3229
                    }
3230
                    if ($pluginType & Core::CLASS_PLUGIN) {
3231
                        $callback = array($pluginName, ($pluginType & Core::COMPILABLE_PLUGIN) ? 'compile' : 'process');
3232
                    } else {
3233
                        $callback = $pluginName . (($pluginType & Core::COMPILABLE_PLUGIN) ? 'Compile' : '');
3234
                    }
3235
                }
3236
                $params = $this->mapParams($params, $callback, $state);
3237
3238
                foreach ($params as &$p) {
3239
                    $p = $p[0];
3240
                }
3241
3242
                // Only for PHP function, who is not a PHP class
3243
                if ($pluginType & Core::FUNC_PLUGIN && !($pluginType & Core::CLASS_PLUGIN)) {
3244
                    if ($pluginType & Core::COMPILABLE_PLUGIN) {
3245
                        if ($mapped) {
3246
                            throw new CompilationException($this, 'The @ operator can not be used on compiled plugins.');
3247
                        }
3248
                        if ($pluginType & Core::CUSTOM_PLUGIN) {
3249
                            $funcCompiler = $this->customPlugins[$func]['callback'];
3250
                        } else {
3251
                            if (function_exists('Plugin' . Core::toCamelCase($func) . 'Compile') !== false) {
3252
                                $funcCompiler = 'Plugin' . Core::toCamelCase($func) . 'Compile';
3253
                            } else {
3254
                                $funcCompiler = Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . Core::toCamelCase($func) .
3255
                                    'Compile';
3256
                            }
3257
                        }
3258
                        array_unshift($params, $this);
3259
                        $output = call_user_func_array($funcCompiler, $params);
3260
                    } else {
3261
                        array_unshift($params, '$this');
3262
3263
                        $params = self::implode_r($params);
3264 View Code Duplication
                        if ($mapped) {
3265
                            $output = '$this->arrayMap(\'' . $pluginName . '\', array(' . $params . '))';
3266
                        } else {
3267
                            $output = $pluginName . '(' . $params . ')';
3268
                        }
3269
                    }
3270
                } else {
3271
                    if ($pluginType & Core::COMPILABLE_PLUGIN) {
3272
                        if ($mapped) {
3273
                            throw new CompilationException($this, 'The @ operator can not be used on compiled plugins.');
3274
                        }
3275
                        if ($pluginType & Core::CUSTOM_PLUGIN) {
3276
                            $callback = $this->customPlugins[$func]['callback'];
3277
                            if (!is_array($callback)) {
3278
                                if (!method_exists($callback, 'compile')) {
3279
                                    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');
3280
                                }
3281
                                if (($ref = new ReflectionMethod($callback, 'compile')) && $ref->isStatic()) {
3282
                                    $funcCompiler = array($callback, 'compile');
3283
                                } else {
3284
                                    $funcCompiler = array(new $callback(), 'compile');
3285
                                }
3286
                            } else {
3287
                                $funcCompiler = $callback;
3288
                            }
3289
                        } else {
3290
                            if (class_exists('Plugin' . Core::toCamelCase($func)) !== false) {
3291
                                $funcCompiler = array('Plugin' . Core::toCamelCase($func), 'compile');
3292
                            } else {
3293
                                $funcCompiler = array(
3294
                                    Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . Core::toCamelCase($func),
3295
                                    'compile'
3296
                                );
3297
                            }
3298
                            array_unshift($params, $this);
3299
                        }
3300
                        $output = call_user_func_array($funcCompiler, $params);
3301
                    } else {
3302
                        $params = self::implode_r($params);
3303
3304
                        if ($pluginType & Core::CUSTOM_PLUGIN) {
3305
                            if (is_object($callback[0])) {
3306
                                $output = ($mapped ? '$this->arrayMap' : 'call_user_func_array') . '(array($this->plugins[\'' . $func . '\'][\'callback\'][0], \'' . $callback[1] . '\'), array(' . $params . '))';
3307
                            } else {
3308
                                $output = ($mapped ? '$this->arrayMap' : 'call_user_func_array') . '(array(\'' . $callback[0] . '\', \'' . $callback[1] . '\'), array(' . $params . '))';
3309
                            }
3310 View Code Duplication
                        } elseif ($mapped) {
3311
                            $output = '$this->arrayMap(array($this->getObjectPlugin(\''.
3312
                                Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . Core::toCamelCase($func) . '\'), 
3313
                            \'process\'), array(' . $params . '))';
3314
                        } else {
3315
                            if (class_exists('Plugin' . Core::toCamelCase($func)) !== false) {
3316
                                $output = '$this->classCall(\'Plugin' . Core::toCamelCase($func) . '\', array(' . $params . '))';
3317 View Code Duplication
                            } elseif (class_exists(Core::NAMESPACE_PLUGINS_BLOCKS . 'Plugin' . Core::toCamelCase($func)) !== false) {
3318
                                $output = '$this->classCall(\'' . Core::NAMESPACE_PLUGINS_BLOCKS . 'Plugin' . $func . '\', array(' . $params . '))';
3319
                            } elseif (class_exists(Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . Core::toCamelCase($func)) !== false) {
3320
                                $output = '$this->classCall(\'' . Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . $func . '\', array(' . $params . '))';
3321
                            } else {
3322
                                $output = '$this->classCall(\'' . $func . '\', array(' . $params . '))';
3323
                            }
3324
                        }
3325
                    }
3326
                }
3327
            }
3328
        }
3329
3330
        if ($curBlock === 'namedparam') {
3331
            return array($output, $output);
3332
        } elseif ($curBlock === 'var' || $m[1] === null) {
3333
            return $output;
3334
        } elseif ($curBlock === 'string' || $curBlock === 'root') {
3335
            return $m[1] . '.' . $output . '.' . $m[1] . (isset($add) ? $add : null);
3336
        }
3337
3338
        return '';
3339
    }
3340
3341
    /**
3342
     * Recursively implodes an array in a similar manner as var_export() does but with some tweaks
3343
     * to handle pre-compiled values and the fact that we do not need to enclose everything with
3344
     * "array" and do not require top-level keys to be displayed.
3345
     *
3346
     * @param array $params        the array to implode
3347
     * @param bool  $recursiveCall if set to true, the function outputs key names for the top level
3348
     *
3349
     * @return string the imploded array
3350
     */
3351
    public static function implode_r(array $params, $recursiveCall = false)
3352
    {
3353
        $out = '';
3354
        foreach ($params as $k => $p) {
3355
            if (is_array($p)) {
3356
                $out2 = 'array(';
3357
                foreach ($p as $k2 => $v) {
3358
                    $out2 .= var_export($k2, true) . ' => ' . (is_array($v) ? 'array(' . self::implode_r($v, true) . ')' : $v) . ', ';
3359
                }
3360
                $p = rtrim($out2, ', ') . ')';
3361
            }
3362
            if ($recursiveCall) {
3363
                $out .= var_export($k, true) . ' => ' . $p . ', ';
3364
            } else {
3365
                $out .= $p . ', ';
3366
            }
3367
        }
3368
3369
        return rtrim($out, ', ');
3370
    }
3371
3372
    /**
3373
     * Returns the plugin type of a plugin and adds it to the used plugins array if required.
3374
     *
3375
     * @param string $name plugin name, as found in the template
3376
     *
3377
     * @return int type as a multi bit flag composed of the Dwoo plugin types constants
3378
     * @throws Exception
3379
     * @throws SecurityException
3380
     * @throws Exception
3381
     */
3382
    protected function getPluginType($name)
3383
    {
3384
        $pluginType = - 1;
3385
3386
        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)) {
3387
            $phpFunc = true;
3388
        } elseif ($this->securityPolicy !== null && function_exists($name) && array_key_exists(strtolower($name), $this->securityPolicy->getAllowedPhpFunctions()) === false) {
3389
            throw new SecurityException('Call to a disallowed php function : ' . $name);
3390
        }
3391
3392
        while ($pluginType <= 0) {
3393
            // Template plugin compilable
3394
            if (isset($this->templatePlugins[$name])) {
3395
                $pluginType = Core::TEMPLATE_PLUGIN | Core::COMPILABLE_PLUGIN;
3396
            } // Custom plugin
3397
            elseif (isset($this->customPlugins[$name])) {
3398
                $pluginType = $this->customPlugins[$name]['type'] | Core::CUSTOM_PLUGIN;
3399
            } // Class blocks plugin
3400
            elseif (class_exists(Core::NAMESPACE_PLUGINS_BLOCKS . 'Plugin' . Core::toCamelCase($name)) !== false) {
3401
                $pluginType = Core::CLASS_PLUGIN;
3402
                if (is_subclass_of(Core::NAMESPACE_PLUGINS_BLOCKS . 'Plugin' . Core::toCamelCase($name), 'Dwoo\Block\Plugin')) {
3403
                    $pluginType += Core::BLOCK_PLUGIN;
3404
                }
3405
                $interfaces = class_implements(Core::NAMESPACE_PLUGINS_BLOCKS . 'Plugin' . Core::toCamelCase($name));
3406 View Code Duplication
                if (in_array('Dwoo\ICompilable', $interfaces) !== false || in_array('Dwoo\ICompilable\Block', $interfaces) !== false) {
3407
                    $pluginType |= Core::COMPILABLE_PLUGIN;
3408
                }
3409
            } // Class functions plugin
3410
            elseif (class_exists(Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . Core::toCamelCase($name)) !== false) {
3411
                $pluginType = Core::FUNC_PLUGIN + Core::CLASS_PLUGIN;
3412
                $interfaces = class_implements(Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . Core::toCamelCase($name));
3413 View Code Duplication
                if (in_array('Dwoo\ICompilable', $interfaces) !== false || in_array('Dwoo\ICompilable\Block', $interfaces) !== false) {
3414
                    $pluginType |= Core::COMPILABLE_PLUGIN;
3415
                }
3416
            } // Class without namespace
3417
            elseif (class_exists('Plugin' . Core::toCamelCase($name)) !== false) {
3418
                $pluginType = Core::CLASS_PLUGIN;
3419
                $interfaces = class_implements('Plugin' . Core::toCamelCase($name));
3420 View Code Duplication
                if (in_array('Dwoo\ICompilable', $interfaces) !== false || in_array('Dwoo\ICompilable\Block', $interfaces) !== false) {
3421
                    $pluginType |= Core::COMPILABLE_PLUGIN;
3422
                }
3423
            } // Function plugin (with/without namespaces)
3424
            elseif (function_exists(Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . Core::toCamelCase ($name)) !==
3425
                false || function_exists('Plugin' . Core::toCamelCase($name)) !== false) {
3426
                $pluginType = Core::FUNC_PLUGIN;
3427
            } // Function plugin compile (with/without namespaces)
3428
            elseif (function_exists(Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . Core::toCamelCase($name) .
3429
                    'Compile') !== false || function_exists('Plugin' . Core::toCamelCase($name) . 'Compile') !==
3430
                false) {
3431
                $pluginType = Core::FUNC_PLUGIN | Core::COMPILABLE_PLUGIN;
3432
            } // Helper plugin class compile
3433 View Code Duplication
            elseif (class_exists(Core::NAMESPACE_PLUGINS_HELPERS . 'Plugin' . Core::toCamelCase($name)) !== false) {
3434
                $pluginType = Core::CLASS_PLUGIN | Core::COMPILABLE_PLUGIN;
3435
            } // Helper plugin function compile
3436 View Code Duplication
            elseif (function_exists(Core::NAMESPACE_PLUGINS_HELPERS . 'Plugin' . Core::toCamelCase($name) . 'Compile') !== false) {
3437
                $pluginType = Core::FUNC_PLUGIN | Core::COMPILABLE_PLUGIN;
3438
            } // Smarty modifier
3439
            elseif (function_exists('smarty_modifier_' . $name) !== false) {
3440
                $pluginType = Core::SMARTY_MODIFIER;
3441
            } // Smarty function
3442
            elseif (function_exists('smarty_function_' . $name) !== false) {
3443
                $pluginType = Core::SMARTY_FUNCTION;
3444
            } // Smarty block
3445
            elseif (function_exists('smarty_block_' . $name) !== false) {
3446
                $pluginType = Core::SMARTY_BLOCK;
3447
            } // Everything else
3448
            else {
3449
                if ($pluginType === - 1) {
3450
                    try {
3451
                        $this->getDwoo()->getLoader()->loadPlugin('Plugin' . Core::toCamelCase($name));
3452
                    }
3453
                    catch (Exception $e) {
3454
                        if (isset($phpFunc)) {
3455
                            $pluginType = Core::NATIVE_PLUGIN;
3456
                        } elseif (is_object($this->getDwoo()->getPluginProxy()) && $this->getDwoo()->getPluginProxy()->handles($name)) {
3457
                            $pluginType = Core::PROXY_PLUGIN;
3458
                            break;
3459
                        } else {
3460
                            throw $e;
3461
                        }
3462
                    }
3463
                } else {
3464
                    throw new Exception('Plugin "' . $name . '" could not be found, type:' . $pluginType);
3465
                }
3466
                ++ $pluginType;
3467
            }
3468
        }
3469
3470
        if (($pluginType & Core::COMPILABLE_PLUGIN) === 0 && ($pluginType & Core::NATIVE_PLUGIN) === 0 && ($pluginType & Core::PROXY_PLUGIN) === 0) {
3471
            $this->addUsedPlugin(Core::toCamelCase($name), $pluginType);
3472
        }
3473
3474
        return $pluginType;
3475
    }
3476
3477
    /**
3478
     * Allows a plugin to load another one at compile time, this will also mark
3479
     * it as used by this template so it will be loaded at runtime (which can be
3480
     * useful for compiled plugins that rely on another plugin when their compiled
3481
     * code runs).
3482
     *
3483
     * @param string $name the plugin name
3484
     *
3485
     * @return void
3486
     */
3487
    public function loadPlugin($name)
3488
    {
3489
        $this->getPluginType($name);
3490
    }
3491
3492
    /**
3493
     * Runs htmlentities over the matched <?php ?> blocks when the security policy enforces that.
3494
     *
3495
     * @param array $match matched php block
3496
     *
3497
     * @return string the htmlentities-converted string
3498
     */
3499
    protected function phpTagEncodingHelper($match)
3500
    {
3501
        return htmlspecialchars($match[0]);
3502
    }
3503
3504
    /**
3505
     * Maps the parameters received from the template onto the parameters required by the given callback.
3506
     *
3507
     * @param array    $params   the array of parameters
3508
     * @param callback $callback the function or method to reflect on to find out the required parameters
3509
     * @param int      $callType the type of call in the template, 0 = no params, 1 = php-style call, 2 = named
3510
     *                           parameters call
3511
     * @param array    $map      the parameter map to use, if not provided it will be built from the callback
3512
     *
3513
     * @return array parameters sorted in the correct order with missing optional parameters filled
3514
     * @throws CompilationException
3515
     */
3516
    protected function mapParams(array $params, $callback, $callType = 2, $map = null)
3517
    {
3518
        if (!$map) {
3519
            $map = $this->getParamMap($callback);
3520
        }
3521
3522
        $paramlist = array();
3523
3524
        // transforms the parameter array from (x=>array('paramname'=>array(values))) to (paramname=>array(values))
3525
        $ps = array();
3526
        foreach ($params as $p) {
3527
            if (is_array($p[1])) {
3528
                $ps[$p[0]] = $p[1];
3529
            } else {
3530
                $ps[] = $p;
3531
            }
3532
        }
3533
3534
        // loops over the param map and assigns values from the template or default value for unset optional params
3535
        foreach ($map as $k => $v){
3536
            if ($v[0] === '*') {
3537
                // "rest" array parameter, fill every remaining params in it and then break
3538
                if (count($ps) === 0) {
3539
                    if ($v[1] === false) {
3540
                        throw new CompilationException(
3541
                            $this, 'Rest argument missing for ' . str_replace(
3542
                                array(
3543
                                    Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin',
3544
                                'Compile'
3545
                                ), '', (is_array($callback) ? $callback[0] : $callback)
3546
                            )
3547
                        );
3548
                    } else {
3549
                        break;
3550
                    }
3551
                }
3552
                $tmp  = array();
3553
                $tmp2 = array();
3554
                $tmp3 = array();
3555
                foreach ($ps as $i => $p) {
3556
                    $tmp[$i]  = $p[0];
3557
                    $tmp2[$i] = $p[1];
3558
                    $tmp3[$i] = isset($p[2]) ? $p[2] : 0;
3559
                    unset($ps[$i]);
3560
                }
3561
                $paramlist[$v[0]] = array($tmp, $tmp2, $tmp3);
3562
                unset($tmp, $tmp2, $i, $p);
3563
                break;
3564
            } elseif (isset($ps[$v[0]])) {
3565
                // parameter is defined as named param
3566
                $paramlist[$v[0]] = $ps[$v[0]];
3567
                unset($ps[$v[0]]);
3568
            } elseif (isset($ps[$k])) {
3569
                // parameter is defined as ordered param
3570
                $paramlist[$v[0]] = $ps[$k];
3571
                unset($ps[$k]);
3572
            } elseif ($v[1] === false) {
3573
                // parameter is not defined and not optional, throw error
3574
                if (is_array($callback)) {
3575
                    if (is_object($callback[0])) {
3576
                        $name = get_class($callback[0]) . '::' . $callback[1];
3577
                    } else {
3578
                        $name = $callback[0];
3579
                    }
3580
                } else {
3581
                    $name = $callback;
3582
                }
3583
3584
                throw new CompilationException(
3585
                    $this, 'Argument ' . $k . '/' . $v[0] . ' missing for ' . str_replace(
3586
                        array(
3587
                            Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin',
3588
                        'Compile'
3589
                        ), '', $name
3590
                    )
3591
                );
3592
            } elseif ($v[2] === null) {
3593
                // enforce lowercased null if default value is null (php outputs NULL with var export)
3594
                $paramlist[$v[0]] = array('null', null, self::T_NULL);
3595
            } else {
3596
                // outputs default value with var_export
3597
                $paramlist[$v[0]] = array(var_export($v[2], true), $v[2]);
3598
            }
3599
        }
3600
3601
        if (count($ps)) {
3602
            foreach ($ps as $i => $p) {
3603
                array_push($paramlist, $p);
3604
            }
3605
        }
3606
3607
        return $paramlist;
3608
    }
3609
3610
    /**
3611
     * Returns the parameter map of the given callback, it filters out entries typed as Dwoo and Compiler and turns the
3612
     * rest parameter into a "*".
3613
     *
3614
     * @param callback $callback the function/method to reflect on
3615
     *
3616
     * @return array processed parameter map
3617
     */
3618
    protected function getParamMap($callback)
3619
    {
3620
        if (is_null($callback)) {
3621
            return array(array('*', true));
3622
        }
3623
        if (is_array($callback)) {
3624
            $ref = new ReflectionMethod($callback[0], $callback[1]);
3625
        } else {
3626
            $ref = new ReflectionFunction($callback);
3627
        }
3628
3629
        $out = array();
3630
        foreach ($ref->getParameters() as $param) {
3631
            if (($class = $param->getClass()) !== null && $class->name === 'Dwoo\Core') {
3632
                continue;
3633
            }
3634
            if (($class = $param->getClass()) !== null && $class->name === 'Dwoo\Compiler') {
3635
                continue;
3636
            }
3637
            if ($param->getName() === 'rest' && $param->isArray() === true) {
3638
                $out[] = array('*', $param->isOptional(), null);
3639
                continue;
3640
            }
3641
            $out[] = array(
3642
                $param->getName(),
3643
                $param->isOptional(),
3644
                $param->isOptional() ? $param->getDefaultValue() : null
3645
            );
3646
        }
3647
3648
        return $out;
3649
    }
3650
3651
    /**
3652
     * Returns a default instance of this compiler, used by default by all Dwoo templates that do not have a
3653
     * specific compiler assigned and when you do not override the default compiler factory function.
3654
     *
3655
     * @see    Core::setDefaultCompilerFactory()
3656
     * @return Compiler
3657
     */
3658
    public static function compilerFactory()
3659
    {
3660
        if (self::$instance === null) {
3661
            self::$instance = new self();
3662
        }
3663
3664
        return self::$instance;
3665
    }
3666
}
3667