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

lib/Dwoo/Compiler.php (3 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * Copyright (c) 2013-2017
4
 *
5
 * @category  Library
6
 * @package   Dwoo
7
 * @author    Jordi Boggiano <[email protected]>
8
 * @author    David Sanchez <[email protected]>
9
 * @copyright 2008-2013 Jordi Boggiano
10
 * @copyright 2013-2017 David Sanchez
11
 * @license   http://dwoo.org/LICENSE 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 {
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 {
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 {
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 {
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 {
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 {
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 {
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)) {
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)) {
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 {
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 {
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