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

lib/Dwoo/Compiler.php (14 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 LGPLv3
12
 * @version   1.4.0
13
 * @date      2017-03-16
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
        }
1212
1213
        return $output;
1214
    }
1215
1216
    /**
1217
     * Returns a reference to the first block of the given type encountered and
1218
     * optionally closes all blocks until it finds it
1219
     * this is mainly used by {else} plugins to close everything that was opened
1220
     * between their parent and themselves.
1221
     *
1222
     * @param string $type       the block type (name)
1223
     * @param bool   $closeAlong whether to close all blocks encountered while going down the block stack or not
1224
     *
1225
     * @return mixed &array the array is as such: array('type'=>pluginName, 'params'=>parameter array,
1226
     *               'custom'=>bool defining whether it's a custom plugin or not, for internal use)
1227
     * @throws CompilationException
1228
     */
1229
    public function &findBlock($type, $closeAlong = false)
1230
    {
1231
        if ($closeAlong === true) {
1232 View Code Duplication
            while ($b = end($this->stack)) {
1233
                if ($b['type'] === $type) {
1234
                    return $this->stack[key($this->stack)];
1235
                }
1236
                $this->push($this->removeTopBlock(), 0);
1237
            }
1238
        } else {
1239
            end($this->stack);
1240 View Code Duplication
            while ($b = current($this->stack)) {
1241
                if ($b['type'] === $type) {
1242
                    return $this->stack[key($this->stack)];
1243
                }
1244
                prev($this->stack);
1245
            }
1246
        }
1247
1248
        throw new CompilationException($this, 'A parent block of type "' . $type . '" is required and can not be found');
1249
    }
1250
1251
    /**
1252
     * Returns a reference to the current block array.
1253
     *
1254
     * @return array the array is as such: array('type'=>pluginName, 'params'=>parameter array,
1255
     *                'custom'=>bool defining whether it's a custom plugin or not, for internal use)
1256
     */
1257
    public function &getCurrentBlock()
1258
    {
1259
        return $this->curBlock;
1260
    }
1261
1262
    /**
1263
     * Removes the block at the top of the stack and calls its postProcessing() method.
1264
     *
1265
     * @return string the postProcessing() method's output
1266
     * @throws CompilationException
1267
     */
1268
    public function removeTopBlock()
1269
    {
1270
        if ($this->debug) {
1271
            echo 'Compiler::' . __FUNCTION__ . "\n";
1272
        }
1273
1274
        $o = array_pop($this->stack);
1275
        if ($o === null) {
1276
            throw new CompilationException($this, 'Syntax malformation, a block of unknown type was closed but was not opened.');
1277
        }
1278 View Code Duplication
        if ($o['custom']) {
1279
            $class = $o['class'];
1280
        } else {
1281
            $class = Core::NAMESPACE_PLUGINS_BLOCKS . 'Plugin' . Core::toCamelCase($o['type']);
1282
        }
1283
1284
        $this->curBlock = &$this->stack[count($this->stack) - 1];
1285
1286
        return call_user_func(array($class, 'postProcessing'), $this, $o['params'], '', '', $o['buffer']);
1287
    }
1288
1289
    /**
1290
     * Returns the compiled parameters (for example a variable's compiled parameter will be "$this->scope['key']") out
1291
     * of the given parameter array.
1292
     *
1293
     * @param array $params parameter array
1294
     *
1295
     * @return array filtered parameters
1296
     */
1297 View Code Duplication
    public function getCompiledParams(array $params)
1298
    {
1299
        foreach ($params as $k => $p) {
1300
            if (is_array($p)) {
1301
                $params[$k] = $p[0];
1302
            }
1303
        }
1304
1305
        return $params;
1306
    }
1307
1308
    /**
1309
     * Returns the real parameters (for example a variable's real parameter will be its key, etc) out of the given
1310
     * parameter array.
1311
     *
1312
     * @param array $params parameter array
1313
     *
1314
     * @return array filtered parameters
1315
     */
1316 View Code Duplication
    public function getRealParams(array $params)
1317
    {
1318
        foreach ($params as $k => $p) {
1319
            if (is_array($p)) {
1320
                $params[$k] = $p[1];
1321
            }
1322
        }
1323
1324
        return $params;
1325
    }
1326
1327
    /**
1328
     * Returns the token of each parameter out of the given parameter array.
1329
     *
1330
     * @param array $params parameter array
1331
     *
1332
     * @return array tokens
1333
     */
1334 View Code Duplication
    public function getParamTokens(array $params)
1335
    {
1336
        foreach ($params as $k => $p) {
1337
            if (is_array($p)) {
1338
                $params[$k] = isset($p[2]) ? $p[2] : 0;
1339
            }
1340
        }
1341
1342
        return $params;
1343
    }
1344
1345
    /**
1346
     * Entry point of the parser, it redirects calls to other parse* functions.
1347
     *
1348
     * @param string $in            the string within which we must parse something
1349
     * @param int    $from          the starting offset of the parsed area
1350
     * @param int    $to            the ending offset of the parsed area
1351
     * @param mixed  $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by
1352
     *                              default
1353
     * @param string $curBlock      the current parser-block being processed
1354
     * @param mixed  $pointer       a reference to a pointer that will be increased by the amount of characters parsed,
1355
     *                              or null by default
1356
     *
1357
     * @return string parsed values
1358
     * @throws CompilationException
1359
     */
1360
    protected function parse($in, $from, $to, $parsingParams = false, $curBlock = '', &$pointer = null)
1361
    {
1362
        if ($this->debug) {
1363
            echo 'Compiler::' . __FUNCTION__ . "\n";
1364
        }
1365
1366
        if ($to === null) {
1367
            $to = strlen($in);
1368
        }
1369
        $first = substr($in, $from, 1);
1370
1371
        if ($first === false) {
1372
            throw new CompilationException($this, 'Unexpected EOF, a template tag was not closed');
1373
        }
1374
1375
        while ($first === ' ' || $first === "\n" || $first === "\t" || $first === "\r") {
1376 View Code Duplication
            if ($curBlock === 'root' && substr($in, $from, strlen($this->rd)) === $this->rd) {
1377
                // end template tag
1378
                $pointer += strlen($this->rd);
1379
                if ($this->debug) {
1380
                    echo 'TEMPLATE PARSING ENDED' . "\n";
1381
                }
1382
1383
                return false;
1384
            }
1385
            ++ $from;
1386
            if ($pointer !== null) {
1387
                ++ $pointer;
1388
            }
1389
            if ($from >= $to) {
1390
                if (is_array($parsingParams)) {
1391
                    return $parsingParams;
1392
                } else {
1393
                    return '';
1394
                }
1395
            }
1396
            $first = $in[$from];
1397
        }
1398
1399
        $substr = substr($in, $from, $to - $from);
1400
1401
        if ($this->debug) {
1402
            echo 'PARSE CALL : PARSING "' . htmlentities(substr($in, $from, min($to - $from, 50))) . (($to - $from) > 50 ? '...' : '') . '" @ ' . $from . ':' . $to . ' in ' . $curBlock . ' : pointer=' . $pointer . "\n";
1403
        }
1404
        $parsed = '';
1405
1406
        if ($curBlock === 'root' && $first === '*') {
1407
            $src      = $this->getTemplateSource();
1408
            $startpos = $this->getPointer() - strlen($this->ld);
1409
            if (substr($src, $startpos, strlen($this->ld)) === $this->ld) {
1410
                if ($startpos > 0) {
1411
                    do {
1412
                        $char = substr($src, -- $startpos, 1);
1413
                        if ($char == "\n") {
1414
                            ++ $startpos;
1415
                            $whitespaceStart = true;
1416
                            break;
1417
                        }
1418
                    }
1419
                    while ($startpos > 0 && ($char == ' ' || $char == "\t"));
1420
                }
1421
1422
                if (!isset($whitespaceStart)) {
1423
                    $startpos = $this->getPointer();
1424
                } else {
1425
                    $pointer -= $this->getPointer() - $startpos;
1426
                }
1427
1428
                if ($this->allowNestedComments && strpos($src, $this->ld . '*', $this->getPointer()) !== false) {
1429
                    $comOpen  = $this->ld . '*';
1430
                    $comClose = '*' . $this->rd;
1431
                    $level    = 1;
1432
                    $ptr      = $this->getPointer();
1433
1434
                    while ($level > 0 && $ptr < strlen($src)) {
1435
                        $open  = strpos($src, $comOpen, $ptr);
1436
                        $close = strpos($src, $comClose, $ptr);
1437
1438
                        if ($open !== false && $close !== false) {
1439
                            if ($open < $close) {
1440
                                $ptr = $open + strlen($comOpen);
1441
                                ++ $level;
1442
                            } else {
1443
                                $ptr = $close + strlen($comClose);
1444
                                -- $level;
1445
                            }
1446
                        } elseif ($open !== false) {
1447
                            $ptr = $open + strlen($comOpen);
1448
                            ++ $level;
1449
                        } elseif ($close !== false) {
1450
                            $ptr = $close + strlen($comClose);
1451
                            -- $level;
1452
                        } else {
1453
                            $ptr = strlen($src);
1454
                        }
1455
                    }
1456
                    $endpos = $ptr - strlen('*' . $this->rd);
1457 View Code Duplication
                } else {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1458
                    $endpos = strpos($src, '*' . $this->rd, $startpos);
1459
                    if ($endpos == false) {
1460
                        throw new CompilationException($this, 'Un-ended comment');
1461
                    }
1462
                }
1463
                $pointer += $endpos - $startpos + strlen('*' . $this->rd);
1464
                if (isset($whitespaceStart) && preg_match('#^[\t ]*\r?\n#', substr($src, $endpos + strlen('*' . $this->rd)), $m)) {
1465
                    $pointer += strlen($m[0]);
1466
                    $this->curBlock['buffer'] = substr($this->curBlock['buffer'], 0, strlen($this->curBlock['buffer']) - ($this->getPointer() - $startpos - strlen($this->ld)));
1467
                }
1468
1469
                return false;
1470
            }
1471
        }
1472
1473
        if ($first === '$') {
1474
            // var
1475
            $out    = $this->parseVar($in, $from, $to, $parsingParams, $curBlock, $pointer);
1476
            $parsed = 'var';
1477
        } elseif ($first === '%' && preg_match('#^%[a-z_\\\\]#i', $substr)) {
1478
            // Short constant
1479
            $out = $this->parseConst($in, $from, $to, $parsingParams, $curBlock, $pointer);
1480
        } elseif (($first === '"' || $first === "'") && !(is_array($parsingParams) && preg_match('#^([\'"])[a-z0-9_]+\1\s*=>?(?:\s+|[^=])#i', $substr))) {
1481
            // string
1482
            $out = $this->parseString($in, $from, $to, $parsingParams, $curBlock, $pointer);
1483
        } 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)) {
1484
            // func
1485
            $out    = $this->parseFunction($in, $from, $to, $parsingParams, $curBlock, $pointer);
1486
            $parsed = 'func';
1487
        } elseif ($first === ';') {
1488
            // instruction end
1489
            if ($this->debug) {
1490
                echo 'END OF INSTRUCTION' . "\n";
1491
            }
1492
            if ($pointer !== null) {
1493
                ++ $pointer;
1494
            }
1495
1496
            return $this->parse($in, $from + 1, $to, false, 'root', $pointer);
1497
        } elseif ($curBlock === 'root' && preg_match('#^/([a-z_][a-z0-9_]*)?#i', $substr, $match)) {
1498
            // close block
1499 View Code Duplication
            if (!empty($match[1]) && $match[1] == 'else') {
1500
                throw new CompilationException($this, 'Else blocks must not be closed explicitly, they are automatically closed when their parent block is closed');
1501
            }
1502 View Code Duplication
            if (!empty($match[1]) && $match[1] == 'elseif') {
1503
                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');
1504
            }
1505
            if ($pointer !== null) {
1506
                $pointer += strlen($match[0]);
1507
            }
1508
            if (empty($match[1])) {
1509
                if ($this->curBlock['type'] == 'else' || $this->curBlock['type'] == 'elseif') {
1510
                    $pointer -= strlen($match[0]);
1511
                }
1512
                if ($this->debug) {
1513
                    echo 'TOP BLOCK CLOSED' . "\n";
1514
                }
1515
1516
                return $this->removeTopBlock();
1517
            } else {
1518
                if ($this->debug) {
1519
                    echo 'BLOCK OF TYPE ' . $match[1] . ' CLOSED' . "\n";
1520
                }
1521
1522
                return $this->removeBlock($match[1]);
1523
            }
1524 View Code Duplication
        } elseif ($curBlock === 'root' && substr($substr, 0, strlen($this->rd)) === $this->rd) {
1525
            // end template tag
1526
            if ($this->debug) {
1527
                echo 'TAG PARSING ENDED' . "\n";
1528
            }
1529
            $pointer += strlen($this->rd);
1530
1531
            return false;
1532
        } elseif (is_array($parsingParams) && preg_match('#^(([\'"]?)[a-z0-9_]+\2\s*=' . ($curBlock === 'array' ? '>?' : '') . ')(?:\s+|[^=]).*#i', $substr, $match)) {
1533
            // named parameter
1534
            if ($this->debug) {
1535
                echo 'NAMED PARAM FOUND' . "\n";
1536
            }
1537
            $len = strlen($match[1]);
1538
            while (substr($in, $from + $len, 1) === ' ') {
1539
                ++ $len;
1540
            }
1541
            if ($pointer !== null) {
1542
                $pointer += $len;
1543
            }
1544
1545
            $output = array(
1546
                trim($match[1], " \t\r\n=>'\""),
1547
                $this->parse($in, $from + $len, $to, false, 'namedparam', $pointer)
1548
            );
1549
1550
            $parsingParams[] = $output;
1551
1552
            return $parsingParams;
1553
        } elseif (preg_match('#^(\\\\?[a-z_](?:\\\\?[a-z0-9_]+)*::\$[a-z0-9_]+)#i', $substr, $match)) {
1554
            // static member access
1555
            $parsed = 'var';
1556
            if (is_array($parsingParams)) {
1557
                $parsingParams[] = array($match[1], $match[1]);
1558
                $out             = $parsingParams;
1559
            } else {
1560
                $out = $match[1];
1561
            }
1562
            $pointer += strlen($match[1]);
1563
        } elseif ($substr !== '' && (is_array($parsingParams) || $curBlock === 'namedparam' || $curBlock === 'condition' || $curBlock === 'expression')) {
1564
            // unquoted string, bool or number
1565
            $out = $this->parseOthers($in, $from, $to, $parsingParams, $curBlock, $pointer);
1566 View Code Duplication
        } else {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1567
            // parse error
1568
            throw new CompilationException($this, 'Parse error in "' . substr($in, $from, $to - $from) . '"');
1569
        }
