Completed
Push — master ( bf2930...494091 )
by David
07:08 queued 03:26
created

Core::getTemplateDir()   A

Complexity

Conditions 1
Paths 1

Duplication

Lines 0
Ratio 0 %

Size

Total Lines 4
Code Lines 2

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * Copyright (c) 2013-2017
4
 *
5
 * @category  Library
6
 * @package   Dwoo
7
 * @author    Jordi Boggiano <[email protected]>
8
 * @author    David Sanchez <[email protected]>
9
 * @copyright 2008-2013 Jordi Boggiano
10
 * @copyright 2013-2017 David Sanchez
11
 * @license   http://dwoo.org/LICENSE Modified BSD License
12
 * @version   1.3.2
13
 * @date      2017-01-04
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.2';
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 = 132;
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(
333
                'Dwoo->get\'s first argument must be a ITemplate (i.e. TemplateFile) or 
334
            a valid path to a template file', E_USER_NOTICE
335
            );
336
        }
337
338
        // save the current template, enters render mode at the same time
339
        // if another rendering is requested it will be proxied to a new Core(instance
340
        $this->template = $_tpl;
341
342
        // load data
343
        if ($data instanceof IDataProvider) {
344
            $this->data = $data->getData();
345
        } elseif (is_array($data)) {
346
            $this->data = $data;
347
        } elseif ($data instanceof ArrayAccess) {
348
            $this->data = $data;
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...
349
        } else {
350
            throw new Exception(
351
                'Dwoo->get/Dwoo->output\'s data argument must be a IDataProvider object (i.e. Data) or
352
            an associative array', E_USER_NOTICE
353
            );
354
        }
355
356
        $this->addGlobal('template', $_tpl->getName());
357
        $this->initRuntimeVars($_tpl);
358
359
        // try to get cached template
360
        $file        = $_tpl->getCachedTemplate($this);
361
        $doCache     = $file === true;
362
        $cacheLoaded = is_string($file);
363
364
        if ($cacheLoaded === true) {
365
            // cache is present, run it
366
            ob_start();
367
            include $file;
368
            $this->template = null;
369
370
            return ob_get_clean();
371
        } else {
372
            $dynamicId = uniqid();
373
374
            // render template
375
            $compiledTemplate = $_tpl->getCompiledTemplate($this, $_compiler);
376
            $out              = include $compiledTemplate;
377
378
            // template returned false so it needs to be recompiled
379
            if ($out === false) {
380
                $_tpl->forceCompilation();
381
                $compiledTemplate = $_tpl->getCompiledTemplate($this, $_compiler);
382
                $out              = include $compiledTemplate;
383
            }
384
385
            if ($doCache === true) {
386
                $out = preg_replace('/(<%|%>|<\?php|<\?|\?>)/', '<?php /*' . $dynamicId . '*/ echo \'$1\'; ?>', $out);
387
                if (!class_exists(self::NAMESPACE_PLUGINS_BLOCKS . 'PluginDynamic')) {
388
                    $this->getLoader()->loadPlugin('PluginDynamic');
389
                }
390
                $out = PluginDynamic::unescape($out, $dynamicId, $compiledTemplate);
391
            }
392
393
            // process filters
394
            foreach ($this->filters as $filter) {
395
                if (is_array($filter) && $filter[0] instanceof Filter) {
396
                    $out = call_user_func($filter, $out);
397
                } else {
398
                    $out = call_user_func($filter, $this, $out);
399
                }
400
            }
401
402
            if ($doCache === true) {
403
                // building cache
404
                $file = $_tpl->cache($this, $out);
405
406
                // run it from the cache to be sure dynamics are rendered
407
                ob_start();
408
                include $file;
409
                // exit render mode
410
                $this->template = null;
411
412
                return ob_get_clean();
413
            } else {
414
                // no need to build cache
415
                // exit render mode
416
                $this->template = null;
417
418
                return $out;
419
            }
420
        }
421
    }
422
423
    /**
424
     * Registers a Global.
425
     * New globals can be added before compiling or rendering a template
426
     * but after, you can only update existing globals.
427
     *
428
     * @param string $name
429
     * @param mixed  $value
430
     *
431
     * @return $this
432
     * @throws Exception
433
     */
434
    public function addGlobal($name, $value)
