Passed
Push — master ( 020752...f1625c )
by David
05:47 queued 02:32
created

lib/Dwoo/Core.php (3 issues)

Upgrade to new PHP Analysis Engine

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

1
<?php
2
/**
3
 * Copyright (c) 2013-2017
4
 *
5
 * @category  Library
6
 * @package   Dwoo
7
 * @author    Jordi Boggiano <[email protected]>
8
 * @author    David Sanchez <[email protected]>
9
 * @copyright 2008-2013 Jordi Boggiano
10
 * @copyright 2013-2017 David Sanchez
11
 * @license   http://dwoo.org/LICENSE LGPLv3
12
 * @version   1.4.0
13
 * @date      2017-03-16
14
 * @link      http://dwoo.org/
15
 */
16
17
namespace Dwoo;
18
19
use ArrayAccess;
20
use Closure;
21
use Countable;
22
use Dwoo\Plugins\Blocks\PluginDynamic;
23
use Dwoo\Security\Policy as SecurityPolicy;
24
use Dwoo\Block\Plugin as BlockPlugin;
25
use Dwoo\Template\File as TemplateFile;
26
use Iterator;
27
use stdClass;
28
use Traversable;
29
30
/**
31
 * Main dwoo class, allows communication between the compiler, template and data classes.
32
 * <pre>
33
 * requirements :
34
 *  php 5.3.0 or above (might work below, it's a rough estimate)
35
 *  SPL and PCRE extensions (for php versions prior to 5.3.0)
36
 *  mbstring extension for some string manipulation plugins (especially if you intend to use UTF-8)
37
 * recommended :
38
 *  hash extension (for Dwoo\Template\Str - minor performance boost)
39
 * project created :
40
 *  2008-01-05
41
 * </pre>
42
 * This software is provided 'as-is', without any express or implied warranty.
43
 * In no event will the authors be held liable for any damages arising from the use of this software.
44
 */
