Completed
Branch master (b65d76)
by David
04:29
created

Dwoo_Core::get()   F

Complexity

Conditions 20
Paths 880

Size

Total Lines 113
Code Lines 66

Duplication

Lines 20
Ratio 17.7 %

Importance

Changes 0
Metric Value
cc 20
eloc 66
nc 880
nop 4
dl 20
loc 113
rs 2.3199
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Copyright (c) 2013-2016
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-2016 David Sanchez
11
 * @license   http://dwoo.org/LICENSE Modified BSD License
12
 * @version   1.3.0
13
 * @date      2016-09-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\String - 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.0';
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 = 130;
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
    public $globals;
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
     * Defines how long (in seconds) the cached files must remain valid.
135
     * can be overriden on a per-template basis
136
     * -1 = never delete
137
     * 0 = disabled
138
     * >0 = duration in seconds
139
     *
140
     * @var int
141
     */
142
    protected $cacheTime = 0;
143
144
    /**
145
     * Security policy object.
146
     *
147
     * @var SecurityPolicy
148
     */
149
    protected $securityPolicy = null;
150
151
    /**
152
     * Stores the custom plugins callbacks.
153
     *
154
     * @see addPlugin
155
     * @see removePlugin
156
     * @var array
157
     */
158
    protected $plugins = array();
159
160
    /**
161
     * Stores the filter callbacks.
162
     *
163
     * @see addFilter
164
     * @see removeFilter
165
     * @var array
166
     */
167
    protected $filters = array();
168
169
    /**
170
     * Stores the resource types and associated
171
     * classes / compiler classes.
172
     *
173
     * @var array
174
     */
175
    protected $resources = array(
176
        'file'   => array(
177
            'class'    => 'Dwoo\Template\File',
178
            'compiler' => null,
179
        ),
180
        'string' => array(
181
            'class'    => 'Dwoo\Template\String',
182
            'compiler' => null,
183
        ),
184
    );
185
186
    /**
187
     * The dwoo loader object used to load plugins by this dwoo instance.
188
     *
189
     * @var ILoader
190
     */
191
    protected $loader = null;
192
193
    /**
194
     * Currently rendered template, set to null when not-rendering.
195
     *
196
     * @var ITemplate
197
     */
198
    protected $template = null;
199
200
    /**
201
     * Stores the instances of the class plugins during template runtime.
202
     *
203
     * @var array
204
     */
205
    protected $runtimePlugins;
206
207
    /**
208
     * Stores the returned values during template runtime.
209
     *
210
     * @var array
211
     */
212
    protected $returnData;
213
214
    /**
215
     * Stores the data during template runtime.
216
     *
217
     * @var array
218
     */
219
    public $data;
220
221
    /**
222
     * Stores the current scope during template runtime.
223
     * this should ideally not be accessed directly from outside template code
224
     *
225
     * @var mixed
226
     */
227
    public $scope;
228
229
    /**
230
     * Stores the scope tree during template runtime.
231
     *
232
     * @var array
233
     */
234
    protected $scopeTree;
235
236
    /**
237
     * Stores the block plugins stack during template runtime.
238
     *
239
     * @var array
240
     */
241
    protected $stack;
242
243
    /**
244
     * Stores the current block plugin at the top of the stack during template runtime.
245
     *
246
     * @var BlockPlugin
247
     */
248
    protected $curBlock;
249
250
    /**
251
     * Stores the output buffer during template runtime.
252
     *
253
     * @var string
254
     */
255
    protected $buffer;
256
257
    /**
258
     * Stores plugin proxy.
259
     *
260
     * @var IPluginProxy
261
     */
262
    protected $pluginProxy;
263
264
    /**
265
     * Constructor, sets the cache and compile dir to the default values if not provided.
266
     *
267
     * @param string $compileDir path to the compiled directory, defaults to lib/compiled
268
     * @param string $cacheDir   path to the cache directory, defaults to lib/cache
269
     */
270
    public function __construct($compileDir = null, $cacheDir = null)
271
    {
272
        if ($compileDir !== null) {
273
            $this->setCompileDir($compileDir);
274
        }
275
        if ($cacheDir !== null) {
276
            $this->setCacheDir($cacheDir);
277
        }
278
        $this->initGlobals();
279
    }
280
281
    /**
282
     * Resets some runtime variables to allow a cloned object to be used to render sub-templates.
283
     *
284
     * @return void
285
     */
286
    public function __clone()
287
    {
288
        $this->template = null;
289
        unset($this->data);
290
        unset($this->returnData);
291
    }
292
293
    /**
294
     * Returns the given template rendered using the provided data and optional compiler.
295
     *
296
     * @param mixed     $_tpl      template, can either be a ITemplate object (i.e. TemplateFile), a
297
     *                             valid path to a template, or a template as a string it is recommended to
298
     *                             provide a ITemplate as it will probably make things faster, especially if
299
     *                             you render a template multiple times
300
     * @param mixed     $data      the data to use, can either be a IDataProvider object (i.e. Data) or
301
     *                             an associative array. if you're rendering the template from cache, it can be
302
     *                             left null
303
     * @param ICompiler $_compiler the compiler that must be used to compile the template, if left empty a default
304
     *                             Compiler will be used
305
     *
306
     * @return string|void or the template output if $output is false
307
     * @throws Exception
308
     */
309
    public function get($_tpl, $data = array(), $_compiler = null)
310
    {
311
        // a render call came from within a template, so we need a new dwoo instance in order to avoid breaking this one
312
        if ($this->template instanceof ITemplate) {
313
            $clone = clone $this;
314
315
            return $clone->get($_tpl, $data, $_compiler);
316
        }
317
318
        // auto-create template if required
319
        if ($_tpl instanceof ITemplate) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
320
            // valid, skip
321
        } elseif (is_string($_tpl) && file_exists($_tpl)) {
322
            $_tpl = new TemplateFile($_tpl);
323
        } else {
324
            throw new Exception(
325
                'Dwoo->get\'s first argument must be a ITemplate (i.e. TemplateFile) or 
326
            a valid path to a template file', E_USER_NOTICE
327
            );
328
        }
329
330
        // save the current template, enters render mode at the same time
331
        // if another rendering is requested it will be proxied to a new Core(instance
332
        $this->template = $_tpl;
333
334
        // load data
335
        if ($data instanceof IDataProvider) {
336
            $this->data = $data->getData();
337
        } elseif (is_array($data)) {
338
            $this->data = $data;
339
        } elseif ($data instanceof ArrayAccess) {
340
            $this->data = $data;
0 ignored issues
show
Documentation Bug introduced by
It seems like $data of type object<ArrayAccess> is incompatible with the declared type array of property $data.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
341
        } else {
342
            throw new Exception(
343
                'Dwoo->get/Dwoo->output\'s data argument must be a IDataProvider object (i.e. Data) or
344
            an associative array', E_USER_NOTICE
345
            );
346
        }