435
    {
436
        if (null === $this->globals) {
437
            $this->initGlobals();
438
        }
439
440
        $this->globals[$name] = $value;
441
442
        return $this;
443
    }
444
445
    /**
446
     * Gets the registered Globals.
447
     *
448
     * @return array
449
     */
450
    public function getGlobals()
451
    {
452
        return $this->globals;
453
    }
454
455
    /**
456
     * Re-initializes the globals array before each template run.
457
     * this method is only callede once when the Dwoo object is created
458
     *
459
     * @return void
460
     */
461
    protected function initGlobals()
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...
462
    {
463
        $this->globals = array(
464
            'version' => self::VERSION,
465
            'ad'      => '<a href="http://dwoo.org/">Powered by Dwoo</a>',
466
            'now'     => $_SERVER['REQUEST_TIME'],
467
            'charset' => $this->getCharset(),
468
        );
469
    }
470
471
    /**
472
     * Re-initializes the runtime variables before each template run.
473
     * override this method to inject data in the globals array if needed, this
474
     * method is called before each template execution
475
     *
476
     * @param ITemplate $tpl the template that is going to be rendered
477
     *
478
     * @return void
479
     */
480
    protected function initRuntimeVars(ITemplate $tpl)
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...
481
    {
482
        $this->runtimePlugins = array();
483
        $this->scope          = &$this->data;
484
        $this->scopeTree      = array();
485
        $this->stack          = array();
486
        $this->curBlock       = null;
487
        $this->buffer         = '';
488
        $this->returnData     = array();
489
    }
490
491
    /**
492
     * Adds a custom plugin that is not in one of the plugin directories.
493
     *
494
     * @param string   $name       the plugin name to be used in the templates
495
     * @param callback $callback   the plugin callback, either a function name,
496
     *                             a class name or an array containing an object
497
     *                             or class name and a method name
498
     * @param bool     $compilable if set to true, the plugin is assumed to be compilable
499
     *
500
     * @return void
501
     * @throws Exception
502
     */
503
    public function addPlugin($name, $callback, $compilable = false)
504
    {
505
        $compilable = $compilable ? self::COMPILABLE_PLUGIN : 0;
506
        if (is_array($callback)) {
507
            if (is_subclass_of(is_object($callback[0]) ? get_class($callback[0]) : $callback[0], 'Dwoo\Block\Plugin')) {
508
                $this->plugins[$name] = array(
509
                    'type'     => self::BLOCK_PLUGIN | $compilable,
510
                    'callback' => $callback,
511
                    'class'    => (is_object($callback[0]) ? get_class($callback[0]) : $callback[0])
512
                );
513
            } else {
514
                $this->plugins[$name] = array(
515
                    'type'     => self::CLASS_PLUGIN | $compilable,
516
                    'callback' => $callback,
517
                    'class'    => (is_object($callback[0]) ? get_class($callback[0]) : $callback[0]),
518
                    'function' => $callback[1]
519
                );
520
            }
521
        } elseif (is_string($callback)) {
522
            if (class_exists($callback)) {
523
                if (is_subclass_of($callback, 'Dwoo\Block\Plugin')) {
524
                    $this->plugins[$name] = array(
525
                        'type'     => self::BLOCK_PLUGIN | $compilable,
526
                        'callback' => $callback,
527
                        'class'    => $callback
528
                    );
529
                } else {
530
                    $this->plugins[$name] = array(
531
                        'type'     => self::CLASS_PLUGIN | $compilable,
532
                        'callback' => $callback,
533
                        'class'    => $callback,
534
                        'function' => ($compilable ? 'compile' : 'process')
535
                    );
536
                }
537
            } elseif (function_exists($callback)) {
538
                $this->plugins[$name] = array(
539
                    'type'     => self::FUNC_PLUGIN | $compilable,
540
                    'callback' => $callback
541
                );
542
            } else {
543
                throw new Exception(
544
                    'Callback could not be processed correctly, please check that the function/class 
545
                you used exists'
546
                );
547
            }
548
        } elseif ($callback instanceof Closure) {
549
            $this->plugins[$name] = array(
550
                'type'     => self::FUNC_PLUGIN | $compilable,
551
                'callback' => $callback
552
            );
553
        } else {
554
            throw new Exception(
555
                'Callback could not be processed correctly, please check that the function/class you 
556
            used exists'
557
            );
558
        }
559
    }