1570
1571
        if (empty($out)) {
1572
            return '';
1573
        }
1574
1575
        $substr = substr($in, $pointer, $to - $pointer);
1576
1577
        // var parsed, check if any var-extension applies
1578
        if ($parsed === 'var') {
1579
            if (preg_match('#^\s*([/%+*-])\s*([a-z0-9]|\$)#i', $substr, $match)) {
1580
                if ($this->debug) {
1581
                    echo 'PARSING POST-VAR EXPRESSION ' . $substr . "\n";
1582
                }
1583
                // parse expressions
1584
                $pointer += strlen($match[0]) - 1;
1585
                if (is_array($parsingParams)) {
1586
                    if ($match[2] == '$') {
1587
                        $expr = $this->parseVar($in, $pointer, $to, array(), $curBlock, $pointer);
1588
                    } else {
1589
                        $expr = $this->parse($in, $pointer, $to, array(), 'expression', $pointer);
1590
                    }
1591
                    $out[count($out) - 1][0] .= $match[1] . $expr[0][0];
1592
                    $out[count($out) - 1][1] .= $match[1] . $expr[0][1];
1593
                } else {
1594
                    if ($match[2] == '$') {
1595
                        $expr = $this->parseVar($in, $pointer, $to, false, $curBlock, $pointer);
1596
                    } else {
1597
                        $expr = $this->parse($in, $pointer, $to, false, 'expression', $pointer);
1598
                    }
1599
                    if (is_array($out) && is_array($expr)) {
1600
                        $out[0] .= $match[1] . $expr[0];
1601
                        $out[1] .= $match[1] . $expr[1];
1602
                    } elseif (is_array($out)) {
1603
                        $out[0] .= $match[1] . $expr;
1604
                        $out[1] .= $match[1] . $expr;
1605
                    } elseif (is_array($expr)) {
1606
                        $out .= $match[1] . $expr[0];
1607
                    } else {
1608
                        $out .= $match[1] . $expr;
1609
                    }
1610
                }
1611
            } elseif ($curBlock === 'root' && preg_match('#^(\s*(?:[+/*%-.]=|=|\+\+|--)\s*)(.*)#s', $substr, $match)) {
1612
                if ($this->debug) {
1613
                    echo 'PARSING POST-VAR ASSIGNMENT ' . $substr . "\n";
1614
                }
1615
                // parse assignment
1616
                $value    = $match[2];
1617
                $operator = trim($match[1]);
1618
                if (substr($value, 0, 1) == '=') {
1619
                    throw new CompilationException($this, 'Unexpected "=" in <em>' . $substr . '</em>');
1620
                }
1621
1622
                if ($pointer !== null) {
1623
                    $pointer += strlen($match[1]);
1624
                }
1625
1626
                if ($operator !== '++' && $operator !== '--') {
1627
                    $parts = array();
1628
                    $ptr   = 0;
1629
                    $parts = $this->parse($value, 0, strlen($value), $parts, 'condition', $ptr);
1630
                    $pointer += $ptr;
1631
1632
                    // load if plugin
1633
                    try {
1634
                        $this->getPluginType('if');
1635
                    }
1636
                    catch (Exception $e) {
1637
                        throw new CompilationException($this, 'Assignments require the "if" plugin to be accessible');
1638
                    }
1639
1640
                    $parts  = $this->mapParams($parts, array(Core::NAMESPACE_PLUGINS_BLOCKS . 'PluginIf', 'init'), 1);
1641
                    $tokens = $this->getParamTokens($parts);
1642
                    $parts  = $this->getCompiledParams($parts);
1643
1644
                    $value = PluginIf::replaceKeywords($parts['*'], $tokens['*'], $this);
1645
                    $echo  = '';
1646
                } else {
1647
                    $value = array();
1648
                    $echo  = 'echo ';
1649
                }
1650
1651
                if ($this->autoEscape) {
1652
                    $out = preg_replace('#\(is_string\(\$tmp=(.+?)\) \? htmlspecialchars\(\$tmp, ENT_QUOTES, \$this->charset\) : \$tmp\)#', '$1', $out);
1653
                }
1654
                $out = self::PHP_OPEN . $echo . $out . $operator . implode(' ', $value) . self::PHP_CLOSE;
1655
            } elseif ($curBlock === 'array' && is_array($parsingParams) && preg_match('#^(\s*=>?\s*)#', $substr, $match)) {
1656
                // parse namedparam with var as name (only for array)
1657
                if ($this->debug) {
1658
                    echo 'VARIABLE NAMED PARAM (FOR ARRAY) FOUND' . "\n";
1659
                }
1660
                $len = strlen($match[1]);
1661
                $var = $out[count($out) - 1];
1662
                $pointer += $len;
1663
1664
                $output = array($var[0], $this->parse($substr, $len, null, false, 'namedparam', $pointer));
1665
1666
                $parsingParams[] = $output;
1667
1668
                return $parsingParams;
1669
            }
1670
        }
1671
1672
        if ($curBlock !== 'modifier' && ($parsed === 'func' || $parsed === 'var') && preg_match('#^(\|@?[a-z0-9_]+(:.*)?)+#i', $substr, $match)) {
1673
            // parse modifier on funcs or vars
1674
            $srcPointer = $pointer;
1675
            if (is_array($parsingParams)) {
1676
                $tmp                     = $this->replaceModifiers(
1677
                    array(
1678
                    null,
1679
                    null,
1680
                    $out[count($out) - 1][0],
1681
                    $match[0]
1682
                    ), $curBlock, $pointer
1683
                );
1684
                $out[count($out) - 1][0] = $tmp;
1685
                $out[count($out) - 1][1] .= substr($substr, $srcPointer, $srcPointer - $pointer);
1686
            } else {
1687
                $out = $this->replaceModifiers(array(null, null, $out, $match[0]), $curBlock, $pointer);
1688
            }
1689
        }
1690
1691
        // func parsed, check if any func-extension applies
1692
        if ($parsed === 'func' && preg_match('#^->[a-z0-9_]+(\s*\(.+|->[a-z_].*)?#is', $substr, $match)) {
1693
            // parse method call or property read
1694
            $ptr = 0;
1695
1696
            if (is_array($parsingParams)) {
1697
                $output = $this->parseMethodCall($out[count($out) - 1][1], $match[0], $curBlock, $ptr);
1698
1699
                $out[count($out) - 1][0] = $output;
1700
                $out[count($out) - 1][1] .= substr($match[0], 0, $ptr);
1701
            } else {
1702
                $out = $this->parseMethodCall($out, $match[0], $curBlock, $ptr);
1703
            }
1704
1705
            $pointer += $ptr;
1706
        }
1707
1708
        if ($curBlock === 'root' && substr($out, 0, strlen(self::PHP_OPEN)) !== self::PHP_OPEN) {
1709
            return self::PHP_OPEN . 'echo ' . $out . ';' . self::PHP_CLOSE;
1710
        }
1711
1712
        return $out;
1713
    }
1714
1715
    /**
1716
     * Parses a function call.
1717
     *
1718
     * @param string $in            the string within which we must parse something
1719
     * @param int    $from          the starting offset of the parsed area
1720
     * @param int    $to            the ending offset of the parsed area
1721
     * @param mixed  $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by
1722
     *                              default
1723
     * @param string $curBlock      the current parser-block being processed
1724
     * @param mixed  $pointer       a reference to a pointer that will be increased by the amount of characters parsed,
1725
     *                              or null by default
1726
     *
1727
     * @return string parsed values
1728
     * @throws CompilationException
1729
     * @throws Exception
1730
     * @throws SecurityException
1731
     */
1732
    protected function parseFunction($in, $from, $to, $parsingParams = false, $curBlock = '', &$pointer = null)
