Issues (292)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

lib/Dwoo/Compiler.php (1 issue)

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.3.6
13
 * @date      2017-03-22
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 $core;
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->getCore()->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 getCore()
654
    {
655
        return $this->core;
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      $core
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 $core, ITemplate $template)
713
    {
714
        // init vars
715
        //		$compiled = '';
716
        $tpl                  = $template->getSource();
717
        $ptr                  = 0;
718
        $this->core           = $core;
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->getCore()->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 = current(array_filter([
1084
            'Plugin' . Core::toCamelCase($type),
1085
            Core::NAMESPACE_PLUGINS_BLOCKS . 'Plugin' . Core::toCamelCase($type)
1086
        ], 'class_exists'));
1087
        if (false === $class) {
1088
            $this->getCore()->getLoader()->loadPlugin($type);
1089
        }
1090
        $params = $this->mapParams($params, array($class, 'init'), $paramtype);
1091
1092
        $this->stack[]  = array(
1093
            'type'   => $type,
1094
            'params' => $params,
1095
            'custom' => false,
1096
            'class'  => $class,
1097
            'buffer' => null
1098
        );
1099
        $this->curBlock = &$this->stack[count($this->stack) - 1];
1100
1101
        return call_user_func(array($class, 'preProcessing'), $this, $params, '', '', $type);
1102
    }
1103
1104
    /**
1105
     * Adds a custom block to the top of the block stack.
1106
     *
1107
     * @param string $type      block type (name)
1108
     * @param array  $params    the parameters array
1109
     * @param int    $paramtype the parameters type (see mapParams), 0, 1 or 2
1110
     *
1111
     * @return string the preProcessing() method's output
1112
     */
1113
    public function addCustomBlock($type, array $params, $paramtype)
1114
    {
1115
        $callback = $this->customPlugins[$type]['callback'];
1116
        if (is_array($callback)) {
1117
            $class = is_object($callback[0]) ? get_class($callback[0]) : $callback[0];
1118
        } else {
1119
            $class = $callback;
1120
        }
1121
1122
        $params = $this->mapParams($params, array($class, 'init'), $paramtype);
1123
1124
        $this->stack[]  = array(
1125
            'type'   => $type,
1126
            'params' => $params,
1127
            'custom' => true,
1128
            'class'  => $class,
1129
            'buffer' => null
1130
        );
1131
        $this->curBlock = &$this->stack[count($this->stack) - 1];
1132
1133
        return call_user_func(array($class, 'preProcessing'), $this, $params, '', '', $type);
1134
    }
1135
1136
    /**
1137
     * Injects a block at the top of the plugin stack without calling its preProcessing method.
1138
     * used by {else} blocks to re-add themselves after having closed everything up to their parent
1139
     *
1140
     * @param string $type   block type (name)
1141
     * @param array  $params parameters array
1142
     */
1143
    public function injectBlock($type, array $params)
1144
    {
1145
        if ($this->debug) {
1146
            echo 'Compiler::' . __FUNCTION__ . "\n";
1147
        }
1148
1149
        $class = Core::NAMESPACE_PLUGINS_BLOCKS . 'Plugin' . Core::toCamelCase($type);
1150
        if (class_exists($class) === false) {
1151
            $this->getCore()->getLoader()->loadPlugin($type);
1152
        }
1153
        $this->stack[]  = array(
1154
            'type'   => $type,
1155
            'params' => $params,
1156
            'custom' => false,
1157
            'class'  => $class,
1158
            'buffer' => null
1159
        );
1160
        $this->curBlock = &$this->stack[count($this->stack) - 1];
1161
    }
1162
1163
    /**
1164
     * Removes the closest-to-top block of the given type and all other
1165
     * blocks encountered while going down the block stack.
1166
     *
1167
     * @param string $type block type (name)
1168
     *
1169
     * @return string the output of all postProcessing() method's return values of the closed blocks
1170
     * @throws CompilationException
1171
     */
1172
    public function removeBlock($type)
1173
    {
1174
        if ($this->debug) {
1175
            echo 'Compiler::' . __FUNCTION__ . "\n";
1176
        }
1177
1178
        $output = '';
1179
1180
        $pluginType = $this->getPluginType($type);
1181
        if ($pluginType & Core::SMARTY_BLOCK) {
1182
            $type = 'Smartyinterface';
1183
        }
1184
        while (true) {
1185
            while ($top = array_pop($this->stack)) {
1186
                if ($top['custom']) {
1187
                    $class = $top['class'];
1188 View Code Duplication
                } else {
1189
                    $class = current(array_filter([
1190
                        'Plugin' . Core::toCamelCase($top['type']),
1191
                        Core::NAMESPACE_PLUGINS_BLOCKS . 'Plugin' . Core::toCamelCase($top['type'])
1192
                    ], 'class_exists'));
1193
                }
1194
                if (count($this->stack)) {
1195
                    $this->curBlock = &$this->stack[count($this->stack) - 1];
1196
                    $this->push(call_user_func(array(
1197
                        $class,
1198
                        'postProcessing'
1199
                    ), $this, $top['params'], '', '', $top['buffer']), 0);
1200
                } else {
1201
                    $null           = null;
1202
                    $this->curBlock = &$null;
1203
                    $output         = call_user_func(
1204
                        array(
1205
                        $class,
1206
                        'postProcessing'
1207
                        ), $this, $top['params'], '', '', $top['buffer']
1208
                    );
1209
                }
1210
1211
                if ($top['type'] === $type) {
1212
                    break 2;
1213
                }
1214
            }
1215
1216
            throw new CompilationException($this, 'Syntax malformation, a block of type "' . $type . '" was closed but was not opened');
1217
        }
1218
1219
        return $output;
1220
    }
1221
1222
    /**
1223
     * Returns a reference to the first block of the given type encountered and
1224
     * optionally closes all blocks until it finds it
1225
     * this is mainly used by {else} plugins to close everything that was opened
1226
     * between their parent and themselves.
1227
     *
1228
     * @param string $type       the block type (name)
1229
     * @param bool   $closeAlong whether to close all blocks encountered while going down the block stack or not
1230
     *
1231
     * @return mixed &array the array is as such: array('type'=>pluginName, 'params'=>parameter array,
1232
     *               'custom'=>bool defining whether it's a custom plugin or not, for internal use)
1233
     * @throws CompilationException
1234
     */
1235
    public function &findBlock($type, $closeAlong = false)
1236
    {
1237
        if ($closeAlong === true) {
1238 View Code Duplication
            while ($b = end($this->stack)) {
1239
                if ($b['type'] === $type) {
1240
                    return $this->stack[key($this->stack)];
1241
                }
1242
                $this->push($this->removeTopBlock(), 0);
1243
            }
1244
        } else {
1245
            end($this->stack);
1246 View Code Duplication
            while ($b = current($this->stack)) {
1247
                if ($b['type'] === $type) {
1248
                    return $this->stack[key($this->stack)];
1249
                }
1250
                prev($this->stack);
1251
            }
1252
        }
1253
1254
        throw new CompilationException($this, 'A parent block of type "' . $type . '" is required and can not be found');
1255
    }
1256
1257
    /**
1258
     * Returns a reference to the current block array.
1259
     *
1260
     * @return array the array is as such: array('type'=>pluginName, 'params'=>parameter array,
1261
     *                'custom'=>bool defining whether it's a custom plugin or not, for internal use)
1262
     */
1263
    public function &getCurrentBlock()
1264
    {
1265
        return $this->curBlock;
1266
    }
1267
1268
    /**
1269
     * Removes the block at the top of the stack and calls its postProcessing() method.
1270
     *
1271
     * @return string the postProcessing() method's output
1272
     * @throws CompilationException
1273
     */
1274
    public function removeTopBlock()
1275
    {
1276
        if ($this->debug) {
1277
            echo 'Compiler::' . __FUNCTION__ . "\n";
1278
        }
1279
1280
        $o = array_pop($this->stack);
1281
        if ($o === null) {
1282
            throw new CompilationException($this, 'Syntax malformation, a block of unknown type was closed but was not opened.');
1283
        }
1284
        if ($o['custom']) {
1285
            $class = $o['class'];
1286
        } else {
1287
            $class = Core::NAMESPACE_PLUGINS_BLOCKS . 'Plugin' . Core::toCamelCase($o['type']);
1288
        }
1289
1290
        $this->curBlock = &$this->stack[count($this->stack) - 1];
1291
1292
        return call_user_func(array($class, 'postProcessing'), $this, $o['params'], '', '', $o['buffer']);
1293
    }
1294
1295
    /**
1296
     * Returns the compiled parameters (for example a variable's compiled parameter will be "$this->scope['key']") out
1297
     * of the given parameter array.
1298
     *
1299
     * @param array $params parameter array
1300
     *
1301
     * @return array filtered parameters
1302
     */
1303 View Code Duplication
    public function getCompiledParams(array $params)
1304
    {
1305
        foreach ($params as $k => $p) {
1306
            if (is_array($p)) {
1307
                $params[$k] = $p[0];
1308
            }
1309
        }
1310
1311
        return $params;
1312
    }
1313
1314
    /**
1315
     * Returns the real parameters (for example a variable's real parameter will be its key, etc) out of the given
1316
     * parameter array.
1317
     *
1318
     * @param array $params parameter array
1319
     *
1320
     * @return array filtered parameters
1321
     */
1322 View Code Duplication
    public function getRealParams(array $params)
1323
    {
1324
        foreach ($params as $k => $p) {
1325
            if (is_array($p)) {
1326
                $params[$k] = $p[1];
1327
            }
1328
        }
1329
1330
        return $params;
1331
    }
1332
1333
    /**
1334
     * Returns the token of each parameter out of the given parameter array.
1335
     *
1336
     * @param array $params parameter array
1337
     *
1338
     * @return array tokens
1339
     */
1340 View Code Duplication
    public function getParamTokens(array $params)
1341
    {
1342
        foreach ($params as $k => $p) {
1343
            if (is_array($p)) {
1344
                $params[$k] = isset($p[2]) ? $p[2] : 0;
1345
            }
1346
        }
1347
1348
        return $params;
1349
    }
1350
1351
    /**
1352
     * Entry point of the parser, it redirects calls to other parse* functions.
1353
     *
1354
     * @param string $in            the string within which we must parse something
1355
     * @param int    $from          the starting offset of the parsed area
1356
     * @param int    $to            the ending offset of the parsed area
1357
     * @param mixed  $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by
1358
     *                              default
1359
     * @param string $curBlock      the current parser-block being processed
1360
     * @param mixed  $pointer       a reference to a pointer that will be increased by the amount of characters parsed,
1361
     *                              or null by default
1362
     *
1363
     * @return string parsed values
1364
     * @throws CompilationException
1365
     */
1366
    protected function parse($in, $from, $to, $parsingParams = false, $curBlock = '', &$pointer = null)