347
348
        $this->globals['template'] = $_tpl->getName();
349
        $this->initRuntimeVars($_tpl);
350
351
        // try to get cached template
352
        $file        = $_tpl->getCachedTemplate($this);
353
        $doCache     = $file === true;
354
        $cacheLoaded = is_string($file);
355
356
        if ($cacheLoaded === true) {
357
            // cache is present, run it
358
            ob_start();
359
            include $file;
360
            $this->template = null;
361
362
            return ob_get_clean();
363
        } else {
364
            $dynamicId = uniqid();
365
366
            // render template
367
            $compiledTemplate = $_tpl->getCompiledTemplate($this, $_compiler);
368
            $out              = include $compiledTemplate;
369
370
            // template returned false so it needs to be recompiled
371
            if ($out === false) {
372
                $_tpl->forceCompilation();
373
                $compiledTemplate = $_tpl->getCompiledTemplate($this, $_compiler);
374
                $out              = include $compiledTemplate;
375
            }
376
377
            if ($doCache === true) {
378
                $out = preg_replace('/(<%|%>|<\?php|<\?|\?>)/', '<?php /*' . $dynamicId . '*/ echo \'$1\'; ?>', $out);
379
                if (!class_exists(self::NAMESPACE_PLUGINS_BLOCKS . 'PluginDynamic')) {
380
                    $this->getLoader()->loadPlugin('PluginDynamic');
381
                }
382
                $out = PluginDynamic::unescape($out, $dynamicId, $compiledTemplate);
383
            }
384
385
            // process filters
386
            foreach ($this->filters as $filter) {
387
                if (is_array($filter) && $filter[0] instanceof Filter) {
388
                    $out = call_user_func($filter, $out);
389
                } else {
390
                    $out = call_user_func($filter, $this, $out);
391
                }
392
            }
393
394
            if ($doCache === true) {
395
                // building cache
396
                $file = $_tpl->cache($this, $out);
397
398
                // run it from the cache to be sure dynamics are rendered
399
                ob_start();
400
                include $file;
401
                // exit render mode
402
                $this->template = null;
403
404
                return ob_get_clean();
405
            } else {
406
                // no need to build cache
407
                // exit render mode
408
                $this->template = null;
409
410
                return $out;
411
            }
412
        }
413
    }
414
415
    /**
416
     * Re-initializes the globals array before each template run.
417
     * this method is only callede once when the Dwoo object is created
418
     *
419
     * @return void
420
     */
421
    protected function initGlobals()
0 ignored issues
show
Coding Style introduced by
initGlobals uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
422
    {
423
        $this->globals = array(
424
            'version' => self::VERSION,
425
            'ad'      => '<a href="http://dwoo.org/">Powered by Dwoo</a>',
426
            'now'     => $_SERVER['REQUEST_TIME'],
427
            'charset' => $this->charset,
428
        );
429
    }
430
431
    /**
432
     * Re-initializes the runtime variables before each template run.
433
     * override this method to inject data in the globals array if needed, this
434
     * method is called before each template execution
435
     *
436
     * @param ITemplate $tpl the template that is going to be rendered
437
     *
438
     * @return void
439
     */
440
    protected function initRuntimeVars(ITemplate $tpl)
0 ignored issues
show
Unused Code introduced by
The parameter $tpl is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
441
    {
442
        $this->runtimePlugins = array();
443
        $this->scope          = &$this->data;
444
        $this->scopeTree      = array();
445
        $this->stack          = array();
446
        $this->curBlock       = null;
447
        $this->buffer         = '';
448
        $this->returnData     = array();
449
    }
450
451
    /**
452
     * Adds a custom plugin that is not in one of the plugin directories.
453
     *
454
     * @param string   $name       the plugin name to be used in the templates
455
     * @param callback $callback   the plugin callback, either a function name,
456
     *                             a class name or an array containing an object
457
     *                             or class name and a method name
458
     * @param bool     $compilable if set to true, the plugin is assumed to be compilable
459
     *
460
     * @return void
461
     * @throws Exception
462
     */
463
    public function addPlugin($name, $callback, $compilable = false)
464
    {
465
        $compilable = $compilable ? self::COMPILABLE_PLUGIN : 0;
466
        if (is_array($callback)) {
467
            if (is_subclass_of(is_object($callback[0]) ? get_class($callback[0]) : $callback[0], 'Dwoo\Block\Plugin')) {
468
                $this->plugins[$name] = array(
469
                    'type'     => self::BLOCK_PLUGIN | $compilable,
470
                    'callback' => $callback,
471
                    'class'    => (is_object($callback[0]) ? get_class($callback[0]) : $callback[0])
472
                );
473
            } else {
474
                $this->plugins[$name] = array(
475
                    'type'     => self::CLASS_PLUGIN | $compilable,
476
                    'callback' => $callback,
477
                    'class'    => (is_object($callback[0]) ? get_class($callback[0]) : $callback[0]),
478
                    'function' => $callback[1]
479
                );
480
            }
481
        } elseif (is_string($callback)) {
482
            if (class_exists($callback)) {
483
                if (is_subclass_of($callback, 'Dwoo\Block\Plugin')) {
484
                    $this->plugins[$name] = array(
485
                        'type'     => self::BLOCK_PLUGIN | $compilable,
486
                        'callback' => $callback,
487
                        'class'    => $callback
488
                    );
489
                } else {
490
                    $this->plugins[$name] = array(
491
                        'type'     => self::CLASS_PLUGIN | $compilable,
492
                        'callback' => $callback,
493
                        'class'    => $callback,
494
                        'function' => ($compilable ? 'compile' : 'process')
495
                    );
496
                }
497
            } elseif (function_exists($callback)) {
498
                $this->plugins[$name] = array(
499
                    'type'     => self::FUNC_PLUGIN | $compilable,
500
                    'callback' => $callback
501
                );
502
            } else {
503
                throw new Exception(
504
                    'Callback could not be processed correctly, please check that the function/class 
505
                you used exists'
506
                );
507
            }
508
        } elseif ($callback instanceof Closure) {
509
            $this->plugins[$name] = array(
510
                'type'     => self::FUNC_PLUGIN | $compilable,
511
                'callback' => $callback
512
            );
513
        } else {
514
            throw new Exception(
515
                'Callback could not be processed correctly, please check that the function/class you 
516
            used exists'
517
            );
518
        }
519
    }
520
521
    /**
522
     * Removes a custom plugin.
523
     *
524
     * @param string $name the plugin name
525
     *
526
     * @return void
527
     */
528
    public function removePlugin($name)
529
    {
530
        if (isset($this->plugins[$name])) {
531
            unset($this->plugins[$name]);
532
        }
533
    }