560
561
    /**
562
     * Removes a custom plugin.
563
     *
564
     * @param string $name the plugin name
565
     *
566
     * @return void
567
     */
568
    public function removePlugin($name)
569
    {
570
        if (isset($this->plugins[$name])) {
571
            unset($this->plugins[$name]);
572
        }
573
    }
574
575
    /**
576
     * Adds a filter to this Dwoo instance, it will be used to filter the output of all the templates rendered by this
577
     * instance.
578
     *
579
     * @param mixed $callback a callback or a filter name if it is autoloaded from a plugin directory
580
     * @param bool  $autoload if true, the first parameter must be a filter name from one of the plugin directories
581
     *
582
     * @return void
583
     * @throws Exception
584
     */
585
    public function addFilter($callback, $autoload = false)
586
    {
587
        if ($autoload) {
588
            $class = self::NAMESPACE_PLUGINS_FILTERS . self::toCamelCase($callback);
589
            if (!class_exists($class) && !function_exists($class)) {
590
                try {
591
                    $this->getLoader()->loadPlugin($callback);
592
                }
593
                catch (Exception $e) {
594
                    if (strstr($callback, self::NAMESPACE_PLUGINS_FILTERS)) {
595
                        throw new Exception(
596
                            'Wrong filter name : ' . $callback . ', the "Dwoo_Filter_" prefix should 
597
                        not be used, please only use "' . str_replace('Dwoo_Filter_', '', $callback) . '"'
598
                        );
599
                    } else {
600
                        throw new Exception(
601
                            'Wrong filter name : ' . $callback . ', when using autoload the filter must
602
                         be in one of your plugin dir as "name.php" containig a class or function named
603
                         "Dwoo_Filter_name"'
604
                        );
605
                    }
606
                }
607
            }
608
609
            if (class_exists($class)) {
610
                $callback = array(new $class($this), 'process');
611
            } elseif (function_exists($class)) {
612
                $callback = $class;
613
            } else {
614
                throw new Exception(
615
                    'Wrong filter name : ' . $callback . ', when using autoload the filter must be in
616
                one of your plugin dir as "name.php" containig a class or function named "Dwoo_Filter_name"'
617
                );
618
            }
619
620
            $this->filters[] = $callback;
621
        } else {
622
            $this->filters[] = $callback;
623
        }
624
    }
625
626
    /**
627
     * Removes a filter.
628
     *
629
     * @param mixed $callback callback or filter name if it was autoloaded
630
     *
631
     * @return void
632
     */
633
    public function removeFilter($callback)
634
    {
635
        if (($index = array_search(self::NAMESPACE_PLUGINS_FILTERS. 'Filter' . self::toCamelCase($callback), $this->filters,
636
                true)) !==
637
            false) {
638
            unset($this->filters[$index]);
639
        } elseif (($index = array_search($callback, $this->filters, true)) !== false) {
640
            unset($this->filters[$index]);
641
        } else {
642
            $class = self::NAMESPACE_PLUGINS_FILTERS . 'Filter' . $callback;
643
            foreach ($this->filters as $index => $filter) {
644
                if (is_array($filter) && $filter[0] instanceof $class) {
645
                    unset($this->filters[$index]);
646
                    break;
647
                }
648
            }
649
        }
650
    }
651
652
    /**
653
     * Adds a resource or overrides a default one.
654
     *
655
     * @param string   $name            the resource name
656
     * @param string   $class           the resource class (which must implement ITemplate)
657
     * @param callback $compilerFactory the compiler factory callback, a function that must return a compiler instance
658
     *                                  used to compile this resource, if none is provided. by default it will produce
659
     *                                  a Compiler object
660
     *
661
     * @return void
662
     * @throws Exception
663
     */
664
    public function addResource($name, $class, $compilerFactory = null)
665
    {
666
        if (strlen($name) < 2) {
667
            throw new Exception('Resource names must be at least two-character long to avoid conflicts with Windows paths');
668
        }
669
670
        if (!class_exists($class)) {
671
            throw new Exception(sprintf('Resource class %s does not exist', $class));
672
        }
673
674
        $interfaces = class_implements($class);
675
        if (in_array('Dwoo\ITemplate', $interfaces) === false) {
676
            throw new Exception('Resource class must implement ITemplate');
677
        }
678
679
        $this->resources[$name] = array(
680
            'class'    => $class,
681
            'compiler' => $compilerFactory
682
        );
683
    }