1367
    {
1368
        if ($this->debug) {
1369
            echo 'Compiler::' . __FUNCTION__ . "\n";
1370
        }
1371
1372
        if ($to === null) {
1373
            $to = strlen($in);
1374
        }
1375
        $first = substr($in, $from, 1);
1376
1377
        if ($first === false) {
1378
            throw new CompilationException($this, 'Unexpected EOF, a template tag was not closed');
1379
        }
1380
1381
        while ($first === ' ' || $first === "\n" || $first === "\t" || $first === "\r") {
1382 View Code Duplication
            if ($curBlock === 'root' && substr($in, $from, strlen($this->rd)) === $this->rd) {
1383
                // end template tag
1384
                $pointer += strlen($this->rd);
1385
                if ($this->debug) {
1386
                    echo 'TEMPLATE PARSING ENDED' . "\n";
1387
                }
1388
1389
                return false;
1390
            }
1391
            ++ $from;
1392
            if ($pointer !== null) {
1393
                ++ $pointer;
1394
            }
1395
            if ($from >= $to) {
1396
                if (is_array($parsingParams)) {
1397
                    return $parsingParams;
1398
                } else {
1399
                    return '';
1400
                }
1401
            }
1402
            $first = $in[$from];
1403
        }
1404
1405
        $substr = substr($in, $from, $to - $from);
1406
1407
        if ($this->debug) {
1408
            echo 'PARSE CALL : PARSING "' . htmlentities(substr($in, $from, min($to - $from, 50))) . (($to - $from) > 50 ? '...' : '') . '" @ ' . $from . ':' . $to . ' in ' . $curBlock . ' : pointer=' . $pointer . "\n";
1409
        }
1410
        $parsed = '';
1411
1412
        if ($curBlock === 'root' && $first === '*') {
1413
            $src      = $this->getTemplateSource();
1414
            $startpos = $this->getPointer() - strlen($this->ld);
1415
            if (substr($src, $startpos, strlen($this->ld)) === $this->ld) {
1416
                if ($startpos > 0) {
1417
                    do {
1418
                        $char = substr($src, -- $startpos, 1);
1419
                        if ($char == "\n") {
1420
                            ++ $startpos;
1421
                            $whitespaceStart = true;
1422
                            break;
1423
                        }
1424
                    }
1425
                    while ($startpos > 0 && ($char == ' ' || $char == "\t"));
1426
                }
1427
1428
                if (!isset($whitespaceStart)) {
1429
                    $startpos = $this->getPointer();
1430
                } else {
1431
                    $pointer -= $this->getPointer() - $startpos;
1432
                }
1433
1434
                if ($this->allowNestedComments && strpos($src, $this->ld . '*', $this->getPointer()) !== false) {
1435
                    $comOpen  = $this->ld . '*';
1436
                    $comClose = '*' . $this->rd;
1437
                    $level    = 1;
1438
                    $ptr      = $this->getPointer();
1439
1440
                    while ($level > 0 && $ptr < strlen($src)) {
1441
                        $open  = strpos($src, $comOpen, $ptr);
1442
                        $close = strpos($src, $comClose, $ptr);
1443
1444
                        if ($open !== false && $close !== false) {
1445
                            if ($open < $close) {
1446
                                $ptr = $open + strlen($comOpen);
1447
                                ++ $level;
1448
                            } else {
1449
                                $ptr = $close + strlen($comClose);
1450
                                -- $level;
1451
                            }
1452
                        } elseif ($open !== false) {
1453
                            $ptr = $open + strlen($comOpen);
1454
                            ++ $level;
1455
                        } elseif ($close !== false) {
1456
                            $ptr = $close + strlen($comClose);
1457
                            -- $level;
1458
                        } else {
1459
                            $ptr = strlen($src);
1460
                        }
1461
                    }
1462
                    $endpos = $ptr - strlen('*' . $this->rd);
1463 View Code Duplication
                } else {
1464
                    $endpos = strpos($src, '*' . $this->rd, $startpos);
1465
                    if ($endpos == false) {
1466
                        throw new CompilationException($this, 'Un-ended comment');
1467
                    }
1468
                }
1469
                $pointer += $endpos - $startpos + strlen('*' . $this->rd);
1470
                if (isset($whitespaceStart) && preg_match('#^[\t ]*\r?\n#', substr($src, $endpos + strlen('*' . $this->rd)), $m)) {
1471
                    $pointer += strlen($m[0]);
1472
                    $this->curBlock['buffer'] = substr($this->curBlock['buffer'], 0, strlen($this->curBlock['buffer']) - ($this->getPointer() - $startpos - strlen($this->ld)));
1473
                }
1474
1475
                return false;
1476
            }
1477
        }
1478
1479
        if ($first === '$') {
1480
            // var
1481
            $out    = $this->parseVar($in, $from, $to, $parsingParams, $curBlock, $pointer);
1482
            $parsed = 'var';
1483
        } elseif ($first === '%' && preg_match('#^%[a-z_\\\\]#i', $substr)) {
1484
            // Short constant
1485
            $out = $this->parseConst($in, $from, $to, $parsingParams, $curBlock, $pointer);
1486
        } elseif (($first === '"' || $first === "'") && !(is_array($parsingParams) && preg_match('#^([\'"])[a-z0-9_]+\1\s*=>?(?:\s+|[^=])#i', $substr))) {
1487
            // string
1488
            $out = $this->parseString($in, $from, $to, $parsingParams, $curBlock, $pointer);
1489
        } 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)) {
1490
            // func
1491
            $out    = $this->parseFunction($in, $from, $to, $parsingParams, $curBlock, $pointer);
1492
            $parsed = 'func';
1493
        } elseif ($first === ';') {
1494
            // instruction end
1495
            if ($this->debug) {
1496
                echo 'END OF INSTRUCTION' . "\n";
1497
            }
1498
            if ($pointer !== null) {
1499
                ++ $pointer;
1500
            }
1501
1502
            return $this->parse($in, $from + 1, $to, false, 'root', $pointer);
1503
        } elseif ($curBlock === 'root' && preg_match('#^/([a-z_][a-z0-9_]*)?#i', $substr, $match)) {
1504
            // close block
1505 View Code Duplication
            if (!empty($match[1]) && $match[1] == 'else') {
1506
                throw new CompilationException($this, 'Else blocks must not be closed explicitly, they are automatically closed when their parent block is closed');
1507
            }
1508 View Code Duplication
            if (!empty($match[1]) && $match[1] == 'elseif') {
1509
                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');
1510
            }
1511
            if ($pointer !== null) {
1512
                $pointer += strlen($match[0]);
1513
            }
1514
            if (empty($match[1])) {
1515
                if ($this->curBlock['type'] == 'else' || $this->curBlock['type'] == 'elseif') {
1516
                    $pointer -= strlen($match[0]);
1517
                }
1518
                if ($this->debug) {
1519
                    echo 'TOP BLOCK CLOSED' . "\n";
1520
                }
1521
1522
                return $this->removeTopBlock();
1523
            } else {
1524
                if ($this->debug) {
1525
                    echo 'BLOCK OF TYPE ' . $match[1] . ' CLOSED' . "\n";
1526
                }
1527
1528
                return $this->removeBlock($match[1]);
1529
            }
1530 View Code Duplication
        } elseif ($curBlock === 'root' && substr($substr, 0, strlen($this->rd)) === $this->rd) {
1531
            // end template tag
1532
            if ($this->debug) {
1533
                echo 'TAG PARSING ENDED' . "\n";
1534
            }
1535
            $pointer += strlen($this->rd);
1536
1537
            return false;
1538
        } elseif (is_array($parsingParams) && preg_match('#^(([\'"]?)[a-z0-9_]+\2\s*=' . ($curBlock === 'array' ? '>?' : '') . ')(?:\s+|[^=]).*#i', $substr, $match)) {
1539
            // named parameter
1540
            if ($this->debug) {
1541
                echo 'NAMED PARAM FOUND' . "\n";
1542
            }
1543
            $len = strlen($match[1]);
1544
            while (substr($in, $from + $len, 1) === ' ') {
1545
                ++ $len;
1546
            }
1547
            if ($pointer !== null) {
1548
                $pointer += $len;
1549
            }
1550
1551
            $output = array(
1552
                trim($match[1], " \t\r\n=>'\""),
1553
                $this->parse($in, $from + $len, $to, false, 'namedparam', $pointer)
1554
            );
1555
1556
            $parsingParams[] = $output;
1557
1558
            return $parsingParams;
1559
        } elseif (preg_match('#^(\\\\?[a-z_](?:\\\\?[a-z0-9_]+)*::\$[a-z0-9_]+)#i', $substr, $match)) {
1560
            // static member access
1561
            $parsed = 'var';
1562
            if (is_array($parsingParams)) {
1563
                $parsingParams[] = array($match[1], $match[1]);
1564
                $out             = $parsingParams;
1565
            } else {
1566
                $out = $match[1];
1567
            }
1568
            $pointer += strlen($match[1]);
1569
        } elseif ($substr !== '' && (is_array($parsingParams) || $curBlock === 'namedparam' || $curBlock === 'condition' || $curBlock === 'expression')) {
1570
            // unquoted string, bool or number
1571
            $out = $this->parseOthers($in, $from, $to, $parsingParams, $curBlock, $pointer);
1572 View Code Duplication
        } else {
1573
            // parse error
1574
            throw new CompilationException($this, 'Parse error in "' . substr($in, $from, $to - $from) . '"');
1575
        }
1576
1577
        if (empty($out)) {
1578
            return '';
1579
        }
1580
1581
        $substr = substr($in, $pointer, $to - $pointer);
1582
1583
        // var parsed, check if any var-extension applies
1584
        if ($parsed === 'var') {
1585
            if (preg_match('#^\s*([/%+*-])\s*([a-z0-9]|\$)#i', $substr, $match)) {
1586
                if ($this->debug) {
1587
                    echo 'PARSING POST-VAR EXPRESSION ' . $substr . "\n";
1588
                }
1589
                // parse expressions
1590
                $pointer += strlen($match[0]) - 1;
1591
                if (is_array($parsingParams)) {
1592
                    if ($match[2] == '$') {
1593
                        $expr = $this->parseVar($in, $pointer, $to, array(), $curBlock, $pointer);
1594
                    } else {
1595
                        $expr = $this->parse($in, $pointer, $to, array(), 'expression', $pointer);
1596
                    }
1597
                    $out[count($out) - 1][0] .= $match[1] . $expr[0][0];
1598
                    $out[count($out) - 1][1] .= $match[1] . $expr[0][1];
1599
                } else {
1600
                    if ($match[2] == '$') {
1601
                        $expr = $this->parseVar($in, $pointer, $to, false, $curBlock, $pointer);
1602
                    } else {
1603
                        $expr = $this->parse($in, $pointer, $to, false, 'expression', $pointer);
1604
                    }
1605
                    if (is_array($out) && is_array($expr)) {
1606
                        $out[0] .= $match[1] . $expr[0];
1607
                        $out[1] .= $match[1] . $expr[1];
1608
                    } elseif (is_array($out)) {
1609
                        $out[0] .= $match[1] . $expr;
1610
                        $out[1] .= $match[1] . $expr;
1611
                    } elseif (is_array($expr)) {
1612
                        $out .= $match[1] . $expr[0];
1613
                    } else {
1614
                        $out .= $match[1] . $expr;
1615
                    }
1616
                }
1617
            } elseif ($curBlock === 'root' && preg_match('#^(\s*(?:[+/*%-.]=|=|\+\+|--)\s*)(.*)#s', $substr, $match)) {
1618
                if ($this->debug) {
1619
                    echo 'PARSING POST-VAR ASSIGNMENT ' . $substr . "\n";
1620
                }
1621
                // parse assignment
1622
                $value    = $match[2];
1623
                $operator = trim($match[1]);
1624
                if (substr($value, 0, 1) == '=') {
1625
                    throw new CompilationException($this, 'Unexpected "=" in <em>' . $substr . '</em>');
1626
                }
1627
1628
                if ($pointer !== null) {
1629
                    $pointer += strlen($match[1]);
1630
                }
1631
1632
                if ($operator !== '++' && $operator !== '--') {
1633
                    $parts = array();
1634
                    $ptr   = 0;
1635
                    $parts = $this->parse($value, 0, strlen($value), $parts, 'condition', $ptr);
1636
                    $pointer += $ptr;
1637
1638
                    // load if plugin
1639
                    try {
1640
                        $this->getPluginType('if');
1641
                    }
1642
                    catch (Exception $e) {
1643
                        throw new CompilationException($this, 'Assignments require the "if" plugin to be accessible');
1644
                    }
1645
1646
                    $parts  = $this->mapParams($parts, array(Core::NAMESPACE_PLUGINS_BLOCKS . 'PluginIf', 'init'), 1);
1647
                    $tokens = $this->getParamTokens($parts);
1648
                    $parts  = $this->getCompiledParams($parts);
1649
1650
                    $value = PluginIf::replaceKeywords($parts['*'], $tokens['*'], $this);
1651
                    $echo  = '';
1652
                } else {
1653
                    $value = array();
1654
                    $echo  = 'echo ';
1655
                }
1656
1657
                if ($this->autoEscape) {
1658
                    $out = preg_replace('#\(is_string\(\$tmp=(.+?)\) \? htmlspecialchars\(\$tmp, ENT_QUOTES, \$this->charset\) : \$tmp\)#', '$1', $out);
1659
                }
1660
                $out = self::PHP_OPEN . $echo . $out . $operator . implode(' ', $value) . self::PHP_CLOSE;
1661
            } elseif ($curBlock === 'array' && is_array($parsingParams) && preg_match('#^(\s*=>?\s*)#', $substr, $match)) {
1662
                // parse namedparam with var as name (only for array)
1663
                if ($this->debug) {
1664
                    echo 'VARIABLE NAMED PARAM (FOR ARRAY) FOUND' . "\n";
1665
                }
1666
                $len = strlen($match[1]);
1667
                $var = $out[count($out) - 1];
1668
                $pointer += $len;
1669
1670
                $output = array($var[0], $this->parse($substr, $len, null, false, 'namedparam', $pointer));
1671
1672
                $parsingParams[] = $output;
1673
1674
                return $parsingParams;
1675
            }
1676
        }