1733
    {
1734
        $output = '';
1735
        $cmdstr = substr($in, $from, $to - $from);
1736
        preg_match('/^(\\\\?[a-z_](?:\\\\?[a-z0-9_]+)*(?:::[a-z_][a-z0-9_]*)?)(\s*' . $this->rdr . '|\s*;)?/i', $cmdstr, $match);
1737
1738 View Code Duplication
        if (empty($match[1])) {
1739
            throw new CompilationException($this, 'Parse error, invalid function name : ' . substr($cmdstr, 0, 15));
1740
        }
1741
1742
        $func = $match[1];
1743
1744
        if (!empty($match[2])) {
1745
            $cmdstr = $match[1];
1746
        }
1747
1748
        if ($this->debug) {
1749
            echo 'FUNC FOUND (' . $func . ')' . "\n";
1750
        }
1751
1752
        $paramsep = '';
1753
1754
        if (is_array($parsingParams) || $curBlock != 'root') {
1755
            $paramspos = strpos($cmdstr, '(');
1756
            $paramsep  = ')';
1757
        } elseif (preg_match_all('#^\s*[\\\\:a-z0-9_]+(\s*\(|\s+[^(])#i', $cmdstr, $match, PREG_OFFSET_CAPTURE)) {
1758
            $paramspos = $match[1][0][1];
1759
            $paramsep  = substr($match[1][0][0], - 1) === '(' ? ')' : '';
1760
            if ($paramsep === ')') {
1761
                $paramspos += strlen($match[1][0][0]) - 1;
1762
                if (substr($cmdstr, 0, 2) === 'if' || substr($cmdstr, 0, 6) === 'elseif') {
1763
                    $paramsep = '';
1764
                    if (strlen($match[1][0][0]) > 1) {
1765
                        -- $paramspos;
1766
                    }
1767
                }
1768
            }
1769
        } else {
1770
            $paramspos = false;
1771
        }
1772
1773
        $state = 0;
1774
1775
        if ($paramspos === false) {
1776
            $params = array();
1777
1778
            if ($curBlock !== 'root') {
1779
                return $this->parseOthers($in, $from, $to, $parsingParams, $curBlock, $pointer);
1780
            }
1781
        } else {
1782
            if ($curBlock === 'condition') {
1783
                // load if plugin
1784
                $this->getPluginType('if');
1785
1786
                if (PluginIf::replaceKeywords(array($func), array(self::T_UNQUOTED_STRING), $this) !== array($func)) {
1787
                    return $this->parseOthers($in, $from, $to, $parsingParams, $curBlock, $pointer);
1788
                }
1789
            }
1790
            $whitespace = strlen(substr($cmdstr, strlen($func), $paramspos - strlen($func)));
1791
            $paramstr   = substr($cmdstr, $paramspos + 1);
1792 View Code Duplication
            if (substr($paramstr, - 1, 1) === $paramsep) {
1793
                $paramstr = substr($paramstr, 0, - 1);
1794
            }
1795
1796
            if (strlen($paramstr) === 0) {
1797
                $params   = array();
1798
                $paramstr = '';
1799
            } else {
1800
                $ptr    = 0;
1801
                $params = array();
1802
                if ($func === 'empty') {
1803
                    $params = $this->parseVar($paramstr, $ptr, strlen($paramstr), $params, 'root', $ptr);
1804
                } else {
1805
                    while ($ptr < strlen($paramstr)) {
1806
                        while (true) {
1807
                            if ($ptr >= strlen($paramstr)) {
1808
                                break 2;
1809
                            }
1810
1811
                            if ($func !== 'if' && $func !== 'elseif' && $paramstr[$ptr] === ')') {
1812
                                if ($this->debug) {
1813
                                    echo 'PARAM PARSING ENDED, ")" FOUND, POINTER AT ' . $ptr . "\n";
1814
                                }
1815
                                break 2;
1816
                            } elseif ($paramstr[$ptr] === ';') {
1817
                                ++ $ptr;
1818
                                if ($this->debug) {
1819
                                    echo 'PARAM PARSING ENDED, ";" FOUND, POINTER AT ' . $ptr . "\n";
1820
                                }
1821
                                break 2;
1822
                            } elseif ($func !== 'if' && $func !== 'elseif' && $paramstr[$ptr] === '/') {
1823
                                if ($this->debug) {
1824
                                    echo 'PARAM PARSING ENDED, "/" FOUND, POINTER AT ' . $ptr . "\n";
1825
                                }
1826
                                break 2;
1827
                            } elseif (substr($paramstr, $ptr, strlen($this->rd)) === $this->rd) {
1828
                                if ($this->debug) {
1829
                                    echo 'PARAM PARSING ENDED, RIGHT DELIMITER FOUND, POINTER AT ' . $ptr . "\n";
1830
                                }
1831
                                break 2;
1832
                            }
1833
1834
                            if ($paramstr[$ptr] === ' ' || $paramstr[$ptr] === ',' || $paramstr[$ptr] === "\r" || $paramstr[$ptr] === "\n" || $paramstr[$ptr] === "\t") {
1835
                                ++ $ptr;
1836
                            } else {
1837
                                break;
1838
                            }
1839
                        }
1840
1841
                        if ($this->debug) {
1842
                            echo 'FUNC START PARAM PARSING WITH POINTER AT ' . $ptr . "\n";
1843
                        }
1844
1845
                        if ($func === 'if' || $func === 'elseif' || $func === 'tif') {
1846
                            $params = $this->parse($paramstr, $ptr, strlen($paramstr), $params, 'condition', $ptr);
1847
                        } elseif ($func === 'array') {
1848
                            $params = $this->parse($paramstr, $ptr, strlen($paramstr), $params, 'array', $ptr);
1849
                        } else {
1850
                            $params = $this->parse($paramstr, $ptr, strlen($paramstr), $params, 'function', $ptr);
1851
                        }
1852
1853 View Code Duplication
                        if ($this->debug) {
1854
                            echo 'PARAM PARSED, POINTER AT ' . $ptr . ' (' . substr($paramstr, $ptr - 1, 3) . ')' . "\n";
1855
                        }
1856
                    }
1857
                }
1858
                $paramstr = substr($paramstr, 0, $ptr);
1859
                $state    = 0;
1860
                foreach ($params as $k => $p) {
1861
                    if (is_array($p) && is_array($p[1])) {
1862
                        $state |= 2;
1863
                    } else {
1864
                        if (($state & 2) && preg_match('#^(["\'])(.+?)\1$#', $p[0], $m) && $func !== 'array') {
1865
                            $params[$k] = array($m[2], array('true', 'true'));
1866
                        } else {
1867
                            if ($state & 2 && $func !== 'array') {
1868
                                throw new CompilationException($this, 'You can not use an unnamed parameter after a named one');
1869
                            }
1870
                            $state |= 1;
1871
                        }
1872
                    }
1873
                }
1874
            }
1875
        }
1876
1877
        if ($pointer !== null) {
1878
            $pointer += (isset($paramstr) ? strlen($paramstr) : 0) + (')' === $paramsep ? 2 : ($paramspos === false ? 0 : 1)) + strlen($func) + (isset($whitespace) ? $whitespace : 0);
1879
            if ($this->debug) {
1880
                echo 'FUNC ADDS ' . ((isset($paramstr) ? strlen($paramstr) : 0) + (')' === $paramsep ? 2 : ($paramspos === false ? 0 : 1)) + strlen($func)) . ' TO POINTER' . "\n";
1881
            }
1882
        }
1883
1884
        if ($curBlock === 'method' || $func === 'do' || strstr($func, '::') !== false) {
1885
            // handle static method calls with security policy
1886
            if (strstr($func, '::') !== false && $this->securityPolicy !== null && $this->securityPolicy->isMethodAllowed(explode('::', strtolower($func))) !== true) {
1887
                throw new SecurityException('Call to a disallowed php function : ' . $func);
1888
            }
1889
            $pluginType = Core::NATIVE_PLUGIN;
1890
        } else {
1891
            $pluginType = $this->getPluginType($func);
1892
        }
1893
1894
        // Blocks plugin
1895
        if ($pluginType & Core::BLOCK_PLUGIN) {
1896
            if ($curBlock !== 'root' || is_array($parsingParams)) {
1897
                throw new CompilationException($this, 'Block plugins can not be used as other plugin\'s arguments');
1898
            }
1899
            if ($pluginType & Core::CUSTOM_PLUGIN) {
1900
                return $this->addCustomBlock($func, $params, $state);
1901
            } else {
1902
                return $this->addBlock($func, $params, $state);
1903
            }
1904
        } elseif ($pluginType & Core::SMARTY_BLOCK) {
1905
            if ($curBlock !== 'root' || is_array($parsingParams)) {
1906
                throw new CompilationException($this, 'Block plugins can not be used as other plugin\'s arguments');
1907
            }
1908
1909
            if ($state & 2) {
1910
                array_unshift($params, array('__functype', array($pluginType, $pluginType)));
1911
                array_unshift($params, array('__funcname', array($func, $func)));
1912
            } else {
1913
                array_unshift($params, array($pluginType, $pluginType));
1914
                array_unshift($params, array($func, $func));
1915
            }
1916
1917
            return $this->addBlock('smartyinterface', $params, $state);
1918
        }
1919
1920
        // Native & Smarty plugins
1921
        if ($pluginType & Core::NATIVE_PLUGIN || $pluginType & Core::SMARTY_FUNCTION || $pluginType & Core::SMARTY_BLOCK) {
1922
            $params = $this->mapParams($params, null, $state);
0 ignored issues
show
null is of type null, but the function expects a callable.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1923
        } // PHP class plugin
1924
        elseif ($pluginType & Core::CLASS_PLUGIN) {
1925
            if ($pluginType & Core::CUSTOM_PLUGIN) {
1926
                $params = $this->mapParams(
1927
                    $params, array(
1928
                    $this->customPlugins[$func]['class'],
1929
                    $this->customPlugins[$func]['function']
1930
                ), $state);
1931
            } else {
1932
                if (class_exists('Plugin' . Core::toCamelCase($func)) !== false) {
1933
                    $params = $this->mapParams($params, array(
1934
                        'Plugin' . Core::toCamelCase($func),
1935
                        ($pluginType & Core::COMPILABLE_PLUGIN) ? 'compile' : 'process'
1936
                    ), $state);
1937
                } elseif (class_exists(Core::NAMESPACE_PLUGINS_HELPERS . 'Plugin' . Core::toCamelCase($func)) !== false) {
1938
                    $params = $this->mapParams($params, array(
1939
                        Core::NAMESPACE_PLUGINS_HELPERS . 'Plugin' . Core::toCamelCase($func),
1940
                        ($pluginType & Core::COMPILABLE_PLUGIN) ? 'compile' : 'process'
1941
                    ), $state);
1942 View Code Duplication
                } else {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1943
                    $params = $this->mapParams($params, array(
1944
                        Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . Core::toCamelCase($func),
1945
                        ($pluginType & Core::COMPILABLE_PLUGIN) ? 'compile' : 'process'
1946
                    ), $state);
1947
                }
1948
            }
1949
        } // PHP function plugin
1950
        elseif ($pluginType & Core::FUNC_PLUGIN) {
1951
            if ($pluginType & Core::CUSTOM_PLUGIN) {
1952
                $params = $this->mapParams($params, $this->customPlugins[$func]['callback'], $state);
1953
            } else {
1954
                // Custom plugin
1955
                if (function_exists('Plugin' . Core::toCamelCase($func) . (($pluginType & Core::COMPILABLE_PLUGIN) ?
1956
                        'Compile' : '')) !== false) {
1957
                    $params = $this->mapParams($params, 'Plugin' . Core::toCamelCase($func) . (($pluginType &
1958
                            Core::COMPILABLE_PLUGIN) ? 'Compile' : ''), $state);
1959
                } // Builtin helper plugin
1960
                elseif (function_exists(Core::NAMESPACE_PLUGINS_HELPERS . 'Plugin' . Core::toCamelCase($func) . (
1961
                    ($pluginType & Core::COMPILABLE_PLUGIN) ? 'Compile' : '')) !== false) {
1962
                    $params = $this->mapParams($params, Core::NAMESPACE_PLUGINS_HELPERS . 'Plugin' . Core::toCamelCase
1963
                        ($func) . (($pluginType & Core::COMPILABLE_PLUGIN) ? 'Compile' : ''), $state);
1964
                } // Builtin function plugin
1965 View Code Duplication
                else {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1966
                    $params = $this->mapParams($params, Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . Core::toCamelCase
1967
                        ($func) . (($pluginType & Core::COMPILABLE_PLUGIN) ? 'Compile' : ''), $state);
1968
                }
1969
            }
1970
        } // Smarty modifier
1971
        elseif ($pluginType & Core::SMARTY_MODIFIER) {
1972
            $output = 'smarty_modifier_' . $func . '(' . implode(', ', $params) . ')';
1973
        } // Proxy plugin
1974
        elseif ($pluginType & Core::PROXY_PLUGIN) {
1975
            $params = $this->mapParams($params, $this->getDwoo()->getPluginProxy()->getCallback($func), $state);
1976
        } // Template plugin
1977
        elseif ($pluginType & Core::TEMPLATE_PLUGIN) {
1978
            // transforms the parameter array from (x=>array('paramname'=>array(values))) to (paramname=>array(values))
1979
            $map = array();
1980
            foreach ($this->templatePlugins[$func]['params'] as $param => $defValue) {
1981
                if ($param == 'rest') {
1982
                    $param = '*';
1983
                }
1984
                $hasDefault = $defValue !== null;
1985
                if ($defValue === 'null') {
1986
                    $defValue = null;
1987
                } elseif ($defValue === 'false') {
1988
                    $defValue = false;
1989
                } elseif ($defValue === 'true') {
1990
                    $defValue = true;
1991
                } elseif (preg_match('#^([\'"]).*?\1$#', $defValue)) {
1992
                    $defValue = substr($defValue, 1, - 1);
1993
                }
1994
                $map[] = array($param, $hasDefault, $defValue);
1995
            }
1996
1997
            $params = $this->mapParams($params, null, $state, $map);
1998
        }
1999
2000
        // only keep php-syntax-safe values for non-block plugins
2001
        $tokens = array();
2002
        foreach ($params as $k => $p) {
2003
            $tokens[$k] = isset($p[2]) ? $p[2] : 0;
2004
            $params[$k] = $p[0];
2005
        }
2006
2007
        // Native plugin
2008
        if ($pluginType & Core::NATIVE_PLUGIN) {
2009
            if ($func === 'do') {
2010
                $output = '';
2011
                if (isset($params['*'])) {
2012
                    $output = implode(';', $params['*']) . ';';
2013
                }
2014
2015
                if (is_array($parsingParams) || $curBlock !== 'root') {
2016
                    throw new CompilationException($this, 'Do can not be used inside another function or block');
2017
                }
2018
2019
                return self::PHP_OPEN . $output . self::PHP_CLOSE;
2020
            } else {
2021
                if (isset($params['*'])) {
2022
                    $output = $func . '(' . implode(', ', $params['*']) . ')';
2023
                } else {
2024
                    $output = $func . '()';
2025
                }
2026
            }
2027
        } // Block class OR Function class
2028
        elseif ($pluginType & Core::CLASS_PLUGIN || ($pluginType & Core::FUNC_PLUGIN && $pluginType & Core::CLASS_PLUGIN)) {
2029
            if ($pluginType & Core::COMPILABLE_PLUGIN) {
2030
                if ($pluginType & Core::CUSTOM_PLUGIN) {
2031
                    $callback = $this->customPlugins[$func]['callback'];
2032
                    if (!is_array($callback)) {
2033
                        if (!method_exists($callback, 'compile')) {
2034
                            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');
2035
                        }
2036
                        if (($ref = new ReflectionMethod($callback, 'compile')) && $ref->isStatic()) {
2037
                            $funcCompiler = array($callback, 'compile');
2038
                        } else {
2039
                            $funcCompiler = array(new $callback(), 'compile');
2040
                        }
2041
                    } else {
2042
                        $funcCompiler = $callback;
2043
                    }
2044
                } else {
2045
                    if (class_exists('Plugin' . Core::toCamelCase($func)) !== false) {
2046
                        $funcCompiler = array('Plugin' . Core::toCamelCase($func), 'compile');
2047
                    } elseif (class_exists(Core::NAMESPACE_PLUGINS_HELPERS . 'Plugin' . Core::toCamelCase($func)) !== false) {
2048
                        $funcCompiler = array(Core::NAMESPACE_PLUGINS_HELPERS . 'Plugin' . Core::toCamelCase($func), 'compile');
2049
                    } else {
2050
                        $funcCompiler = array(
2051
                            Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . Core::toCamelCase($func),
2052
                            'compile'
2053
                        );
2054
                    }
2055
                    array_unshift($params, $this);
2056
                }
2057
                // @TODO: Is it a real fix ?
2058
                if ($func === 'tif') {
2059
                    $params[] = $tokens;
2060
                }
2061
                $output = call_user_func_array($funcCompiler, $params);
2062
            } else {
2063
                $params = self::implode_r($params);
2064
                if ($pluginType & Core::CUSTOM_PLUGIN) {
2065
                    $callback = $this->customPlugins[$func]['callback'];
2066
                    if (!is_array($callback)) {
2067
                        if (!method_exists($callback, 'process')) {
2068
                            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');
2069
                        }
2070
                        if (($ref = new ReflectionMethod($callback, 'process')) && $ref->isStatic()) {
2071
                            $output = 'call_user_func(array(\'' . $callback . '\', \'process\'), ' . $params . ')';
2072
                        } else {
2073
                            $output = 'call_user_func(array($this->getObjectPlugin(\'' . $callback . '\'), \'process\'), ' . $params . ')';
2074
                        }
2075 View Code Duplication
                    } elseif (is_object($callback[0])) {
2076
                        $output = 'call_user_func(array($this->plugins[\'' . $func . '\'][\'callback\'][0], \'' . $callback[1] . '\'), ' . $params . ')';
2077
                    } elseif (($ref = new ReflectionMethod($callback[0], $callback[1])) && $ref->isStatic()) {
2078
                        $output = 'call_user_func(array(\'' . $callback[0] . '\', \'' . $callback[1] . '\'), ' . $params . ')';
2079 View Code Duplication
                    } else {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2080
                        $output = 'call_user_func(array($this->getObjectPlugin(\'' . $callback[0] . '\'), \'' . $callback[1] . '\'), ' . $params . ')';
2081
                    }
2082
                    if (empty($params)) {
2083
                        $output = substr($output, 0, - 3) . ')';
2084
                    }
2085
                } else {
2086
                    if (class_exists('Plugin' . Core::toCamelCase($func)) !== false) {
2087
                        $output = '$this->classCall(\'Plugin' . $func . '\', array(' . $params . '))';
2088 View Code Duplication
                    } elseif (class_exists(Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . Core::toCamelCase($func)) !== false) {
2089
                        $output = '$this->classCall(\'' . Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . $func . '\', 
2090
                        array(' . $params . '))';
2091
                    } else {
2092
                        $output = '$this->classCall(\'' . $func . '\', array(' . $params . '))';
2093
                    }
2094
                }
2095
            }
2096
        } // Function plugin only (cannot be a class)