534
535
    /**
536
     * Adds a filter to this Dwoo instance, it will be used to filter the output of all the templates rendered by this
537
     * instance.
538
     *
539
     * @param mixed $callback a callback or a filter name if it is autoloaded from a plugin directory
540
     * @param bool  $autoload if true, the first parameter must be a filter name from one of the plugin directories
541
     *
542
     * @return void
543
     * @throws Exception
544
     */
545
    public function addFilter($callback, $autoload = false)
546
    {
547
        if ($autoload) {
548
            $class = self::NAMESPACE_PLUGINS_FILTERS . self::toCamelCase($callback);
549
            if (!class_exists($class) && !function_exists($class)) {
550
                try {
551
                    $this->getLoader()->loadPlugin($callback);
552
                }
553
                catch (Exception $e) {
554
                    if (strstr($callback, self::NAMESPACE_PLUGINS_FILTERS)) {
555
                        throw new Exception(
556
                            'Wrong filter name : ' . $callback . ', the "Dwoo_Filter_" prefix should 
557
                        not be used, please only use "' . str_replace('Dwoo_Filter_', '', $callback) . '"'
558
                        );
559
                    } else {
560
                        throw new Exception(
561
                            'Wrong filter name : ' . $callback . ', when using autoload the filter must
562
                         be in one of your plugin dir as "name.php" containig a class or function named
563
                         "Dwoo_Filter_name"'
564
                        );
565
                    }
566
                }
567
            }
568
569
            if (class_exists($class)) {
570
                $callback = array(new $class($this), 'process');
571
            } elseif (function_exists($class)) {
572
                $callback = $class;
573
            } else {
574
                throw new Exception(
575
                    'Wrong filter name : ' . $callback . ', when using autoload the filter must be in
576
                one of your plugin dir as "name.php" containig a class or function named "Dwoo_Filter_name"'
577
                );
578
            }
579
580
            $this->filters[] = $callback;
581
        } else {
582
            $this->filters[] = $callback;
583
        }
584
    }
585
586
    /**
587
     * Removes a filter.
588
     *
589
     * @param mixed $callback callback or filter name if it was autoloaded
590
     *
591
     * @return void
592
     */
593
    public function removeFilter($callback)
594
    {
595
        if (($index = array_search(self::NAMESPACE_PLUGINS_FILTERS. 'Filter' . self::toCamelCase($callback), $this->filters,
596
                true)) !==
597
            false) {
598
            unset($this->filters[$index]);
599
        } elseif (($index = array_search($callback, $this->filters, true)) !== false) {
600
            unset($this->filters[$index]);
601
        } else {
602
            $class = self::NAMESPACE_PLUGINS_FILTERS . 'Filter' . $callback;
603
            foreach ($this->filters as $index => $filter) {
604
                if (is_array($filter) && $filter[0] instanceof $class) {
605
                    unset($this->filters[$index]);
606
                    break;
607
                }
608
            }
609
        }
610
    }
611
612
    /**
613
     * Adds a resource or overrides a default one.
614
     *
615
     * @param string   $name            the resource name
616
     * @param string   $class           the resource class (which must implement ITemplate)
617
     * @param callback $compilerFactory the compiler factory callback, a function that must return a compiler instance
618
     *                                  used to compile this resource, if none is provided. by default it will produce
619
     *                                  a Compiler object
620
     *
621
     * @return void
622
     * @throws Exception
623
     */
624
    public function addResource($name, $class, $compilerFactory = null)
625
    {
626
        if (strlen($name) < 2) {
627
            throw new Exception('Resource names must be at least two-character long to avoid conflicts with Windows paths');
628
        }
629
630
        if (!class_exists($class)) {
631
            throw new Exception('Resource class does not exist');
632
        }
633
634
        $interfaces = class_implements($class);
635
        if (in_array('Dwoo\ITemplate', $interfaces) === false) {
636
            throw new Exception('Resource class must implement ITemplate');
637
        }
638
639
        $this->resources[$name] = array(
640
            'class'    => $class,
641
            'compiler' => $compilerFactory
642
        );
643
    }
644
645
    /**
646
     * Removes a custom resource.
647
     *
648
     * @param string $name the resource name
649
     *
650
     * @return void
651
     */
652
    public function removeResource($name)
653
    {
654
        unset($this->resources[$name]);
655
        if ($name === 'file') {
656
            $this->resources['file'] = array(
657
                'class'    => 'Dwoo\Template\File',
658
                'compiler' => null
659
            );
660
        }
661
    }
662
663
    /**
664
     * Sets the loader object to use to load plugins.
665
     *
666
     * @param ILoader $loader loader
667
     *
668
     * @return void
669
     */
670
    public function setLoader(ILoader $loader)
671
    {
672
        $this->loader = $loader;
673
    }
674
675
    /**
676
     * Returns the current loader object or a default one if none is currently found.
677
     *
678
     * @return ILoader|Loader
679
     */
680
    public function getLoader()
681
    {
682
        if ($this->loader === null) {
683
            $this->loader = new Loader($this->getCompileDir());
684
        }
685
686
        return $this->loader;
687
    }
688
689
    /**
690
     * Returns the custom plugins loaded.
691
     * Used by the ITemplate classes to pass the custom plugins to their ICompiler instance.
692
     *
693
     * @return array
694
     */
695
    public function getCustomPlugins()
696
    {
697
        return $this->plugins;
698
    }
699
700
    /**
701
     * Return a specified custom plugin loaded by his name.
702
     * Used by the compiler, for executing a Closure.
703
     *
704
     * @param string $name
705
     *
706
     * @return mixed|null
707
     */
708
    public function getCustomPlugin($name)
709
    {
710
        if (isset($this->plugins[$name])) {
711
            return $this->plugins[$name]['callback'];
712
        }
713
714
        return null;
715
    }
716
717
    /**
718
     * Returns the cache directory with a trailing DIRECTORY_SEPARATOR.
719
     *
720
     * @return string
721
     */
722
    public function getCacheDir()
723
    {
724
        if ($this->cacheDir === null) {
725
            $this->setCacheDir(dirname(__DIR__) . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR);
726
        }
727
728
        return $this->cacheDir;
729
    }
730
731
    /**
732
     * Sets the cache directory and automatically appends a DIRECTORY_SEPARATOR.
733
     *
734
     * @param string $dir the cache directory
735
     *
736
     * @return void
737
     * @throws Exception
738
     */
739 View Code Duplication
    public function setCacheDir($dir)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
740
    {
741
        $this->cacheDir = rtrim($dir, '/\\') . DIRECTORY_SEPARATOR;
742
        if (is_writable($this->cacheDir) === false) {
743
            throw new Exception('The cache directory must be writable, chmod "' . $this->cacheDir . '" to make it writable');
744
        }
745
    }
746
747
    /**
748
     * Returns the compile directory with a trailing DIRECTORY_SEPARATOR.
749
     *
750
     * @return string
751
     */
752
    public function getCompileDir()