1677
1678
        if ($curBlock !== 'modifier' && ($parsed === 'func' || $parsed === 'var') && preg_match('#^(\|@?[a-z0-9_]+(:.*)?)+#i', $substr, $match)) {
1679
            // parse modifier on funcs or vars
1680
            $srcPointer = $pointer;
1681
            if (is_array($parsingParams)) {
1682
                $tmp                     = $this->replaceModifiers(
1683
                    array(
1684
                    null,
1685
                    null,
1686
                    $out[count($out) - 1][0],
1687
                    $match[0]
1688
                    ), $curBlock, $pointer
1689
                );
1690
                $out[count($out) - 1][0] = $tmp;
1691
                $out[count($out) - 1][1] .= substr($substr, $srcPointer, $srcPointer - $pointer);
1692
            } else {
1693
                $out = $this->replaceModifiers(array(null, null, $out, $match[0]), $curBlock, $pointer);
1694
            }
1695
        }
1696
1697
        // func parsed, check if any func-extension applies
1698
        if ($parsed === 'func' && preg_match('#^->[a-z0-9_]+(\s*\(.+|->[a-z_].*)?#is', $substr, $match)) {
1699
            // parse method call or property read
1700
            $ptr = 0;
1701
1702
            if (is_array($parsingParams)) {
1703
                $output = $this->parseMethodCall($out[count($out) - 1][1], $match[0], $curBlock, $ptr);
1704
1705
                $out[count($out) - 1][0] = $output;
1706
                $out[count($out) - 1][1] .= substr($match[0], 0, $ptr);
1707
            } else {
1708
                $out = $this->parseMethodCall($out, $match[0], $curBlock, $ptr);
1709
            }
1710
1711
            $pointer += $ptr;
1712
        }
1713
1714
        if ($curBlock === 'root' && substr($out, 0, strlen(self::PHP_OPEN)) !== self::PHP_OPEN) {
1715
            return self::PHP_OPEN . 'echo ' . $out . ';' . self::PHP_CLOSE;
1716
        }
1717
1718
        return $out;
1719
    }
1720
1721
    /**
1722
     * Parses a function call.
1723
     *
1724
     * @param string $in            the string within which we must parse something
1725
     * @param int    $from          the starting offset of the parsed area
1726
     * @param int    $to            the ending offset of the parsed area
1727
     * @param mixed  $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by
1728
     *                              default
1729
     * @param string $curBlock      the current parser-block being processed
1730
     * @param mixed  $pointer       a reference to a pointer that will be increased by the amount of characters parsed,
1731
     *                              or null by default
1732
     *
1733
     * @return string parsed values
1734
     * @throws CompilationException
1735
     * @throws Exception
1736
     * @throws SecurityException
1737
     */
1738
    protected function parseFunction($in, $from, $to, $parsingParams = false, $curBlock = '', &$pointer = null)