45
class Core
46
{
47
    /**
48
     * Current version number.
49
     *
50
     * @var string
51
     */
52
    const VERSION = '1.3.4';
53
54
    /**
55
     * Unique number of this dwoo release, based on version number.
56
     * this can be used by templates classes to check whether the compiled template
57
     * has been compiled before this release or not, so that old templates are
58
     * recompiled automatically when Dwoo is updated
59
     */
60
    const RELEASE_TAG = 134;
61
62
    /**
63
     * Constants that represents all plugin types
64
     * these are bitwise-operation-safe values to allow multiple types
65
     * on a single plugin
66
     *
67
     * @var int
68
     */
69
    const CLASS_PLUGIN      = 1;
70
    const FUNC_PLUGIN       = 2;
71
    const NATIVE_PLUGIN     = 4;
72
    const BLOCK_PLUGIN      = 8;
73
    const COMPILABLE_PLUGIN = 16;
74
    const CUSTOM_PLUGIN     = 32;
75
    const SMARTY_MODIFIER   = 64;
76
    const SMARTY_BLOCK      = 128;
77
    const SMARTY_FUNCTION   = 256;
78
    const PROXY_PLUGIN      = 512;
79
    const TEMPLATE_PLUGIN   = 1024;
80
81
    /**
82
     * Constant to default namespaces of builtin plugins
83
     *
84
     * @var string
85
     */
86
    const NAMESPACE_PLUGINS_BLOCKS     = 'Dwoo\Plugins\Blocks\\';
87
    const NAMESPACE_PLUGINS_FILTERS    = 'Dwoo\Plugins\Filters\\';
88
    const NAMESPACE_PLUGINS_FUNCTIONS  = 'Dwoo\Plugins\Functions\\';
89
    const NAMESPACE_PLUGINS_HELPERS    = 'Dwoo\Plugins\Helpers\\';
90
    const NAMESPACE_PLUGINS_PROCESSORS = 'Dwoo\Plugins\Processors\\';
91
92
    /**
93
     * Character set of the template, used by string manipulation plugins.
94
     * it must be lowercase, but setCharset() will take care of that
95
     *
96
     * @see setCharset
97
     * @see getCharset
98
     * @var string
99
     */
100
    protected $charset = 'UTF-8';
101
102
    /**
103
     * Global variables that are accessible through $dwoo.* in the templates.
104
     * default values include:
105
     * $dwoo.version - current version number
106
     * $dwoo.ad - a Powered by Dwoo link pointing to dwoo.org
107
     * $dwoo.now - the current time
108
     * $dwoo.template - the current template filename
109
     * $dwoo.charset - the character set used by the template
110
     * on top of that, foreach and other plugins can store special values in there,
111
     * see their documentation for more details.
112
     *
113
     * @var array
114
     */
115
    protected $globals = array();
116
117
    /**
118
     * Directory where the compiled templates are stored.
119
     * defaults to DWOO_COMPILEDIR (= dwoo_dir/compiled by default)
120
     *
121
     * @var string
122
     */
123
    protected $compileDir;
124
125
    /**
126
     * Directory where the cached templates are stored.
127
     * defaults to DWOO_CACHEDIR (= dwoo_dir/cache by default)
128
     *
129
     * @var string
130
     */
131
    protected $cacheDir;
132
133
    /**
134
     * Directory where the template files are stored
135
     *
136
     * @var array
137
     */
138
    protected $templateDir = array();
139
140
    /**
141
     * Defines how long (in seconds) the cached files must remain valid.
142
     * can be overridden on a per-template basis
143
     * -1 = never delete
144
     * 0 = disabled
145
     * >0 = duration in seconds
146
     *
147
     * @var int
148
     */
149
    protected $cacheTime = 0;
150
151
    /**
152
     * Security policy object.
153
     *
154
     * @var SecurityPolicy
155
     */
156
    protected $securityPolicy = null;
157
158
    /**
159
     * Stores the custom plugins callbacks.
160
     *
161
     * @see addPlugin
162
     * @see removePlugin
163
     * @var array
164
     */
165
    protected $plugins = array();
166
167
    /**
168
     * Stores the filter callbacks.
169
     *
170
     * @see addFilter
171
     * @see removeFilter
172
     * @var array
173
     */
174
    protected $filters = array();
175
176
    /**
177
     * Stores the resource types and associated
178
     * classes / compiler classes.
179
     *
180
     * @var array
181
     */
182
    protected $resources = array(
183
        'file'   => array(
184
            'class'    => 'Dwoo\Template\File',
185
            'compiler' => null,
186
        ),
187
        'string' => array(
188
            'class'    => 'Dwoo\Template\Str',
189
            'compiler' => null,
190
        ),
191
    );
192
193
    /**
194
     * The dwoo loader object used to load plugins by this dwoo instance.
195
     *
196
     * @var ILoader
197
     */
198
    protected $loader = null;
199
200
    /**
201
     * Currently rendered template, set to null when not-rendering.
202
     *
203
     * @var ITemplate
204
     */
205
    protected $template = null;
206
207
    /**
208
     * Stores the instances of the class plugins during template runtime.
209
     *
210
     * @var array
211
     */
212
    protected $runtimePlugins = array();
213
214
    /**
215
     * Stores the returned values during template runtime.
216
     *
217
     * @var array
218
     */
219
    protected $returnData = array();
220
221
    /**
222
     * Stores the data during template runtime.
223
     *
224
     * @var array
225
     */
226
    protected $data = array();
227
228
    /**
229
     * Stores the current scope during template runtime.
230
     * this should ideally not be accessed directly from outside template code
231
     *
232
     * @var mixed
233
     */
234
    public $scope;
235
236
    /**
237
     * Stores the scope tree during template runtime.
238
     *
239
     * @var array
240
     */
241
    protected $scopeTree = array();
242
243
    /**
244
     * Stores the block plugins stack during template runtime.
245
     *
246
     * @var array
247
     */
248
    protected $stack = array();
249
250
    /**
251
     * Stores the current block plugin at the top of the stack during template runtime.
252
     *
253
     * @var BlockPlugin
254
     */
255
    protected $curBlock;
256
257
    /**
258
     * Stores the output buffer during template runtime.
259
     *
260
     * @var string
261
     */
262
    protected $buffer;
263
264
    /**
265
     * Stores plugin proxy.
266
     *
267
     * @var IPluginProxy
268
     */
269
    protected $pluginProxy;
270
271
    /**
272
     * Constructor, sets the cache and compile dir to the default values if not provided.
273
     *
274
     * @param string $compileDir path to the compiled directory, defaults to lib/compiled
275
     * @param string $cacheDir   path to the cache directory, defaults to lib/cache
276
     */
277
    public function __construct($compileDir = null, $cacheDir = null)
278
    {
279
        if ($compileDir !== null) {
280
            $this->setCompileDir($compileDir);
281
        }
282
        if ($cacheDir !== null) {
283
            $this->setCacheDir($cacheDir);
284
        }
285
        $this->initGlobals();
286
    }
287
288
    /**
289
     * Resets some runtime variables to allow a cloned object to be used to render sub-templates.
290
     *
291
     * @return void
292
     */
293
    public function __clone()
294
    {
295
        $this->template = null;
296
        unset($this->data);
297
        unset($this->returnData);
298
    }
299
300
    /**
301
     * Returns the given template rendered using the provided data and optional compiler.
302
     *
303
     * @param mixed     $_tpl      template, can either be a ITemplate object (i.e. TemplateFile), a
304
     *                             valid path to a template, or a template as a string it is recommended to
305
     *                             provide a ITemplate as it will probably make things faster, especially if
306
     *                             you render a template multiple times
307
     * @param mixed     $data      the data to use, can either be a IDataProvider object (i.e. Data) or
308
     *                             an associative array. if you're rendering the template from cache, it can be
309
     *                             left null
310
     * @param ICompiler $_compiler the compiler that must be used to compile the template, if left empty a default
311
     *                             Compiler will be used
312
     *
313
     * @return string|void or the template output if $output is false
314
     * @throws Exception
315
     */
316
    public function get($_tpl, $data = array(), $_compiler = null)
317
    {
318
        // a render call came from within a template, so we need a new dwoo instance in order to avoid breaking this one
319
        if ($this->template instanceof ITemplate) {
320
            $clone = clone $this;
321
322
            return $clone->get($_tpl, $data, $_compiler);
323
        }
324
325
        // auto-create template if required
326
        if ($_tpl instanceof ITemplate) {
327
            // valid, skip
328
        } elseif (is_string($_tpl)) {
329
            $_tpl = new TemplateFile($_tpl);
330
            $_tpl->setIncludePath($this->getTemplateDir());
331
        } else {
332
            throw new Exception('Dwoo->get\'s first argument must be a ITemplate (i.e. TemplateFile) or a valid path to a template file', E_USER_NOTICE);
333
        }
334
335
        // save the current template, enters render mode at the same time
336
        // if another rendering is requested it will be proxied to a new Core(instance
337
        $this->template = $_tpl;
338
339
        // load data
340
        if ($data instanceof IDataProvider) {
341
            $this->data = $data->getData();
342
        } elseif (is_array($data)) {
343
            $this->data = $data;
344
        } elseif ($data instanceof ArrayAccess) {
345
            $this->data = $data;
346
        } else {
347
            throw new Exception('Dwoo->get/Dwoo->output\'s data argument must be a IDataProvider object (i.e. Data) or an associative array', E_USER_NOTICE);
348
        }
349
350
        $this->addGlobal('template', $_tpl->getName());
351
        $this->initRuntimeVars($_tpl);
352
353
        // try to get cached template
354
        $file        = $_tpl->getCachedTemplate($this);
355
        $doCache     = $file === true;
356
        $cacheLoaded = is_string($file);
357
358
        if ($cacheLoaded === true) {
359
            // cache is present, run it
360
            ob_start();
361
            include $file;
362
            $this->template = null;
363
364
            return ob_get_clean();
365
        } else {
366
            $dynamicId = uniqid();
367
368
            // render template
369
            $compiledTemplate = $_tpl->getCompiledTemplate($this, $_compiler);
370
            $out              = include $compiledTemplate;
371
372
            // template returned false so it needs to be recompiled
373
            if ($out === false) {
374
                $_tpl->forceCompilation();
375
                $compiledTemplate = $_tpl->getCompiledTemplate($this, $_compiler);
376
                $out              = include $compiledTemplate;
377
            }
378
379
            if ($doCache === true) {
380
                $out = preg_replace('/(<%|%>|<\?php|<\?|\?>)/', '<?php /*' . $dynamicId . '*/ echo \'$1\'; ?>', $out);
381
                if (!class_exists(self::NAMESPACE_PLUGINS_BLOCKS . 'PluginDynamic')) {
382
                    $this->getLoader()->loadPlugin('PluginDynamic');
383
                }
384
                $out = PluginDynamic::unescape($out, $dynamicId, $compiledTemplate);
385
            }
386
387
            // process filters
388
            foreach ($this->filters as $filter) {
389
                if (is_array($filter) && $filter[0] instanceof Filter) {
390
                    $out = call_user_func($filter, $out);
391
                } else {
392
                    $out = call_user_func($filter, $this, $out);
393
                }
394
            }
395
396
            if ($doCache === true) {
397
                // building cache
398
                $file = $_tpl->cache($this, $out);
399
400
                // run it from the cache to be sure dynamics are rendered
401
                ob_start();
402
                include $file;
403
                // exit render mode
404
                $this->template = null;
405
406
                return ob_get_clean();
407
            } else {
408
                // no need to build cache
409
                // exit render mode
410
                $this->template = null;
411
412
                return $out;
413
            }
414
        }
415
    }
416
417
    /**
418
     * Registers a Global.
419
     * New globals can be added before compiling or rendering a template
420
     * but after, you can only update existing globals.
421
     *
422
     * @param string $name
423
     * @param mixed  $value
424
     *
425
     * @return $this
426
     * @throws Exception
427
     */
428
    public function addGlobal($name, $value)
429
    {
430
        if (null === $this->globals) {
431
            $this->initGlobals();
432
        }
433
434
        $this->globals[$name] = $value;
435
436
        return $this;
437
    }
438
439
    /**
440
     * Gets the registered Globals.
441
     *
442
     * @return array
443
     */
444
    public function getGlobals()
445
    {
446
        return $this->globals;
447
    }
448
449
    /**
450
     * Re-initializes the globals array before each template run.
451
     * this method is only callede once when the Dwoo object is created
452
     *
453
     * @return void
454
     */
455
    protected function initGlobals()
456
    {
457
        $this->globals = array(
458
            'version' => self::VERSION,
459
            'ad'      => '<a href="http://dwoo.org/">Powered by Dwoo</a>',
460
            'now'     => $_SERVER['REQUEST_TIME'],
461
            'charset' => $this->getCharset(),
462
        );
463
    }
464
465
    /**
466
     * Re-initializes the runtime variables before each template run.
467
     * override this method to inject data in the globals array if needed, this
468
     * method is called before each template execution
469
     *
470
     * @param ITemplate $tpl the template that is going to be rendered
471
     *
472
     * @return void
473
     */
474
    protected function initRuntimeVars(ITemplate $tpl)
475
    {
476
        $this->runtimePlugins = array();
477
        $this->scope          = &$this->data;
478
        $this->scopeTree      = array();
479
        $this->stack          = array();
480
        $this->curBlock       = null;
481
        $this->buffer         = '';
482
        $this->returnData     = array();
483
    }
484
485
    /**
486
     * Adds a custom plugin that is not in one of the plugin directories.
487
     *
488
     * @param string   $name       the plugin name to be used in the templates
489
     * @param callback $callback   the plugin callback, either a function name,
490
     *                             a class name or an array containing an object
491
     *                             or class name and a method name
492
     * @param bool     $compilable if set to true, the plugin is assumed to be compilable
493
     *
494
     * @return void
495
     * @throws Exception
496
     */
497
    public function addPlugin($name, $callback, $compilable = false)
498
    {
499
        $compilable = $compilable ? self::COMPILABLE_PLUGIN : 0;
500
        if (is_array($callback)) {
501
            if (is_subclass_of(is_object($callback[0]) ? get_class($callback[0]) : $callback[0], 'Dwoo\Block\Plugin')) {
502
                $this->plugins[$name] = array(
503
                    'type'     => self::BLOCK_PLUGIN | $compilable,
504
                    'callback' => $callback,
505
                    'class'    => (is_object($callback[0]) ? get_class($callback[0]) : $callback[0])
506
                );
507
            } else {
508
                $this->plugins[$name] = array(
509
                    'type'     => self::CLASS_PLUGIN | $compilable,
510
                    'callback' => $callback,
511
                    'class'    => (is_object($callback[0]) ? get_class($callback[0]) : $callback[0]),
512
                    'function' => $callback[1]
513
                );
514
            }
515
        } elseif (is_string($callback)) {
516
            if (class_exists($callback)) {
517
                if (is_subclass_of($callback, 'Dwoo\Block\Plugin')) {
518
                    $this->plugins[$name] = array(
519
                        'type'     => self::BLOCK_PLUGIN | $compilable,
520
                        'callback' => $callback,
521
                        'class'    => $callback
522
                    );
523
                } else {
524
                    $this->plugins[$name] = array(
525
                        'type'     => self::CLASS_PLUGIN | $compilable,
526
                        'callback' => $callback,
527
                        'class'    => $callback,
528
                        'function' => ($compilable ? 'compile' : 'process')
529
                    );
530
                }
531
            } elseif (function_exists($callback)) {
532
                $this->plugins[$name] = array(
533
                    'type'     => self::FUNC_PLUGIN | $compilable,
534
                    'callback' => $callback
535
                );
536
            } else {
537
                throw new Exception(
538
                    'Callback could not be processed correctly, please check that the function/class 
539
                you used exists'
540
                );
541
            }
542
        } elseif ($callback instanceof Closure) {
543
            $this->plugins[$name] = array(
544
                'type'     => self::FUNC_PLUGIN | $compilable,
545
                'callback' => $callback
546
            );
547
        } else {
548
            throw new Exception(
549
                'Callback could not be processed correctly, please check that the function/class you 
550
            used exists'
551
            );
552
        }
553
    }
554
555
    /**
556
     * Removes a custom plugin.
557
     *
558
     * @param string $name the plugin name
559
     *
560
     * @return void
561
     */
562
    public function removePlugin($name)
563
    {
564
        if (isset($this->plugins[$name])) {
565
            unset($this->plugins[$name]);
566
        }
567
    }
568
569
    /**
570
     * Adds a filter to this Dwoo instance, it will be used to filter the output of all the templates rendered by this
571
     * instance.
572
     *
573
     * @param mixed $callback a callback or a filter name if it is autoloaded from a plugin directory
574
     * @param bool  $autoload if true, the first parameter must be a filter name from one of the plugin directories
575
     *
576
     * @return void
577
     * @throws Exception
578
     */
579
    public function addFilter($callback, $autoload = false)
580
    {
581
        if ($autoload) {
582
            $class = self::NAMESPACE_PLUGINS_FILTERS . self::toCamelCase($callback);
583
            if (!class_exists($class) && !function_exists($class)) {
584
                try {
585
                    $this->getLoader()->loadPlugin($callback);
586
                }
587
                catch (Exception $e) {
588
                    if (strstr($callback, self::NAMESPACE_PLUGINS_FILTERS)) {
589
                        throw new Exception(
590
                            'Wrong filter name : ' . $callback . ', the "Filter" prefix should 
591
                        not be used, please only use "' . str_replace('Filter', '', $callback) . '"'
592
                        );
593
                    } else {
594
                        throw new Exception(
595
                            'Wrong filter name : ' . $callback . ', when using autoload the filter must
596
                         be in one of your plugin dir as "name.php" containig a class or function named
597
                         "Filter<name>"'
598
                        );
599
                    }
600
                }
601
            }
602
603
            if (class_exists($class)) {
604
                $callback = array(new $class($this), 'process');
605
            } elseif (function_exists($class)) {
606
                $callback = $class;
607
            } else {
608
                throw new Exception(
609
                    'Wrong filter name : ' . $callback . ', when using autoload the filter must be in
610
                one of your plugin dir as "name.php" containig a class or function named "Filter<name>"'
611
                );
612
            }
613
614
            $this->filters[] = $callback;
615
        } else {
616
            $this->filters[] = $callback;
617
        }
618
    }
619
620
    /**
621
     * Removes a filter.
622
     *
623
     * @param mixed $callback callback or filter name if it was autoloaded
624
     *
625
     * @return void
626
     */
627
    public function removeFilter($callback)
628
    {
629
        if (($index = array_search(self::NAMESPACE_PLUGINS_FILTERS. 'Filter' . self::toCamelCase($callback), $this->filters,
630
                true)) !==
631
            false) {
632
            unset($this->filters[$index]);
633
        } elseif (($index = array_search($callback, $this->filters, true)) !== false) {
634
            unset($this->filters[$index]);
635
        } else {
636
            $class = self::NAMESPACE_PLUGINS_FILTERS . 'Filter' . $callback;
637
            foreach ($this->filters as $index => $filter) {
638
                if (is_array($filter) && $filter[0] instanceof $class) {
639
                    unset($this->filters[$index]);
640
                    break;
641
                }
642
            }
643
        }
644
    }
645
646
    /**
647
     * Adds a resource or overrides a default one.
648
     *
649
     * @param string   $name            the resource name
650
     * @param string   $class           the resource class (which must implement ITemplate)
651
     * @param callback $compilerFactory the compiler factory callback, a function that must return a compiler instance
652
     *                                  used to compile this resource, if none is provided. by default it will produce
653
     *                                  a Compiler object
654
     *
655
     * @return void
656
     * @throws Exception
657
     */
658
    public function addResource($name, $class, $compilerFactory = null)
659
    {
660
        if (strlen($name) < 2) {
661
            throw new Exception('Resource names must be at least two-character long to avoid conflicts with Windows paths');
662
        }
663
664
        if (!class_exists($class)) {
665
            throw new Exception(sprintf('Resource class %s does not exist', $class));
666
        }
667
668
        $interfaces = class_implements($class);
669
        if (in_array('Dwoo\ITemplate', $interfaces) === false) {
670
            throw new Exception('Resource class must implement ITemplate');
671
        }
672
673
        $this->resources[$name] = array(
674
            'class'    => $class,
675
            'compiler' => $compilerFactory
676
        );
677
    }
678
679
    /**
680
     * Removes a custom resource.
681
     *
682
     * @param string $name the resource name
683
     *
684
     * @return void
685
     */
686
    public function removeResource($name)
687
    {
688
        unset($this->resources[$name]);
689
        if ($name === 'file') {
690
            $this->resources['file'] = array(
691
                'class'    => 'Dwoo\Template\File',
692
                'compiler' => null
693
            );
694
        }
695
    }
696
697
    /**
698
     * Sets the loader object to use to load plugins.
699
     *
700
     * @param ILoader $loader loader
701
     *
702
     * @return void
703
     */
704
    public function setLoader(ILoader $loader)
705
    {
706
        $this->loader = $loader;
707
    }
708
709
    /**
710
     * Returns the current loader object or a default one if none is currently found.
711
     *
712
     * @return ILoader|Loader
713
     */
714
    public function getLoader()
715
    {
716
        if ($this->loader === null) {
717
            $this->loader = new Loader($this->getCompileDir());
718
        }
719
720
        return $this->loader;
721
    }
722
723
    /**
724
     * Returns the custom plugins loaded.
725
     * Used by the ITemplate classes to pass the custom plugins to their ICompiler instance.
726
     *
727
     * @return array
728
     */
729
    public function getCustomPlugins()
730
    {
731
        return $this->plugins;
732
    }
733
734
    /**
735
     * Return a specified custom plugin loaded by his name.
736
     * Used by the compiler, for executing a Closure.
737
     *
738
     * @param string $name
739
     *
740
     * @return mixed|null
741
     */
742
    public function getCustomPlugin($name)
743
    {
744
        if (isset($this->plugins[$name])) {
745
            return $this->plugins[$name]['callback'];
746
        }
747
748
        return null;
749
    }
750
751
    /**
752
     * Returns the cache directory with a trailing DIRECTORY_SEPARATOR.
753
     *
754
     * @return string
755
     */
756
    public function getCacheDir()
757
    {
758
        if ($this->cacheDir === null) {
759
            $this->setCacheDir(dirname(__DIR__) . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR);
760
        }
761
762
        return $this->cacheDir;
763
    }
764
765
    /**
766
     * Sets the cache directory and automatically appends a DIRECTORY_SEPARATOR.
767
     *
768
     * @param string $dir the cache directory
769
     *
770
     * @return void
771
     * @throws Exception
772
     */
773 View Code Duplication
    public function setCacheDir($dir)
774
    {
775
        $this->cacheDir = rtrim($dir, '/\\') . DIRECTORY_SEPARATOR;
776
        if (is_writable($this->cacheDir) === false) {
777
            throw new Exception('The cache directory must be writable, chmod "' . $this->cacheDir . '" to make it writable');
778
        }
779
    }
780
781
    /**
782
     * Returns the compile directory with a trailing DIRECTORY_SEPARATOR.
783
     *
784
     * @return string
785
     */
786
    public function getCompileDir()
787
    {
788
        if ($this->compileDir === null) {
789
            $this->setCompileDir(dirname(__DIR__) . DIRECTORY_SEPARATOR . 'compiled' . DIRECTORY_SEPARATOR);
790
        }
791
792
        return $this->compileDir;
793
    }
794
795
    /**
796
     * Sets the compile directory and automatically appends a DIRECTORY_SEPARATOR.
797
     *
798
     * @param string $dir the compile directory
799
     *
800
     * @return void
801
     * @throws Exception
802
     */
803 View Code Duplication
    public function setCompileDir($dir)
804
    {
805
        $this->compileDir = rtrim($dir, '/\\') . DIRECTORY_SEPARATOR;
806
        if (is_writable($this->compileDir) === false) {
807
            throw new Exception('The compile directory must be writable, chmod "' . $this->compileDir . '" to make it writable');
808
        }
809
    }
810
811
    /**
812
     * Returns an array of the template directory with a trailing DIRECTORY_SEPARATOR
813
     *
814
     * @return array
815
     */
816
    public function getTemplateDir()
817
    {
818
        return $this->templateDir;
819
    }
820
821
    /**
822
     * sets the template directory and automatically appends a DIRECTORY_SEPARATOR
823
     * template directory is stored in an array
824
     *
825
     * @param string $dir
826
     *
827
     * @throws Exception
828
     */
829
    public function setTemplateDir($dir)
830
    {
831
        $tmpDir = rtrim($dir, '/\\') . DIRECTORY_SEPARATOR;
832
        if (is_dir($tmpDir) === false) {
833
            throw new Exception('The template directory: "' . $tmpDir . '" does not exists, create the directory or specify an other location !');
834
        }
835
        $this->templateDir[] = $tmpDir;
836
    }
837
838
    /**
839
     * Returns the default cache time that is used with templates that do not have a cache time set.
840
     *
841
     * @return int the duration in seconds
842
     */
843
    public function getCacheTime()
844
    {
845
        return $this->cacheTime;
846
    }
847
848
    /**
849
     * Sets the default cache time to use with templates that do not have a cache time set.
850
     *
851
     * @param int $seconds the duration in seconds
852
     *
853
     * @return void
854
     */
855
    public function setCacheTime($seconds)
856
    {
857
        $this->cacheTime = (int)$seconds;
858
    }
859
860
    /**
861
     * Returns the character set used by the string manipulation plugins.
862
     * the charset is automatically lowercased
863
     *
864
     * @return string
865
     */
866
    public function getCharset()
867
    {
868
        return $this->charset;
869
    }
870
871
    /**
872
     * Sets the character set used by the string manipulation plugins.
873
     * the charset will be automatically lowercased
874
     *
875
     * @param string $charset the character set
876
     *
877
     * @return void
878
     */
879
    public function setCharset($charset)
880
    {
881
        $this->charset = strtolower((string)$charset);
882
    }
883
884
    /**
885
     * Returns the current template being rendered, when applicable, or null.
886
     *
887
     * @return ITemplate|null
888
     */
889
    public function getTemplate()
890
    {
891
        return $this->template;
892
    }
893
894
    /**
895
     * Sets the current template being rendered.
896
     *
897
     * @param ITemplate $tpl template object
898
     *
899
     * @return void
900
     */
901
    public function setTemplate(ITemplate $tpl)
902
    {
903
        $this->template = $tpl;
904
    }
905
906
    /**
907
     * Sets the default compiler factory function for the given resource name.
908
     * a compiler factory must return a ICompiler object pre-configured to fit your needs
909
     *
910
     * @param string   $resourceName    the resource name (i.e. file, string)
911
     * @param callback $compilerFactory the compiler factory callback
912
     *
913
     * @return void
914
     */
915
    public function setDefaultCompilerFactory($resourceName, $compilerFactory)
916
    {
917
        $this->resources[$resourceName]['compiler'] = $compilerFactory;
918
    }
919
920
    /**
921
     * Returns the default compiler factory function for the given resource name.
922
     *
923
     * @param string $resourceName the resource name
924
     *
925
     * @return callback the compiler factory callback
926
     */
927
    public function getDefaultCompilerFactory($resourceName)
928
    {
929
        return $this->resources[$resourceName]['compiler'];
930
    }
931
932
    /**
933
     * Sets the security policy object to enforce some php security settings.
934
     * use this if untrusted persons can modify templates
935
     *
936
     * @param SecurityPolicy $policy the security policy object
937
     *
938
     * @return void
939
     */
940
    public function setSecurityPolicy(SecurityPolicy $policy = null)
941
    {
942
        $this->securityPolicy = $policy;
943
    }
944
945
    /**
946
     * Returns the current security policy object or null by default.
947
     *
948
     * @return SecurityPolicy|null the security policy object if any
949
     */
950
    public function getSecurityPolicy()
951
    {
952
        return $this->securityPolicy;
953
    }
954
955
    /**
956
     * Sets the object that must be used as a plugin proxy when plugin can't be found
957
     * by dwoo's loader.
958
     *
959
     * @param IPluginProxy $pluginProxy the proxy object
960
     *
961
     * @return void
962
     */
963
    public function setPluginProxy(IPluginProxy $pluginProxy)
964
    {
965
        $this->pluginProxy = $pluginProxy;
966
    }
967
968
    /**
969
     * Returns the current plugin proxy object or null by default.
970
     *
971
     * @return IPluginProxy
972
     */
973
    public function getPluginProxy()
974
    {
975
        return $this->pluginProxy;
976
    }
977
978
    /**
979
     * Checks whether the given template is cached or not.
980
     *
981
     * @param ITemplate $tpl the template object
982
     *
983
     * @return bool
984
     */
985
    public function isCached(ITemplate $tpl)
986
    {
987
        return is_string($tpl->getCachedTemplate($this));
988
    }
989
990
    /**
991
     * Clear templates inside the compiled directory.
992
     *
993
     * @return int
994
     */
995
    public function clearCompiled()
996
    {
997
        $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->getCompileDir()), \RecursiveIteratorIterator::SELF_FIRST);