2097
        elseif ($pluginType & Core::FUNC_PLUGIN) {
2098
            if ($pluginType & Core::COMPILABLE_PLUGIN) {
2099
                if ($pluginType & Core::CUSTOM_PLUGIN) {
2100
                    $funcCompiler = $this->customPlugins[$func]['callback'];
2101
                } else {
2102
                    // Custom plugin
2103
                    if (function_exists('Plugin' . Core::toCamelCase($func) . 'Compile') !== false) {
2104
                        $funcCompiler = 'Plugin' . Core::toCamelCase($func) . 'Compile';
2105
                    } // Builtin helper plugin
2106 View Code Duplication
                    elseif (function_exists(Core::NAMESPACE_PLUGINS_HELPERS . 'Plugin' . Core::toCamelCase($func) . 'Compile') !== false) {
2107
                        $funcCompiler = Core::NAMESPACE_PLUGINS_HELPERS . 'Plugin' . Core::toCamelCase($func) .
2108
                            'Compile';
2109
                    } // Builtin function plugin
2110
                    else {
2111
                        $funcCompiler = Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . Core::toCamelCase($func) .
2112
                            'Compile';
2113
                    }
2114
                }
2115
                array_unshift($params, $this);
2116
                // @TODO: Is it a real fix ?
2117
                if ($func === 'tif') {
2118
                    $params[] = $tokens;
2119
                }
2120
                $output = call_user_func_array($funcCompiler, $params);
2121
            } else {
2122
                array_unshift($params, '$this');
2123
                $params = self::implode_r($params);
2124
                if ($pluginType & Core::CUSTOM_PLUGIN) {
2125
                    $callback = $this->customPlugins[$func]['callback'];
2126
                    if ($callback instanceof Closure) {
2127
                        $output = 'call_user_func($this->getCustomPlugin(\'' . $func . '\'), ' . $params . ')';
2128
                    } else {
2129
                        $output = 'call_user_func(\'' . $callback . '\', ' . $params . ')';
2130
                    }
2131
                } else {
2132
                    // Custom plugin
2133
                    if (function_exists('Plugin' . Core::toCamelCase($func)) !== false) {
2134
                        $output = 'Plugin' . Core::toCamelCase($func) . '(' . $params .
2135
                            ')';
2136
                    } // Builtin helper plugin
2137 View Code Duplication
                    elseif(function_exists(Core::NAMESPACE_PLUGINS_HELPERS . 'Plugin' . Core::toCamelCase($func)) !==
2138
                        false) {
2139
                        $output = Core::NAMESPACE_PLUGINS_HELPERS . 'Plugin' . Core::toCamelCase($func) . '(' .
2140
                            $params . ')';
2141
                    } // Builtin function plugin
2142 View Code Duplication
                    else {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2143
                        $output = Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . Core::toCamelCase($func) . '(' .
2144
                            $params . ')';
2145
                    }
2146
                }
2147
            }
2148
        } // Proxy plugin
2149
        elseif ($pluginType & Core::PROXY_PLUGIN) {
2150
            $output = call_user_func(array($this->getDwoo()->getPluginProxy(), 'getCode'), $func, $params);
2151
        } // Smarty function (@deprecated)
2152
        elseif ($pluginType & Core::SMARTY_FUNCTION) {
2153
            $params = '';
2154
            if (isset($params['*'])) {
2155
                $params = self::implode_r($params['*'], true);
2156
            }
2157
2158
            if ($pluginType & Core::CUSTOM_PLUGIN) {
2159
                $callback = $this->customPlugins[$func]['callback'];
2160
                if (is_array($callback)) {
2161
                    if (is_object($callback[0])) {
2162
                        $output = 'call_user_func_array(array($this->plugins[\'' . $func . '\'][\'callback\'][0], \'' . $callback[1] . '\'), array(array(' . $params . '), $this))';
2163 View Code Duplication
                    } else {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2164
                        $output = 'call_user_func_array(array(\'' . $callback[0] . '\', \'' . $callback[1] . '\'), array(array(' . $params . '), $this))';
2165
                    }
2166
                } else {
2167
                    $output = $callback . '(array(' . $params . '), $this)';
2168
                }
2169
            } else {
2170
                $output = 'smarty_function_' . $func . '(array(' . $params . '), $this)';
2171
            }
2172
        } // Template plugin
2173
        elseif ($pluginType & Core::TEMPLATE_PLUGIN) {
2174
            array_unshift($params, '$this');
2175
            $params                                 = self::implode_r($params);
2176
            $output                                 = 'Plugin' . Core::toCamelCase($func) .
2177
                $this->templatePlugins[$func]['uuid'] . '(' . $params . ')';
2178
            $this->templatePlugins[$func]['called'] = true;
2179
        }
2180
2181 View Code Duplication
        if (is_array($parsingParams)) {
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...
2182
            $parsingParams[] = array($output, $output);
2183
2184
            return $parsingParams;
2185
        } elseif ($curBlock === 'namedparam') {
2186
            return array($output, $output);
2187
        }
2188
2189
        return $output;
2190
    }
2191
2192
    /**
2193
     * Parses a string.
2194
     *
2195
     * @param string $in            the string within which we must parse something
2196
     * @param int    $from          the starting offset of the parsed area
2197
     * @param int    $to            the ending offset of the parsed area
2198
     * @param mixed  $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by
2199
     *                              default
2200
     * @param string $curBlock      the current parser-block being processed
2201
     * @param mixed  $pointer       a reference to a pointer that will be increased by the amount of characters parsed,
2202
     *                              or null by default
2203
     *
2204
     * @return string parsed values
2205
     * @throws CompilationException
2206
     */
2207
    protected function parseString($in, $from, $to, $parsingParams = false, $curBlock = '', &$pointer = null)
2208
    {
2209
        $substr = substr($in, $from, $to - $from);
2210
        $first  = $substr[0];
2211
2212
        if ($this->debug) {
2213
            echo 'STRING FOUND (in ' . htmlentities(substr($in, $from, min($to - $from, 50))) . (($to - $from) > 50 ? '...' : '') . ')' . "\n";
2214
        }
2215
        $strend = false;
2216
        $o      = $from + 1;
2217
        while ($strend === false) {
2218
            $strend = strpos($in, $first, $o);
2219 View Code Duplication
            if ($strend === false) {
2220
                throw new CompilationException($this, 'Unfinished string, started with ' . substr($in, $from, $to - $from));
2221
            }
2222
            if (substr($in, $strend - 1, 1) === '\\') {
2223
                $o      = $strend + 1;
2224
                $strend = false;
2225
            }
2226
        }
2227
        if ($this->debug) {
2228
            echo 'STRING DELIMITED: ' . substr($in, $from, $strend + 1 - $from) . "\n";
2229
        }
2230
2231
        $srcOutput = substr($in, $from, $strend + 1 - $from);
2232
2233
        if ($pointer !== null) {
2234
            $pointer += strlen($srcOutput);
2235
        }
2236
2237
        $output = $this->replaceStringVars($srcOutput, $first);
2238
2239
        // handle modifiers
2240
        if ($curBlock !== 'modifier' && preg_match('#^((?:\|(?:@?[a-z0-9_]+(?::.*)*))+)#i', substr($substr, $strend + 1 - $from), $match)) {
2241
            $modstr = $match[1];
2242
2243
            if ($curBlock === 'root' && substr($modstr, - 1) === '}') {
2244
                $modstr = substr($modstr, 0, - 1);
2245
            }
2246
            $modstr = str_replace('\\' . $first, $first, $modstr);
2247
            $ptr    = 0;
2248
            $output = $this->replaceModifiers(array(null, null, $output, $modstr), 'string', $ptr);
2249
2250
            $strend += $ptr;
2251
            if ($pointer !== null) {
2252
                $pointer += $ptr;
2253
            }
2254
            $srcOutput .= substr($substr, $strend + 1 - $from, $ptr);
2255
        }
2256
2257
        if (is_array($parsingParams)) {
2258
            $parsingParams[] = array($output, substr($srcOutput, 1, - 1));
2259
2260
            return $parsingParams;
2261
        } elseif ($curBlock === 'namedparam') {
2262
            return array($output, substr($srcOutput, 1, - 1));
2263
        }
2264
2265
        return $output;
2266
    }
2267
2268
    /**
2269
     * Parses a constant.
2270
     *
2271
     * @param string $in            the string within which we must parse something
2272
     * @param int    $from          the starting offset of the parsed area
2273
     * @param int    $to            the ending offset of the parsed area
2274
     * @param mixed  $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by
2275
     *                              default
2276
     * @param string $curBlock      the current parser-block being processed
2277
     * @param mixed  $pointer       a reference to a pointer that will be increased by the amount of characters parsed,
2278
     *                              or null by default
2279
     *
2280
     * @return string parsed values
2281
     * @throws CompilationException
2282
     */
2283
    protected function parseConst($in, $from, $to, $parsingParams = false, $curBlock = '', &$pointer = null)
2284
    {
2285
        $substr = substr($in, $from, $to - $from);
2286
2287
        if ($this->debug) {
2288
            echo 'CONST FOUND : ' . $substr . "\n";
2289
        }
2290
2291
        if (!preg_match('#^%([\\\\a-z0-9_:]+)#i', $substr, $m)) {
2292
            throw new CompilationException($this, 'Invalid constant');
2293
        }
2294
2295
        if ($pointer !== null) {
2296
            $pointer += strlen($m[0]);
2297
        }
2298
2299
        $output = $this->parseConstKey($m[1], $curBlock);
2300
2301 View Code Duplication
        if (is_array($parsingParams)) {
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...
2302
            $parsingParams[] = array($output, $m[1]);
2303
2304
            return $parsingParams;
2305
        } elseif ($curBlock === 'namedparam') {
2306
            return array($output, $m[1]);
2307
        }
2308
2309
        return $output;
2310
    }
2311
2312
    /**
2313
     * Parses a constant.
2314
     *
2315
     * @param string $key      the constant to parse
2316
     * @param string $curBlock the current parser-block being processed
2317
     *
2318
     * @return string parsed constant
2319
     */
2320
    protected function parseConstKey($key, $curBlock)
2321
    {
2322
        $key = str_replace('\\\\', '\\', $key);
2323
2324
        if ($this->securityPolicy !== null && $this->securityPolicy->getConstantHandling() === SecurityPolicy::CONST_DISALLOW) {
2325
            return 'null';
2326
        }
2327
2328
        if ($curBlock !== 'root') {
2329
            return '(defined("' . $key . '") ? ' . $key . ' : null)';
2330
        }
2331
2332
        return $key;
2333
    }
2334
2335
    /**
2336
     * Parses a variable.
2337
     *
2338
     * @param string $in            the string within which we must parse something
2339
     * @param int    $from          the starting offset of the parsed area
2340
     * @param int    $to            the ending offset of the parsed area
2341
     * @param mixed  $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by
2342
     *                              default
2343
     * @param string $curBlock      the current parser-block being processed
2344
     * @param mixed  $pointer       a reference to a pointer that will be increased by the amount of characters parsed,
2345
     *                              or null by default
2346
     *
2347
     * @return string parsed values
2348
     * @throws CompilationException
2349
     */
2350
    protected function parseVar($in, $from, $to, $parsingParams = false, $curBlock = '', &$pointer = null)