1739
    {
1740
        $output = '';
1741
        $cmdstr = substr($in, $from, $to - $from);
1742
        preg_match('/^(\\\\?[a-z_](?:\\\\?[a-z0-9_]+)*(?:::[a-z_][a-z0-9_]*)?)(\s*' . $this->rdr . '|\s*;)?/i', $cmdstr, $match);
1743
1744 View Code Duplication
        if (empty($match[1])) {
1745
            throw new CompilationException($this, 'Parse error, invalid function name : ' . substr($cmdstr, 0, 15));
1746
        }
1747
1748
        $func = $match[1];
1749
1750
        if (!empty($match[2])) {
1751
            $cmdstr = $match[1];
1752
        }
1753
1754
        if ($this->debug) {
1755
            echo 'FUNC FOUND (' . $func . ')' . "\n";
1756
        }
1757
1758
        $paramsep = '';
1759
1760
        if (is_array($parsingParams) || $curBlock != 'root') {
1761
            $paramspos = strpos($cmdstr, '(');
1762
            $paramsep  = ')';
1763
        } elseif (preg_match_all('#^\s*[\\\\:a-z0-9_]+(\s*\(|\s+[^(])#i', $cmdstr, $match, PREG_OFFSET_CAPTURE)) {
1764
            $paramspos = $match[1][0][1];
1765
            $paramsep  = substr($match[1][0][0], - 1) === '(' ? ')' : '';
1766
            if ($paramsep === ')') {
1767
                $paramspos += strlen($match[1][0][0]) - 1;
1768
                if (substr($cmdstr, 0, 2) === 'if' || substr($cmdstr, 0, 6) === 'elseif') {
1769
                    $paramsep = '';
1770
                    if (strlen($match[1][0][0]) > 1) {
1771
                        -- $paramspos;
1772
                    }
1773
                }
1774
            }
1775
        } else {
1776
            $paramspos = false;
1777
        }
1778
1779
        $state = 0;
1780
1781
        if ($paramspos === false) {
1782
            $params = array();
1783
1784
            if ($curBlock !== 'root') {
1785
                return $this->parseOthers($in, $from, $to, $parsingParams, $curBlock, $pointer);
1786
            }
1787
        } else {
1788
            if ($curBlock === 'condition') {
1789
                // load if plugin
1790
                $this->getPluginType('if');
1791
1792
                if (PluginIf::replaceKeywords(array($func), array(self::T_UNQUOTED_STRING), $this) !== array($func)) {
1793
                    return $this->parseOthers($in, $from, $to, $parsingParams, $curBlock, $pointer);
1794
                }
1795
            }
1796
            $whitespace = strlen(substr($cmdstr, strlen($func), $paramspos - strlen($func)));
1797
            $paramstr   = substr($cmdstr, $paramspos + 1);
1798 View Code Duplication
            if (substr($paramstr, - 1, 1) === $paramsep) {
1799
                $paramstr = substr($paramstr, 0, - 1);
1800
            }
1801
1802
            if (strlen($paramstr) === 0) {
1803
                $params   = array();
1804
                $paramstr = '';
1805
            } else {
1806
                $ptr    = 0;
1807
                $params = array();
1808
                if ($func === 'empty') {
1809
                    $params = $this->parseVar($paramstr, $ptr, strlen($paramstr), $params, 'root', $ptr);
1810
                } else {
1811
                    while ($ptr < strlen($paramstr)) {
1812
                        while (true) {
1813
                            if ($ptr >= strlen($paramstr)) {
1814
                                break 2;
1815
                            }
1816
1817
                            if ($func !== 'if' && $func !== 'elseif' && $paramstr[$ptr] === ')') {
1818
                                if ($this->debug) {
1819
                                    echo 'PARAM PARSING ENDED, ")" FOUND, POINTER AT ' . $ptr . "\n";
1820
                                }
1821
                                break 2;
1822
                            } elseif ($paramstr[$ptr] === ';') {
1823
                                ++ $ptr;
1824
                                if ($this->debug) {
1825
                                    echo 'PARAM PARSING ENDED, ";" FOUND, POINTER AT ' . $ptr . "\n";
1826
                                }
1827
                                break 2;
1828
                            } elseif ($func !== 'if' && $func !== 'elseif' && $paramstr[$ptr] === '/') {
1829
                                if ($this->debug) {
1830
                                    echo 'PARAM PARSING ENDED, "/" FOUND, POINTER AT ' . $ptr . "\n";
1831
                                }
1832
                                break 2;
1833
                            } elseif (substr($paramstr, $ptr, strlen($this->rd)) === $this->rd) {
1834
                                if ($this->debug) {
1835
                                    echo 'PARAM PARSING ENDED, RIGHT DELIMITER FOUND, POINTER AT ' . $ptr . "\n";
1836
                                }
1837
                                break 2;
1838
                            }
1839
1840
                            if ($paramstr[$ptr] === ' ' || $paramstr[$ptr] === ',' || $paramstr[$ptr] === "\r" || $paramstr[$ptr] === "\n" || $paramstr[$ptr] === "\t") {
1841
                                ++ $ptr;
1842
                            } else {
1843
                                break;
1844
                            }
1845
                        }
1846
1847
                        if ($this->debug) {
1848
                            echo 'FUNC START PARAM PARSING WITH POINTER AT ' . $ptr . "\n";
1849
                        }
1850
1851
                        if ($func === 'if' || $func === 'elseif' || $func === 'tif') {
1852
                            $params = $this->parse($paramstr, $ptr, strlen($paramstr), $params, 'condition', $ptr);
1853
                        } elseif ($func === 'array') {
1854
                            $params = $this->parse($paramstr, $ptr, strlen($paramstr), $params, 'array', $ptr);
1855
                        } else {
1856
                            $params = $this->parse($paramstr, $ptr, strlen($paramstr), $params, 'function', $ptr);
1857
                        }
1858
1859 View Code Duplication
                        if ($this->debug) {
1860
                            echo 'PARAM PARSED, POINTER AT ' . $ptr . ' (' . substr($paramstr, $ptr - 1, 3) . ')' . "\n";
1861
                        }
1862
                    }
1863
                }
1864
                $paramstr = substr($paramstr, 0, $ptr);
1865
                $state    = 0;
1866
                foreach ($params as $k => $p) {
1867
                    if (is_array($p) && is_array($p[1])) {
1868
                        $state |= 2;
1869
                    } else {
1870
                        if (($state & 2) && preg_match('#^(["\'])(.+?)\1$#', $p[0], $m) && $func !== 'array') {
1871
                            $params[$k] = array($m[2], array('true', 'true'));
1872
                        } else {
1873
                            if ($state & 2 && $func !== 'array') {
1874
                                throw new CompilationException($this, 'You can not use an unnamed parameter after a named one');
1875
                            }
1876
                            $state |= 1;
1877
                        }
1878
                    }
1879
                }
1880
            }
1881
        }
1882
1883
        if ($pointer !== null) {
1884
            $pointer += (isset($paramstr) ? strlen($paramstr) : 0) + (')' === $paramsep ? 2 : ($paramspos === false ? 0 : 1)) + strlen($func) + (isset($whitespace) ? $whitespace : 0);
1885
            if ($this->debug) {
1886
                echo 'FUNC ADDS ' . ((isset($paramstr) ? strlen($paramstr) : 0) + (')' === $paramsep ? 2 : ($paramspos === false ? 0 : 1)) + strlen($func)) . ' TO POINTER' . "\n";
1887
            }
1888
        }
1889
1890
        if ($curBlock === 'method' || $func === 'do' || strstr($func, '::') !== false) {
1891
            // handle static method calls with security policy
1892
            if (strstr($func, '::') !== false && $this->securityPolicy !== null && $this->securityPolicy->isMethodAllowed(explode('::', strtolower($func))) !== true) {
1893
                throw new SecurityException('Call to a disallowed php function : ' . $func);
1894
            }
1895
            $pluginType = Core::NATIVE_PLUGIN;
1896
        } else {
1897
            $pluginType = $this->getPluginType($func);
1898
        }
1899
1900
        // Blocks plugin
1901
        if ($pluginType & Core::BLOCK_PLUGIN) {
1902
            if ($curBlock !== 'root' || is_array($parsingParams)) {
1903
                throw new CompilationException($this, 'Block plugins can not be used as other plugin\'s arguments');
1904
            }
1905
            if ($pluginType & Core::CUSTOM_PLUGIN) {
1906
                return $this->addCustomBlock($func, $params, $state);
1907
            } else {
1908
                return $this->addBlock($func, $params, $state);
1909
            }
1910
        } elseif ($pluginType & Core::SMARTY_BLOCK) {
1911
            if ($curBlock !== 'root' || is_array($parsingParams)) {
1912
                throw new CompilationException($this, 'Block plugins can not be used as other plugin\'s arguments');
1913
            }
1914
1915
            if ($state & 2) {
1916
                array_unshift($params, array('__functype', array($pluginType, $pluginType)));
1917
                array_unshift($params, array('__funcname', array($func, $func)));
1918
            } else {
1919
                array_unshift($params, array($pluginType, $pluginType));
1920
                array_unshift($params, array($func, $func));
1921
            }
1922
1923
            return $this->addBlock('smartyinterface', $params, $state);
1924
        }
1925
1926
        // Native & Smarty plugins
1927
        if ($pluginType & Core::NATIVE_PLUGIN || $pluginType & Core::SMARTY_FUNCTION || $pluginType & Core::SMARTY_BLOCK) {
1928
            $params = $this->mapParams($params, null, $state);
1929
        } // PHP class plugin
1930
        elseif ($pluginType & Core::CLASS_PLUGIN) {
1931
            if ($pluginType & Core::CUSTOM_PLUGIN) {
1932
                $params = $this->mapParams(
1933
                    $params, array(
1934
                    $this->customPlugins[$func]['class'],
1935
                    $this->customPlugins[$func]['function']
1936
                ), $state);
1937
            } else {
1938
                if (class_exists('Plugin' . Core::toCamelCase($func)) !== false) {
1939
                    $params = $this->mapParams($params, array(
1940
                        'Plugin' . Core::toCamelCase($func),
1941
                        ($pluginType & Core::COMPILABLE_PLUGIN) ? 'compile' : 'process'
1942
                    ), $state);
1943
                } elseif (class_exists(Core::NAMESPACE_PLUGINS_HELPERS . 'Plugin' . Core::toCamelCase($func)) !== false) {
1944
                    $params = $this->mapParams($params, array(
1945
                        Core::NAMESPACE_PLUGINS_HELPERS . 'Plugin' . Core::toCamelCase($func),
1946
                        ($pluginType & Core::COMPILABLE_PLUGIN) ? 'compile' : 'process'
1947
                    ), $state);
1948 View Code Duplication
                } else {
1949
                    $params = $this->mapParams($params, array(
1950
                        Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . Core::toCamelCase($func),
1951
                        ($pluginType & Core::COMPILABLE_PLUGIN) ? 'compile' : 'process'
1952
                    ), $state);
1953
                }
1954
            }
1955
        } // PHP function plugin
1956
        elseif ($pluginType & Core::FUNC_PLUGIN) {
1957
            if ($pluginType & Core::CUSTOM_PLUGIN) {
1958
                $params = $this->mapParams($params, $this->customPlugins[$func]['callback'], $state);
1959
            } else {
1960
                // Custom plugin
1961
                if (function_exists('Plugin' . Core::toCamelCase($func) . (($pluginType & Core::COMPILABLE_PLUGIN) ?
1962
                        'Compile' : '')) !== false) {
1963
                    $params = $this->mapParams($params, 'Plugin' . Core::toCamelCase($func) . (($pluginType &
1964
                            Core::COMPILABLE_PLUGIN) ? 'Compile' : ''), $state);
1965
                } // Builtin helper plugin
1966
                elseif (function_exists(Core::NAMESPACE_PLUGINS_HELPERS . 'Plugin' . Core::toCamelCase($func) . (
1967
                    ($pluginType & Core::COMPILABLE_PLUGIN) ? 'Compile' : '')) !== false) {
1968
                    $params = $this->mapParams($params, Core::NAMESPACE_PLUGINS_HELPERS . 'Plugin' . Core::toCamelCase
1969
                        ($func) . (($pluginType & Core::COMPILABLE_PLUGIN) ? 'Compile' : ''), $state);
1970
                } // Builtin function plugin
1971 View Code Duplication
                else {
1972
                    $params = $this->mapParams($params, Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . Core::toCamelCase
1973
                        ($func) . (($pluginType & Core::COMPILABLE_PLUGIN) ? 'Compile' : ''), $state);
1974
                }
1975
            }
1976
        } // Smarty modifier
1977
        elseif ($pluginType & Core::SMARTY_MODIFIER) {
1978
            $output = 'smarty_modifier_' . $func . '(' . implode(', ', $params) . ')';
1979
        } // Proxy plugin
1980
        elseif ($pluginType & Core::PROXY_PLUGIN) {
1981
            $params = $this->mapParams($params, $this->getCore()->getPluginProxy()->getCallback($func), $state);
1982
        } // Template plugin
1983
        elseif ($pluginType & Core::TEMPLATE_PLUGIN) {
1984
            // transforms the parameter array from (x=>array('paramname'=>array(values))) to (paramname=>array(values))
1985
            $map = array();
1986
            foreach ($this->templatePlugins[$func]['params'] as $param => $defValue) {
1987
                if ($param == 'rest') {
1988
                    $param = '*';
1989
                }
1990
                $hasDefault = $defValue !== null;
1991
                if ($defValue === 'null') {
1992
                    $defValue = null;
1993
                } elseif ($defValue === 'false') {
1994
                    $defValue = false;
1995
                } elseif ($defValue === 'true') {
1996
                    $defValue = true;
1997
                } elseif (preg_match('#^([\'"]).*?\1$#', $defValue)) {
1998
                    $defValue = substr($defValue, 1, - 1);
1999
                }
2000
                $map[] = array($param, $hasDefault, $defValue);
2001
            }
2002
2003
            $params = $this->mapParams($params, null, $state, $map);
2004
        }
2005
2006
        // only keep php-syntax-safe values for non-block plugins
2007
        $tokens = array();
2008
        foreach ($params as $k => $p) {
2009
            $tokens[$k] = isset($p[2]) ? $p[2] : 0;
2010
            $params[$k] = $p[0];
2011
        }
2012
2013
        // Native plugin
2014
        if ($pluginType & Core::NATIVE_PLUGIN) {
2015
            if ($func === 'do') {
2016
                $output = '';
2017
                if (isset($params['*'])) {
2018
                    $output = implode(';', $params['*']) . ';';
2019
                }
2020
2021
                if (is_array($parsingParams) || $curBlock !== 'root') {
2022
                    throw new CompilationException($this, 'Do can not be used inside another function or block');
2023
                }
2024
2025
                return self::PHP_OPEN . $output . self::PHP_CLOSE;
2026
            } else {
2027
                if (isset($params['*'])) {
2028
                    $output = $func . '(' . implode(', ', $params['*']) . ')';
2029
                } else {
2030
                    $output = $func . '()';
2031
                }
2032
            }
2033
        } // Block class OR Function class
2034
        elseif ($pluginType & Core::CLASS_PLUGIN || ($pluginType & Core::FUNC_PLUGIN && $pluginType & Core::CLASS_PLUGIN)) {
2035
            if ($pluginType & Core::COMPILABLE_PLUGIN) {
2036
                if ($pluginType & Core::CUSTOM_PLUGIN) {
2037
                    $callback = $this->customPlugins[$func]['callback'];
2038 View Code Duplication
                    if (!is_array($callback)) {
2039
                        if (!method_exists($callback, 'compile')) {
2040
                            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');
2041
                        }
2042
                        if (($ref = new ReflectionMethod($callback, 'compile')) && $ref->isStatic()) {
2043
                            $funcCompiler = array($callback, 'compile');
2044
                        } else {
2045
                            $funcCompiler = array(new $callback(), 'compile');
2046
                        }
2047
                    } else {
2048
                        $funcCompiler = $callback;
2049
                    }
2050
                } else {
2051
                    if (class_exists('Plugin' . Core::toCamelCase($func)) !== false) {
2052
                        $funcCompiler = array('Plugin' . Core::toCamelCase($func), 'compile');
2053
                    } elseif (class_exists(Core::NAMESPACE_PLUGINS_HELPERS . 'Plugin' . Core::toCamelCase($func)) !== false) {
2054
                        $funcCompiler = array(Core::NAMESPACE_PLUGINS_HELPERS . 'Plugin' . Core::toCamelCase($func), 'compile');
2055
                    } else {
2056
                        $funcCompiler = array(
2057
                            Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . Core::toCamelCase($func),
2058
                            'compile'
2059
                        );
2060
                    }
2061
                    array_unshift($params, $this);
2062
                }
2063
                // @TODO: Is it a real fix ?
2064
                if ($func === 'tif') {
2065
                    $params[] = $tokens;
2066
                }
2067
                $output = call_user_func_array($funcCompiler, $params);
2068
            } else {
2069
                $params = self::implode_r($params);
2070
                if ($pluginType & Core::CUSTOM_PLUGIN) {
2071
                    $callback = $this->customPlugins[$func]['callback'];
2072
                    if (!is_array($callback)) {
2073
                        if (!method_exists($callback, 'process')) {
2074
                            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');
2075
                        }
2076
                        if (is_object($callback)) {
2077
                            $callback = get_class($callback);
2078
                        }
2079
                        if (($ref = new ReflectionMethod($callback, 'process')) && $ref->isStatic()) {
2080
                            $output = 'call_user_func(array(\'' . $callback . '\', \'process\'), ' . $params . ')';
2081
                        } else {
2082
                            $output = 'call_user_func(array($this->getObjectPlugin(\'' . $callback . '\'), \'process\'), ' . $params . ')';
2083
                        }
2084 View Code Duplication
                    } elseif (is_object($callback[0])) {
2085
                        $output = 'call_user_func(array($this->plugins[\'' . $func . '\'][\'callback\'][0], \'' . $callback[1] . '\'), ' . $params . ')';
2086
                    } elseif (($ref = new ReflectionMethod($callback[0], $callback[1])) && $ref->isStatic()) {
2087
                        $output = 'call_user_func(array(\'' . $callback[0] . '\', \'' . $callback[1] . '\'), ' . $params . ')';
2088 View Code Duplication
                    } else {
2089
                        $output = 'call_user_func(array($this->getObjectPlugin(\'' . $callback[0] . '\'), \'' . $callback[1] . '\'), ' . $params . ')';
2090
                    }
2091
                    if (empty($params)) {
2092
                        $output = substr($output, 0, - 3) . ')';
2093
                    }
2094
                } else {
2095
                    if (class_exists('Plugin' . Core::toCamelCase($func)) !== false) {
2096
                        $output = '$this->classCall(\'Plugin' . $func . '\', array(' . $params . '))';
2097 View Code Duplication
                    } elseif (class_exists(Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . Core::toCamelCase($func)) !== false) {
2098
                        $output = '$this->classCall(\'' . Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . $func . '\',
2099
                        array(' . $params . '))';
2100
                    } else {
2101
                        $output = '$this->classCall(\'' . $func . '\', array(' . $params . '))';
2102
                    }
2103
                }
2104
            }
2105
        } // Function plugin only (cannot be a class)