684
685
    /**
686
     * Removes a custom resource.
687
     *
688
     * @param string $name the resource name
689
     *
690
     * @return void
691
     */
692
    public function removeResource($name)
693
    {
694
        unset($this->resources[$name]);
695
        if ($name === 'file') {
696
            $this->resources['file'] = array(
697
                'class'    => 'Dwoo\Template\File',
698
                'compiler' => null
699
            );
700
        }
701
    }
702
703
    /**
704
     * Sets the loader object to use to load plugins.
705
     *
706
     * @param ILoader $loader loader
707
     *
708
     * @return void
709
     */
710
    public function setLoader(ILoader $loader)
711
    {
712
        $this->loader = $loader;
713
    }
714
715
    /**
716
     * Returns the current loader object or a default one if none is currently found.
717
     *
718
     * @return ILoader|Loader
719
     */
720
    public function getLoader()
721
    {
722
        if ($this->loader === null) {
723
            $this->loader = new Loader($this->getCompileDir());
724
        }
725
726
        return $this->loader;
727
    }
728
729
    /**
730
     * Returns the custom plugins loaded.
731
     * Used by the ITemplate classes to pass the custom plugins to their ICompiler instance.
732
     *
733
     * @return array
734
     */
735
    public function getCustomPlugins()
736
    {
737
        return $this->plugins;
738
    }
739
740
    /**
741
     * Return a specified custom plugin loaded by his name.
742
     * Used by the compiler, for executing a Closure.
743
     *
744
     * @param string $name
745
     *
746
     * @return mixed|null
747
     */
748
    public function getCustomPlugin($name)
749
    {
750
        if (isset($this->plugins[$name])) {
751
            return $this->plugins[$name]['callback'];
752
        }
753
754
        return null;
755
    }
756
757
    /**
758
     * Returns the cache directory with a trailing DIRECTORY_SEPARATOR.
759
     *
760
     * @return string
761
     */
762
    public function getCacheDir()
763
    {
764
        if ($this->cacheDir === null) {
765
            $this->setCacheDir(dirname(__DIR__) . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR);
766
        }
767
768
        return $this->cacheDir;
769
    }
770
771
    /**
772
     * Sets the cache directory and automatically appends a DIRECTORY_SEPARATOR.
773
     *
774
     * @param string $dir the cache directory
775
     *
776
     * @return void
777
     * @throws Exception
778
     */
779 View Code Duplication
    public function setCacheDir($dir)
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...
780
    {
781
        $this->cacheDir = rtrim($dir, '/\\') . DIRECTORY_SEPARATOR;
782
        if (is_writable($this->cacheDir) === false) {
783
            throw new Exception('The cache directory must be writable, chmod "' . $this->cacheDir . '" to make it writable');
784
        }
785
    }
786
787
    /**
788
     * Returns the compile directory with a trailing DIRECTORY_SEPARATOR.
789
     *
790
     * @return string
791
     */
792
    public function getCompileDir()
793
    {
794
        if ($this->compileDir === null) {
795
            $this->setCompileDir(dirname(__DIR__) . DIRECTORY_SEPARATOR . 'compiled' . DIRECTORY_SEPARATOR);
796
        }
797
798
        return $this->compileDir;
799
    }
800
801
    /**
802
     * Sets the compile directory and automatically appends a DIRECTORY_SEPARATOR.
803
     *
804
     * @param string $dir the compile directory
805
     *
806
     * @return void
807
     * @throws Exception
808
     */
809 View Code Duplication
    public function setCompileDir($dir)
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...
810
    {
811
        $this->compileDir = rtrim($dir, '/\\') . DIRECTORY_SEPARATOR;
812
        if (is_writable($this->compileDir) === false) {
813
            throw new Exception('The compile directory must be writable, chmod "' . $this->compileDir . '" to make it writable');
814
        }
815
    }
816
817
    /**
818
     * Returns an array of the template directory with a trailing DIRECTORY_SEPARATOR
819
     *
820
     * @return array
821
     */
822
    public function getTemplateDir()
823
    {
824
        return $this->templateDir;
825
    }
826
827
    /**
828
     * sets the template directory and automatically appends a DIRECTORY_SEPARATOR
829
     * template directory is stored in an array
830
     *
831
     * @param string $dir
832
     *
833
     * @throws Exception
834
     */