2351
    {
2352
        $substr = substr($in, $from, $to - $from);
2353
2354
        // var key
2355
        $varRegex = '(\\$?\\.?[a-z0-9\\\\_:]*(?:(?:(?:\\.|->)(?:[a-z0-9\\\\_:]+|(?R))|\\[(?:[a-z0-9\\\\_:]+|(?R)|(["\'])[^\\2]*?\\2)\\]))*)';
2356
        // method call
2357
        $methodCall = ($curBlock === 'root' || $curBlock === 'function' || $curBlock === 'namedparam' || $curBlock === 'condition' || $curBlock === 'variable' || $curBlock === 'expression' || $curBlock === 'delimited_string' ? '(\(.*)?' : '()');
2358
        // simple math expressions
2359
        $simpleMathExpressions = ($curBlock === 'root' || $curBlock === 'function' || $curBlock === 'namedparam' || $curBlock === 'condition' || $curBlock === 'variable' || $curBlock === 'delimited_string' ? '((?:(?:[+\/*%=-])(?:(?<!=)=?-?[$%][a-z0-9\\\\.[\]>_:-]+(?:\([^)]*\))?|(?<!=)=?-?[0-9\.,]*|[+-]))*)' : '()');
2360
        // modifiers
2361
        $modifiers = $curBlock !== 'modifier' ? '((?:\|(?:@?[a-z0-9\\\\_]+(?:(?::("|\').*?\5|:[^`]*))*))+)?' : '(())';
2362
2363
        $regex = '#';
2364
        $regex .= $varRegex;
2365
        $regex .= $methodCall;
2366
        $regex .= $simpleMathExpressions;
2367
        $regex .= $modifiers;
2368
        $regex .= '#i';
2369
2370
        if (preg_match($regex, $substr, $match)) {
2371
            $key = substr($match[1], 1);
2372
2373
            $matchedLength = strlen($match[0]);
2374
            $hasModifiers  = !empty($match[5]);
2375
            $hasExpression = !empty($match[4]);
2376
            $hasMethodCall = !empty($match[3]);
2377
2378
            if (substr($key, - 1) == '.') {
2379
                $key = substr($key, 0, - 1);
2380
                -- $matchedLength;
2381
            }
2382
2383
            if ($hasMethodCall) {
2384
                $matchedLength -= strlen($match[3]) + strlen(substr($match[1], strrpos($match[1], '->')));
2385
                $key        = substr($match[1], 1, strrpos($match[1], '->') - 1);
2386
                $methodCall = substr($match[1], strrpos($match[1], '->')) . $match[3];
2387
            }
2388
2389
            if ($hasModifiers) {
2390
                $matchedLength -= strlen($match[5]);
2391
            }
2392
2393
            if ($pointer !== null) {
2394
                $pointer += $matchedLength;
2395
            }
2396
2397
            // replace useless brackets by dot accessed vars and strip enclosing quotes if present
2398
            $key = preg_replace('#\[(["\']?)([^$%\[.>-]+)\1\]#', '.$2', $key);
2399
2400 View Code Duplication
            if ($this->debug) {
2401
                if ($hasMethodCall) {
2402
                    echo 'METHOD CALL FOUND : $' . $key . substr($methodCall, 0, 30) . "\n";
2403
                } else {
2404
                    echo 'VAR FOUND : $' . $key . "\n";
2405
                }
2406
            }
2407
2408
            $key = str_replace('"', '\\"', $key);
2409
2410
            $cnt = substr_count($key, '$');
2411
            if ($cnt > 0) {
2412
                $uid           = 0;
2413
                $parsed        = array($uid => '');
2414
                $current       = &$parsed;
2415
                $curTxt        = &$parsed[$uid ++];
2416
                $tree          = array();
2417
                $chars         = str_split($key, 1);
2418
                $inSplittedVar = false;
2419
                $bracketCount  = 0;
2420
2421
                while (($char = array_shift($chars)) !== null) {
2422
                    if ($char === '[') {
2423
                        if (count($tree) > 0) {
2424
                            ++ $bracketCount;
2425
                        } else {
2426
                            $tree[]        = &$current;
2427
                            $current[$uid] = array($uid + 1 => '');
2428
                            $current       = &$current[$uid ++];
2429
                            $curTxt        = &$current[$uid ++];
2430
                            continue;
2431
                        }
2432
                    } elseif ($char === ']') {
2433
                        if ($bracketCount > 0) {
2434
                            -- $bracketCount;
2435
                        } else {
2436
                            $current = &$tree[count($tree) - 1];
2437
                            array_pop($tree);
2438
                            if (current($chars) !== '[' && current($chars) !== false && current($chars) !== ']') {
2439
                                $current[$uid] = '';
2440
                                $curTxt        = &$current[$uid ++];
2441
                            }
2442
                            continue;
2443
                        }
2444
                    } elseif ($char === '$') {
2445
                        if (count($tree) == 0) {
2446
                            $curTxt        = &$current[$uid ++];
2447
                            $inSplittedVar = true;
2448
                        }
2449
                    } elseif (($char === '.' || $char === '-') && count($tree) == 0 && $inSplittedVar) {
2450
                        $curTxt        = &$current[$uid ++];
2451
                        $inSplittedVar = false;
2452
                    }
2453
2454
                    $curTxt .= $char;
2455
                }
2456
                unset($uid, $current, $curTxt, $tree, $chars);
2457
2458
                if ($this->debug) {
2459
                    echo 'RECURSIVE VAR REPLACEMENT : ' . $key . "\n";
2460
                }
2461
2462
                $key = $this->flattenVarTree($parsed);
2463
2464
                if ($this->debug) {
2465
                    echo 'RECURSIVE VAR REPLACEMENT DONE : ' . $key . "\n";
2466
                }
2467
2468
                $output = preg_replace('#(^""\.|""\.|\.""$|(\()""\.|\.""(\)))#', '$2$3', '$this->readVar("' . $key . '")');
2469
            } else {
2470
                $output = $this->parseVarKey($key, $hasModifiers ? 'modifier' : $curBlock);
2471
            }
2472
2473
2474
            // methods
2475
            if ($hasMethodCall) {
2476
                $ptr = 0;
2477
2478
                $output = $this->parseMethodCall($output, $methodCall, $curBlock, $ptr);
2479
2480
                if ($pointer !== null) {
2481
                    $pointer += $ptr;
2482
                }
2483
                $matchedLength += $ptr;
2484
            }
2485
2486
            if ($hasExpression) {
2487
                // expressions
2488
                preg_match_all('#(?:([+/*%=-])(=?-?[%$][a-z0-9\\\\.[\]>_:-]+(?:\([^)]*\))?|=?-?[0-9.,]+|\1))#i', $match[4], $expMatch);
2489
                foreach ($expMatch[1] as $k => $operator) {
2490
                    if (substr($expMatch[2][$k], 0, 1) === '=') {
2491
                        $assign = true;
2492
                        if ($operator === '=') {
2493
                            throw new CompilationException($this, 'Invalid expression <em>' . $substr . '</em>, can not use "==" in expressions');
2494
                        }
2495
                        if ($curBlock !== 'root') {
2496
                            throw new CompilationException($this, 'Invalid expression <em>' . $substr . '</em>, assignments can only be used in top level expressions like {$foo+=3} or {$foo="bar"}');
2497
                        }
2498
                        $operator .= '=';
2499
                        $expMatch[2][$k] = substr($expMatch[2][$k], 1);
2500
                    }
2501
2502
                    if (substr($expMatch[2][$k], 0, 1) === '-' && strlen($expMatch[2][$k]) > 1) {
2503
                        $operator .= '-';
2504
                        $expMatch[2][$k] = substr($expMatch[2][$k], 1);
2505
                    }
2506
                    if (($operator === '+' || $operator === '-') && $expMatch[2][$k] === $operator) {
2507
                        $output = '(' . $output . $operator . $operator . ')';
2508
                        break;
2509
                    } elseif (substr($expMatch[2][$k], 0, 1) === '$') {
2510
                        $output = '(' . $output . ' ' . $operator . ' ' . $this->parseVar($expMatch[2][$k], 0, strlen($expMatch[2][$k]), false, 'expression') . ')';
2511
                    } elseif (substr($expMatch[2][$k], 0, 1) === '%') {
2512
                        $output = '(' . $output . ' ' . $operator . ' ' . $this->parseConst($expMatch[2][$k], 0, strlen($expMatch[2][$k]), false, 'expression') . ')';
2513
                    } elseif (!empty($expMatch[2][$k])) {
2514
                        $output = '(' . $output . ' ' . $operator . ' ' . str_replace(',', '.', $expMatch[2][$k]) . ')';
2515
                    } else {
2516
                        throw new CompilationException($this, 'Unfinished expression <em>' . $substr . '</em>, missing var or number after math operator');
2517
                    }
2518
                }
2519
            }
2520
2521
            if ($this->autoEscape === true && $curBlock !== 'condition') {
2522
                $output = '(is_string($tmp=' . $output . ') ? htmlspecialchars($tmp, ENT_QUOTES, $this->charset) : $tmp)';
2523
            }
2524
2525
            // handle modifiers
2526
            if ($curBlock !== 'modifier' && $hasModifiers) {
2527
                $ptr    = 0;
2528
                $output = $this->replaceModifiers(array(null, null, $output, $match[5]), 'var', $ptr);
2529
                if ($pointer !== null) {
2530
                    $pointer += $ptr;
2531
                }
2532
                $matchedLength += $ptr;
2533
            }
2534
2535
            if (is_array($parsingParams)) {
2536
                $parsingParams[] = array($output, $key);
2537
2538
                return $parsingParams;
2539
            } elseif ($curBlock === 'namedparam') {
2540
                return array($output, $key);
2541
            } elseif ($curBlock === 'string' || $curBlock === 'delimited_string') {
2542
                return array($matchedLength, $output);
2543
            } elseif ($curBlock === 'expression' || $curBlock === 'variable') {
2544
                return $output;
2545
            } elseif (isset($assign)) {
2546
                return self::PHP_OPEN . $output . ';' . self::PHP_CLOSE;
2547
            }
2548
2549
            return $output;
2550
        } else {
2551
            if ($curBlock === 'string' || $curBlock === 'delimited_string') {
2552
                return array(0, '');
2553
            }
2554
            throw new CompilationException($this, 'Invalid variable name <em>' . $substr . '</em>');
2555
        }
2556
    }
2557
2558
    /**
2559
     * Parses any number of chained method calls/property reads.
2560
     *
2561
     * @param string $output     the variable or whatever upon which the method are called
2562
     * @param string $methodCall method call source, starting at "->"
2563
     * @param string $curBlock   the current parser-block being processed
2564
     * @param int    $pointer    a reference to a pointer that will be increased by the amount of characters parsed
2565
     *
2566
     * @return string parsed call(s)/read(s)
2567
     */
2568
    protected function parseMethodCall($output, $methodCall, $curBlock, &$pointer)
2569
    {
2570
        $ptr = 0;
2571
        $len = strlen($methodCall);
2572
2573
        while ($ptr < $len) {
2574
            if (strpos($methodCall, '->', $ptr) === $ptr) {
2575
                $ptr += 2;
2576
            }
2577
2578
            if (in_array(
2579
                $methodCall[$ptr], array(
2580
                    ';',
2581
                    ',',
2582
                    '/',
2583
                    ' ',
2584
                    "\t",
2585
                    "\r",
2586
                    "\n",
2587
                    ')',
2588
                    '+',
2589
                    '*',
2590
                    '%',
2591
                    '=',
2592
                    '-',
2593
                    '|'
2594
                )
2595
            ) || substr($methodCall, $ptr, strlen($this->rd)) === $this->rd
2596
            ) {
2597
                // break char found
2598
                break;
2599
            }
2600
2601
            if (!preg_match('/^([a-z0-9_]+)(\(.*?\))?/i', substr($methodCall, $ptr), $methMatch)) {
2602
                break;
2603
            }
2604
2605
            if (empty($methMatch[2])) {
2606
                // property
2607
                if ($curBlock === 'root') {
2608
                    $output .= '->' . $methMatch[1];
2609
                } else {
2610
                    $output = '(($tmp = ' . $output . ') ? $tmp->' . $methMatch[1] . ' : null)';
2611
                }
2612
                $ptr += strlen($methMatch[1]);
2613
            } else {
2614
                // method
2615
                if (substr($methMatch[2], 0, 2) === '()') {
2616
                    $parsedCall = $methMatch[1] . '()';
2617
                    $ptr += strlen($methMatch[1]) + 2;
2618
                } else {
2619
                    $parsedCall = $this->parseFunction($methodCall, $ptr, strlen($methodCall), false, 'method', $ptr);
2620
                }
2621
                if ($this->securityPolicy !== null) {
2622
                    $argPos = strpos($parsedCall, '(');
2623
                    $method = strtolower(substr($parsedCall, 0, $argPos));
2624
                    $args   = substr($parsedCall, $argPos);
2625
                    if ($curBlock === 'root') {
2626
                        $output = '$this->getSecurityPolicy()->callMethod($this, ' . $output . ', ' . var_export($method, true) . ', array' . $args . ')';
2627
                    } else {
2628
                        $output = '(($tmp = ' . $output . ') ? $this->getSecurityPolicy()->callMethod($this, $tmp, ' . var_export($method, true) . ', array' . $args . ') : null)';
2629
                    }
2630 View Code Duplication
                } else {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2631
                    if ($curBlock === 'root') {
2632
                        $output .= '->' . $parsedCall;
2633
                    } else {
2634
                        $output = '(($tmp = ' . $output . ') ? $tmp->' . $parsedCall . ' : null)';
2635
                    }
2636
                }
2637
            }
2638
        }
2639
2640
        $pointer += $ptr;
2641
2642
        return $output;
2643
    }
2644
2645
    /**
2646
     * Parses a constant variable (a variable that doesn't contain another variable) and preprocesses it to save
2647
     * runtime processing time.
2648
     *
2649
     * @param string $key      the variable to parse
2650
     * @param string $curBlock the current parser-block being processed
2651
     *
2652
     * @return string parsed variable
2653
     */
2654
    protected function parseVarKey($key, $curBlock)
2655
    {
2656
        if ($key === '') {
2657
            return '$this->scope';
2658
        }
2659
        if (substr($key, 0, 1) === '.') {
2660
            $key = 'dwoo' . $key;
2661
        }
2662
        if (preg_match('#dwoo\.(get|post|server|cookies|session|env|request)((?:\.[a-z0-9_-]+)+)#i', $key, $m)) {
2663
            $global = strtoupper($m[1]);
2664
            if ($global === 'COOKIES') {
2665
                $global = 'COOKIE';
2666
            }
2667
            $key = '$_' . $global;
2668
            foreach (explode('.', ltrim($m[2], '.')) as $part) {
2669
                $key .= '[' . var_export($part, true) . ']';
2670
            }
2671
            if ($curBlock === 'root') {
2672
                $output = $key;
2673
            } else {
2674
                $output = '(isset(' . $key . ')?' . $key . ':null)';
2675
            }
2676
        } elseif (preg_match('#dwoo\\.const\\.([a-z0-9\\\\_:]+)#i', $key, $m)) {
2677
            return $this->parseConstKey($m[1], $curBlock);
2678
        } elseif ($this->scope !== null) {
2679
            if (strstr($key, '.') === false && strstr($key, '[') === false && strstr($key, '->') === false) {
2680
                if ($key === 'dwoo') {
2681
                    $output = '$this->globals';
2682
                } elseif ($key === '_root' || $key === '__') {
2683
                    $output = '$this->data';
2684
                } elseif ($key === '_parent' || $key === '_') {
2685
                    $output = '$this->readParentVar(1)';
2686
                } elseif ($key === '_key') {
2687
                    $output = '$tmp_key';
2688 View Code Duplication
                } else {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2689
                    if ($curBlock === 'root') {
2690
                        $output = '$this->scope["' . $key . '"]';
2691
                    } else {
2692
                        $output = '(isset($this->scope["' . $key . '"]) ? $this->scope["' . $key . '"] : null)';
2693
                    }
2694
                }
2695
            } else {
2696
                preg_match_all('#(\[|->|\.)?((?:[a-z0-9_]|-(?!>))+|(\\\?[\'"])[^\3]*?\3)\]?#i', $key, $m);
2697
2698
                $i = $m[2][0];
2699
                if ($i === '_parent' || $i === '_') {
2700
                    $parentCnt = 0;
2701
2702
                    while (true) {
2703
                        ++ $parentCnt;
2704
                        array_shift($m[2]);
2705
                        array_shift($m[1]);
2706
                        if (current($m[2]) === '_parent') {
2707
                            continue;
2708
                        }
2709
                        break;
2710
                    }
2711
2712
                    $output = '$this->readParentVar(' . $parentCnt . ')';
2713
                } else {
2714
                    if ($i === 'dwoo') {
2715
                        $output = '$this->globals';
2716
                        array_shift($m[2]);
2717
                        array_shift($m[1]);
2718 View Code Duplication
                    } elseif ($i === '_root' || $i === '__') {
2719
                        $output = '$this->data';
2720
                        array_shift($m[2]);
2721
                        array_shift($m[1]);
2722
                    } elseif ($i === '_key') {
2723
                        $output = '$tmp_key';
2724
                    } else {
2725
                        $output = '$this->scope';
2726
                    }
2727
2728
                    while (count($m[1]) && $m[1][0] !== '->') {
2729
                        $m[2][0] = preg_replace('/(^\\\([\'"])|\\\([\'"])$)/x', '$2$3', $m[2][0]);
2730
                        if (substr($m[2][0], 0, 1) == '"' || substr($m[2][0], 0, 1) == "'") {
2731
                            $output .= '[' . $m[2][0] . ']';
2732
                        } else {
2733
                            $output .= '["' . $m[2][0] . '"]';
2734
                        }
2735
                        array_shift($m[2]);
2736
                        array_shift($m[1]);
2737
                    }
2738
2739
                    if ($curBlock !== 'root') {
2740
                        $output = '(isset(' . $output . ') ? ' . $output . ':null)';
2741
                    }
2742
                }
2743
2744
                if (count($m[2])) {
2745
                    unset($m[0]);
2746
                    $output = '$this->readVarInto(' . str_replace("\n", '', var_export($m, true)) . ', ' . $output . ', ' . ($curBlock == 'root' ? 'false' : 'true') . ')';
2747
                }
2748
            }
2749
        } else {
2750
            preg_match_all('#(\[|->|\.)?((?:[a-z0-9_]|-(?!>))+)\]?#i', $key, $m);
2751
            unset($m[0]);
2752
            $output = '$this->readVar(' . str_replace("\n", '', var_export($m, true)) . ')';
2753
        }
2754
2755
        return $output;
2756
    }