753
    {
754
        if ($this->compileDir === null) {
755
            $this->setCompileDir(dirname(__DIR__) . DIRECTORY_SEPARATOR . 'compiled' . DIRECTORY_SEPARATOR);
756
        }
757
758
        return $this->compileDir;
759
    }
760
761
    /**
762
     * Sets the compile directory and automatically appends a DIRECTORY_SEPARATOR.
763
     *
764
     * @param string $dir the compile directory
765
     *
766
     * @return void
767
     * @throws Exception
768
     */
769 View Code Duplication
    public function setCompileDir($dir)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
770
    {
771
        $this->compileDir = rtrim($dir, '/\\') . DIRECTORY_SEPARATOR;
772
        if (is_writable($this->compileDir) === false) {
773
            throw new Exception('The compile directory must be writable, chmod "' . $this->compileDir . '" to make it writable');
774
        }
775
    }
776
777
    /**
778
     * Returns the default cache time that is used with templates that do not have a cache time set.
779
     *
780
     * @return int the duration in seconds
781
     */
782
    public function getCacheTime()
783
    {
784
        return $this->cacheTime;
785
    }
786
787
    /**
788
     * Sets the default cache time to use with templates that do not have a cache time set.
789
     *
790
     * @param int $seconds the duration in seconds
791
     *
792
     * @return void
793
     */
794
    public function setCacheTime($seconds)
795
    {
796
        $this->cacheTime = (int)$seconds;
797
    }
798
799
    /**
800
     * Returns the character set used by the string manipulation plugins.
801
     * the charset is automatically lowercased
802
     *
803
     * @return string
804
     */
805
    public function getCharset()
806
    {
807
        return $this->charset;
808
    }
809
810
    /**
811
     * Sets the character set used by the string manipulation plugins.
812
     * the charset will be automatically lowercased
813
     *
814
     * @param string $charset the character set
815
     *
816
     * @return void
817
     */
818
    public function setCharset($charset)
819
    {
820
        $this->charset = strtolower((string)$charset);
821
    }
822
823
    /**
824
     * Returns the current template being rendered, when applicable, or null.
825
     *
826
     * @return ITemplate|null
827
     */
828
    public function getTemplate()
829
    {
830
        return $this->template;
831
    }
832
833
    /**
834
     * Sets the current template being rendered.
835
     *
836
     * @param ITemplate $tpl template object
837
     *
838
     * @return void
839
     */
840
    public function setTemplate(ITemplate $tpl)
841
    {
842
        $this->template = $tpl;
843
    }
844
845
    /**
846
     * Sets the default compiler factory function for the given resource name.
847
     * a compiler factory must return a ICompiler object pre-configured to fit your needs
848
     *
849
     * @param string   $resourceName    the resource name (i.e. file, string)
850
     * @param callback $compilerFactory the compiler factory callback
851
     *
852
     * @return void
853
     */
854
    public function setDefaultCompilerFactory($resourceName, $compilerFactory)
855
    {
856
        $this->resources[$resourceName]['compiler'] = $compilerFactory;
857
    }
858
859
    /**
860
     * Returns the default compiler factory function for the given resource name.
861
     *
862
     * @param string $resourceName the resource name
863
     *
864
     * @return callback the compiler factory callback
865
     */
866
    public function getDefaultCompilerFactory($resourceName)
867
    {
868
        return $this->resources[$resourceName]['compiler'];
869
    }
870
871
    /**
872
     * Sets the security policy object to enforce some php security settings.
873
     * use this if untrusted persons can modify templates
874
     *
875
     * @param SecurityPolicy $policy the security policy object
876
     *
877
     * @return void
878
     */
879
    public function setSecurityPolicy(SecurityPolicy $policy = null)
880
    {
881
        $this->securityPolicy = $policy;
882
    }
883
884
    /**
885
     * Returns the current security policy object or null by default.
886
     *
887
     * @return SecurityPolicy|null the security policy object if any
888
     */
889
    public function getSecurityPolicy()
890
    {
891
        return $this->securityPolicy;
892
    }
893
894
    /**
895
     * Sets the object that must be used as a plugin proxy when plugin can't be found
896
     * by dwoo's loader.
897
     *
898
     * @param IPluginProxy $pluginProxy the proxy object
899
     *
900
     * @return void
901
     */
902
    public function setPluginProxy(IPluginProxy $pluginProxy)
903
    {
904
        $this->pluginProxy = $pluginProxy;
905
    }
906
907
    /**
908
     * Returns the current plugin proxy object or null by default.
909
     *
910
     * @return IPluginProxy
911
     */
912
    public function getPluginProxy()
913
    {
914
        return $this->pluginProxy;
915
    }
916
917
    /**
918
     * Checks whether the given template is cached or not.
919
     *
920
     * @param ITemplate $tpl the template object
921
     *
922
     * @return bool
923
     */
924
    public function isCached(ITemplate $tpl)
925
    {
926
        return is_string($tpl->getCachedTemplate($this));
927
    }
928
929
    /**
930
     * Clear templates inside the compiled directory.
931
     *
932
     * @return int
933
     */
934
    public function clearCompiled()
935
    {
936
        $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->getCompileDir()), \RecursiveIteratorIterator::SELF_FIRST);
937
        $count    = 0;
938
        foreach ($iterator as $file) {
939
            if ($file->isFile()) {
940
                $count += unlink($file->__toString()) ? 1 : 0;
941
            }
942
        }
943
944
        return $count;
945
    }
946
947
    /**
948
     * Clears the cached templates if they are older than the given time.
949
     *
950
     * @param int $olderThan minimum time (in seconds) required for a cached template to be cleared
951
     *
952
     * @return int the amount of templates cleared
953
     */
954
    public function clearCache($olderThan = - 1)
955
    {
956
        $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->getCacheDir()), \RecursiveIteratorIterator::SELF_FIRST);
957
        $expired  = time() - $olderThan;
958
        $count    = 0;
959
        foreach ($iterator as $file) {
960
            if ($file->isFile() && $file->getCTime() < $expired) {
961
                $count += unlink((string)$file) ? 1 : 0;
962
            }
963
        }
964
965
        return $count;
966
    }
967
968
    /**
969
     * Fetches a template object of the given resource.
970
     *
971
     * @param string    $resourceName   the resource name (i.e. file, string)
972
     * @param string    $resourceId     the resource identifier (i.e. file path)
973
     * @param int       $cacheTime      the cache time setting for this resource
974
     * @param string    $cacheId        the unique cache identifier
975
     * @param string    $compileId      the unique compiler identifier
976
     * @param ITemplate $parentTemplate the parent template
977
     *
978
     * @return ITemplate
979
     * @throws Exception
980
     */
981
    public function templateFactory($resourceName, $resourceId, $cacheTime = null, $cacheId = null, $compileId = null, ITemplate $parentTemplate = null)