998
        $count    = 0;
999
        foreach ($iterator as $file) {
1000
            if ($file->isFile()) {
1001
                $count += unlink($file->__toString()) ? 1 : 0;
1002
            }
1003
        }
1004
1005
        return $count;
1006
    }
1007
1008
    /**
1009
     * Clears the cached templates if they are older than the given time.
1010
     *
1011
     * @param int $olderThan minimum time (in seconds) required for a cached template to be cleared
1012
     *
1013
     * @return int the amount of templates cleared
1014
     */
1015
    public function clearCache($olderThan = - 1)
1016
    {
1017
        $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->getCacheDir()), \RecursiveIteratorIterator::SELF_FIRST);
1018
        $expired  = time() - $olderThan;
1019
        $count    = 0;
1020
        foreach ($iterator as $file) {
1021
            if ($file->isFile() && $file->getCTime() < $expired) {
1022
                $count += unlink((string)$file) ? 1 : 0;
1023
            }
1024
        }
1025
1026
        return $count;
1027
    }
1028
1029
    /**
1030
     * Fetches a template object of the given resource.
1031
     *
1032
     * @param string    $resourceName   the resource name (i.e. file, string)
1033
     * @param string    $resourceId     the resource identifier (i.e. file path)
1034
     * @param int       $cacheTime      the cache time setting for this resource
1035
     * @param string    $cacheId        the unique cache identifier
1036
     * @param string    $compileId      the unique compiler identifier
1037
     * @param ITemplate $parentTemplate the parent template
1038
     *
1039
     * @return ITemplate
1040
     * @throws Exception
1041
     */