2106
        elseif ($pluginType & Core::FUNC_PLUGIN) {
2107
            if ($pluginType & Core::COMPILABLE_PLUGIN) {
2108
                if ($pluginType & Core::CUSTOM_PLUGIN) {
2109
                    $funcCompiler = $this->customPlugins[$func]['callback'];
2110
                } else {
2111
                    // Custom plugin
2112
                    if (function_exists('Plugin' . Core::toCamelCase($func) . 'Compile') !== false) {
2113
                        $funcCompiler = 'Plugin' . Core::toCamelCase($func) . 'Compile';
2114
                    } // Builtin helper plugin
2115 View Code Duplication
                    elseif (function_exists(Core::NAMESPACE_PLUGINS_HELPERS . 'Plugin' . Core::toCamelCase($func) . 'Compile') !== false) {
2116
                        $funcCompiler = Core::NAMESPACE_PLUGINS_HELPERS . 'Plugin' . Core::toCamelCase($func) .
2117
                            'Compile';
2118
                    } // Builtin function plugin
2119
                    else {
2120
                        $funcCompiler = Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . Core::toCamelCase($func) .
2121
                            'Compile';
2122
                    }
2123
                }
2124
                array_unshift($params, $this);
2125
                // @TODO: Is it a real fix ?
2126
                if ($func === 'tif') {
2127
                    $params[] = $tokens;
2128
                }
2129
                $output = call_user_func_array($funcCompiler, $params);
2130
            } else {
2131
                array_unshift($params, '$this');
2132
                $params = self::implode_r($params);
2133
                if ($pluginType & Core::CUSTOM_PLUGIN) {
2134
                    $callback = $this->customPlugins[$func]['callback'];
2135
                    if ($callback instanceof Closure) {
2136
                        $output = 'call_user_func($this->getCustomPlugin(\'' . $func . '\'), ' . $params . ')';
2137
                    } else {
2138
                        $output = 'call_user_func(\'' . $callback . '\', ' . $params . ')';
2139
                    }
2140
                } else {
2141
                    // Custom plugin
2142
                    if (function_exists('Plugin' . Core::toCamelCase($func)) !== false) {
2143
                        $output = 'Plugin' . Core::toCamelCase($func) . '(' . $params .
2144
                            ')';
2145
                    } // Builtin helper plugin
2146 View Code Duplication
                    elseif(function_exists(Core::NAMESPACE_PLUGINS_HELPERS . 'Plugin' . Core::toCamelCase($func)) !==
2147
                        false) {
2148
                        $output = Core::NAMESPACE_PLUGINS_HELPERS . 'Plugin' . Core::toCamelCase($func) . '(' .
2149
                            $params . ')';
2150
                    } // Builtin function plugin
2151 View Code Duplication
                    else {
2152
                        $output = Core::NAMESPACE_PLUGINS_FUNCTIONS . 'Plugin' . Core::toCamelCase($func) . '(' .
2153
                            $params . ')';
2154
                    }
2155
                }
2156
            }
2157
        } // Proxy plugin
2158
        elseif ($pluginType & Core::PROXY_PLUGIN) {
2159
            $output = call_user_func(array($this->getCore()->getPluginProxy(), 'getCode'), $func, $params);
2160
        } // Smarty function (@deprecated)
2161
        elseif ($pluginType & Core::SMARTY_FUNCTION) {
2162
            $params = '';
2163
            if (isset($params['*'])) {
2164
                $params = self::implode_r($params['*'], true);
2165
            }
2166
2167
            if ($pluginType & Core::CUSTOM_PLUGIN) {
2168
                $callback = $this->customPlugins[$func]['callback'];
2169
                if (is_array($callback)) {
2170
                    if (is_object($callback[0])) {
2171
                        $output = 'call_user_func_array(array($this->getCustomPlugin(\'' . $func . '\'), \'' . $callback[1] . '\'), array(array(' . $params . '), $this))';
2172 View Code Duplication
                    } else {
2173
                        $output = 'call_user_func_array(array(\'' . $callback[0] . '\', \'' . $callback[1] . '\'), array(array(' . $params . '), $this))';
2174
                    }
2175
                } else {
2176
                    $output = $callback . '(array(' . $params . '), $this)';
2177
                }
2178
            } else {
2179
                $output = 'smarty_function_' . $func . '(array(' . $params . '), $this)';
2180
            }
2181
        } // Template plugin
2182
        elseif ($pluginType & Core::TEMPLATE_PLUGIN) {
2183
            array_unshift($params, '$this');
2184
            $params                                 = self::implode_r($params);
2185
            $output                                 = 'Plugin' . Core::toCamelCase($func) .
2186
                $this->templatePlugins[$func]['uuid'] . '(' . $params . ')';
2187
            $this->templatePlugins[$func]['called'] = true;
2188
        }
2189
2190 View Code Duplication
        if (is_array($parsingParams)) {
2191
            $parsingParams[] = array($output, $output);
2192
2193
            return $parsingParams;
2194
        } elseif ($curBlock === 'namedparam') {
2195
            return array($output, $output);
2196
        }
2197
2198
        return $output;
2199
    }
2200
2201
    /**
2202
     * Parses a string.
2203
     *
2204
     * @param string $in            the string within which we must parse something
2205
     * @param int    $from          the starting offset of the parsed area
2206
     * @param int    $to            the ending offset of the parsed area
2207
     * @param mixed  $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by
2208
     *                              default
2209
     * @param string $curBlock      the current parser-block being processed
2210
     * @param mixed  $pointer       a reference to a pointer that will be increased by the amount of characters parsed,
2211
     *                              or null by default
2212
     *
2213
     * @return string parsed values
2214
     * @throws CompilationException
2215
     */
2216
    protected function parseString($in, $from, $to, $parsingParams = false, $curBlock = '', &$pointer = null)
2217
    {
2218
        $substr = substr($in, $from, $to - $from);
2219
        $first  = $substr[0];
2220
2221
        if ($this->debug) {
2222
            echo 'STRING FOUND (in ' . htmlentities(substr($in, $from, min($to - $from, 50))) . (($to - $from) > 50 ? '...' : '') . ')' . "\n";
2223
        }
2224
        $strend = false;
2225
        $o      = $from + 1;
2226
        while ($strend === false) {
2227
            $strend = strpos($in, $first, $o);
2228 View Code Duplication
            if ($strend === false) {
2229
                throw new CompilationException($this, 'Unfinished string, started with ' . substr($in, $from, $to - $from));
2230
            }
2231
            if (substr($in, $strend - 1, 1) === '\\') {
2232
                $o      = $strend + 1;
2233
                $strend = false;
2234
            }
2235
        }
2236
        if ($this->debug) {
2237
            echo 'STRING DELIMITED: ' . substr($in, $from, $strend + 1 - $from) . "\n";
2238
        }
2239
2240
        $srcOutput = substr($in, $from, $strend + 1 - $from);
2241
2242
        if ($pointer !== null) {
2243
            $pointer += strlen($srcOutput);
2244
        }
2245
2246
        $output = $this->replaceStringVars($srcOutput, $first);
2247
2248
        // handle modifiers
2249
        if ($curBlock !== 'modifier' && preg_match('#^((?:\|(?:@?[a-z0-9_]+(?::.*)*))+)#i', substr($substr, $strend + 1 - $from), $match)) {
2250
            $modstr = $match[1];
2251
2252
            if ($curBlock === 'root' && substr($modstr, - 1) === '}') {
2253
                $modstr = substr($modstr, 0, - 1);
2254
            }
2255
            $modstr = str_replace('\\' . $first, $first, $modstr);
2256
            $ptr    = 0;
2257
            $output = $this->replaceModifiers(array(null, null, $output, $modstr), 'string', $ptr);
2258
2259
            $strend += $ptr;
2260
            if ($pointer !== null) {
2261
                $pointer += $ptr;
2262
            }
2263
            $srcOutput .= substr($substr, $strend + 1 - $from, $ptr);
2264
        }
2265
2266
        if (is_array($parsingParams)) {
2267
            $parsingParams[] = array($output, substr($srcOutput, 1, - 1));
2268
2269
            return $parsingParams;
2270
        } elseif ($curBlock === 'namedparam') {
2271
            return array($output, substr($srcOutput, 1, - 1));
2272
        }
2273
2274
        return $output;
2275
    }
2276
2277
    /**
2278
     * Parses a constant.
2279
     *
2280
     * @param string $in            the string within which we must parse something
2281
     * @param int    $from          the starting offset of the parsed area
2282
     * @param int    $to            the ending offset of the parsed area
2283
     * @param mixed  $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by
2284
     *                              default
2285
     * @param string $curBlock      the current parser-block being processed
2286
     * @param mixed  $pointer       a reference to a pointer that will be increased by the amount of characters parsed,
2287
     *                              or null by default
2288
     *
2289
     * @return string parsed values
2290
     * @throws CompilationException
2291
     */
2292
    protected function parseConst($in, $from, $to, $parsingParams = false, $curBlock = '', &$pointer = null)
2293
    {
2294
        $substr = substr($in, $from, $to - $from);
2295
2296
        if ($this->debug) {
2297
            echo 'CONST FOUND : ' . $substr . "\n";
2298
        }
2299
2300
        if (!preg_match('#^%([\\\\a-z0-9_:]+)#i', $substr, $m)) {
2301
            throw new CompilationException($this, 'Invalid constant');
2302
        }
2303
2304
        if ($pointer !== null) {
2305
            $pointer += strlen($m[0]);
2306
        }
2307
2308
        $output = $this->parseConstKey($m[1], $curBlock);
2309
2310 View Code Duplication
        if (is_array($parsingParams)) {
2311
            $parsingParams[] = array($output, $m[1]);
2312
2313
            return $parsingParams;
2314
        } elseif ($curBlock === 'namedparam') {
2315
            return array($output, $m[1]);
2316
        }
2317
2318
        return $output;
2319
    }