982
    {
983
        if (isset($this->resources[$resourceName])) {
984
            /**
985
             * Interface ITemplate
986
             *
987
             * @var ITemplate $class
988
             */
989
            $class = $this->resources[$resourceName]['class'];
990
991
            return $class::templateFactory($this, $resourceId, $cacheTime, $cacheId, $compileId, $parentTemplate);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The expression $class::templateFactory(...leId, $parentTemplate); of type Dwoo\ITemplate|null|false adds false to the return on line 991 which is incompatible with the return type documented by Dwoo\Core::templateFactory of type Dwoo\ITemplate|null. It seems like you forgot to handle an error condition.
Loading history...
992
        }
993
994
        throw new Exception('Unknown resource type : ' . $resourceName);
995
    }
996
997
    /**
998
     * Checks if the input is an array or arrayaccess object, optionally it can also check if it's
999
     * empty.
1000
     *
1001
     * @param mixed $value        the variable to check
1002
     * @param bool  $checkIsEmpty if true, the function will also check if the array|arrayaccess is empty,
1003
     *                            and return true only if it's not empty
1004
     *
1005
     * @return int|bool true if it's an array|arrayaccess (or the item count if $checkIsEmpty is true) or false if it's
1006
     *                  not an array|arrayaccess (or 0 if $checkIsEmpty is true)
1007
     */
1008
    public function isArray($value, $checkIsEmpty = false)
1009
    {
1010
        if (is_array($value) === true || $value instanceof ArrayAccess) {
1011
            if ($checkIsEmpty === false) {
1012
                return true;
1013
            }
1014
1015
            return $this->count($value);
1016
        }
1017
1018
        return false;
1019
    }
1020
1021
    /**
1022
     * Checks if the input is an array or a traversable object, optionally it can also check if it's
1023
     * empty.
1024
     *
1025
     * @param mixed $value        the variable to check
1026
     * @param bool  $checkIsEmpty if true, the function will also check if the array|traversable is empty,
1027
     *                            and return true only if it's not empty
1028
     *
1029
     * @return int|bool true if it's an array|traversable (or the item count if $checkIsEmpty is true) or false if it's
1030
     *                  not an array|traversable (or 0 if $checkIsEmpty is true)
1031
     */
1032
    public function isTraversable($value, $checkIsEmpty = false)
1033
    {
1034
        if (is_array($value) === true) {
1035
            if ($checkIsEmpty === false) {
1036
                return true;
1037
            } else {
1038
                return count($value) > 0;
1039
            }
1040
        } elseif ($value instanceof Traversable) {
1041
            if ($checkIsEmpty === false) {
1042
                return true;
1043
            } else {
1044
                return $this->count($value);
1045
            }
1046
        }
1047
1048
        return false;
1049
    }
1050
1051
    /**
1052
     * Counts an array or arrayaccess/traversable object.
1053
     *
1054
     * @param mixed $value the value to count
1055
     *
1056
     * @return int|bool the count for arrays and objects that implement countable, true for other objects that don't,
1057
     *                  and 0 for empty elements
1058
     */
1059
    public function count($value)
1060
    {
1061
        if (is_array($value) === true || $value instanceof Countable) {
1062
            return count($value);
1063
        } elseif ($value instanceof ArrayAccess) {
1064
            if ($value->offsetExists(0)) {
1065
                return true;
1066
            }
1067
        } elseif ($value instanceof Iterator) {
1068
            $value->rewind();
1069
            if ($value->valid()) {
1070
                return true;
1071
            }
1072
        } elseif ($value instanceof Traversable) {
1073
            foreach ($value as $dummy) {
1074
                return true;
1075
            }
1076
        }
1077
1078
        return 0;
1079
    }
1080
1081
    /**
1082
     * Triggers a dwoo error.
1083
     *
1084
     * @param string $message the error message
1085
     * @param int    $level   the error level, one of the PHP's E_* constants
1086
     *
1087
     * @return void
1088
     */
1089
    public function triggerError($message, $level = E_USER_NOTICE)
1090
    {
1091
        if (!($tplIdentifier = $this->template->getResourceIdentifier())) {
1092
            $tplIdentifier = $this->template->getResourceName();
1093
        }
1094
        trigger_error('Dwoo error (in ' . $tplIdentifier . ') : ' . $message, $level);
1095
    }
1096
1097
    /**
1098
     * Adds a block to the block stack.
1099
     *
1100
     * @param string $blockName the block name (without Dwoo_Plugin_ prefix)
1101
     * @param array  $args      the arguments to be passed to the block's init() function
1102
     *
1103
     * @return BlockPlugin the newly created block
1104
     */
1105
    public function addStack($blockName, array $args = array())
1106
    {
1107 View Code Duplication
        if (isset($this->plugins[$blockName])) {
0 ignored issues
show
Duplication introduced by
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...
1108
            $class = $this->plugins[$blockName]['class'];
1109
        } else {
1110
            $class = self::NAMESPACE_PLUGINS_BLOCKS . 'Plugin' . self::toCamelCase($blockName);
1111
        }
1112
1113
        if ($this->curBlock !== null) {
1114
            $this->curBlock->buffer(ob_get_contents());
1115
            ob_clean();
1116
        } else {
1117
            $this->buffer .= ob_get_contents();
1118
            ob_clean();
1119
        }
1120
1121
        $block = new $class($this);
1122
1123
        call_user_func_array(array($block, 'init'), $args);
1124
1125
        $this->stack[] = $this->curBlock = $block;
1126
1127
        return $block;
1128
    }
1129
1130
    /**
1131
     * Removes the plugin at the top of the block stack.
1132
     * Calls the block buffer() function, followed by a call to end() and finally a call to process()
1133
     *
1134
     * @return void
1135
     */
1136
    public function delStack()
1137
    {
1138
        $args = func_get_args();
1139
1140
        $this->curBlock->buffer(ob_get_contents());
1141
        ob_clean();
1142
1143
        call_user_func_array(array($this->curBlock, 'end'), $args);
1144
1145
        $tmp = array_pop($this->stack);
1146
1147
        if (count($this->stack) > 0) {
1148
            $this->curBlock = end($this->stack);
1149
            $this->curBlock->buffer($tmp->process());
1150
        } else {
1151
            if ($this->buffer !== '') {
1152
                echo $this->buffer;
1153
                $this->buffer = '';
1154
            }
1155
            $this->curBlock = null;
1156
            echo $tmp->process();
1157
        }
1158
1159
        unset($tmp);
1160
    }
1161
1162
    /**
1163
     * Returns the parent block of the given block.
1164
     *
1165
     * @param BlockPlugin $block the block class plugin
1166
     *
1167
     * @return BlockPlugin|false if the given block isn't in the stack
1168
     */
1169
    public function getParentBlock(BlockPlugin $block)
1170
    {
1171
        $index = array_search($block, $this->stack, true);
1172
        if ($index !== false && $index > 0) {
1173
            return $this->stack[$index - 1];
1174
        }
1175
1176
        return false;
1177
    }