835
    public function setTemplateDir($dir)
836
    {
837
        $tmpDir = rtrim($dir, '/\\') . DIRECTORY_SEPARATOR;
838
        if (is_dir($tmpDir) === false) {
839
            throw new Exception('The template directory: "' . $tmpDir . '" does not exists, create the directory or specify an other location !');
840
        }
841
        $this->templateDir[] = $tmpDir;
842
    }
843
844
    /**
845
     * Returns the default cache time that is used with templates that do not have a cache time set.
846
     *
847
     * @return int the duration in seconds
848
     */
849
    public function getCacheTime()
850
    {
851
        return $this->cacheTime;
852
    }
853
854
    /**
855
     * Sets the default cache time to use with templates that do not have a cache time set.
856
     *
857
     * @param int $seconds the duration in seconds
858
     *
859
     * @return void
860
     */
861
    public function setCacheTime($seconds)
862
    {
863
        $this->cacheTime = (int)$seconds;
864
    }
865
866
    /**
867
     * Returns the character set used by the string manipulation plugins.
868
     * the charset is automatically lowercased
869
     *
870
     * @return string
871
     */
872
    public function getCharset()
873
    {
874
        return $this->charset;
875
    }
876
877
    /**
878
     * Sets the character set used by the string manipulation plugins.
879
     * the charset will be automatically lowercased
880
     *
881
     * @param string $charset the character set
882
     *
883
     * @return void
884
     */
885
    public function setCharset($charset)
886
    {
887
        $this->charset = strtolower((string)$charset);
888
    }
889
890
    /**
891
     * Returns the current template being rendered, when applicable, or null.
892
     *
893
     * @return ITemplate|null
894
     */
895
    public function getTemplate()
896
    {
897
        return $this->template;
898
    }
899
900
    /**
901
     * Sets the current template being rendered.
902
     *
903
     * @param ITemplate $tpl template object
904
     *
905
     * @return void
906
     */
907
    public function setTemplate(ITemplate $tpl)
908
    {
909
        $this->template = $tpl;
910
    }
911
912
    /**
913
     * Sets the default compiler factory function for the given resource name.
914
     * a compiler factory must return a ICompiler object pre-configured to fit your needs
915
     *
916
     * @param string   $resourceName    the resource name (i.e. file, string)
917
     * @param callback $compilerFactory the compiler factory callback
918
     *
919
     * @return void
920
     */
921
    public function setDefaultCompilerFactory($resourceName, $compilerFactory)
922
    {
923
        $this->resources[$resourceName]['compiler'] = $compilerFactory;
924
    }
925
926
    /**
927
     * Returns the default compiler factory function for the given resource name.
928
     *
929
     * @param string $resourceName the resource name
930
     *
931
     * @return callback the compiler factory callback
932
     */
933
    public function getDefaultCompilerFactory($resourceName)
934
    {
935
        return $this->resources[$resourceName]['compiler'];
936
    }
937
938
    /**
939
     * Sets the security policy object to enforce some php security settings.
940
     * use this if untrusted persons can modify templates
941
     *
942
     * @param SecurityPolicy $policy the security policy object
943
     *
944
     * @return void
945
     */
946
    public function setSecurityPolicy(SecurityPolicy $policy = null)
947
    {
948
        $this->securityPolicy = $policy;
949
    }
950
951
    /**
952
     * Returns the current security policy object or null by default.
953
     *
954
     * @return SecurityPolicy|null the security policy object if any
955
     */
956
    public function getSecurityPolicy()
957
    {
958
        return $this->securityPolicy;
959
    }
960
961
    /**
962
     * Sets the object that must be used as a plugin proxy when plugin can't be found
963
     * by dwoo's loader.
964
     *
965
     * @param IPluginProxy $pluginProxy the proxy object
966
     *
967
     * @return void
968
     */
969
    public function setPluginProxy(IPluginProxy $pluginProxy)
970
    {
971
        $this->pluginProxy = $pluginProxy;
972
    }
973
974
    /**
975
     * Returns the current plugin proxy object or null by default.
976
     *
977
     * @return IPluginProxy
978
     */
979
    public function getPluginProxy()
980
    {
981
        return $this->pluginProxy;
982
    }
983
984
    /**
985
     * Checks whether the given template is cached or not.
986
     *
987
     * @param ITemplate $tpl the template object
988
     *
989
     * @return bool
990
     */