2320
2321
    /**
2322
     * Parses a constant.
2323
     *
2324
     * @param string $key      the constant to parse
2325
     * @param string $curBlock the current parser-block being processed
2326
     *
2327
     * @return string parsed constant
2328
     */
2329
    protected function parseConstKey($key, $curBlock)
2330
    {
2331
        $key = str_replace('\\\\', '\\', $key);
2332
2333
        if ($this->securityPolicy !== null && $this->securityPolicy->getConstantHandling() === SecurityPolicy::CONST_DISALLOW) {
2334
            return 'null';
2335
        }
2336
2337
        if ($curBlock !== 'root') {
2338
            return '(defined("' . $key . '") ? ' . $key . ' : null)';
2339
        }
2340
2341
        return $key;
2342
    }
2343
2344
    /**
2345
     * Parses a variable.
2346
     *
2347
     * @param string $in            the string within which we must parse something
2348
     * @param int    $from          the starting offset of the parsed area
2349
     * @param int    $to            the ending offset of the parsed area
2350
     * @param mixed  $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by
2351
     *                              default
2352
     * @param string $curBlock      the current parser-block being processed
2353
     * @param mixed  $pointer       a reference to a pointer that will be increased by the amount of characters parsed,
2354
     *                              or null by default
2355
     *
2356
     * @return string parsed values
2357
     * @throws CompilationException
2358
     */
2359
    protected function parseVar($in, $from, $to, $parsingParams = false, $curBlock = '', &$pointer = null)
2360
    {
2361
        $substr = substr($in, $from, $to - $from);
2362
2363
        // var key
2364
        $varRegex = '(\\$?\\.?[a-z0-9\\\\_:]*(?:(?:(?:\\.|->)(?:[a-z0-9\\\\_:]+|(?R))|\\[(?:[a-z0-9\\\\_:]+|(?R)|(["\'])[^\\2]*?\\2)\\]))*)';
2365
        // method call
2366
        $methodCall = ($curBlock === 'root' || $curBlock === 'function' || $curBlock === 'namedparam' || $curBlock === 'condition' || $curBlock === 'variable' || $curBlock === 'expression' || $curBlock === 'delimited_string' ? '(\(.*)?' : '()');
2367
        // simple math expressions
2368
        $simpleMathExpressions = ($curBlock === 'root' || $curBlock === 'function' || $curBlock === 'namedparam' || $curBlock === 'condition' || $curBlock === 'variable' || $curBlock === 'delimited_string' ? '((?:(?:[+\/*%=-])(?:(?<!=)=?-?[$%][a-z0-9\\\\.[\]>_:-]+(?:\([^)]*\))?|(?<!=)=?-?[0-9\.,]*|[+-]))*)' : '()');
2369
        // modifiers
2370
        $modifiers = $curBlock !== 'modifier' ? '((?:\|(?:@?[a-z0-9\\\\_]+(?:(?::("|\').*?\5|:[^`]*))*))+)?' : '(())';
2371
2372
        $regex = '#';
2373
        $regex .= $varRegex;
2374
        $regex .= $methodCall;
2375
        $regex .= $simpleMathExpressions;
2376
        $regex .= $modifiers;
2377
        $regex .= '#i';
2378
2379
        if (preg_match($regex, $substr, $match)) {
2380
            $key = substr($match[1], 1);
2381
2382
            $matchedLength = strlen($match[0]);
2383
            $hasModifiers  = !empty($match[5]);
2384
            $hasExpression = !empty($match[4]);
2385
            $hasMethodCall = !empty($match[3]);
2386
2387
            if (substr($key, - 1) == '.') {
2388
                $key = substr($key, 0, - 1);
2389
                -- $matchedLength;
2390
            }
2391
2392
            if ($hasMethodCall) {
2393
                $matchedLength -= strlen($match[3]) + strlen(substr($match[1], strrpos($match[1], '->')));
2394
                $key        = substr($match[1], 1, strrpos($match[1], '->') - 1);
2395
                $methodCall = substr($match[1], strrpos($match[1], '->')) . $match[3];
2396
            }
2397
2398
            if ($hasModifiers) {
2399
                $matchedLength -= strlen($match[5]);
2400
            }
2401
2402
            if ($pointer !== null) {
2403
                $pointer += $matchedLength;
2404
            }
2405
2406
            // replace useless brackets by dot accessed vars and strip enclosing quotes if present
2407
            $key = preg_replace('#\[(["\']?)([^$%\[.>-]+)\1\]#', '.$2', $key);
2408
2409 View Code Duplication
            if ($this->debug) {
2410
                if ($hasMethodCall) {
2411
                    echo 'METHOD CALL FOUND : $' . $key . substr($methodCall, 0, 30) . "\n";
2412
                } else {
2413
                    echo 'VAR FOUND : $' . $key . "\n";
2414
                }
2415
            }
2416
2417
            $key = str_replace('"', '\\"', $key);
2418
2419
            $cnt = substr_count($key, '$');
2420
            if ($cnt > 0) {
2421
                $uid           = 0;
2422
                $parsed        = array($uid => '');
2423
                $current       = &$parsed;
2424
                $curTxt        = &$parsed[$uid ++];
2425
                $tree          = array();
2426
                $chars         = str_split($key, 1);
2427
                $inSplittedVar = false;
2428
                $bracketCount  = 0;
2429
2430
                while (($char = array_shift($chars)) !== null) {
2431
                    if ($char === '[') {
2432
                        if (count($tree) > 0) {
2433
                            ++ $bracketCount;
2434
                        } else {
2435
                            $tree[]        = &$current;
2436
                            $current[$uid] = array($uid + 1 => '');
2437
                            $current       = &$current[$uid ++];
2438
                            $curTxt        = &$current[$uid ++];
2439
                            continue;
2440
                        }
2441
                    } elseif ($char === ']') {
2442
                        if ($bracketCount > 0) {
2443
                            -- $bracketCount;
2444
                        } else {
2445
                            $current = &$tree[count($tree) - 1];
2446
                            array_pop($tree);
2447
                            if (current($chars) !== '[' && current($chars) !== false && current($chars) !== ']') {
2448
                                $current[$uid] = '';
2449
                                $curTxt        = &$current[$uid ++];
2450
                            }
2451
                            continue;
2452
                        }
2453
                    } elseif ($char === '$') {
2454
                        if (count($tree) == 0) {
2455
                            $curTxt        = &$current[$uid ++];
2456
                            $inSplittedVar = true;
2457
                        }
2458
                    } elseif (($char === '.' || $char === '-') && count($tree) == 0 && $inSplittedVar) {
2459
                        $curTxt        = &$current[$uid ++];
2460
                        $inSplittedVar = false;
2461
                    }
2462
2463
                    $curTxt .= $char;
2464
                }
2465
                unset($uid, $current, $curTxt, $tree, $chars);
2466
2467
                if ($this->debug) {
2468
                    echo 'RECURSIVE VAR REPLACEMENT : ' . $key . "\n";
2469
                }
2470
2471
                $key = $this->flattenVarTree($parsed);
2472
2473
                if ($this->debug) {
2474
                    echo 'RECURSIVE VAR REPLACEMENT DONE : ' . $key . "\n";
2475
                }
2476
2477
                $output = preg_replace('#(^""\.|""\.|\.""$|(\()""\.|\.""(\)))#', '$2$3', '$this->readVar("' . $key . '")');
2478
            } else {
2479
                $output = $this->parseVarKey($key, $hasModifiers ? 'modifier' : $curBlock);
2480
            }
2481
2482
2483
            // methods
2484
            if ($hasMethodCall) {
2485
                $ptr = 0;
2486
2487
                $output = $this->parseMethodCall($output, $methodCall, $curBlock, $ptr);
2488
2489
                if ($pointer !== null) {
2490
                    $pointer += $ptr;
2491
                }
2492
                $matchedLength += $ptr;
2493
            }
2494
2495
            if ($hasExpression) {
2496
                // expressions
2497
                preg_match_all('#(?:([+/*%=-])(=?-?[%$][a-z0-9\\\\.[\]>_:-]+(?:\([^)]*\))?|=?-?[0-9.,]+|\1))#i', $match[4], $expMatch);
2498
                foreach ($expMatch[1] as $k => $operator) {
2499
                    if (substr($expMatch[2][$k], 0, 1) === '=') {
2500
                        $assign = true;
2501
                        if ($operator === '=') {
2502
                            throw new CompilationException($this, 'Invalid expression <em>' . $substr . '</em>, can not use "==" in expressions');
2503
                        }
2504
                        if ($curBlock !== 'root') {
2505
                            throw new CompilationException($this, 'Invalid expression <em>' . $substr . '</em>, assignments can only be used in top level expressions like {$foo+=3} or {$foo="bar"}');
2506
                        }
2507
                        $operator .= '=';
2508
                        $expMatch[2][$k] = substr($expMatch[2][$k], 1);
2509
                    }
2510
2511
                    if (substr($expMatch[2][$k], 0, 1) === '-' && strlen($expMatch[2][$k]) > 1) {
2512
                        $operator .= '-';
2513
                        $expMatch[2][$k] = substr($expMatch[2][$k], 1);
2514
                    }
2515
                    if (($operator === '+' || $operator === '-') && $expMatch[2][$k] === $operator) {
2516
                        $output = '(' . $output . $operator . $operator . ')';
2517
                        break;
2518
                    } elseif (substr($expMatch[2][$k], 0, 1) === '$') {
2519
                        $output = '(' . $output . ' ' . $operator . ' ' . $this->parseVar($expMatch[2][$k], 0, strlen($expMatch[2][$k]), false, 'expression') . ')';
2520
                    } elseif (substr($expMatch[2][$k], 0, 1) === '%') {
2521
                        $output = '(' . $output . ' ' . $operator . ' ' . $this->parseConst($expMatch[2][$k], 0, strlen($expMatch[2][$k]), false, 'expression') . ')';
2522
                    } elseif (!empty($expMatch[2][$k])) {
2523
                        $output = '(' . $output . ' ' . $operator . ' ' . str_replace(',', '.', $expMatch[2][$k]) . ')';
2524
                    } else {
2525
                        throw new CompilationException($this, 'Unfinished expression <em>' . $substr . '</em>, missing var or number after math operator');
2526
                    }
2527
                }
2528
            }
2529
2530
            if ($this->autoEscape === true && $curBlock !== 'condition') {
2531
                $output = '(is_string($tmp=' . $output . ') ? htmlspecialchars($tmp, ENT_QUOTES, $this->charset) : $tmp)';
2532
            }
2533
2534
            // handle modifiers
2535
            if ($curBlock !== 'modifier' && $hasModifiers) {
2536
                $ptr    = 0;
2537
                $output = $this->replaceModifiers(array(null, null, $output, $match[5]), 'var', $ptr);
2538
                if ($pointer !== null) {
2539
                    $pointer += $ptr;
2540
                }
2541
                $matchedLength += $ptr;
2542
            }
2543
2544
            if (is_array($parsingParams)) {
2545
                $parsingParams[] = array($output, $key);
2546
2547
                return $parsingParams;
2548
            } elseif ($curBlock === 'namedparam') {
2549
                return array($output, $key);
2550
            } elseif ($curBlock === 'string' || $curBlock === 'delimited_string') {
2551
                return array($matchedLength, $output);
2552
            } elseif ($curBlock === 'expression' || $curBlock === 'variable') {
2553
                return $output;
2554
            } elseif (isset($assign)) {
2555
                return self::PHP_OPEN . $output . ';' . self::PHP_CLOSE;
2556
            }
2557
2558
            return $output;
2559
        } else {
2560
            if ($curBlock === 'string' || $curBlock === 'delimited_string') {
2561
                return array(0, '');
2562
            }
2563
            throw new CompilationException($this, 'Invalid variable name <em>' . $substr . '</em>');
2564
        }
2565
    }