1042
    public function templateFactory($resourceName, $resourceId, $cacheTime = null, $cacheId = null, $compileId = null, ITemplate $parentTemplate = null)
1043
    {
1044
        if (isset($this->resources[$resourceName])) {
1045
            /**
1046
             * Interface ITemplate
1047
             *
1048
             * @var ITemplate $class
1049
             */
1050
            $class = $this->resources[$resourceName]['class'];
1051
1052
            return $class::templateFactory($this, $resourceId, $cacheTime, $cacheId, $compileId, $parentTemplate);
1053
        }
1054
1055
        throw new Exception('Unknown resource type : ' . $resourceName);
1056
    }
1057
1058
    /**
1059
     * Checks if the input is an array or arrayaccess object, optionally it can also check if it's
1060
     * empty.
1061
     *
1062
     * @param mixed $value        the variable to check
1063
     * @param bool  $checkIsEmpty if true, the function will also check if the array|arrayaccess is empty,
1064
     *                            and return true only if it's not empty
1065
     *
1066
     * @return int|bool true if it's an array|arrayaccess (or the item count if $checkIsEmpty is true) or false if it's
1067
     *                  not an array|arrayaccess (or 0 if $checkIsEmpty is true)
1068
     */
1069
    public function isArray($value, $checkIsEmpty = false)
