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

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