1178
1179
    /**
1180
     * Finds the closest block of the given type, starting at the top of the stack.
1181
     *
1182
     * @param string $type the type of plugin you want to find
1183
     *
1184
     * @return BlockPlugin|false if no plugin of such type is in the stack
1185
     */
1186
    public function findBlock($type)
1187
    {
1188 View Code Duplication
        if (isset($this->plugins[$type])) {
0 ignored issues
show
Duplication introduced by
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...
1189
            $type = $this->plugins[$type]['class'];
1190
        } else {
1191
            $type = self::NAMESPACE_PLUGINS_BLOCKS . 'Plugin_' . str_replace(self::NAMESPACE_PLUGINS_BLOCKS.'Plugin',
1192
                    '', $type);
1193
        }
1194
1195
        $keys = array_keys($this->stack);
1196
        while (($key = array_pop($keys)) !== false) {
1197
            if ($this->stack[$key] instanceof $type) {
1198
                return $this->stack[$key];
1199
            }
1200
        }
1201
1202
        return false;
1203
    }
1204
1205
    /**
1206
     * Returns a Plugin of the given class.
1207
     * this is so a single instance of every class plugin is created at each template run,
1208
     * allowing class plugins to have "per-template-run" static variables
1209
     *
1210
     * @param string $class the class name
1211
     *
1212
     * @return mixed an object of the given class
1213
     */
1214
    public function getObjectPlugin($class)
1215
    {
1216
        if (isset($this->runtimePlugins[$class])) {
1217
            return $this->runtimePlugins[$class];
1218
        }
1219
1220
        return $this->runtimePlugins[$class] = new $class($this);
1221
    }
1222
1223
    /**
1224
     * Calls the process() method of the given class-plugin name.
1225
     *
1226
     * @param string $plugName the class plugin name (without Dwoo_Plugin_ prefix)
1227
     * @param array  $params   an array of parameters to send to the process() method
1228
     *
1229
     * @return string the process() return value
1230
     */
1231
    public function classCall($plugName, array $params = array())
1232
    {
1233
        $class  = self::toCamelCase($plugName);
1234
        $plugin = $this->getObjectPlugin($class);
1235
1236
        return call_user_func_array(array($plugin, 'process'), $params);
1237
    }
1238
1239
    /**
1240
     * Calls a php function.
1241
     *
1242
     * @param string $callback the function to call
1243
     * @param array  $params   an array of parameters to send to the function
1244
     *
1245
     * @return mixed the return value of the called function
1246
     */
1247
    public function arrayMap($callback, array $params)
1248
    {
1249
        if ($params[0] === $this) {
1250
            $addThis = true;
1251
            array_shift($params);
1252
        }
1253
        if ((is_array($params[0]) || ($params[0] instanceof Iterator && $params[0] instanceof ArrayAccess))) {
1254
            if (empty($params[0])) {
1255
                return $params[0];
1256
            }
1257
1258
            // array map
1259
            $out = array();
1260
            $cnt = count($params);
1261
1262
            if (isset($addThis)) {
1263
                array_unshift($params, $this);
1264
                $items = $params[1];
1265
                $keys  = array_keys($items);
1266
1267
                if (is_string($callback) === false) {
1268
                    while (($i = array_shift($keys)) !== null) {
1269
                        $out[] = call_user_func_array($callback, array(1 => $items[$i]) + $params);
1270
                    }
1271
                } elseif ($cnt === 1) {
1272
                    while (($i = array_shift($keys)) !== null) {
1273
                        $out[] = $callback($this, $items[$i]);
1274
                    }
1275 View Code Duplication
                } elseif ($cnt === 2) {
0 ignored issues
show
Duplication introduced by
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...
1276
                    while (($i = array_shift($keys)) !== null) {
1277
                        $out[] = $callback($this, $items[$i], $params[2]);
1278
                    }
1279
                } elseif ($cnt === 3) {
1280
                    while (($i = array_shift($keys)) !== null) {
1281
                        $out[] = $callback($this, $items[$i], $params[2], $params[3]);
1282
                    }
1283
                } else {
1284
                    while (($i = array_shift($keys)) !== null) {
1285
                        $out[] = call_user_func_array($callback, array(1 => $items[$i]) + $params);
1286
                    }
1287
                }
1288
            } else {
1289
                $items = $params[0];
1290
                $keys  = array_keys($items);
1291
1292
                if (is_string($callback) === false) {
1293
                    while (($i = array_shift($keys)) !== null) {
1294
                        $out[] = call_user_func_array($callback, array($items[$i]) + $params);
1295
                    }
1296
                } elseif ($cnt === 1) {
1297
                    while (($i = array_shift($keys)) !== null) {
1298
                        $out[] = $callback($items[$i]);
1299
                    }
1300 View Code Duplication
                } elseif ($cnt === 2) {
0 ignored issues
show
Duplication introduced by
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...
1301
                    while (($i = array_shift($keys)) !== null) {
1302
                        $out[] = $callback($items[$i], $params[1]);
1303
                    }
1304
                } elseif ($cnt === 3) {
1305
                    while (($i = array_shift($keys)) !== null) {
1306
                        $out[] = $callback($items[$i], $params[1], $params[2]);
1307
                    }
1308 View Code Duplication
                } elseif ($cnt === 4) {
0 ignored issues
show
Duplication introduced by
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...
1309
                    while (($i = array_shift($keys)) !== null) {
1310
                        $out[] = $callback($items[$i], $params[1], $params[2], $params[3]);
1311
                    }
1312
                } else {
1313
                    while (($i = array_shift($keys)) !== null) {
1314
                        $out[] = call_user_func_array($callback, array($items[$i]) + $params);
1315
                    }
1316
                }
1317
            }
1318
1319
            return $out;
1320
        } else {
1321
            return $params[0];
1322
        }
1323
    }
1324
1325
    /**
1326
     * Reads a variable into the given data array.
1327
     *
1328
     * @param string $varstr   the variable string, using dwoo variable syntax (i.e. "var.subvar[subsubvar]->property")
1329
     * @param mixed  $data     the data array or object to read from
1330
     * @param bool   $safeRead if true, the function will check whether the index exists to prevent any notices from
1331
     *                         being output
1332
     *
1333
     * @return mixed
1334
     */
1335
    public function readVarInto($varstr, $data, $safeRead = false)