991
    public function isCached(ITemplate $tpl)
992
    {
993
        return is_string($tpl->getCachedTemplate($this));
994
    }
995
996
    /**
997
     * Clear templates inside the compiled directory.
998
     *
999
     * @return int
1000
     */
1001
    public function clearCompiled()
1002
    {
1003
        $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->getCompileDir()), \RecursiveIteratorIterator::SELF_FIRST);
1004
        $count    = 0;
1005
        foreach ($iterator as $file) {
1006
            if ($file->isFile()) {
1007
                $count += unlink($file->__toString()) ? 1 : 0;
1008
            }
1009
        }
1010
1011
        return $count;
1012
    }
1013
1014
    /**
1015
     * Clears the cached templates if they are older than the given time.
1016
     *
1017
     * @param int $olderThan minimum time (in seconds) required for a cached template to be cleared
1018
     *
1019
     * @return int the amount of templates cleared
1020
     */
1021
    public function clearCache($olderThan = - 1)
1022
    {
1023
        $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->getCacheDir()), \RecursiveIteratorIterator::SELF_FIRST);
1024
        $expired  = time() - $olderThan;
1025
        $count    = 0;
1026
        foreach ($iterator as $file) {
1027
            if ($file->isFile() && $file->getCTime() < $expired) {
1028
                $count += unlink((string)$file) ? 1 : 0;
1029
            }
1030
        }
1031
1032
        return $count;
1033
    }
1034
1035
    /**
1036
     * Fetches a template object of the given resource.
1037
     *
1038
     * @param string    $resourceName   the resource name (i.e. file, string)
1039
     * @param string    $resourceId     the resource identifier (i.e. file path)
1040
     * @param int       $cacheTime      the cache time setting for this resource
1041
     * @param string    $cacheId        the unique cache identifier
1042
     * @param string    $compileId      the unique compiler identifier
1043
     * @param ITemplate $parentTemplate the parent template
1044
     *
1045
     * @return ITemplate
1046
     * @throws Exception
1047
     */
1048
    public function templateFactory($resourceName, $resourceId, $cacheTime = null, $cacheId = null, $compileId = null, ITemplate $parentTemplate = null)
1049
    {
1050
        if (isset($this->resources[$resourceName])) {
1051
            /**
1052
             * Interface ITemplate
1053
             *
1054
             * @var ITemplate $class
1055
             */
1056
            $class = $this->resources[$resourceName]['class'];
1057
1058
            return $class::templateFactory($this, $resourceId, $cacheTime, $cacheId, $compileId, $parentTemplate);
0 ignored issues
show
Comprehensibility Best Practice introduced by DSanchez
The expression $class::templateFactory(...leId, $parentTemplate); of type Dwoo\ITemplate|null|false adds false to the return on line 1058 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...
1059
        }
1060
1061
        throw new Exception('Unknown resource type : ' . $resourceName);
1062
    }
1063
1064
    /**
1065
     * Checks if the input is an array or arrayaccess object, optionally it can also check if it's
1066
     * empty.
1067
     *
1068
     * @param mixed $value        the variable to check
1069
     * @param bool  $checkIsEmpty if true, the function will also check if the array|arrayaccess is empty,
1070
     *                            and return true only if it's not empty
1071
     *
1072
     * @return int|bool true if it's an array|arrayaccess (or the item count if $checkIsEmpty is true) or false if it's
1073
     *                  not an array|arrayaccess (or 0 if $checkIsEmpty is true)
1074
     */
1075
    public function isArray($value, $checkIsEmpty = false)
1076
    {
1077
        if (is_array($value) === true || $value instanceof ArrayAccess) {
1078
            if ($checkIsEmpty === false) {
1079
                return true;
1080
            }
1081
1082
            return $this->count($value);
1083
        }
1084
1085
        return false;
1086
    }
1087
1088
    /**
1089
     * Checks if the input is an array or a traversable object, optionally it can also check if it's
1090
     * empty.
1091
     *
1092
     * @param mixed $value        the variable to check
1093
     * @param bool  $checkIsEmpty if true, the function will also check if the array|traversable is empty,
1094
     *                            and return true only if it's not empty
1095
     *
1096
     * @return int|bool true if it's an array|traversable (or the item count if $checkIsEmpty is true) or false if it's
1097
     *                  not an array|traversable (or 0 if $checkIsEmpty is true)
1098
     */