2757
2758
    /**
2759
     * Flattens a variable tree, this helps in parsing very complex variables such as $var.foo[$foo.bar->baz].baz,
2760
     * it computes the contents of the brackets first and works out from there.
2761
     *
2762
     * @param array $tree     the variable tree parsed by he parseVar() method that must be flattened
2763
     * @param bool  $recursed leave that to false by default, it is only for internal use
2764
     *
2765
     * @return string flattened tree
2766
     */
2767
    protected function flattenVarTree(array $tree, $recursed = false)
2768
    {
2769
        $out = $recursed ? '".$this->readVarInto(' : '';
2770
        foreach ($tree as $bit) {
2771
            if (is_array($bit)) {
2772
                $out .= '.' . $this->flattenVarTree($bit, false);
2773
            } else {
2774
                $key = str_replace('"', '\\"', $bit);
2775
2776
                if (substr($key, 0, 1) === '$') {
2777
                    $out .= '".' . $this->parseVar($key, 0, strlen($key), false, 'variable') . '."';
2778
                } else {
2779
                    $cnt = substr_count($key, '$');
2780
2781
                    if ($this->debug) {
2782
                        echo 'PARSING SUBVARS IN : ' . $key . "\n";
2783
                    }
2784
                    if ($cnt > 0) {
2785
                        while (-- $cnt >= 0) {
2786
                            if (isset($last)) {
2787
                                $last = strrpos($key, '$', - (strlen($key) - $last + 1));
2788
                            } else {
2789
                                $last = strrpos($key, '$');
2790
                            }
2791
                            preg_match('#\$[a-z0-9_]+((?:(?:\.|->)(?:[a-z0-9_]+|(?R))|\[(?:[a-z0-9_]+|(?R))\]))*' . '((?:(?:[+/*%-])(?:\$[a-z0-9.[\]>_:-]+(?:\([^)]*\))?|[0-9.,]*))*)#i', substr($key, $last), $submatch);
2792
2793
                            $len = strlen($submatch[0]);
2794
                            $key = substr_replace(
2795
                                $key, preg_replace_callback(
2796
                                    '#(\$[a-z0-9_]+((?:(?:\.|->)(?:[a-z0-9_]+|(?R))|\[(?:[a-z0-9_]+|(?R))\]))*)' . '((?:(?:[+/*%-])(?:\$[a-z0-9.[\]>_:-]+(?:\([^)]*\))?|[0-9.,]*))*)#i', array(
2797
                                        $this,
2798
                                        'replaceVarKeyHelper'
2799
                                    ), substr($key, $last, $len)
2800
                                ), $last, $len
2801
                            );
2802
                            if ($this->debug) {
2803
                                echo 'RECURSIVE VAR REPLACEMENT DONE : ' . $key . "\n";
2804
                            }
2805
                        }
2806
                        unset($last);
2807
2808
                        $out .= $key;
2809
                    } else {
2810
                        $out .= $key;
2811
                    }
2812
                }
2813
            }
2814
        }
2815
        $out .= $recursed ? ', true)."' : '';
2816
2817
        return $out;
2818
    }
2819
2820
    /**
2821
     * Helper function that parses a variable.
2822
     *
2823
     * @param array $match the matched variable, array(1=>"string match")
2824
     *
2825
     * @return string parsed variable
2826
     */
2827
    protected function replaceVarKeyHelper($match)
2828
    {
2829
        return '".' . $this->parseVar($match[0], 0, strlen($match[0]), false, 'variable') . '."';
2830
    }
2831
2832
    /**
2833
     * Parses various constants, operators or non-quoted strings.
2834
     *
2835
     * @param string $in            the string within which we must parse something
2836
     * @param int    $from          the starting offset of the parsed area
2837
     * @param int    $to            the ending offset of the parsed area
2838
     * @param mixed  $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by
2839
     *                              default
2840
     * @param string $curBlock      the current parser-block being processed
2841
     * @param mixed  $pointer       a reference to a pointer that will be increased by the amount of characters parsed,
2842
     *                              or null by default
2843
     *
2844
     * @return string parsed values
2845
     * @throws Exception
2846
     */
2847
    protected function parseOthers($in, $from, $to, $parsingParams = false, $curBlock = '', &$pointer = null)
2848
    {
2849
        $substr = substr($in, $from, $to - $from);
2850
2851
        $end = strlen($substr);
2852
2853
        if ($curBlock === 'condition') {
2854
            $breakChars = array(
2855
                '(',
2856
                ')',
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
                $this->rd,
2885
                ';'
2886
            );
2887
        } elseif ($curBlock === 'modifier') {
2888
            $breakChars = array(' ', ',', ')', ':', '|', "\r", "\n", "\t", ';', $this->rd);
2889
        } elseif ($curBlock === 'expression') {
2890
            $breakChars = array('/', '%', '+', '-', '*', ' ', ',', ')', "\r", "\n", "\t", ';', $this->rd);
2891
        } else {
2892
            $breakChars = array(' ', ',', ')', "\r", "\n", "\t", ';', $this->rd);
2893
        }
2894
2895
        $breaker = false;
2896
        foreach ($breakChars as $k => $char) {
2897
            $test = strpos($substr, $char);
2898
            if ($test !== false && $test < $end) {
2899
                $end     = $test;
2900
                $breaker = $k;
2901
            }
2902
        }
2903
2904
        if ($curBlock === 'condition') {
2905
            if ($end === 0 && $breaker !== false) {
2906
                $end = strlen($breakChars[$breaker]);
2907
            }
2908
        }
2909
2910
        if ($end !== false) {
2911
            $substr = substr($substr, 0, $end);
2912
        }
2913
2914
        if ($pointer !== null) {
2915
            $pointer += strlen($substr);
2916
        }
2917
2918
        $src    = $substr;
2919
        $substr = trim($substr);
2920
2921
        if (strtolower($substr) === 'false' || strtolower($substr) === 'no' || strtolower($substr) === 'off') {
2922
            if ($this->debug) {
2923
                echo 'BOOLEAN(FALSE) PARSED' . "\n";
2924
            }
2925
            $substr = 'false';
2926
            $type   = self::T_BOOL;
2927
        } elseif (strtolower($substr) === 'true' || strtolower($substr) === 'yes' || strtolower($substr) === 'on') {
2928
            if ($this->debug) {
2929
                echo 'BOOLEAN(TRUE) PARSED' . "\n";
2930
            }
2931
            $substr = 'true';
2932
            $type   = self::T_BOOL;
2933 View Code Duplication
        } elseif ($substr === 'null' || $substr === 'NULL') {
2934
            if ($this->debug) {
2935
                echo 'NULL PARSED' . "\n";
2936
            }
2937
            $substr = 'null';
2938
            $type   = self::T_NULL;
2939
        } elseif (is_numeric($substr)) {
2940
            $substr = (float)$substr;
2941
            if ((int)$substr == $substr) {
2942
                $substr = (int)$substr;
2943
            }
2944
            $type = self::T_NUMERIC;
2945
            if ($this->debug) {
2946
                echo 'NUMBER (' . $substr . ') PARSED' . "\n";
2947
            }
2948 View Code Duplication
        } elseif (preg_match('{^-?(\d+|\d*(\.\d+))\s*([/*%+-]\s*-?(\d+|\d*(\.\d+)))+$}', $substr)) {
2949
            if ($this->debug) {
2950
                echo 'SIMPLE MATH PARSED . "\n"';
2951
            }
2952
            $type   = self::T_MATH;
2953
            $substr = '(' . $substr . ')';
2954
        } elseif ($curBlock === 'condition' && array_search($substr, $breakChars, true) !== false) {
2955
            if ($this->debug) {
2956
                echo 'BREAKCHAR (' . $substr . ') PARSED' . "\n";
2957
            }
2958
            $type = self::T_BREAKCHAR;
2959
            //$substr = '"'.$substr.'"';
2960
        } else {
2961
            $substr = $this->replaceStringVars('\'' . str_replace('\'', '\\\'', $substr) . '\'', '\'', $curBlock);
2962
            $type   = self::T_UNQUOTED_STRING;
2963
            if ($this->debug) {
2964
                echo 'BLABBER (' . $substr . ') CASTED AS STRING' . "\n";
2965
            }
2966
        }
2967
2968
        if (is_array($parsingParams)) {
2969
            $parsingParams[] = array($substr, $src, $type);
2970
2971
            return $parsingParams;
2972
        } elseif ($curBlock === 'namedparam') {
2973
            return array($substr, $src, $type);
2974
        } elseif ($curBlock === 'expression') {
2975
            return $substr;
2976
        } else {
2977
            throw new Exception('Something went wrong');
2978
        }
2979
    }
2980
2981
    /**
2982
     * Replaces variables within a parsed string.
2983
     *
2984
     * @param string $string   the parsed string
2985
     * @param string $first    the first character parsed in the string, which is the string delimiter (' or ")
2986
     * @param string $curBlock the current parser-block being processed
2987
     *
2988
     * @return string the original string with variables replaced
2989
     */
2990
    protected function replaceStringVars($string, $first, $curBlock = '')
2991
    {
2992
        $pos = 0;
2993
        if ($this->debug) {
2994
            echo 'STRING VAR REPLACEMENT : ' . $string . "\n";
2995
        }
2996
        // replace vars
2997
        while (($pos = strpos($string, '$', $pos)) !== false) {
2998
            $prev = substr($string, $pos - 1, 1);
2999
            if ($prev === '\\') {
3000
                ++ $pos;
3001
                continue;
3002
            }
3003
3004
            $var = $this->parse($string, $pos, null, false, ($curBlock === 'modifier' ? 'modifier' : ($prev === '`' ? 'delimited_string' : 'string')));
3005
            $len = $var[0];
3006
            $var = $this->parse(str_replace('\\' . $first, $first, $string), $pos, null, false, ($curBlock === 'modifier' ? 'modifier' : ($prev === '`' ? 'delimited_string' : 'string')));
3007
3008
            if ($prev === '`' && substr($string, $pos + $len, 1) === '`') {
3009
                $string = substr_replace($string, $first . '.' . $var[1] . '.' . $first, $pos - 1, $len + 2);
3010
            } else {
3011
                $string = substr_replace($string, $first . '.' . $var[1] . '.' . $first, $pos, $len);
3012
            }
3013
            $pos += strlen($var[1]) + 2;
3014
            if ($this->debug) {
3015
                echo 'STRING VAR REPLACEMENT DONE : ' . $string . "\n";
3016
            }
3017
        }
3018
3019
        // handle modifiers
3020
        // TODO Obsolete?
3021
        $string = preg_replace_callback(
3022
            '#("|\')\.(.+?)\.\1((?:\|(?:@?[a-z0-9_]+(?:(?::("|\').+?\4|:[^`]*))*))+)#i', array(
3023
            $this,
3024
            'replaceModifiers'
3025
            ), $string
3026
        );
3027
3028
        // replace escaped dollar operators by unescaped ones if required
3029
        if ($first === "'") {
3030
            $string = str_replace('\\$', '$', $string);
3031
        }
3032
3033
        return $string;
3034
    }
3035
3036
    /**
3037
     * Replaces the modifiers applied to a string or a variable.
3038
     *
3039
     * @param array  $m        the regex matches that must be array(1=>"double or single quotes enclosing a string,
3040
     *                         when applicable", 2=>"the string or var", 3=>"the modifiers matched")
3041
     * @param string $curBlock the current parser-block being processed
3042
     * @param null   $pointer
3043
     *
3044
     * @return string the input enclosed with various function calls according to the modifiers found
3045
     * @throws CompilationException
3046
     * @throws Exception
3047
     */
3048
    protected function replaceModifiers(array $m, $curBlock = null, &$pointer = null)