1336
    {
1337
        if ($data === null) {
1338
            return null;
1339
        }
1340
1341
        if (is_array($varstr) === false) {
1342
            preg_match_all('#(\[|->|\.)?((?:[^.[\]-]|-(?!>))+)\]?#i', $varstr, $m);
1343
        } else {
1344
            $m = $varstr;
1345
        }
1346
        unset($varstr);
1347
1348
        while (list($k, $sep) = each($m[1])) {
1349
            if ($sep === '.' || $sep === '[' || $sep === '') {
1350
                // strip enclosing quotes if present
1351
                $m[2][$k] = preg_replace('#^(["\']?)(.*?)\1$#', '$2', $m[2][$k]);
1352
1353 View Code Duplication
                if ((is_array($data) || $data instanceof ArrayAccess) && ($safeRead === false || isset($data[$m[2][$k]]))) {
0 ignored issues
show
Duplication introduced by
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...
1354
                    $data = $data[$m[2][$k]];
1355
                } else {
1356
                    return null;
1357
                }
1358
            } else {
1359
                if (is_object($data) && ($safeRead === false || isset($data->$m[2][$k]))) {
1360
                    $data = $data->$m[2][$k];
1361
                } else {
1362
                    return null;
1363
                }
1364
            }
1365
        }
1366
1367
        return $data;
1368
    }
1369
1370
    /**
1371
     * Reads a variable into the parent scope.
1372
     *
1373
     * @param int    $parentLevels the amount of parent levels to go from the current scope
1374
     * @param string $varstr       the variable string, using dwoo variable syntax (i.e.
1375
     *                             "var.subvar[subsubvar]->property")
1376
     *
1377
     * @return mixed
1378
     */
1379
    public function readParentVar($parentLevels, $varstr = null)
1380
    {
1381
        $tree = $this->scopeTree;
1382
        $cur  = $this->data;
1383
1384
        while ($parentLevels -- !== 0) {
1385
            array_pop($tree);
1386
        }
1387
1388 View Code Duplication
        while (($i = array_shift($tree)) !== null) {
0 ignored issues
show
Duplication introduced by
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...
1389
            if (is_object($cur)) {
1390
                $cur = $cur->$i;
1391
            } else {
1392
                $cur = $cur[$i];
1393
            }
1394
        }
1395
1396
        if ($varstr !== null) {
1397
            return $this->readVarInto($varstr, $cur);
1398
        } else {
1399
            return $cur;
1400
        }
1401
    }
1402
1403
    /**
1404
     * Reads a variable into the current scope.
1405
     *
1406
     * @param string $varstr the variable string, using dwoo variable syntax (i.e. "var.subvar[subsubvar]->property")
1407
     *
1408
     * @return mixed
1409
     */
1410
    public function readVar($varstr)
0 ignored issues
show
Coding Style introduced by
readVar uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
readVar uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
readVar uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
readVar uses the super-global variable $_COOKIE which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
readVar uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
readVar uses the super-global variable $_ENV which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
readVar uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1411
    {
1412
        if (is_array($varstr) === true) {
1413
            $m = $varstr;
1414
            unset($varstr);
1415
        } else {
1416
            if (strstr($varstr, '.') === false && strstr($varstr, '[') === false && strstr($varstr, '->') === false) {
1417
                if ($varstr === 'dwoo') {
1418
                    return $this->globals;
1419
                } elseif ($varstr === '__' || $varstr === '_root') {
1420
                    return $this->data;
1421
                } elseif ($varstr === '_' || $varstr === '_parent') {
1422
                    $varstr = '.' . $varstr;
0 ignored issues
show
Unused Code introduced by
$varstr is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1423
                    $tree   = $this->scopeTree;
1424
                    $cur    = $this->data;
1425
                    array_pop($tree);
1426
1427 View Code Duplication
                    while (($i = array_shift($tree)) !== null) {
0 ignored issues
show
Duplication introduced by
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...
1428
                        if (is_object($cur)) {
1429
                            $cur = $cur->$i;
1430
                        } else {
1431
                            $cur = $cur[$i];
1432
                        }
1433
                    }
1434
1435
                    return $cur;
1436
                }
1437
1438
                $cur = $this->scope;
1439
1440
                if (isset($cur[$varstr])) {
1441
                    return $cur[$varstr];
1442
                } else {
1443
                    return null;
1444
                }
1445
            }
1446
1447
            if (substr($varstr, 0, 1) === '.') {
1448
                $varstr = 'dwoo' . $varstr;
1449
            }
1450
1451
            preg_match_all('#(\[|->|\.)?((?:[^.[\]-]|-(?!>))+)\]?#i', $varstr, $m);
1452
        }
1453
1454
        $i = $m[2][0];
1455
        if ($i === 'dwoo') {
1456
            $cur = $this->globals;
1457
            array_shift($m[2]);
1458
            array_shift($m[1]);
1459
            switch ($m[2][0]) {
1460
            case 'get':
1461
                $cur = $_GET;
1462
                break;
1463
            case 'post':
1464
                $cur = $_POST;
1465
                break;
1466
            case 'session':
1467
                $cur = $_SESSION;
1468
                break;
1469
            case 'cookies':
1470
            case 'cookie':
1471
                $cur = $_COOKIE;
1472
                break;
1473
            case 'server':
1474
                $cur = $_SERVER;
1475
                break;
1476
            case 'env':
1477
                $cur = $_ENV;
1478
                break;
1479
            case 'request':
1480
                $cur = $_REQUEST;
1481
                break;
1482
            case 'const':
1483
                array_shift($m[2]);
1484
                if (defined($m[2][0])) {
1485
                    return constant($m[2][0]);
1486
                } else {
1487
                    return null;
1488
                }
1489
            }
1490
            if ($cur !== $this->globals) {
1491
                array_shift($m[2]);
1492
                array_shift($m[1]);
1493
            }
1494 View Code Duplication
        } elseif ($i === '__' || $i === '_root') {
0 ignored issues
show
Duplication introduced by
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
            $cur = $this->data;
1496
            array_shift($m[2]);
1497
            array_shift($m[1]);
1498
        } elseif ($i === '_' || $i === '_parent') {
1499
            $tree = $this->scopeTree;
1500
            $cur  = $this->data;
1501
1502
            while (true) {
1503
                array_pop($tree);
1504
                array_shift($m[2]);
1505
                array_shift($m[1]);
1506
                if (current($m[2]) === '_' || current($m[2]) === '_parent') {
1507
                    continue;
1508
                }
1509
1510 View Code Duplication
                while (($i = array_shift($tree)) !== null) {
0 ignored issues
show
Duplication introduced by
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...
1511
                    if (is_object($cur)) {
1512
                        $cur = $cur->$i;
1513
                    } else {
1514
                        $cur = $cur[$i];
1515
                    }
1516
                }
1517
                break;
1518
            }
1519
        } else {
1520
            $cur = $this->scope;
1521
        }
1522
1523
        while (list($k, $sep) = each($m[1])) {
1524
            if ($sep === '.' || $sep === '[' || $sep === '') {
1525 View Code Duplication
                if ((is_array($cur) || $cur instanceof ArrayAccess) && isset($cur[$m[2][$k]])) {
0 ignored issues
show
Duplication introduced by
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...
1526
                    $cur = $cur[$m[2][$k]];
1527
                } else {
1528
                    return null;
1529
                }
1530 View Code Duplication
            } elseif ($sep === '->') {
0 ignored issues
show
Duplication introduced by
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...
1531
                if (is_object($cur)) {
1532
                    $cur = $cur->$m[2][$k];
1533
                } else {
1534
                    return null;
1535
                }
1536
            } else {
1537
                return null;
1538
            }
1539
        }
1540
1541
        return $cur;
1542
    }
