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