3049
    {
3050
        if ($this->debug) {
3051
            echo 'PARSING MODIFIERS : ' . $m[3] . "\n";
3052
        }
3053
3054
        if ($pointer !== null) {
3055
            $pointer += strlen($m[3]);
3056
        }
3057
        // remove first pipe
3058
        $cmdstrsrc = substr($m[3], 1);
3059
        // remove last quote if present
3060
        if (substr($cmdstrsrc, - 1, 1) === $m[1]) {
3061
            $cmdstrsrc = substr($cmdstrsrc, 0, - 1);
3062
            $add       = $m[1];
3063
        }
3064
3065
        $output = $m[2];
3066
3067
        $continue = true;
3068
        while (strlen($cmdstrsrc) > 0 && $continue) {
3069
            if ($cmdstrsrc[0] === '|') {
3070
                $cmdstrsrc = substr($cmdstrsrc, 1);
3071
                continue;
3072
            }
3073
            if ($cmdstrsrc[0] === ' ' || $cmdstrsrc[0] === ';' || substr($cmdstrsrc, 0, strlen($this->rd)) === $this->rd) {
3074
                if ($this->debug) {
3075
                    echo 'MODIFIER PARSING ENDED, RIGHT DELIMITER or ";" FOUND' . "\n";
3076
                }
3077
                $continue = false;
3078
                if ($pointer !== null) {
3079
                    $pointer -= strlen($cmdstrsrc);
3080
                }
3081
                break;
3082
            }
3083
            $cmdstr   = $cmdstrsrc;
3084
            $paramsep = ':';
3085 View Code Duplication
            if (!preg_match('/^(@{0,2}[a-z_][a-z0-9_]*)(:)?/i', $cmdstr, $match)) {
3086
                throw new CompilationException($this, 'Invalid modifier name, started with : ' . substr($cmdstr, 0, 10));
3087
            }
3088
            $paramspos = !empty($match[2]) ? strlen($match[1]) : false;
3089
            $func      = $match[1];
3090
3091
            $state = 0;
3092
            if ($paramspos === false) {
3093
                $cmdstrsrc = substr($cmdstrsrc, strlen($func));
3094
                $params    = array();
3095
                if ($this->debug) {
3096
                    echo 'MODIFIER (' . $func . ') CALLED WITH NO PARAMS' . "\n";
3097
                }
3098
            } else {
3099
                $paramstr = substr($cmdstr, $paramspos + 1);
3100 View Code Duplication
                if (substr($paramstr, - 1, 1) === $paramsep) {
3101
                    $paramstr = substr($paramstr, 0, - 1);
3102
                }
3103
3104
                $ptr    = 0;
3105
                $params = array();
3106
                while ($ptr < strlen($paramstr)) {
3107
                    if ($this->debug) {
3108
                        echo 'MODIFIER (' . $func . ') START PARAM PARSING WITH POINTER AT ' . $ptr . "\n";
3109
                    }
3110
                    if ($this->debug) {
3111
                        echo $paramstr . '--' . $ptr . '--' . strlen($paramstr) . '--modifier' . "\n";
3112
                    }
3113
                    $params = $this->parse($paramstr, $ptr, strlen($paramstr), $params, 'modifier', $ptr);
3114
                    if ($this->debug) {
3115
                        echo 'PARAM PARSED, POINTER AT ' . $ptr . "\n";
3116
                    }
3117
3118
                    if ($ptr >= strlen($paramstr)) {
3119
                        if ($this->debug) {
3120
                            echo 'PARAM PARSING ENDED, PARAM STRING CONSUMED' . "\n";
3121
                        }
3122
                        break;
3123
                    }
3124
3125
                    if ($paramstr[$ptr] === ' ' || $paramstr[$ptr] === '|' || $paramstr[$ptr] === ';' || substr($paramstr, $ptr, strlen($this->rd)) === $this->rd) {
3126
                        if ($this->debug) {
3127
                            echo 'PARAM PARSING ENDED, " ", "|", RIGHT DELIMITER or ";" FOUND, POINTER AT ' . $ptr . "\n";
3128
                        }
3129
                        if ($paramstr[$ptr] !== '|') {
3130
                            $continue = false;
3131
                            if ($pointer !== null) {
3132
                                $pointer -= strlen($paramstr) - $ptr;
3133
                            }
3134
                        }
3135
                        ++ $ptr;
3136
                        break;
3137
                    }
3138
                    if ($ptr < strlen($paramstr) && $paramstr[$ptr] === ':') {
3139
                        ++ $ptr;
3140
                    }
3141
                }
3142
                $cmdstrsrc = substr($cmdstrsrc, strlen($func) + 1 + $ptr);
3143
                foreach ($params as $k => $p) {
3144
                    if (is_array($p) && is_array($p[1])) {
3145
                        $state |= 2;
3146
                    } else {
3147
                        if (($state & 2) && preg_match('#^(["\'])(.+?)\1$#', $p[0], $m)) {
3148
                            $params[$k] = array($m[2], array('true', 'true'));
3149
                        } else {
3150
                            if ($state & 2) {
3151
                                throw new CompilationException($this, 'You can not use an unnamed parameter after a named one');
3152
                            }
3153
                            $state |= 1;
3154
                        }
3155
                    }
3156
                }
3157
            }
3158
3159
            // check if we must use array_map with this plugin or not
3160
            $mapped = false;
3161
            if (substr($func, 0, 1) === '@') {
3162
                $func   = substr($func, 1);
3163
                $mapped = true;
3164
            }
3165
3166
            $pluginType = $this->getPluginType($func);
3167
3168
            if ($state & 2) {
3169
                array_unshift($params, array('value', is_array($output) ? $output : array($output, $output)));
3170
            } else {
3171
                array_unshift($params, is_array($output) ? $output : array($output, $output));
3172
            }
3173
3174
            if ($pluginType & Core::NATIVE_PLUGIN) {
3175
                $params = $this->mapParams($params, null, $state);
0 ignored issues
show
null is of type null, but the function expects a callable.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
3176
3177
                $params = $params['*'][0];
3178
3179
                $params = self::implode_r($params);
3180
3181 View Code Duplication
                if ($mapped) {
3182
                    $output = '$this->arrayMap(\'' . $func . '\', array(' . $params . '))';
3183
                } else {
3184
                    $output = $func . '(' . $params . ')';
3185
                }
3186
            } elseif ($pluginType & Core::PROXY_PLUGIN) {
3187
                $params = $this->mapParams($params, $this->getDwoo()->getPluginProxy()->getCallback($func), $state);
3188
                foreach ($params as &$p) {
3189
                    $p = $p[0];
3190
                }
3191
                $output = call_user_func(array($this->getDwoo()->getPluginProxy(), 'getCode'), $func, $params);
3192
            } elseif ($pluginType & Core::SMARTY_MODIFIER) {
3193
                $params = $this->mapParams($params, null, $state);
0 ignored issues
show
null is of type null, but the function expects a callable.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
3194
                $params = $params['*'][0];
3195
3196
                $params = self::implode_r($params);
3197
3198
                if ($pluginType & Core::CUSTOM_PLUGIN) {
3199
                    $callback = $this->customPlugins[$func]['callback'];
3200
                    if (is_array($callback)) {
3201
                        if (is_object($callback[0])) {
3202
                            $output = ($mapped ? '$this->arrayMap' : 'call_user_func_array') . '(array($this->plugins[\'' . $func . '\'][\'callback\'][0], \'' . $callback[1] . '\'), array(' . $params . '))';
3203
                        } else {
3204
                            $output = ($mapped ? '$this->arrayMap' : 'call_user_func_array') . '(array(\'' . $callback[0] . '\', \'' . $callback[1] . '\'), array(' . $params . '))';
3205
                        }
3206
                    } elseif ($mapped) {
3207
                        $output = '$this->arrayMap(\'' . $callback . '\', array(' . $params . '))';
3208
                    } else {
3209
                        $output = $callback . '(' . $params . ')';
3210
                    }
3211
                } elseif ($mapped) {
3212
                    $output = '$this->arrayMap(\'smarty_modifier_' . $func . '\', array(' . $params . '))';
3213
                } else {
3214
                    $output = 'smarty_modifier_' . $func . '(' . $params . ')';
3215
                }
3216
            } else {
3217
                if ($pluginType & Core::CUSTOM_PLUGIN) {
3218
                    $callback   = $this->customPlugins[$func]['callback'];
3219
                    $pluginName = $callback;
3220
                } else {
3221
                    if (class_exists('Plugin' . Core::toCamelCase($func)) !== false || function_exists('Plugin' .
3222
                            Core::toCamelCase($func) . (($pluginType & Core::COMPILABLE_PLUGIN) ? 'Compile' : ''))
3223
                        !== false) {
3224
                        $pluginName = 'Plugin' . Core::toCamelCase($func);
3225
                    } else {
3226
                        $pluginName = Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . Core::toCamelCase($func);
3227
                    }
3228
                    if ($pluginType & Core::CLASS_PLUGIN) {
3229
                        $callback = array($pluginName, ($pluginType & Core::COMPILABLE_PLUGIN) ? 'compile' : 'process');
3230
                    } else {
3231
                        $callback = $pluginName . (($pluginType & Core::COMPILABLE_PLUGIN) ? 'Compile' : '');
3232
                    }
3233
                }
3234
                $params = $this->mapParams($params, $callback, $state);
3235
3236
                foreach ($params as &$p) {
3237
                    $p = $p[0];
3238
                }
3239
3240
                // Only for PHP function, who is not a PHP class
3241
                if ($pluginType & Core::FUNC_PLUGIN && !($pluginType & Core::CLASS_PLUGIN)) {
3242
                    if ($pluginType & Core::COMPILABLE_PLUGIN) {
3243
                        if ($mapped) {
3244
                            throw new CompilationException($this, 'The @ operator can not be used on compiled plugins.');
3245
                        }
3246
                        if ($pluginType & Core::CUSTOM_PLUGIN) {
3247
                            $funcCompiler = $this->customPlugins[$func]['callback'];
3248
                        } else {
3249
                            if (function_exists('Plugin' . Core::toCamelCase($func) . 'Compile') !== false) {
3250
                                $funcCompiler = 'Plugin' . Core::toCamelCase($func) . 'Compile';
3251
                            } else {
3252
                                $funcCompiler = Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . Core::toCamelCase($func) .
3253
                                    'Compile';
3254
                            }
3255
                        }
3256
                        array_unshift($params, $this);
3257
                        $output = call_user_func_array($funcCompiler, $params);
3258
                    } else {
3259
                        array_unshift($params, '$this');
3260
3261
                        $params = self::implode_r($params);
3262 View Code Duplication
                        if ($mapped) {
3263
                            $output = '$this->arrayMap(\'' . $pluginName . '\', array(' . $params . '))';
3264
                        } else {
3265
                            $output = $pluginName . '(' . $params . ')';
3266
                        }
3267
                    }
3268
                } else {
3269
                    if ($pluginType & Core::COMPILABLE_PLUGIN) {
3270
                        if ($mapped) {
3271
                            throw new CompilationException($this, 'The @ operator can not be used on compiled plugins.');
3272
                        }
3273
                        if ($pluginType & Core::CUSTOM_PLUGIN) {
3274
                            $callback = $this->customPlugins[$func]['callback'];
3275
                            if (!is_array($callback)) {
3276
                                if (!method_exists($callback, 'compile')) {
3277
                                    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');
3278
                                }
3279
                                if (($ref = new ReflectionMethod($callback, 'compile')) && $ref->isStatic()) {
3280
                                    $funcCompiler = array($callback, 'compile');
3281
                                } else {
3282
                                    $funcCompiler = array(new $callback(), 'compile');
3283
                                }
3284
                            } else {
3285
                                $funcCompiler = $callback;
3286
                            }
3287
                        } else {
3288
                            if (class_exists('Plugin' . Core::toCamelCase($func)) !== false) {
3289
                                $funcCompiler = array('Plugin' . Core::toCamelCase($func), 'compile');
3290
                            } else {
3291
                                $funcCompiler = array(
3292
                                    Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . Core::toCamelCase($func),
3293
                                    'compile'
3294
                                );
3295
                            }
3296
                            array_unshift($params, $this);
3297
                        }
3298
                        $output = call_user_func_array($funcCompiler, $params);
3299
                    } else {
3300
                        $params = self::implode_r($params);
3301
3302
                        if ($pluginType & Core::CUSTOM_PLUGIN) {
3303
                            if (is_object($callback[0])) {
3304
                                $output = ($mapped ? '$this->arrayMap' : 'call_user_func_array') . '(array($this->plugins[\'' . $func . '\'][\'callback\'][0], \'' . $callback[1] . '\'), array(' . $params . '))';
3305
                            } else {
3306
                                $output = ($mapped ? '$this->arrayMap' : 'call_user_func_array') . '(array(\'' . $callback[0] . '\', \'' . $callback[1] . '\'), array(' . $params . '))';
3307
                            }
3308 View Code Duplication
                        } elseif ($mapped) {
3309
                            $output = '$this->arrayMap(array($this->getObjectPlugin(\''.
3310
                                Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . Core::toCamelCase($func) . '\'), 
3311
                            \'process\'), array(' . $params . '))';
3312
                        } else {
3313
                            if (class_exists('Plugin' . Core::toCamelCase($func)) !== false) {
3314
                                $output = '$this->classCall(\'Plugin' . Core::toCamelCase($func) . '\', array(' . $params . '))';
3315 View Code Duplication
                            } elseif (class_exists(Core::NAMESPACE_PLUGINS_BLOCKS . 'Plugin' . Core::toCamelCase($func)) !== false) {
3316
                                $output = '$this->classCall(\'' . Core::NAMESPACE_PLUGINS_BLOCKS . 'Plugin' . $func . '\', array(' . $params . '))';
3317
                            } elseif (class_exists(Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . Core::toCamelCase($func)) !== false) {
3318
                                $output = '$this->classCall(\'' . Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . $func . '\', array(' . $params . '))';
3319
                            } else {
3320
                                $output = '$this->classCall(\'' . $func . '\', array(' . $params . '))';
3321
                            }
3322
                        }
3323
                    }
3324
                }
3325
            }
3326
        }
3327
3328
        if ($curBlock === 'namedparam') {
3329
            return array($output, $output);
3330
        } elseif ($curBlock === 'var' || $m[1] === null) {
3331
            return $output;
3332
        } elseif ($curBlock === 'string' || $curBlock === 'root') {
3333
            return $m[1] . '.' . $output . '.' . $m[1] . (isset($add) ? $add : null);
3334
        }
3335
3336
        return '';
3337
    }
3338
3339
    /**
3340
     * Recursively implodes an array in a similar manner as var_export() does but with some tweaks
3341
     * to handle pre-compiled values and the fact that we do not need to enclose everything with
3342
     * "array" and do not require top-level keys to be displayed.
3343
     *
3344
     * @param array $params        the array to implode
3345
     * @param bool  $recursiveCall if set to true, the function outputs key names for the top level
3346
     *
3347
     * @return string the imploded array
3348
     */