1543
1544
    /**
1545
     * Assign the value to the given variable.
1546
     *
1547
     * @param mixed  $value the value to assign
1548
     * @param string $scope the variable string, using dwoo variable syntax (i.e. "var.subvar[subsubvar]->property")
1549
     *
1550
     * @return bool true if assigned correctly or false if a problem occured while parsing the var string
1551
     */
1552
    public function assignInScope($value, $scope)
1553
    {
1554
        if (!is_string($scope)) {
1555
            $this->triggerError('Assignments must be done into strings, (' . gettype($scope) . ') ' . var_export($scope, true) . ' given', E_USER_ERROR);
1556
        }
1557
        if (strstr($scope, '.') === false && strstr($scope, '->') === false) {
1558
            $this->scope[$scope] = $value;
1559
        } else {
1560
            // TODO handle _root/_parent scopes ?
1561
            preg_match_all('#(\[|->|\.)?([^.[\]-]+)\]?#i', $scope, $m);
1562
1563
            $cur  = &$this->scope;
1564
            $last = array(
1565
                array_pop($m[1]),
1566
                array_pop($m[2])
1567
            );
1568
1569
            while (list($k, $sep) = each($m[1])) {
1570
                if ($sep === '.' || $sep === '[' || $sep === '') {
1571
                    if (is_array($cur) === false) {
1572
                        $cur = array();
1573
                    }
1574
                    $cur = &$cur[$m[2][$k]];
1575 View Code Duplication
                } elseif ($sep === '->') {
0 ignored issues
show
Duplication introduced by
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...
1576
                    if (is_object($cur) === false) {
1577
                        $cur = new stdClass();
1578
                    }
1579
                    $cur = &$cur->$m[2][$k];
1580
                } else {
1581
                    return false;
1582
                }
1583
            }
1584
1585
            if ($last[0] === '.' || $last[0] === '[' || $last[0] === '') {
1586
                if (is_array($cur) === false) {
1587
                    $cur = array();
1588
                }
1589
                $cur[$last[1]] = $value;
1590
            } elseif ($last[0] === '->') {
1591
                if (is_object($cur) === false) {
1592
                    $cur = new stdClass();
1593
                }
1594
                $cur->$last[1] = $value;
1595
            } else {
1596
                return false;
1597
            }
1598
        }
1599
    }
1600
1601
    /**
1602
     * Sets the scope to the given scope string or array.
1603
     *
1604
     * @param mixed $scope    a string i.e. "level1.level2" or an array i.e. array("level1", "level2")
1605
     * @param bool  $absolute if true, the scope is set from the top level scope and not from the current scope
1606
     *
1607
     * @return array the current scope tree
1608
     */
1609
    public function setScope($scope, $absolute = false)
1610
    {
1611
        $old = $this->scopeTree;
1612
1613
        if (is_string($scope) === true) {
1614
            $scope = explode('.', $scope);
1615
        }
1616
1617
        if ($absolute === true) {
1618
            $this->scope     = &$this->data;
1619
            $this->scopeTree = array();
1620
        }
1621
1622
        while (($bit = array_shift($scope)) !== null) {
1623
            if ($bit === '_' || $bit === '_parent') {
1624
                array_pop($this->scopeTree);
1625
                $this->scope = &$this->data;
1626
                $cnt         = count($this->scopeTree);
1627 View Code Duplication
                for ($i = 0; $i < $cnt; ++ $i) {
0 ignored issues
show
Duplication introduced by
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...
1628
                    $this->scope = &$this->scope[$this->scopeTree[$i]];
1629
                }
1630 View Code Duplication
            } elseif ($bit === '__' || $bit === '_root') {
0 ignored issues
show
Duplication introduced by
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...
1631
                $this->scope     = &$this->data;
1632
                $this->scopeTree = array();
1633
            } elseif (isset($this->scope[$bit])) {
1634
                if ($this->scope instanceof ArrayAccess) {
1635
                    $tmp         = $this->scope[$bit];
1636
                    $this->scope = &$tmp;
1637
                } else {
1638
                    $this->scope = &$this->scope[$bit];
1639
                }
1640
                $this->scopeTree[] = $bit;
1641
            } else {
1642
                unset($this->scope);
1643
                $this->scope = null;
1644
            }
1645
        }
1646
1647
        return $old;
1648
    }
1649
1650
    /**
1651
     * Returns the entire data array.
1652
     *
1653
     * @return array
1654
     */
1655
    public function getData()
1656
    {
1657
        return $this->data;
1658
    }
1659
1660
    /**
1661
     * Sets a return value for the currently running template.
1662
     *
1663
     * @param string $name  var name
1664
     * @param mixed  $value var value
1665
     *
1666
     * @return void
1667
     */
1668
    public function setReturnValue($name, $value)
1669
    {
1670
        $this->returnData[$name] = $value;
1671
    }
1672
1673
    /**
1674
     * Retrieves the return values set by the template.
1675
     *
1676
     * @return array
1677
     */
1678
    public function getReturnValues()
1679
    {
1680
        return $this->returnData;
1681
    }
1682
1683
    /**
1684
     * Returns a reference to the current scope.
1685
     *
1686
     * @return mixed
1687
     */
1688
    public function &getScope()
1689
    {
1690
        return $this->scope;
1691
    }
1692
1693
    /**
1694
     * Redirects all calls to unexisting to plugin proxy.
1695
     *
1696
     * @param string $method the method name
1697
     * @param array  $args   array of arguments
1698
     *
1699
     * @return mixed
1700
     * @throws Exception
1701
     */
1702
    public function __call($method, $args)
1703
    {
1704
        $proxy = $this->getPluginProxy();
1705
        if (!$proxy) {
1706
            throw new Exception('Call to undefined method ' . __CLASS__ . '::' . $method . '()');
1707
        }
1708
1709
        return call_user_func_array($proxy->getCallback($method), $args);
1710
    }
1711
1712
    /**
1713
     * Convert plugin name from `auto_escape` to `AutoEscape`.
1714
     * @param string $input
1715
     * @param string $separator
1716
     *
1717
     * @return mixed
1718
     */
1719
    public static function toCamelCase($input, $separator = '_')
1720
    {
1721
        return join(array_map('ucfirst', explode($separator, $input)));
1722
1723
        // TODO >= PHP5.4.32
1724
        //return str_replace($separator, '', ucwords($input, $separator));
0 ignored issues
show
Unused Code Comprehensibility introduced by
69% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1725
    }
1726
}
1727