1099
    public function isTraversable($value, $checkIsEmpty = false)
1100
    {
1101
        if (is_array($value) === true) {
1102
            if ($checkIsEmpty === false) {
1103
                return true;
1104
            } else {
1105
                return count($value) > 0;
1106
            }
1107
        } elseif ($value instanceof Traversable) {
1108
            if ($checkIsEmpty === false) {
1109
                return true;
1110
            } else {
1111
                return $this->count($value);
1112
            }
1113
        }
1114
1115
        return false;
1116
    }
1117
1118
    /**
1119
     * Counts an array or arrayaccess/traversable object.
1120
     *
1121
     * @param mixed $value the value to count
1122
     *
1123
     * @return int|bool the count for arrays and objects that implement countable, true for other objects that don't,
1124
     *                  and 0 for empty elements
1125
     */
1126
    public function count($value)
1127
    {
1128
        if (is_array($value) === true || $value instanceof Countable) {
1129
            return count($value);
1130
        } elseif ($value instanceof ArrayAccess) {
1131
            if ($value->offsetExists(0)) {
1132
                return true;
1133
            }
1134
        } elseif ($value instanceof Iterator) {
1135
            $value->rewind();
1136
            if ($value->valid()) {
1137
                return true;
1138
            }
1139
        } elseif ($value instanceof Traversable) {
1140
            foreach ($value as $dummy) {
1141
                return true;
1142
            }
1143
        }
1144
1145
        return 0;
1146
    }
1147
1148
    /**
1149
     * Triggers a dwoo error.
1150
     *
1151
     * @param string $message the error message
1152
     * @param int    $level   the error level, one of the PHP's E_* constants
1153
     *
1154
     * @return void
1155
     */
1156
    public function triggerError($message, $level = E_USER_NOTICE)
1157
    {
1158
        if (!($tplIdentifier = $this->template->getResourceIdentifier())) {
1159
            $tplIdentifier = $this->template->getResourceName();
1160
        }
1161
        trigger_error('Dwoo error (in ' . $tplIdentifier . ') : ' . $message, $level);
1162
    }
1163
1164
    /**
1165
     * Adds a block to the block stack.
1166
     *
1167
     * @param string $blockName the block name (without Dwoo_Plugin_ prefix)
1168
     * @param array  $args      the arguments to be passed to the block's init() function
1169
     *
1170
     * @return BlockPlugin the newly created block
1171
     */
1172
    public function addStack($blockName, array $args = array())
1173
    {
1174 View Code Duplication
        if (isset($this->plugins[$blockName])) {
0 ignored issues
show
Duplication introduced by seldaek
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...
1175
            $class = $this->plugins[$blockName]['class'];
1176
        } else {
1177
            $class = self::NAMESPACE_PLUGINS_BLOCKS . 'Plugin' . self::toCamelCase($blockName);
1178
        }
1179
1180
        if ($this->curBlock !== null) {
1181
            $this->curBlock->buffer(ob_get_contents());
1182
            ob_clean();
1183
        } else {
1184
            $this->buffer .= ob_get_contents();
1185
            ob_clean();
1186
        }
1187
1188
        $block = new $class($this);
1189
1190
        call_user_func_array(array($block, 'init'), $args);
1191
1192
        $this->stack[] = $this->curBlock = $block;
1193
1194
        return $block;
1195
    }
1196
1197
    /**
1198
     * Removes the plugin at the top of the block stack.
1199
     * Calls the block buffer() function, followed by a call to end() and finally a call to process()
1200
     *
1201
     * @return void
1202
     */
1203
    public function delStack()
1204
    {
1205
        $args = func_get_args();
1206
1207
        $this->curBlock->buffer(ob_get_contents());
1208
        ob_clean();
1209
1210
        call_user_func_array(array($this->curBlock, 'end'), $args);
1211
1212
        $tmp = array_pop($this->stack);
1213
1214
        if (count($this->stack) > 0) {
1215
            $this->curBlock = end($this->stack);
1216
            $this->curBlock->buffer($tmp->process());
1217
        } else {
1218
            if ($this->buffer !== '') {
1219
                echo $this->buffer;
1220
                $this->buffer = '';
1221
            }
1222
            $this->curBlock = null;
1223
            echo $tmp->process();
1224
        }
1225
1226
        unset($tmp);
1227