3349
    public static function implode_r(array $params, $recursiveCall = false)
3350
    {
3351
        $out = '';
3352
        foreach ($params as $k => $p) {
3353
            if (is_array($p)) {
3354
                $out2 = 'array(';
3355
                foreach ($p as $k2 => $v) {
3356
                    $out2 .= var_export($k2, true) . ' => ' . (is_array($v) ? 'array(' . self::implode_r($v, true) . ')' : $v) . ', ';
3357
                }
3358
                $p = rtrim($out2, ', ') . ')';
3359
            }
3360
            if ($recursiveCall) {
3361
                $out .= var_export($k, true) . ' => ' . $p . ', ';
3362
            } else {
3363
                $out .= $p . ', ';
3364
            }
3365
        }
3366
3367
        return rtrim($out, ', ');
3368
    }
3369
3370
    /**
3371
     * Returns the plugin type of a plugin and adds it to the used plugins array if required.
3372
     *
3373
     * @param string $name plugin name, as found in the template
3374
     *
3375
     * @return int type as a multi bit flag composed of the Dwoo plugin types constants
3376
     * @throws Exception
3377
     * @throws SecurityException
3378
     * @throws Exception
3379
     */
3380
    protected function getPluginType($name)
3381
    {
3382
        $pluginType = - 1;
3383
3384
        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)) {
3385
            $phpFunc = true;
3386
        } elseif ($this->securityPolicy !== null && function_exists($name) && array_key_exists(strtolower($name), $this->securityPolicy->getAllowedPhpFunctions()) === false) {
3387
            throw new SecurityException('Call to a disallowed php function : ' . $name);
3388
        }
3389
3390
        while ($pluginType <= 0) {
3391
            // Template plugin compilable
3392
            if (isset($this->templatePlugins[$name])) {
3393
                $pluginType = Core::TEMPLATE_PLUGIN | Core::COMPILABLE_PLUGIN;
3394
            } // Custom plugin
3395
            elseif (isset($this->customPlugins[$name])) {
3396
                $pluginType = $this->customPlugins[$name]['type'] | Core::CUSTOM_PLUGIN;
3397
            } // Class blocks plugin
3398
            elseif (class_exists(Core::NAMESPACE_PLUGINS_BLOCKS . 'Plugin' . Core::toCamelCase($name)) !== false) {
3399
                $pluginType = Core::CLASS_PLUGIN;
3400
                if (is_subclass_of(Core::NAMESPACE_PLUGINS_BLOCKS . 'Plugin' . Core::toCamelCase($name), 'Dwoo\Block\Plugin')) {
3401
                    $pluginType += Core::BLOCK_PLUGIN;
3402
                }
3403
                $interfaces = class_implements(Core::NAMESPACE_PLUGINS_BLOCKS . 'Plugin' . Core::toCamelCase($name));
3404 View Code Duplication
                if (in_array('Dwoo\ICompilable', $interfaces) !== false || in_array('Dwoo\ICompilable\Block', $interfaces) !== false) {
3405
                    $pluginType |= Core::COMPILABLE_PLUGIN;
3406
                }
3407
            } // Class functions plugin
3408
            elseif (class_exists(Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . Core::toCamelCase($name)) !== false) {
3409
                $pluginType = Core::FUNC_PLUGIN + Core::CLASS_PLUGIN;
3410
                $interfaces = class_implements(Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . Core::toCamelCase($name));
3411 View Code Duplication
                if (in_array('Dwoo\ICompilable', $interfaces) !== false || in_array('Dwoo\ICompilable\Block', $interfaces) !== false) {
3412
                    $pluginType |= Core::COMPILABLE_PLUGIN;
3413
                }
3414
            } // Class without namespace
3415
            elseif (class_exists('Plugin' . Core::toCamelCase($name)) !== false) {
3416
                $pluginType = Core::CLASS_PLUGIN;
3417
                $interfaces = class_implements('Plugin' . Core::toCamelCase($name));
3418 View Code Duplication
                if (in_array('Dwoo\ICompilable', $interfaces) !== false || in_array('Dwoo\ICompilable\Block', $interfaces) !== false) {
3419
                    $pluginType |= Core::COMPILABLE_PLUGIN;
3420
                }
3421
            } // Function plugin (with/without namespaces)
3422
            elseif (function_exists(Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . Core::toCamelCase ($name)) !==
3423
                false || function_exists('Plugin' . Core::toCamelCase($name)) !== false) {
3424
                $pluginType = Core::FUNC_PLUGIN;
3425
            } // Function plugin compile (with/without namespaces)
3426
            elseif (function_exists(Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . Core::toCamelCase($name) .
3427
                    'Compile') !== false || function_exists('Plugin' . Core::toCamelCase($name) . 'Compile') !==
3428
                false) {
3429
                $pluginType = Core::FUNC_PLUGIN | Core::COMPILABLE_PLUGIN;
3430
            } // Helper plugin class compile
3431 View Code Duplication
            elseif (class_exists(Core::NAMESPACE_PLUGINS_HELPERS . 'Plugin' . Core::toCamelCase($name)) !== false) {
3432
                $pluginType = Core::CLASS_PLUGIN | Core::COMPILABLE_PLUGIN;
3433
            } // Helper plugin function compile
3434 View Code Duplication
            elseif (function_exists(Core::NAMESPACE_PLUGINS_HELPERS . 'Plugin' . Core::toCamelCase($name) . 'Compile') !== false) {
3435
                $pluginType = Core::FUNC_PLUGIN | Core::COMPILABLE_PLUGIN;
3436
            } // Smarty modifier
3437
            elseif (function_exists('smarty_modifier_' . $name) !== false) {
3438
                $pluginType = Core::SMARTY_MODIFIER;
3439
            } // Smarty function
3440
            elseif (function_exists('smarty_function_' . $name) !== false) {
3441
                $pluginType = Core::SMARTY_FUNCTION;
3442
            } // Smarty block
3443
            elseif (function_exists('smarty_block_' . $name) !== false) {
3444
                $pluginType = Core::SMARTY_BLOCK;
3445
            } // Everything else
3446
            else {
3447
                if ($pluginType === - 1) {
3448
                    try {
3449
                        $this->getDwoo()->getLoader()->loadPlugin('Plugin' . Core::toCamelCase($name));
3450
                    }
3451
                    catch (Exception $e) {
3452
                        if (isset($phpFunc)) {
3453
                            $pluginType = Core::NATIVE_PLUGIN;
3454
                        } elseif (is_object($this->getDwoo()->getPluginProxy()) && $this->getDwoo()->getPluginProxy()->handles($name)) {
3455
                            $pluginType = Core::PROXY_PLUGIN;
3456
                            break;
3457
                        } else {
3458
                            throw $e;
3459
                        }
3460
                    }
3461
                } else {
3462
                    throw new Exception('Plugin "' . $name . '" could not be found, type:' . $pluginType);
3463
                }
3464
                ++ $pluginType;
3465
            }
3466
        }
3467
3468
        if (($pluginType & Core::COMPILABLE_PLUGIN) === 0 && ($pluginType & Core::NATIVE_PLUGIN) === 0 && ($pluginType & Core::PROXY_PLUGIN) === 0) {
3469
            $this->addUsedPlugin(Core::toCamelCase($name), $pluginType);
3470
        }
3471
3472
        return $pluginType;
3473
    }
3474
3475
    /**
3476
     * Allows a plugin to load another one at compile time, this will also mark
3477
     * it as used by this template so it will be loaded at runtime (which can be
3478
     * useful for compiled plugins that rely on another plugin when their compiled
3479
     * code runs).
3480
     *
3481
     * @param string $name the plugin name
3482
     *
3483
     * @return void
3484
     */
3485
    public function loadPlugin($name)
3486
    {
3487
        $this->getPluginType($name);
3488
    }
3489
3490
    /**
3491
     * Runs htmlentities over the matched <?php ?> blocks when the security policy enforces that.
3492
     *
3493
     * @param array $match matched php block
3494
     *
3495
     * @return string the htmlentities-converted string
3496
     */
3497
    protected function phpTagEncodingHelper($match)
3498
    {
3499
        return htmlspecialchars($match[0]);
3500
    }
3501
3502
    /**
3503
     * Maps the parameters received from the template onto the parameters required by the given callback.
3504
     *
3505
     * @param array    $params   the array of parameters
3506
     * @param callback $callback the function or method to reflect on to find out the required parameters
3507
     * @param int      $callType the type of call in the template, 0 = no params, 1 = php-style call, 2 = named
3508
     *                           parameters call
3509
     * @param array    $map      the parameter map to use, if not provided it will be built from the callback
3510
     *
3511
     * @return array parameters sorted in the correct order with missing optional parameters filled
3512
     * @throws CompilationException
3513
     */
3514
    protected function mapParams(array $params, $callback, $callType = 2, $map = null)
3515
    {
3516
        if (!$map) {
3517
            $map = $this->getParamMap($callback);
3518
        }
3519
3520
        $paramlist = array();
3521
3522
        // transforms the parameter array from (x=>array('paramname'=>array(values))) to (paramname=>array(values))
3523
        $ps = array();
3524
        foreach ($params as $p) {
3525
            if (is_array($p[1])) {
3526
                $ps[$p[0]] = $p[1];
3527
            } else {
3528
                $ps[] = $p;
3529
            }
3530
        }
3531
3532
        // loops over the param map and assigns values from the template or default value for unset optional params
3533
        foreach ($map as $k => $v){
3534
            if ($v[0] === '*') {
3535
                // "rest" array parameter, fill every remaining params in it and then break
3536
                if (count($ps) === 0) {
3537
                    if ($v[1] === false) {
3538
                        throw new CompilationException(
3539
                            $this, 'Rest argument missing for ' . str_replace(
3540
                                array(
3541
                                    Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin',
3542
                                'Compile'
3543
                                ), '', (is_array($callback) ? $callback[0] : $callback)
3544
                            )
3545
                        );
3546
                    } else {
3547
                        break;
3548
                    }
3549
                }
3550
                $tmp  = array();
3551
                $tmp2 = array();
3552
                $tmp3 = array();
3553
                foreach ($ps as $i => $p) {
3554
                    $tmp[$i]  = $p[0];
3555
                    $tmp2[$i] = $p[1];
3556
                    $tmp3[$i] = isset($p[2]) ? $p[2] : 0;
3557
                    unset($ps[$i]);
3558
                }
3559
                $paramlist[$v[0]] = array($tmp, $tmp2, $tmp3);
3560
                unset($tmp, $tmp2, $i, $p);
3561
                break;
3562
            } elseif (isset($ps[$v[0]])) {
3563
                // parameter is defined as named param
3564
                $paramlist[$v[0]] = $ps[$v[0]];
3565
                unset($ps[$v[0]]);
3566
            } elseif (isset($ps[$k])) {
3567
                // parameter is defined as ordered param
3568
                $paramlist[$v[0]] = $ps[$k];
3569
                unset($ps[$k]);
3570
            } elseif ($v[1] === false) {
3571
                // parameter is not defined and not optional, throw error
3572
                if (is_array($callback)) {
3573
                    if (is_object($callback[0])) {
3574
                        $name = get_class($callback[0]) . '::' . $callback[1];
3575
                    } else {
3576
                        $name = $callback[0];
3577
                    }
3578
                } else {
3579
                    $name = $callback;
3580
                }
3581
3582
                throw new CompilationException(
3583
                    $this, 'Argument ' . $k . '/' . $v[0] . ' missing for ' . str_replace(
3584
                        array(
3585
                            Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin',
3586
                        'Compile'
3587
                        ), '', $name
3588
                    )
3589
                );
3590
            } elseif ($v[2] === null) {
3591
                // enforce lowercased null if default value is null (php outputs NULL with var export)
3592
                $paramlist[$v[0]] = array('null', null, self::T_NULL);
3593
            } else {
3594
                // outputs default value with var_export
3595
                $paramlist[$v[0]] = array(var_export($v[2], true), $v[2]);
3596
            }
3597
        }
3598
3599
        if (count($ps)) {
3600
            foreach ($ps as $i => $p) {
3601
                array_push($paramlist, $p);
3602
            }
3603
        }
3604
3605
        return $paramlist;
3606
    }
3607
3608
    /**
3609
     * Returns the parameter map of the given callback, it filters out entries typed as Dwoo and Compiler and turns the
3610
     * rest parameter into a "*".
3611
     *
3612
     * @param callback $callback the function/method to reflect on
3613
     *
3614
     * @return array processed parameter map
3615
     */
3616
    protected function getParamMap($callback)
3617
    {
3618
        if (is_null($callback)) {
3619
            return array(array('*', true));
3620
        }
3621
        if (is_array($callback)) {
3622
            $ref = new ReflectionMethod($callback[0], $callback[1]);
3623
        } else {
3624
            $ref = new ReflectionFunction($callback);
3625
        }
3626
3627
        $out = array();
3628
        foreach ($ref->getParameters() as $param) {
3629
            if (($class = $param->getClass()) !== null && $class->name === 'Dwoo\Core') {
3630
                continue;
3631
            }
3632
            if (($class = $param->getClass()) !== null && $class->name === 'Dwoo\Compiler') {
3633
                continue;
3634
            }
3635
            if ($param->getName() === 'rest' && $param->isArray() === true) {
3636
                $out[] = array('*', $param->isOptional(), null);
3637
                continue;
3638
            }
3639
            $out[] = array(
3640
                $param->getName(),
3641
                $param->isOptional(),
3642
                $param->isOptional() ? $param->getDefaultValue() : null
3643
            );
3644
        }
3645
3646
        return $out;
3647
    }
3648
3649
    /**
3650
     * Returns a default instance of this compiler, used by default by all Dwoo templates that do not have a
3651
     * specific compiler assigned and when you do not override the default compiler factory function.
3652
     *
3653
     * @see    Core::setDefaultCompilerFactory()
3654
     * @return Compiler
3655
     */
3656
    public static function compilerFactory()
3657
    {
3658
        if (self::$instance === null) {
3659
            self::$instance = new self();
3660
        }
3661
3662
        return self::$instance;
3663
    }
3664
}
3665