2566
2567
    /**
2568
     * Parses any number of chained method calls/property reads.
2569
     *
2570
     * @param string $output     the variable or whatever upon which the method are called
2571
     * @param string $methodCall method call source, starting at "->"
2572
     * @param string $curBlock   the current parser-block being processed
2573
     * @param int    $pointer    a reference to a pointer that will be increased by the amount of characters parsed
2574
     *
2575
     * @return string parsed call(s)/read(s)
2576
     */
2577
    protected function parseMethodCall($output, $methodCall, $curBlock, &$pointer)
2578
    {
2579
        $ptr = 0;
2580
        $len = strlen($methodCall);
2581
2582
        while ($ptr < $len) {
2583
            if (strpos($methodCall, '->', $ptr) === $ptr) {
2584
                $ptr += 2;
2585
            }
2586
2587
            if (in_array(
2588
                $methodCall[$ptr], array(
2589
                    ';',
2590
                    ',',
2591
                    '/',
2592
                    ' ',
2593
                    "\t",
2594
                    "\r",
2595
                    "\n",
2596
                    ')',
2597
                    '+',
2598
                    '*',
2599
                    '%',
2600
                    '=',
2601
                    '-',
2602
                    '|'
2603
                )
2604
            ) || substr($methodCall, $ptr, strlen($this->rd)) === $this->rd
2605
            ) {
2606
                // break char found
2607
                break;
2608
            }
2609
2610
            if (!preg_match('/^([a-z0-9_]+)(\(.*?\))?/i', substr($methodCall, $ptr), $methMatch)) {
2611
                break;
2612
            }
2613
2614
            if (empty($methMatch[2])) {
2615
                // property
2616
                if ($curBlock === 'root') {
2617
                    $output .= '->' . $methMatch[1];
2618
                } else {
2619
                    $output = '(($tmp = ' . $output . ') ? $tmp->' . $methMatch[1] . ' : null)';
2620
                }
2621
                $ptr += strlen($methMatch[1]);
2622
            } else {
2623
                // method
2624
                if (substr($methMatch[2], 0, 2) === '()') {
2625
                    $parsedCall = $methMatch[1] . '()';
2626
                    $ptr += strlen($methMatch[1]) + 2;
2627
                } else {
2628
                    $parsedCall = $this->parseFunction($methodCall, $ptr, strlen($methodCall), false, 'method', $ptr);
2629
                }
2630
                if ($this->securityPolicy !== null) {
2631
                    $argPos = strpos($parsedCall, '(');
2632
                    $method = strtolower(substr($parsedCall, 0, $argPos));
2633
                    $args   = substr($parsedCall, $argPos);
2634
                    if ($curBlock === 'root') {
2635
                        $output = '$this->getSecurityPolicy()->callMethod($this, ' . $output . ', ' . var_export($method, true) . ', array' . $args . ')';
2636
                    } else {
2637
                        $output = '(($tmp = ' . $output . ') ? $this->getSecurityPolicy()->callMethod($this, $tmp, ' . var_export($method, true) . ', array' . $args . ') : null)';
2638
                    }
2639
                } else {
2640
                    if ($curBlock === 'root') {
2641
                        $output .= '->' . $parsedCall;
2642
                    } else {
2643
                        $output = '(($tmp = ' . $output . ') ? $tmp->' . $parsedCall . ' : null)';
2644
                    }
2645
                }
2646
            }
2647
        }
2648
2649
        $pointer += $ptr;
2650
2651
        return $output;
2652
    }
2653
2654
    /**
2655
     * Parses a constant variable (a variable that doesn't contain another variable) and preprocesses it to save
2656
     * runtime processing time.
2657
     *
2658
     * @param string $key      the variable to parse
2659
     * @param string $curBlock the current parser-block being processed
2660
     *
2661
     * @return string parsed variable
2662
     */
2663
    protected function parseVarKey($key, $curBlock)
2664
    {
2665
        if ($key === '') {
2666
            return '$this->scope';
2667
        }
2668
        if (substr($key, 0, 1) === '.') {
2669
            $key = 'dwoo' . $key;
2670
        }
2671
        if (preg_match('#dwoo\.(get|post|server|cookies|session|env|request)((?:\.[a-z0-9_-]+)+)#i', $key, $m)) {
2672
            $global = strtoupper($m[1]);
2673
            if ($global === 'COOKIES') {
2674
                $global = 'COOKIE';
2675
            }
2676
            $key = '$_' . $global;
2677
            foreach (explode('.', ltrim($m[2], '.')) as $part) {
2678
                $key .= '[' . var_export($part, true) . ']';
2679
            }
2680 View Code Duplication
            if ($curBlock === 'root') {
2681
                $output = $key;
2682
            } else {
2683
                $output = '(isset(' . $key . ')?' . $key . ':null)';
2684
            }
2685
        } elseif (preg_match('#dwoo\\.const\\.([a-z0-9\\\\_:]+)#i', $key, $m)) {
2686
            return $this->parseConstKey($m[1], $curBlock);
2687
        } elseif ($this->scope !== null) {
2688
            if (strstr($key, '.') === false && strstr($key, '[') === false && strstr($key, '->') === false) {
2689
                if ($key === 'dwoo') {
2690
                    $output = '$this->globals';
2691
                } elseif ($key === '_root' || $key === '__') {
2692
                    $output = '$this->data';
2693
                } elseif ($key === '_parent' || $key === '_') {
2694
                    $output = '$this->readParentVar(1)';
2695
                } elseif ($key === '_key') {
2696
                    $output = '$tmp_key';
2697 View Code Duplication
                } else {
2698
                    if ($curBlock === 'root') {
2699
                        $output = '$this->scope["' . $key . '"]';
2700
                    } else {
2701
                        $output = '(isset($this->scope["' . $key . '"]) ? $this->scope["' . $key . '"] : null)';
2702
                    }
2703
                }
2704
            } else {
2705
                preg_match_all('#(\[|->|\.)?((?:[a-z0-9_]|-(?!>))+|(\\\?[\'"])[^\3]*?\3)\]?#i', $key, $m);
2706
2707
                $i = $m[2][0];
2708
                if ($i === '_parent' || $i === '_') {
2709
                    $parentCnt = 0;
2710
2711
                    while (true) {
2712
                        ++ $parentCnt;
2713
                        array_shift($m[2]);
2714
                        array_shift($m[1]);
2715
                        if (current($m[2]) === '_parent') {
2716
                            continue;
2717
                        }
2718
                        break;
2719
                    }
2720
2721
                    $output = '$this->readParentVar(' . $parentCnt . ')';
2722
                } else {
2723
                    if ($i === 'dwoo') {
2724
                        $output = '$this->globals';
2725
                        array_shift($m[2]);
2726
                        array_shift($m[1]);
2727 View Code Duplication
                    } elseif ($i === '_root' || $i === '__') {
2728
                        // $output = '$this->data';
2729
                        $output = '$this->getData()';
2730
                        array_shift($m[2]);
2731
                        array_shift($m[1]);
2732
                    } elseif ($i === '_key') {
2733
                        $output = '$tmp_key';
2734
                    } else {
2735
                        $output = '$this->scope';
2736
                    }
2737
2738
                    while (count($m[1]) && $m[1][0] !== '->') {
2739
                        $m[2][0] = preg_replace('/(^\\\([\'"])|\\\([\'"])$)/x', '$2$3', $m[2][0]);
2740
                        if (substr($m[2][0], 0, 1) == '"' || substr($m[2][0], 0, 1) == "'") {
2741
                            $output .= '[' . $m[2][0] . ']';
2742
                        } else {
2743
                            $output .= '["' . $m[2][0] . '"]';
2744
                        }
2745
                        array_shift($m[2]);
2746
                        array_shift($m[1]);
2747
                    }
2748
2749
                    if ($curBlock !== 'root') {
2750
                        $output = '(isset(' . $output . ') ? ' . $output . ':null)';
2751
                    }
2752
                }
2753
2754
                if (count($m[2])) {
2755
                    unset($m[0]);
2756
                    $output = '$this->readVarInto(' . str_replace("\n", '', var_export($m, true)) . ', ' . $output . ', ' . ($curBlock == 'root' ? 'false' : 'true') . ')';
2757
                }
2758
            }
2759
        } else {
2760
            preg_match_all('#(\[|->|\.)?((?:[a-z0-9_]|-(?!>))+)\]?#i', $key, $m);
2761
            unset($m[0]);
2762
            $output = '$this->readVar(' . str_replace("\n", '', var_export($m, true)) . ')';
2763
        }
2764
2765
        return $output;
2766
    }
2767
2768
    /**
2769
     * Flattens a variable tree, this helps in parsing very complex variables such as $var.foo[$foo.bar->baz].baz,
2770
     * it computes the contents of the brackets first and works out from there.
2771
     *
2772
     * @param array $tree     the variable tree parsed by he parseVar() method that must be flattened
2773
     * @param bool  $recursed leave that to false by default, it is only for internal use
2774
     *
2775
     * @return string flattened tree
2776
     */
2777
    protected function flattenVarTree(array $tree, $recursed = false)
2778
    {
2779
        $out = $recursed ? '".$this->readVarInto(' : '';
2780
        foreach ($tree as $bit) {
2781
            if (is_array($bit)) {
2782
                $out .= '.' . $this->flattenVarTree($bit, false);
2783
            } else {
2784
                $key = str_replace('"', '\\"', $bit);
2785
2786
                if (substr($key, 0, 1) === '$') {
2787
                    $out .= '".' . $this->parseVar($key, 0, strlen($key), false, 'variable') . '."';
2788
                } else {
2789
                    $cnt = substr_count($key, '$');
2790
2791
                    if ($this->debug) {
2792
                        echo 'PARSING SUBVARS IN : ' . $key . "\n";
2793
                    }
2794
                    if ($cnt > 0) {
2795
                        while (-- $cnt >= 0) {
2796
                            if (isset($last)) {
2797
                                $last = strrpos($key, '$', - (strlen($key) - $last + 1));
2798
                            } else {
2799
                                $last = strrpos($key, '$');
2800
                            }
2801
                            preg_match('#\$[a-z0-9_]+((?:(?:\.|->)(?:[a-z0-9_]+|(?R))|\[(?:[a-z0-9_]+|(?R))\]))*' . '((?:(?:[+/*%-])(?:\$[a-z0-9.[\]>_:-]+(?:\([^)]*\))?|[0-9.,]*))*)#i', substr($key, $last), $submatch);
2802
2803
                            $len = strlen($submatch[0]);
2804
                            $key = substr_replace(
2805
                                $key, preg_replace_callback(
2806
                                    '#(\$[a-z0-9_]+((?:(?:\.|->)(?:[a-z0-9_]+|(?R))|\[(?:[a-z0-9_]+|(?R))\]))*)' . '((?:(?:[+/*%-])(?:\$[a-z0-9.[\]>_:-]+(?:\([^)]*\))?|[0-9.,]*))*)#i', array(
2807
                                        $this,
2808
                                        'replaceVarKeyHelper'
2809
                                    ), substr($key, $last, $len)
2810
                                ), $last, $len
2811
                            );
2812
                            if ($this->debug) {
2813
                                echo 'RECURSIVE VAR REPLACEMENT DONE : ' . $key . "\n";
2814
                            }
2815
                        }
2816
                        unset($last);
2817
2818
                        $out .= $key;
2819
                    } else {
2820
                        $out .= $key;
2821
                    }
2822
                }
2823
            }
2824
        }
2825
        $out .= $recursed ? ', true)."' : '';
2826
2827
        return $out;
2828
    }
