Issues (293)

Security Analysis    not enabled

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

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

lib/Dwoo/Core.php (33 issues)

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) {
0 ignored issues
show
Unused Code introduced by seldaek
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...
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;
0 ignored issues
show
Documentation Bug introduced by Kamil Szot
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...
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()
0 ignored issues
show
Coding Style introduced by seldaek
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...
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)
0 ignored issues
show
Unused Code introduced by seldaek
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...
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
Duplication introduced by DSanchez
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
Duplication introduced by DSanchez
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)
0 ignored issues
show
Duplication introduced by seldaek
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...
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)
0 ignored issues
show
Duplication introduced by seldaek
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...
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);
0 ignored issues
show
Comprehensibility Best Practice introduced by DSanchez
The expression $class::templateFactory(...leId, $parentTemplate); of type Dwoo\ITemplate|false|null adds false to the return on line 1057 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...
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])) {
0 ignored issues
show
Duplication introduced by seldaek