1070
    {
1071
        if (is_array($value) === true || $value instanceof ArrayAccess) {
1072
            if ($checkIsEmpty === false) {
1073
                return true;
1074
            }
1075
1076
            return $this->count($value);
1077
        }
1078
1079
        return false;
1080
    }
1081
1082
    /**
1083
     * Checks if the input is an array or a traversable object, optionally it can also check if it's
1084
     * empty.
1085
     *
1086
     * @param mixed $value        the variable to check
1087
     * @param bool  $checkIsEmpty if true, the function will also check if the array|traversable is empty,
1088
     *                            and return true only if it's not empty
1089
     *
1090
     * @return int|bool true if it's an array|traversable (or the item count if $checkIsEmpty is true) or false if it's
1091
     *                  not an array|traversable (or 0 if $checkIsEmpty is true)
1092
     */
1093
    public function isTraversable($value, $checkIsEmpty = false)
1094
    {
1095
        if (is_array($value) === true) {
1096
            if ($checkIsEmpty === false) {
1097
                return true;
1098
            } else {
1099
                return count($value) > 0;
1100
            }
1101
        } elseif ($value instanceof Traversable) {
1102
            if ($checkIsEmpty === false) {
1103
                return true;
1104
            } else {
1105
                return $this->count($value);
1106
            }
1107
        }
1108
1109
        return false;
1110
    }
1111
1112
    /**
1113
     * Counts an array or arrayaccess/traversable object.
1114
     *
1115
     * @param mixed $value the value to count
1116
     *
1117
     * @return int|bool the count for arrays and objects that implement countable, true for other objects that don't,
1118
     *                  and 0 for empty elements
1119
     */
1120
    public function count($value)
1121
    {
1122
        if (is_array($value) === true || $value instanceof Countable) {
1123
            return count($value);
1124
        } elseif ($value instanceof ArrayAccess) {
1125
            if ($value->offsetExists(0)) {
1126
                return true;
1127
            }
1128
        } elseif ($value instanceof Iterator) {
1129
            $value->rewind();
1130
            if ($value->valid()) {
1131
                return true;
1132
            }
1133
        } elseif ($value instanceof Traversable) {
1134
            foreach ($value as $dummy) {
1135
                return true;
1136
            }
1137
        }
1138
1139
        return 0;
1140
    }
1141
1142
    /**
1143
     * Triggers a dwoo error.
1144
     *
1145
     * @param string $message the error message
1146
     * @param int    $level   the error level, one of the PHP's E_* constants
1147
     *
1148
     * @return void
1149
     */
1150
    public function triggerError($message, $level = E_USER_NOTICE)