2829
2830
    /**
2831
     * Helper function that parses a variable.
2832
     *
2833
     * @param array $match the matched variable, array(1=>"string match")
2834
     *
2835
     * @return string parsed variable
2836
     */
2837
    protected function replaceVarKeyHelper($match)
2838
    {
2839
        return '".' . $this->parseVar($match[0], 0, strlen($match[0]), false, 'variable') . '."';
2840
    }
2841
2842
    /**
2843
     * Parses various constants, operators or non-quoted strings.
2844
     *
2845
     * @param string $in            the string within which we must parse something
2846
     * @param int    $from          the starting offset of the parsed area
2847
     * @param int    $to            the ending offset of the parsed area
2848
     * @param mixed  $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by
2849
     *                              default
2850
     * @param string $curBlock      the current parser-block being processed
2851
     * @param mixed  $pointer       a reference to a pointer that will be increased by the amount of characters parsed,
2852
     *                              or null by default
2853
     *
2854
     * @return string parsed values
2855
     * @throws Exception
2856
     */
2857
    protected function parseOthers($in, $from, $to, $parsingParams = false, $curBlock = '', &$pointer = null)
2858
    {
2859
        $substr = substr($in, $from, $to - $from);
2860
2861
        $end = strlen($substr);
2862
2863
        if ($curBlock === 'condition') {
2864
            $breakChars = array(
2865
                '(',
2866
                ')',
2867
                ' ',
2868
                '||',
2869
                '&&',
2870
                '|',
2871
                '&',
2872
                '>=',
2873
                '<=',
2874
                '===',
2875
                '==',
2876
                '=',
2877
                '!==',
2878
                '!=',
2879
                '<<',
2880
                '<',
2881
                '>>',
2882
                '>',
2883
                '^',
2884
                '~',
2885
                ',',
2886
                '+',
2887
                '-',
2888
                '*',
2889
                '/',
2890
                '%',
2891
                '!',
2892
                '?',
2893
                ':',
2894
                $this->rd,
2895
                ';'
2896
            );
2897
        } elseif ($curBlock === 'modifier') {
2898
            $breakChars = array(' ', ',', ')', ':', '|', "\r", "\n", "\t", ';', $this->rd);
2899
        } elseif ($curBlock === 'expression') {
2900
            $breakChars = array('/', '%', '+', '-', '*', ' ', ',', ')', "\r", "\n", "\t", ';', $this->rd);
2901
        } else {
2902
            $breakChars = array(' ', ',', ')', "\r", "\n", "\t", ';', $this->rd);
2903
        }
2904
2905
        $breaker = false;
2906
        foreach ($breakChars as $k => $char) {
2907
            $test = strpos($substr, $char);
2908
            if ($test !== false && $test < $end) {
2909
                $end     = $test;
2910
                $breaker = $k;
2911
            }
2912
        }
2913
2914
        if ($curBlock === 'condition') {
2915
            if ($end === 0 && $breaker !== false) {
2916
                $end = strlen($breakChars[$breaker]);
2917
            }
2918
        }
2919
2920
        if ($end !== false) {
2921
            $substr = substr($substr, 0, $end);
2922
        }
2923
2924
        if ($pointer !== null) {
2925
            $pointer += strlen($substr);
2926
        }
2927
2928
        $src    = $substr;
2929
        $substr = trim($substr);
2930
2931
        if (strtolower($substr) === 'false' || strtolower($substr) === 'no' || strtolower($substr) === 'off') {
2932
            if ($this->debug) {
2933
                echo 'BOOLEAN(FALSE) PARSED' . "\n";
2934
            }
2935
            $substr = 'false';
2936
            $type   = self::T_BOOL;
2937
        } elseif (strtolower($substr) === 'true' || strtolower($substr) === 'yes' || strtolower($substr) === 'on') {
2938
            if ($this->debug) {
2939
                echo 'BOOLEAN(TRUE) PARSED' . "\n";
2940
            }
2941
            $substr = 'true';
2942
            $type   = self::T_BOOL;
2943 View Code Duplication
        } elseif ($substr === 'null' || $substr === 'NULL') {
2944
            if ($this->debug) {
2945
                echo 'NULL PARSED' . "\n";
2946
            }
2947
            $substr = 'null';
2948
            $type   = self::T_NULL;
2949
        } elseif (is_numeric($substr)) {
2950
            $substr = (float)$substr;
2951
            if ((int)$substr == $substr) {
2952
                $substr = (int)$substr;
2953
            }
2954
            $type = self::T_NUMERIC;
2955
            if ($this->debug) {
2956
                echo 'NUMBER (' . $substr . ') PARSED' . "\n";
2957
            }
2958 View Code Duplication
        } elseif (preg_match('{^-?(\d+|\d*(\.\d+))\s*([/*%+-]\s*-?(\d+|\d*(\.\d+)))+$}', $substr)) {
2959
            if ($this->debug) {
2960
                echo 'SIMPLE MATH PARSED . "\n"';
2961
            }
2962
            $type   = self::T_MATH;
2963
            $substr = '(' . $substr . ')';
2964
        } elseif ($curBlock === 'condition' && array_search($substr, $breakChars, true) !== false) {
2965
            if ($this->debug) {
2966
                echo 'BREAKCHAR (' . $substr . ') PARSED' . "\n";
2967
            }
2968
            $type = self::T_BREAKCHAR;
2969
            //$substr = '"'.$substr.'"';
2970
        } else {
2971
            $substr = $this->replaceStringVars('\'' . str_replace('\'', '\\\'', $substr) . '\'', '\'', $curBlock);
2972
            $type   = self::T_UNQUOTED_STRING;
2973
            if ($this->debug) {
2974
                echo 'BLABBER (' . $substr . ') CASTED AS STRING' . "\n";
2975
            }
2976
        }
2977
2978
        if (is_array($parsingParams)) {
2979
            $parsingParams[] = array($substr, $src, $type);
2980
2981
            return $parsingParams;
2982
        } elseif ($curBlock === 'namedparam') {
2983
            return array($substr, $src, $type);
2984
        } elseif ($curBlock === 'expression') {
2985
            return $substr;
2986
        } else {
2987
            throw new Exception('Something went wrong');
2988
        }
2989
    }
2990
2991
    /**
2992
     * Replaces variables within a parsed string.
2993
     *
2994
     * @param string $string   the parsed string
2995
     * @param string $first    the first character parsed in the string, which is the string delimiter (' or ")
2996
     * @param string $curBlock the current parser-block being processed
2997
     *
2998
     * @return string the original string with variables replaced
2999
     */
3000
    protected function replaceStringVars($string, $first, $curBlock = '')
3001
    {
3002
        $pos = 0;
3003
        if ($this->debug) {
3004
            echo 'STRING VAR REPLACEMENT : ' . $string . "\n";
3005
        }
3006
        // replace vars
3007
        while (($pos = strpos($string, '$', $pos)) !== false) {
3008
            $prev = substr($string, $pos - 1, 1);
3009
            if ($prev === '\\') {
3010
                ++ $pos;
3011
                continue;
3012
            }
3013
3014
            $var = $this->parse($string, $pos, null, false, ($curBlock === 'modifier' ? 'modifier' : ($prev === '`' ? 'delimited_string' : 'string')));
3015
            $len = $var[0];
3016
            $var = $this->parse(str_replace('\\' . $first, $first, $string), $pos, null, false, ($curBlock === 'modifier' ? 'modifier' : ($prev === '`' ? 'delimited_string' : 'string')));
3017
3018
            if ($prev === '`' && substr($string, $pos + $len, 1) === '`') {
3019
                $string = substr_replace($string, $first . '.' . $var[1] . '.' . $first, $pos - 1, $len + 2);
3020
            } else {
3021
                $string = substr_replace($string, $first . '.' . $var[1] . '.' . $first, $pos, $len);
3022
            }
3023
            $pos += strlen($var[1]) + 2;
3024
            if ($this->debug) {
3025
                echo 'STRING VAR REPLACEMENT DONE : ' . $string . "\n";
3026
            }
3027
        }
3028
3029
        // handle modifiers
3030
        // TODO Obsolete?
3031
        $string = preg_replace_callback(
3032
            '#("|\')\.(.+?)\.\1((?:\|(?:@?[a-z0-9_]+(?:(?::("|\').+?\4|:[^`]*))*))+)#i', array(
3033
            $this,
3034
            'replaceModifiers'
3035
            ), $string
3036
        );
3037
3038
        // replace escaped dollar operators by unescaped ones if required
3039
        if ($first === "'") {
3040
            $string = str_replace('\\$', '$', $string);
3041
        }
3042
3043
        return $string;
3044
    }
3045
3046
    /**
3047
     * Replaces the modifiers applied to a string or a variable.
3048
     *
3049
     * @param array  $m        the regex matches that must be array(1=>"double or single quotes enclosing a string,
3050
     *                         when applicable", 2=>"the string or var", 3=>"the modifiers matched")
3051
     * @param string $curBlock the current parser-block being processed
3052
     * @param null   $pointer
3053
     *
3054
     * @return string the input enclosed with various function calls according to the modifiers found
3055
     * @throws CompilationException
3056
     * @throws Exception
3057
     */
3058
    protected function replaceModifiers(array $m, $curBlock = null, &$pointer = null)
3059
    {
3060
        if ($this->debug) {
3061
            echo 'PARSING MODIFIERS : ' . $m[3] . "\n";
3062
        }
3063
3064
        if ($pointer !== null) {
3065
            $pointer += strlen($m[3]);
3066
        }
3067
        // remove first pipe
3068
        $cmdstrsrc = substr($m[3], 1);
3069
        // remove last quote if present
3070
        if (substr($cmdstrsrc, - 1, 1) === $m[1]) {
3071
            $cmdstrsrc = substr($cmdstrsrc, 0, - 1);
3072
            $add       = $m[1];
3073
        }
3074
3075
        $output = $m[2];
3076
3077
        $continue = true;
3078
        while (strlen($cmdstrsrc) > 0 && $continue) {
3079
            if ($cmdstrsrc[0] === '|') {
3080
                $cmdstrsrc = substr($cmdstrsrc, 1);
3081
                continue;
3082
            }
3083
            if ($cmdstrsrc[0] === ' ' || $cmdstrsrc[0] === ';' || substr($cmdstrsrc, 0, strlen($this->rd)) === $this->rd) {
3084
                if ($this->debug) {
3085
                    echo 'MODIFIER PARSING ENDED, RIGHT DELIMITER or ";" FOUND' . "\n";
3086
                }
3087
                $continue = false;
3088
                if ($pointer !== null) {
3089
                    $pointer -= strlen($cmdstrsrc);
3090
                }
3091
                break;
3092
            }
3093
            $cmdstr   = $cmdstrsrc;
3094
            $paramsep = ':';
3095 View Code Duplication
            if (!preg_match('/^(@{0,2}[a-z_][a-z0-9_]*)(:)?/i', $cmdstr, $match)) {
0 ignored issues
show
This code seems to be duplicated across your project.

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

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

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