1151
    {
1152
        if (!($tplIdentifier = $this->template->getResourceIdentifier())) {
1153
            $tplIdentifier = $this->template->getResourceName();
1154
        }
1155
        trigger_error('Dwoo error (in ' . $tplIdentifier . ') : ' . $message, $level);
1156
    }
1157
1158
    /**
1159
     * Adds a block to the block stack.
1160
     *
1161
     * @param string $blockName the block name (without `Plugin` prefix)
1162
     * @param array  $args      the arguments to be passed to the block's init() function
1163
     *
1164
     * @return BlockPlugin the newly created block
1165
     */
1166
    public function addStack($blockName, array $args = array())
1167
    {
1168 View Code Duplication
        if (isset($this->plugins[$blockName])) {
1169
            $class = $this->plugins[$blockName]['class'];
1170
        } else {
1171
            $class = self::NAMESPACE_PLUGINS_BLOCKS . 'Plugin' . self::toCamelCase($blockName);
1172
        }
1173
1174
        if ($this->curBlock !== null) {
1175
            $this->curBlock->buffer(ob_get_contents());
1176
            ob_clean();
1177
        } else {
1178
            $this->buffer .= ob_get_contents();
1179
            ob_clean();
1180
        }
1181
1182
        $block = new $class($this);
1183
1184
        call_user_func_array(array($block, 'init'), $args);
1185
1186
        $this->stack[] = $this->curBlock = $block;
1187
1188
        return $block;
1189
    }
1190
1191
    /**
1192
     * Removes the plugin at the top of the block stack.
1193
     * Calls the block buffer() function, followed by a call to end() and finally a call to process()
1194
     *
1195
     * @return void
1196
     */
1197
    public function delStack()
1198
    {
1199
        $args = func_get_args();
1200
1201
        $this->curBlock->buffer(ob_get_contents());
1202
        ob_clean();
1203
1204
        call_user_func_array(array($this->curBlock, 'end'), $args);
1205
1206
        $tmp = array_pop($this->stack);
1207
1208
        if (count($this->stack) > 0) {
1209
            $this->curBlock = end($this->stack);
1210
            $this->curBlock->buffer($tmp->process());
1211
        } else {
1212
            if ($this->buffer !== '') {
1213
                echo $this->buffer;
1214
                $this->buffer = '';
1215
            }
1216
            $this->curBlock = null;
1217
            echo $tmp->process();
1218
        }
1219
1220
        unset($tmp);
1221
    }
1222
1223
    /**
1224
     * Returns the parent block of the given block.
1225
     *
1226
     * @param BlockPlugin $block the block class plugin
1227
     *
1228
     * @return BlockPlugin|false if the given block isn't in the stack
1229
     */
1230
    public function getParentBlock(BlockPlugin $block)
1231
    {
1232
        $index = array_search($block, $this->stack, true);
1233
        if ($index !== false && $index > 0) {
1234
            return $this->stack[$index - 1];
1235
        }
1236
1237
        return false;
1238
    }
1239
1240
    /**
1241
     * Finds the closest block of the given type, starting at the top of the stack.
1242
     *
1243
     * @param string $type the type of plugin you want to find
1244
     *
1245
     * @return BlockPlugin|false if no plugin of such type is in the stack
1246
     */
1247
    public function findBlock($type)
1248
    {
1249 View Code Duplication
        if (isset($this->plugins[$type])) {
1250
            $type = $this->plugins[$type]['class'];
1251
        } else {
1252
            $type = self::NAMESPACE_PLUGINS_BLOCKS . 'Plugin_' . str_replace(self::NAMESPACE_PLUGINS_BLOCKS.'Plugin',
1253
                    '', $type);
1254
        }
1255
1256
        $keys = array_keys($this->stack);
1257
        while (($key = array_pop($keys)) !== false) {
1258
            if ($this->stack[$key] instanceof $type) {
1259
                return $this->stack[$key];
1260
            }
1261
        }
1262
1263
        return false;
1264
    }
1265
1266
    /**
1267
     * Returns a Plugin of the given class.
1268
     * this is so a single instance of every class plugin is created at each template run,
1269
     * allowing class plugins to have "per-template-run" static variables
1270
     *
1271
     * @param string $class the class name
1272
     *
1273
     * @return mixed an object of the given class
1274
     */
1275
    public function getObjectPlugin($class)
1276
    {
1277
        if (isset($this->runtimePlugins[$class])) {
1278
            return $this->runtimePlugins[$class];
1279
        }
1280
1281
        return $this->runtimePlugins[$class] = new $class($this);
1282
    }
1283
1284
    /**
1285
     * Calls the process() method of the given class-plugin name.
1286
     *
1287
     * @param string $plugName the class plugin name (without `Plugin` prefix)
1288
     * @param array  $params   an array of parameters to send to the process() method
1289
     *
1290
     * @return string the process() return value
1291
     */
1292
    public function classCall($plugName, array $params = array())
1293
    {
1294
        $class  = self::toCamelCase($plugName);
1295
        $plugin = $this->getObjectPlugin($class);
1296
1297
        return call_user_func_array(array($plugin, 'process'), $params);
1298
    }
1299
1300
    /**
1301
     * Calls a php function.
1302
     *
1303
     * @param string $callback the function to call
1304
     * @param array  $params   an array of parameters to send to the function
1305
     *
1306
     * @return mixed the return value of the called function
1307
     */
1308
    public function arrayMap($callback, array $params)
1309
    {
1310
        if ($params[0] === $this) {
1311
            $addThis = true;
1312
            array_shift($params);
1313
        }
1314
        if ((is_array($params[0]) || ($params[0] instanceof Iterator && $params[0] instanceof ArrayAccess))) {
1315
            if (empty($params[0])) {
1316
                return $params[0];
1317
            }
1318
1319
            // array map
1320
            $out = array();
1321
            $cnt = count($params);
1322
1323
            if (isset($addThis)) {
1324
                array_unshift($params, $this);
1325
                $items = $params[1];
1326
                $keys  = array_keys($items);
1327
1328
                if (is_string($callback) === false) {
1329
                    while (($i = array_shift($keys)) !== null) {
1330
                        $out[] = call_user_func_array($callback, array(1 => $items[$i]) + $params);
1331
                    }
1332
                } elseif ($cnt === 1) {
1333
                    while (($i = array_shift($keys)) !== null) {
1334
                        $out[] = $callback($this, $items[$i]);
1335
                    }
1336 View Code Duplication
                } elseif ($cnt === 2) {
1337
                    while (($i = array_shift($keys)) !== null) {
1338
                        $out[] = $callback($this, $items[$i], $params[2]);
1339
                    }
1340
                } elseif ($cnt === 3) {
1341
                    while (($i = array_shift($keys)) !== null) {
1342
                        $out[] = $callback($this, $items[$i], $params[2], $params[3]);
1343
                    }
1344
                } else {
1345
                    while (($i = array_shift($keys)) !== null) {
1346
                        $out[] = call_user_func_array($callback, array(1 => $items[$i]) + $params);
1347
                    }
1348
                }
1349
            } else {
1350
                $items = $params[0];
1351
                $keys  = array_keys($items);
1352
1353
                if (is_string($callback) === false) {
1354
                    while (($i = array_shift($keys)) !== null) {
1355
                        $out[] = call_user_func_array($callback, array($items[$i]) + $params);
1356
                    }
1357
                } elseif ($cnt === 1) {
1358
                    while (($i = array_shift($keys)) !== null) {
1359
                        $out[] = $callback($items[$i]);
1360
                    }
1361 View Code Duplication
                } elseif ($cnt === 2) {
1362
                    while (($i = array_shift($keys)) !== null) {
1363
                        $out[] = $callback($items[$i], $params[1]);
1364
                    }
1365
                } elseif ($cnt === 3) {
1366
                    while (($i = array_shift($keys)) !== null) {
1367
                        $out[] = $callback($items[$i], $params[1], $params[2]);
1368
                    }
1369 View Code Duplication
                } elseif ($cnt === 4) {
1370
                    while (($i = array_shift($keys)) !== null) {
1371
                        $out[] = $callback($items[$i], $params[1], $params[2], $params[3]);
1372
                    }
1373
                } else {
1374
                    while (($i = array_shift($keys)) !== null) {
1375
                        $out[] = call_user_func_array($callback, array($items[$i]) + $params);
1376
                    }
1377
                }
1378
            }
1379
1380
            return $out;
1381
        } else {
1382
            return $params[0];
1383
        }
1384
    }
1385
1386
    /**
1387
     * Reads a variable into the given data array.
1388
     *
1389
     * @param string $varstr   the variable string, using dwoo variable syntax (i.e. "var.subvar[subsubvar]->property")
1390
     * @param mixed  $data     the data array or object to read from
1391
     * @param bool   $safeRead if true, the function will check whether the index exists to prevent any notices from
1392
     *                         being output
1393
     *
1394
     * @return mixed
1395
     */
1396
    public function readVarInto($varstr, $data, $safeRead = false)
1397
    {
1398
        if ($data === null) {
1399
            return null;
1400
        }
1401
1402
        if (is_array($varstr) === false) {
1403
            preg_match_all('#(\[|->|\.)?((?:[^.[\]-]|-(?!>))+)\]?#i', $varstr, $m);
1404
        } else {
1405
            $m = $varstr;
1406
        }
1407
        unset($varstr);
1408
1409
        foreach ($m[1] as $k => $sep) {
1410
            if ($sep === '.' || $sep === '[' || $sep === '') {
1411
                // strip enclosing quotes if present
1412
                $m[2][$k] = preg_replace('#^(["\']?)(.*?)\1$#', '$2', $m[2][$k]);
1413
1414 View Code Duplication
                if ((is_array($data) || $data instanceof ArrayAccess) && ($safeRead === false || isset($data[$m[2][$k]]))) {
1415
                    $data = $data[$m[2][$k]];
1416
                } else {
1417
                    return null;
1418
                }
1419
            } else {
1420
                if (is_object($data) && ($safeRead === false || isset($data->{$m[2][$k]}))) {
1421
                    $data = $data->{$m[2][$k]};
1422
                } else {
1423
                    return null;
1424
                }
1425
            }
1426
        }
1427
1428
        return $data;
1429
    }
1430
1431
    /**
1432
     * Reads a variable into the parent scope.
1433
     *
1434
     * @param int    $parentLevels the amount of parent levels to go from the current scope
1435
     * @param string $varstr       the variable string, using dwoo variable syntax (i.e.
1436
     *                             "var.subvar[subsubvar]->property")
1437
     *
1438
     * @return mixed
1439
     */
1440
    public function readParentVar($parentLevels, $varstr = null)
1441
    {
1442
        $tree = $this->scopeTree;
1443
        $cur  = $this->data;
1444
1445
        while ($parentLevels -- !== 0) {
1446
            array_pop($tree);
1447
        }
1448
1449 View Code Duplication
        while (($i = array_shift($tree)) !== null) {
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...
1450
            if (is_object($cur)) {
1451
                $cur = $cur->{$i};
1452
            } else {
1453
                $cur = $cur[$i];
1454
            }
1455
        }
1456
1457
        if ($varstr !== null) {
1458
            return $this->readVarInto($varstr, $cur);
1459
        } else {
1460
            return $cur;
1461
        }
1462
    }
1463
1464
    /**
1465
     * Reads a variable into the current scope.
1466
     *
1467
     * @param string $varstr the variable string, using dwoo variable syntax (i.e. "var.subvar[subsubvar]->property")
1468
     *
1469
     * @return mixed
1470
     */
1471
    public function readVar($varstr)
1472
    {
1473
        if (is_array($varstr) === true) {
1474
            $m = $varstr;
1475
            unset($varstr);
1476
        } else {
1477
            if (strstr($varstr, '.') === false && strstr($varstr, '[') === false && strstr($varstr, '->') === false) {
1478
                if ($varstr === 'dwoo') {
1479
                    return $this->getGlobals();
1480
                } elseif ($varstr === '__' || $varstr === '_root') {
1481
                    return $this->data;
1482
                } elseif ($varstr === '_' || $varstr === '_parent') {
1483
                    $varstr = '.' . $varstr;
1484
                    $tree   = $this->scopeTree;
1485
                    $cur    = $this->data;
1486
                    array_pop($tree);
1487
1488 View Code Duplication
                    while (($i = array_shift($tree)) !== null) {
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...
1489
                        if (is_object($cur)) {
1490
                            $cur = $cur->{$i};
1491
                        } else {
1492
                            $cur = $cur[$i];
1493
                        }
1494
                    }
1495
1496
                    return $cur;
1497
                }
1498
1499
                $cur = $this->scope;
1500
1501
                if (isset($cur[$varstr])) {
1502
                    return $cur[$varstr];
1503
                } else {
1504
                    return null;
1505
                }
1506
            }
1507
1508
            if (substr($varstr, 0, 1) === '.') {
1509
                $varstr = 'dwoo' . $varstr;
1510
            }
1511
1512
            preg_match_all('#(\[|->|\.)?((?:[^.[\]-]|-(?!>))+)\]?#i', $varstr, $m);
1513
        }
1514
1515
        $i = $m[2][0];
1516
        if ($i === 'dwoo') {
1517
            $cur = $this->getGlobals();
1518
            array_shift($m[2]);
1519
            array_shift($m[1]);
1520
            switch ($m[2][0]) {
1521
            case 'get':
1522
                $cur = $_GET;
1523
                break;
1524
            case 'post':
1525
                $cur = $_POST;
1526
                break;
1527
            case 'session':
1528
                $cur = $_SESSION;
1529
                break;
1530
            case 'cookies':
1531
            case 'cookie':
1532
                $cur = $_COOKIE;
1533
                break;
1534
            case 'server':
1535
                $cur = $_SERVER;
1536
                break;
1537
            case 'env':
1538
                $cur = $_ENV;
1539
                break;
1540
            case 'request':
1541
                $cur = $_REQUEST;
1542
                break;
1543
            case 'const':
1544
                array_shift($m[2]);
1545
                if (defined($m[2][0])) {
1546
                    return constant($m[2][0]);
1547
                } else {
1548
                    return null;
1549
                }
1550
            }
1551
            if ($cur !== $this->getGlobals()) {
1552
                array_shift($m[2]);
1553
                array_shift($m[1]);
1554
            }
1555 View Code Duplication
        } elseif ($i === '__' || $i === '_root') {
1556
            $cur = $this->data;
1557
            array_shift($m[2]);
1558
            array_shift($m[1]);
1559
        } elseif ($i === '_' || $i === '_parent') {
1560
            $tree = $this->scopeTree;
1561
            $cur  = $this->data;
1562
1563
            while (true) {
1564
                array_pop($tree);
1565
                array_shift($m[2]);
1566
                array_shift($m[1]);
1567
                if (current($m[2]) === '_' || current($m[2]) === '_parent') {
1568
                    continue;
1569
                }
1570
1571 View Code Duplication
                while (($i = array_shift($tree)) !== null) {
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...
1572
                    if (is_object($cur)) {
1573
                        $cur = $cur->{$i};
1574
                    } else {
1575
                        $cur = $cur[$i];
1576
                    }
1577
                }
1578
                break;
1579
            }
1580
        } else {
1581
            $cur = $this->scope;
1582
        }
1583
1584
        foreach ($m[1] as $k => $sep) {
1585
            if ($sep === '.' || $sep === '[' || $sep === '') {
1586 View Code Duplication
                if ((is_array($cur) || $cur instanceof ArrayAccess) && isset($cur[$m[2][$k]])) {
1587
                    $cur = $cur[$m[2][$k]];
1588
                } else {
1589
                    return null;
1590
                }
1591 View Code Duplication
            } elseif ($sep === '->') {
1592
                if (is_object($cur)) {
1593
                    $cur = $cur->{$m[2][$k]};
1594
                } else {
1595
                    return null;
1596
                }
1597
            } else {
1598
                return null;
1599
            }
1600
        }
1601
1602
        return $cur;
1603
    }
1604
1605
    /**
1606
     * Assign the value to the given variable.
1607
     *
1608
     * @param mixed  $value the value to assign
1609
     * @param string $scope the variable string, using dwoo variable syntax (i.e. "var.subvar[subsubvar]->property")
1610
     *
1611
     * @return bool true if assigned correctly or false if a problem occured while parsing the var string
1612
     */
1613
    public function assignInScope($value, $scope)
1614
    {
1615
        if (!is_string($scope)) {
1616
            $this->triggerError('Assignments must be done into strings, (' . gettype($scope) . ') ' . var_export($scope, true) . ' given', E_USER_ERROR);
1617
        }
1618
        if (strstr($scope, '.') === false && strstr($scope, '->') === false) {
1619
            $this->scope[$scope] = $value;
1620
        } else {
1621
            // TODO handle _root/_parent scopes ?
1622
            preg_match_all('#(\[|->|\.)?([^.[\]-]+)\]?#i', $scope, $m);
1623
1624
            $cur  = &$this->scope;
1625
            $last = array(
1626
                array_pop($m[1]),
1627
                array_pop($m[2])
1628
            );
1629
1630
            foreach ($m[1] as $k => $sep) {
1631
                if ($sep === '.' || $sep === '[' || $sep === '') {
1632
                    if (is_array($cur) === false) {
1633
                        $cur = array();
1634
                    }
1635
                    $cur = &$cur[$m[2][$k]];
1636 View Code Duplication
                } elseif ($sep === '->') {
1637
                    if (is_object($cur) === false) {
1638
                        $cur = new stdClass();
1639
                    }
1640
                    $cur = &$cur->{$m[2][$k]};
1641
                } else {
1642
                    return false;
1643
                }
1644
            }
1645
1646
            if ($last[0] === '.' || $last[0] === '[' || $last[0] === '') {
1647
                if (is_array($cur) === false) {
1648
                    $cur = array();
1649
                }
1650
                $cur[$last[1]] = $value;
1651
            } elseif ($last[0] === '->') {
1652
                if (is_object($cur) === false) {
1653
                    $cur = new stdClass();
1654
                }
1655
                $cur->{$last[1]} = $value;
1656
            } else {
1657
                return false;
1658
            }
1659
        }
1660
    }
1661
1662
    /**
1663
     * Sets the scope to the given scope string or array.
1664
     *
1665
     * @param mixed $scope    a string i.e. "level1.level2" or an array i.e. array("level1", "level2")
1666
     * @param bool  $absolute if true, the scope is set from the top level scope and not from the current scope
1667
     *
1668
     * @return array the current scope tree
1669
     */
1670
    public function setScope($scope, $absolute = false)
1671
    {
1672
        $old = $this->scopeTree;
1673
1674
        if (is_string($scope) === true) {
1675
            $scope = explode('.', $scope);
1676
        }
1677
1678
        if ($absolute === true) {
1679
            $this->scope     = &$this->data;
1680
            $this->scopeTree = array();
1681
        }
1682
1683
        while (($bit = array_shift($scope)) !== null) {
1684
            if ($bit === '_' || $bit === '_parent') {
1685
                array_pop($this->scopeTree);
1686
                $this->scope = &$this->data;
1687
                $cnt         = count($this->scopeTree);
1688 View Code Duplication
                for ($i = 0; $i < $cnt; ++ $i) {
1689
                    $this->scope = &$this->scope[$this->scopeTree[$i]];
1690
                }
1691 View Code Duplication
            } elseif ($bit === '__' || $bit === '_root') {
1692
                $this->scope     = &$this->data;
1693
                $this->scopeTree = array();
1694
            } elseif (isset($this->scope[$bit])) {
1695
                if ($this->scope instanceof ArrayAccess) {
1696
                    $tmp         = $this->scope[$bit];
1697
                    $this->scope = &$tmp;
1698
                } else {
1699
                    $this->scope = &$this->scope[$bit];
1700
                }
1701
                $this->scopeTree[] = $bit;
1702
            } else {
1703
                unset($this->scope);
1704
                $this->scope = null;
1705
            }
1706
        }
1707
1708
        return $old;
1709
    }
1710
1711
    /**
1712
     * Returns the entire data array.
1713
     *
1714
     * @return array
1715
     */
1716
    public function getData()
1717
    {
1718
        return $this->data;
1719
    }
1720
1721
    /**
1722
     * Sets a return value for the currently running template.
1723
     *
1724
     * @param string $name  var name
1725
     * @param mixed  $value var value
1726
     *
1727
     * @return void
1728
     */
1729
    public function setReturnValue($name, $value)
1730
    {
1731
        $this->returnData[$name] = $value;
1732
    }
1733
1734
    /**
1735
     * Retrieves the return values set by the template.
1736
     *
1737
     * @return array
1738
     */
1739
    public function getReturnValues()
1740
    {
1741
        return $this->returnData;
1742
    }
1743
1744
    /**
1745
     * Returns a reference to the current scope.
1746
     *
1747
     * @return mixed
1748
     */
1749
    public function &getScope()
1750
    {
1751
        return $this->scope;
1752
    }
1753
1754
    /**
1755
     * Redirects all calls to unexisting to plugin proxy.
1756
     *
1757
     * @param string $method the method name
1758
     * @param array  $args   array of arguments
1759
     *
1760
     * @return mixed
1761
     * @throws Exception
1762
     */
1763
    public function __call($method, $args)
1764
    {
1765
        $proxy = $this->getPluginProxy();
1766
        if (!$proxy) {
1767
            throw new Exception('Call to undefined method ' . __CLASS__ . '::' . $method . '()');
1768
        }
1769
1770
        return call_user_func_array($proxy->getCallback($method), $args);
1771
    }
1772
1773
    /**
1774
     * Convert plugin name from `auto_escape` to `AutoEscape`.
1775
     * @param string $input
1776
     * @param string $separator
1777
     *
1778
     * @return mixed
1779
     */
1780
    public static function toCamelCase($input, $separator = '_')
1781
    {
1782
        return join(array_map('ucfirst', explode($separator, $input)));
1783
1784
        // TODO >= PHP5.4.32
1785
        //return str_replace($separator, '', ucwords($input, $separator));
1786
    }
1787
}
1788