Completed
Push — master ( 494091...32c874 )
by David
27s
created
lib/Dwoo/Template/Str.php 1 patch
Indentation   +505 added lines, -505 removed lines patch added patch discarded remove patch
@@ -29,509 +29,509 @@
 block discarded – undo
29 29
  */
30 30
 class Str implements ITemplate
31 31
 {
32
-    /**
33
-     * Template name.
34
-     *
35
-     * @var string
36
-     */
37
-    protected $name;
38
-
39
-    /**
40
-     * Template compilation id.
41
-     *
42
-     * @var string
43
-     */
44
-    protected $compileId;
45
-
46
-    /**
47
-     * Template cache id, if not provided in the constructor, it is set to
48
-     * the md4 hash of the request_uri. it is however highly recommended to
49
-     * provide one that will fit your needs.
50
-     * in all cases, the compilation id is prepended to the cache id to separate
51
-     * templates with similar cache ids from one another
52
-     *
53
-     * @var string
54
-     */
55
-    protected $cacheId;
56
-
57
-    /**
58
-     * Validity duration of the generated cache file (in seconds).
59
-     * set to -1 for infinite cache, 0 to disable and null to inherit the Dwoo instance's cache time
60
-     *
61
-     * @var int
62
-     */
63
-    protected $cacheTime;
64
-
65
-    /**
66
-     * Boolean flag that defines whether the compilation should be enforced (once) or
67
-     * not use this if you have issues with the compiled templates not being updated
68
-     * but if you do need this it's most likely that you should file a bug report.
69
-     *
70
-     * @var bool
71
-     */
72
-    protected $compilationEnforced;
73
-
74
-    /**
75
-     * Caches the results of the file checks to save some time when the same
76
-     * templates is rendered several times.
77
-     *
78
-     * @var array
79
-     */
80
-    protected static $cache = array(
81
-        'cached'   => array(),
82
-        'compiled' => array()
83
-    );
84
-
85
-    /**
86
-     * Holds the compiler that built this template.
87
-     *
88
-     * @var ICompiler
89
-     */
90
-    protected $compiler;
91
-
92
-    /**
93
-     * Chmod value for all files written (cached or compiled ones).
94
-     * set to null if you don't want any chmod operation to happen
95
-     *
96
-     * @var int
97
-     */
98
-    protected $chmod = 0777;
99
-
100
-    /**
101
-     * Containing template string.
102
-     *
103
-     * @var string
104
-     */
105
-    protected $template;
106
-
107
-    /**
108
-     * Creates a template from a string.
109
-     *
110
-     * @param string $templateString the template to use
111
-     * @param int    $cacheTime      duration of the cache validity for this template,
112
-     *                               if null it defaults to the Dwoo instance that will
113
-     *                               render this template, set to -1 for infinite cache or 0 to disable
114
-     * @param string $cacheId        the unique cache identifier of this page or anything else that
115
-     *                               makes this template's content unique, if null it defaults
116
-     *                               to the current url
117
-     * @param string $compileId      the unique compiled identifier, which is used to distinguish this
118
-     *                               template from others, if null it defaults to the md4 hash of the template
119
-     */
120
-    public function __construct($templateString, $cacheTime = null, $cacheId = null, $compileId = null)
121
-    {
122
-        $this->template  = $templateString;
123
-        $this->name      = hash('md4', $templateString);
124
-        $this->cacheTime = $cacheTime;
125
-
126
-        if ($compileId !== null) {
127
-            $this->compileId = str_replace('../', '__', strtr($compileId, '\\%?=!:;' . PATH_SEPARATOR, '/-------'));
128
-        }
129
-
130
-        if ($cacheId !== null) {
131
-            $this->cacheId = str_replace('../', '__', strtr($cacheId, '\\%?=!:;' . PATH_SEPARATOR, '/-------'));
132
-        }
133
-    }
134
-
135
-    /**
136
-     * Returns the cache duration for this template.
137
-     * defaults to null if it was not provided
138
-     *
139
-     * @return int|null
140
-     */
141
-    public function getCacheTime()
142
-    {
143
-        return $this->cacheTime;
144
-    }
145
-
146
-    /**
147
-     * Sets the cache duration for this template.
148
-     * can be used to set it after the object is created if you did not provide
149
-     * it in the constructor
150
-     *
151
-     * @param int $seconds duration of the cache validity for this template, if
152
-     *                     null it defaults to the Dwoo instance's cache time. 0 = disable and
153
-     *                     -1 = infinite cache
154
-     */
155
-    public function setCacheTime($seconds = null)
156
-    {
157
-        $this->cacheTime = $seconds;
158
-    }
159
-
160
-    /**
161
-     * Returns the chmod value for all files written (cached or compiled ones).
162
-     * defaults to 0777
163
-     *
164
-     * @return int|null
165
-     */
166
-    public function getChmod()
167
-    {
168
-        return $this->chmod;
169
-    }
170
-
171
-    /**
172
-     * Set the chmod value for all files written (cached or compiled ones).
173
-     * set to null if you don't want to do any chmod() operation
174
-     *
175
-     * @param int $mask new bitmask to use for all files
176
-     */
177
-    public function setChmod($mask = null)
178
-    {
179
-        $this->chmod = $mask;
180
-    }
181
-
182
-    /**
183
-     * Returns the template name.
184
-     *
185
-     * @return string
186
-     */
187
-    public function getName()
188
-    {
189
-        return $this->name;
190
-    }
191
-
192
-    /**
193
-     * Returns the resource name for this template class.
194
-     *
195
-     * @return string
196
-     */
197
-    public function getResourceName()
198
-    {
199
-        return 'string';
200
-    }
201
-
202
-    /**
203
-     * Returns the resource identifier for this template, false here as strings don't have identifiers.
204
-     *
205
-     * @return false
206
-     */
207
-    public function getResourceIdentifier()
208
-    {
209
-        return false;
210
-    }
211
-
212
-    /**
213
-     * Returns the template source of this template.
214
-     *
215
-     * @return string
216
-     */
217
-    public function getSource()
218
-    {
219
-        return $this->template;
220
-    }
221
-
222
-    /**
223
-     * Returns an unique value identifying the current version of this template,
224
-     * in this case it's the md4 hash of the content.
225
-     *
226
-     * @return string
227
-     */
228
-    public function getUid()
229
-    {
230
-        return $this->name;
231
-    }
232
-
233
-    /**
234
-     * Returns the compiler used by this template, if it was just compiled, or null.
235
-     *
236
-     * @return ICompiler
237
-     */
238
-    public function getCompiler()
239
-    {
240
-        return $this->compiler;
241
-    }
242
-
243
-    /**
244
-     * Marks this template as compile-forced, which means it will be recompiled even if it
245
-     * was already saved and wasn't modified since the last compilation. do not use this in production,
246
-     * it's only meant to be used in development (and the development of dwoo particularly).
247
-     */
248
-    public function forceCompilation()
249
-    {
250
-        $this->compilationEnforced = true;
251
-    }
252
-
253
-    /**
254
-     * Returns the cached template output file name, true if it's cache-able but not cached
255
-     * or false if it's not cached.
256
-     *
257
-     * @param Core $core the dwoo instance that requests it
258
-     *
259
-     * @return string|bool
260
-     */
261
-    public function getCachedTemplate(Core $core)
262
-    {
263
-        if ($this->cacheTime !== null) {
264
-            $cacheLength = $this->cacheTime;
265
-        } else {
266
-            $cacheLength = $core->getCacheTime();
267
-        }
268
-
269
-        // file is not cacheable
270
-        if ($cacheLength == 0) {
271
-            return false;
272
-        }
273
-
274
-        $cachedFile = $this->getCacheFilename($core);
275
-
276
-        if (isset(self::$cache['cached'][$this->cacheId]) === true && file_exists($cachedFile)) {
277
-            // already checked, return cache file
278
-            return $cachedFile;
279
-        } elseif ($this->compilationEnforced !== true && file_exists($cachedFile) && ($cacheLength === - 1 || filemtime($cachedFile) > ($_SERVER['REQUEST_TIME'] - $cacheLength)) && $this->isValidCompiledFile($this->getCompiledFilename($core))) {
280
-            // cache is still valid and can be loaded
281
-            self::$cache['cached'][$this->cacheId] = true;
282
-
283
-            return $cachedFile;
284
-        } else {
285
-            // file is cacheable
286
-            return true;
287
-        }
288
-    }
289
-
290
-    /**
291
-     * Caches the provided output into the cache file.
292
-     *
293
-     * @param Core   $core   the dwoo instance that requests it
294
-     * @param string $output the template output
295
-     *
296
-     * @return mixed full path of the cached file or false upon failure
297
-     */
298
-    public function cache(Core $core, $output)
299
-    {
300
-        $cacheDir   = $core->getCacheDir();
301
-        $cachedFile = $this->getCacheFilename($core);
302
-
303
-        // the code below is courtesy of Rasmus Schultz,
304
-        // thanks for his help on avoiding concurency issues
305
-        $temp = tempnam($cacheDir, 'temp');
306
-        if (!($file = @fopen($temp, 'wb'))) {
307
-            $temp = $cacheDir . uniqid('temp');
308
-            if (!($file = @fopen($temp, 'wb'))) {
309
-                trigger_error('Error writing temporary file \'' . $temp . '\'', E_USER_WARNING);
310
-
311
-                return false;
312
-            }
313
-        }
314
-
315
-        fwrite($file, $output);
316
-        fclose($file);
317
-
318
-        $this->makeDirectory(dirname($cachedFile), $cacheDir);
319
-        if (!@rename($temp, $cachedFile)) {
320
-            @unlink($cachedFile);
321
-            @rename($temp, $cachedFile);
322
-        }
323
-
324
-        if ($this->chmod !== null) {
325
-            chmod($cachedFile, $this->chmod);
326
-        }
327
-
328
-        self::$cache['cached'][$this->cacheId] = true;
329
-
330
-        return $cachedFile;
331
-    }
332
-
333
-    /**
334
-     * Clears the cached template if it's older than the given time.
335
-     *
336
-     * @param Core $core      the dwoo instance that was used to cache that template
337
-     * @param int  $olderThan minimum time (in seconds) required for the cache to be cleared
338
-     *
339
-     * @return bool true if the cache was not present or if it was deleted, false if it remains there
340
-     */
341
-    public function clearCache(Core $core, $olderThan = - 1)
342
-    {
343
-        $cachedFile = $this->getCacheFilename($core);
344
-
345
-        return !file_exists($cachedFile) || (filectime($cachedFile) < (time() - $olderThan) && unlink($cachedFile));
346
-    }
347
-
348
-    /**
349
-     * Returns the compiled template file name.
350
-     *
351
-     * @param Core      $core     the dwoo instance that requests it
352
-     * @param ICompiler $compiler the compiler that must be used
353
-     *
354
-     * @return string
355
-     */
356
-    public function getCompiledTemplate(Core $core, ICompiler $compiler = null)
357
-    {
358
-        $compiledFile = $this->getCompiledFilename($core);
359
-
360
-        if ($this->compilationEnforced !== true && isset(self::$cache['compiled'][$this->compileId]) === true) {
361
-            // already checked, return compiled file
362
-        } elseif ($this->compilationEnforced !== true && $this->isValidCompiledFile($compiledFile)) {
363
-            // template is compiled
364
-            self::$cache['compiled'][$this->compileId] = true;
365
-        } else {
366
-            // compiles the template
367
-            $this->compilationEnforced = false;
368
-
369
-            if ($compiler === null) {
370
-                $compiler = $core->getDefaultCompilerFactory($this->getResourceName());
371
-
372
-                if ($compiler === null || $compiler === array('Dwoo\Compiler', 'compilerFactory')) {
373
-                    $compiler = Compiler::compilerFactory();
374
-                } else {
375
-                    $compiler = call_user_func($compiler);
376
-                }
377
-            }
378
-
379
-            $this->compiler = $compiler;
380
-
381
-            $compiler->setCustomPlugins($core->getCustomPlugins());
382
-            $compiler->setSecurityPolicy($core->getSecurityPolicy());
383
-            $this->makeDirectory(dirname($compiledFile), $core->getCompileDir());
384
-            file_put_contents($compiledFile, $compiler->compile($core, $this));
385
-            if ($this->chmod !== null) {
386
-                chmod($compiledFile, $this->chmod);
387
-            }
388
-
389
-            if (extension_loaded('Zend OPcache')) {
390
-                opcache_invalidate($compiledFile);
391
-            } elseif (extension_loaded('apc') && ini_get('apc.enabled')) {
392
-                apc_delete_file($compiledFile);
393
-            }
394
-
395
-            self::$cache['compiled'][$this->compileId] = true;
396
-        }
397
-
398
-        return $compiledFile;
399
-    }
400
-
401
-    /**
402
-     * Checks if compiled file is valid (it exists).
403
-     *
404
-     * @param string $file
405
-     *
406
-     * @return bool True cache file existence
407
-     */
408
-    protected function isValidCompiledFile($file)
409
-    {
410
-        return file_exists($file);
411
-    }
412
-
413
-    /**
414
-     * Returns a new template string object with the resource id being the template source code.
415
-     *
416
-     * @param Core      $core           the dwoo instance requiring it
417
-     * @param mixed     $resourceId     the filename (relative to this template's dir) of the template to include
418
-     * @param int       $cacheTime      duration of the cache validity for this template, if null it defaults to the
419
-     *                                  Dwoo instance that will render this template if null it defaults to the Dwoo
420
-     *                                  instance that will render this template
421
-     * @param string    $cacheId        the unique cache identifier of this page or anything else that makes this
422
-     *                                  template's content unique, if null it defaults to the current url makes this
423
-     *                                  template's content unique, if null it defaults to the current url
424
-     * @param string    $compileId      the unique compiled identifier, which is used to distinguish this template from
425
-     *                                  others, if null it defaults to the filename+bits of the path template from
426
-     *                                  others, if null it defaults to the filename+bits of the path
427
-     * @param ITemplate $parentTemplate the template that is requesting a new template object (through an include,
428
-     *                                  extends or any other plugin) an include, extends or any other plugin)
429
-     *
430
-     * @return $this
431
-     */
432
-    public static function templateFactory(Core $core, $resourceId, $cacheTime = null, $cacheId = null, $compileId = null, ITemplate $parentTemplate = null)
433
-    {
434
-        return new self($resourceId, $cacheTime, $cacheId, $compileId);
435
-    }
436
-
437
-    /**
438
-     * Returns the full compiled file name and assigns a default value to it if
439
-     * required.
440
-     *
441
-     * @param Core $core the dwoo instance that requests the file name
442
-     *
443
-     * @return string the full path to the compiled file
444
-     */
445
-    protected function getCompiledFilename(Core $core)
446
-    {
447
-        // no compile id was provided, set default
448
-        if ($this->compileId === null) {
449
-            $this->compileId = $this->name;
450
-        }
451
-
452
-        return $core->getCompileDir() . $this->compileId . '.d' . Core::RELEASE_TAG . '.php';
453
-    }
454
-
455
-    /**
456
-     * Returns the full cached file name and assigns a default value to it if
457
-     * required.
458
-     *
459
-     * @param Core $core the dwoo instance that requests the file name
460
-     *
461
-     * @return string the full path to the cached file
462
-     */
463
-    protected function getCacheFilename(Core $core)
464
-    {
465
-        // no cache id provided, use request_uri as default
466
-        if ($this->cacheId === null) {
467
-            if (isset($_SERVER['REQUEST_URI']) === true) {
468
-                $cacheId = $_SERVER['REQUEST_URI'];
469
-            } elseif (isset($_SERVER['SCRIPT_FILENAME']) && isset($_SERVER['argv'])) {
470
-                $cacheId = $_SERVER['SCRIPT_FILENAME'] . '-' . implode('-', $_SERVER['argv']);
471
-            } else {
472
-                $cacheId = '';
473
-            }
474
-            // force compiled id generation
475
-            $this->getCompiledFilename($core);
476
-
477
-            $this->cacheId = str_replace('../', '__', $this->compileId . strtr($cacheId, '\\%?=!:;' . PATH_SEPARATOR, '/-------'));
478
-        }
479
-
480
-        return $core->getCacheDir() . $this->cacheId . '.html';
481
-    }
482
-
483
-    /**
484
-     * Returns some php code that will check if this template has been modified or not.
485
-     * if the function returns null, the template will be instanciated and then the Uid checked
486
-     *
487
-     * @return string
488
-     */
489
-    public function getIsModifiedCode()
490
-    {
491
-        return null;
492
-    }
493
-
494
-    /**
495
-     * Ensures the given path exists.
496
-     *
497
-     * @param string $path    any path
498
-     * @param string $baseDir the base directory where the directory is created
499
-     *                        ($path must still contain the full path, $baseDir
500
-     *                        is only used for unix permissions)
501
-     *
502
-     * @throws Exception
503
-     */
504
-    protected function makeDirectory($path, $baseDir = null)
505
-    {
506
-        if (is_dir($path) === true) {
507
-            return;
508
-        }
509
-
510
-        if ($this->chmod === null) {
511
-            $chmod = 0777;
512
-        } else {
513
-            $chmod = $this->chmod;
514
-        }
515
-
516
-        $retries = 3;
517
-        while ($retries --) {
518
-            @mkdir($path, $chmod, true);
519
-            if (is_dir($path)) {
520
-                break;
521
-            }
522
-            usleep(20);
523
-        }
524
-
525
-        // enforce the correct mode for all directories created
526
-        if (strpos(PHP_OS, 'WIN') !== 0 && $baseDir !== null) {
527
-            $path    = strtr(str_replace($baseDir, '', $path), '\\', '/');
528
-            $folders = explode('/', trim($path, '/'));
529
-            foreach ($folders as $folder) {
530
-                $baseDir .= $folder . DIRECTORY_SEPARATOR;
531
-                if (!chmod($baseDir, $chmod)) {
532
-                    throw new Exception('Unable to chmod ' . "$baseDir to $chmod: " . print_r(error_get_last(), true));
533
-                }
534
-            }
535
-        }
536
-    }
32
+	/**
33
+	 * Template name.
34
+	 *
35
+	 * @var string
36
+	 */
37
+	protected $name;
38
+
39
+	/**
40
+	 * Template compilation id.
41
+	 *
42
+	 * @var string
43
+	 */
44
+	protected $compileId;
45
+
46
+	/**
47
+	 * Template cache id, if not provided in the constructor, it is set to
48
+	 * the md4 hash of the request_uri. it is however highly recommended to
49
+	 * provide one that will fit your needs.
50
+	 * in all cases, the compilation id is prepended to the cache id to separate
51
+	 * templates with similar cache ids from one another
52
+	 *
53
+	 * @var string
54
+	 */
55
+	protected $cacheId;
56
+
57
+	/**
58
+	 * Validity duration of the generated cache file (in seconds).
59
+	 * set to -1 for infinite cache, 0 to disable and null to inherit the Dwoo instance's cache time
60
+	 *
61
+	 * @var int
62
+	 */
63
+	protected $cacheTime;
64
+
65
+	/**
66
+	 * Boolean flag that defines whether the compilation should be enforced (once) or
67
+	 * not use this if you have issues with the compiled templates not being updated
68
+	 * but if you do need this it's most likely that you should file a bug report.
69
+	 *
70
+	 * @var bool
71
+	 */
72
+	protected $compilationEnforced;
73
+
74
+	/**
75
+	 * Caches the results of the file checks to save some time when the same
76
+	 * templates is rendered several times.
77
+	 *
78
+	 * @var array
79
+	 */
80
+	protected static $cache = array(
81
+		'cached'   => array(),
82
+		'compiled' => array()
83
+	);
84
+
85
+	/**
86
+	 * Holds the compiler that built this template.
87
+	 *
88
+	 * @var ICompiler
89
+	 */
90
+	protected $compiler;
91
+
92
+	/**
93
+	 * Chmod value for all files written (cached or compiled ones).
94
+	 * set to null if you don't want any chmod operation to happen
95
+	 *
96
+	 * @var int
97
+	 */
98
+	protected $chmod = 0777;
99
+
100
+	/**
101
+	 * Containing template string.
102
+	 *
103
+	 * @var string
104
+	 */
105
+	protected $template;
106
+
107
+	/**
108
+	 * Creates a template from a string.
109
+	 *
110
+	 * @param string $templateString the template to use
111
+	 * @param int    $cacheTime      duration of the cache validity for this template,
112
+	 *                               if null it defaults to the Dwoo instance that will
113
+	 *                               render this template, set to -1 for infinite cache or 0 to disable
114
+	 * @param string $cacheId        the unique cache identifier of this page or anything else that
115
+	 *                               makes this template's content unique, if null it defaults
116
+	 *                               to the current url
117
+	 * @param string $compileId      the unique compiled identifier, which is used to distinguish this
118
+	 *                               template from others, if null it defaults to the md4 hash of the template
119
+	 */
120
+	public function __construct($templateString, $cacheTime = null, $cacheId = null, $compileId = null)
121
+	{
122
+		$this->template  = $templateString;
123
+		$this->name      = hash('md4', $templateString);
124
+		$this->cacheTime = $cacheTime;
125
+
126
+		if ($compileId !== null) {
127
+			$this->compileId = str_replace('../', '__', strtr($compileId, '\\%?=!:;' . PATH_SEPARATOR, '/-------'));
128
+		}
129
+
130
+		if ($cacheId !== null) {
131
+			$this->cacheId = str_replace('../', '__', strtr($cacheId, '\\%?=!:;' . PATH_SEPARATOR, '/-------'));
132
+		}
133
+	}
134
+
135
+	/**
136
+	 * Returns the cache duration for this template.
137
+	 * defaults to null if it was not provided
138
+	 *
139
+	 * @return int|null
140
+	 */
141
+	public function getCacheTime()
142
+	{
143
+		return $this->cacheTime;
144
+	}
145
+
146
+	/**
147
+	 * Sets the cache duration for this template.
148
+	 * can be used to set it after the object is created if you did not provide
149
+	 * it in the constructor
150
+	 *
151
+	 * @param int $seconds duration of the cache validity for this template, if
152
+	 *                     null it defaults to the Dwoo instance's cache time. 0 = disable and
153
+	 *                     -1 = infinite cache
154
+	 */
155
+	public function setCacheTime($seconds = null)
156
+	{
157
+		$this->cacheTime = $seconds;
158
+	}
159
+
160
+	/**
161
+	 * Returns the chmod value for all files written (cached or compiled ones).
162
+	 * defaults to 0777
163
+	 *
164
+	 * @return int|null
165
+	 */
166
+	public function getChmod()
167
+	{
168
+		return $this->chmod;
169
+	}
170
+
171
+	/**
172
+	 * Set the chmod value for all files written (cached or compiled ones).
173
+	 * set to null if you don't want to do any chmod() operation
174
+	 *
175
+	 * @param int $mask new bitmask to use for all files
176
+	 */
177
+	public function setChmod($mask = null)
178
+	{
179
+		$this->chmod = $mask;
180
+	}
181
+
182
+	/**
183
+	 * Returns the template name.
184
+	 *
185
+	 * @return string
186
+	 */
187
+	public function getName()
188
+	{
189
+		return $this->name;
190
+	}
191
+
192
+	/**
193
+	 * Returns the resource name for this template class.
194
+	 *
195
+	 * @return string
196
+	 */
197
+	public function getResourceName()
198
+	{
199
+		return 'string';
200
+	}
201
+
202
+	/**
203
+	 * Returns the resource identifier for this template, false here as strings don't have identifiers.
204
+	 *
205
+	 * @return false
206
+	 */
207
+	public function getResourceIdentifier()
208
+	{
209
+		return false;
210
+	}
211
+
212
+	/**
213
+	 * Returns the template source of this template.
214
+	 *
215
+	 * @return string
216
+	 */
217
+	public function getSource()
218
+	{
219
+		return $this->template;
220
+	}
221
+
222
+	/**
223
+	 * Returns an unique value identifying the current version of this template,
224
+	 * in this case it's the md4 hash of the content.
225
+	 *
226
+	 * @return string
227
+	 */
228
+	public function getUid()
229
+	{
230
+		return $this->name;
231
+	}
232
+
233
+	/**
234
+	 * Returns the compiler used by this template, if it was just compiled, or null.
235
+	 *
236
+	 * @return ICompiler
237
+	 */
238
+	public function getCompiler()
239
+	{
240
+		return $this->compiler;
241
+	}
242
+
243
+	/**
244
+	 * Marks this template as compile-forced, which means it will be recompiled even if it
245
+	 * was already saved and wasn't modified since the last compilation. do not use this in production,
246
+	 * it's only meant to be used in development (and the development of dwoo particularly).
247
+	 */
248
+	public function forceCompilation()
249
+	{
250
+		$this->compilationEnforced = true;
251
+	}
252
+
253
+	/**
254
+	 * Returns the cached template output file name, true if it's cache-able but not cached
255
+	 * or false if it's not cached.
256
+	 *
257
+	 * @param Core $core the dwoo instance that requests it
258
+	 *
259
+	 * @return string|bool
260
+	 */
261
+	public function getCachedTemplate(Core $core)
262
+	{
263
+		if ($this->cacheTime !== null) {
264
+			$cacheLength = $this->cacheTime;
265
+		} else {
266
+			$cacheLength = $core->getCacheTime();
267
+		}
268
+
269
+		// file is not cacheable
270
+		if ($cacheLength == 0) {
271
+			return false;
272
+		}
273
+
274
+		$cachedFile = $this->getCacheFilename($core);
275
+
276
+		if (isset(self::$cache['cached'][$this->cacheId]) === true && file_exists($cachedFile)) {
277
+			// already checked, return cache file
278
+			return $cachedFile;
279
+		} elseif ($this->compilationEnforced !== true && file_exists($cachedFile) && ($cacheLength === - 1 || filemtime($cachedFile) > ($_SERVER['REQUEST_TIME'] - $cacheLength)) && $this->isValidCompiledFile($this->getCompiledFilename($core))) {
280
+			// cache is still valid and can be loaded
281
+			self::$cache['cached'][$this->cacheId] = true;
282
+
283
+			return $cachedFile;
284
+		} else {
285
+			// file is cacheable
286
+			return true;
287
+		}
288
+	}
289
+
290
+	/**
291
+	 * Caches the provided output into the cache file.
292
+	 *
293
+	 * @param Core   $core   the dwoo instance that requests it
294
+	 * @param string $output the template output
295
+	 *
296
+	 * @return mixed full path of the cached file or false upon failure
297
+	 */
298
+	public function cache(Core $core, $output)
299
+	{
300
+		$cacheDir   = $core->getCacheDir();
301
+		$cachedFile = $this->getCacheFilename($core);
302
+
303
+		// the code below is courtesy of Rasmus Schultz,
304
+		// thanks for his help on avoiding concurency issues
305
+		$temp = tempnam($cacheDir, 'temp');
306
+		if (!($file = @fopen($temp, 'wb'))) {
307
+			$temp = $cacheDir . uniqid('temp');
308
+			if (!($file = @fopen($temp, 'wb'))) {
309
+				trigger_error('Error writing temporary file \'' . $temp . '\'', E_USER_WARNING);
310
+
311
+				return false;
312
+			}
313
+		}
314
+
315
+		fwrite($file, $output);
316
+		fclose($file);
317
+
318
+		$this->makeDirectory(dirname($cachedFile), $cacheDir);
319
+		if (!@rename($temp, $cachedFile)) {
320
+			@unlink($cachedFile);
321
+			@rename($temp, $cachedFile);
322
+		}
323
+
324
+		if ($this->chmod !== null) {
325
+			chmod($cachedFile, $this->chmod);
326
+		}
327
+
328
+		self::$cache['cached'][$this->cacheId] = true;
329
+
330
+		return $cachedFile;
331
+	}
332
+
333
+	/**
334
+	 * Clears the cached template if it's older than the given time.
335
+	 *
336
+	 * @param Core $core      the dwoo instance that was used to cache that template
337
+	 * @param int  $olderThan minimum time (in seconds) required for the cache to be cleared
338
+	 *
339
+	 * @return bool true if the cache was not present or if it was deleted, false if it remains there
340
+	 */
341
+	public function clearCache(Core $core, $olderThan = - 1)
342
+	{
343
+		$cachedFile = $this->getCacheFilename($core);
344
+
345
+		return !file_exists($cachedFile) || (filectime($cachedFile) < (time() - $olderThan) && unlink($cachedFile));
346
+	}
347
+
348
+	/**
349
+	 * Returns the compiled template file name.
350
+	 *
351
+	 * @param Core      $core     the dwoo instance that requests it
352
+	 * @param ICompiler $compiler the compiler that must be used
353
+	 *
354
+	 * @return string
355
+	 */
356
+	public function getCompiledTemplate(Core $core, ICompiler $compiler = null)
357
+	{
358
+		$compiledFile = $this->getCompiledFilename($core);
359
+
360
+		if ($this->compilationEnforced !== true && isset(self::$cache['compiled'][$this->compileId]) === true) {
361
+			// already checked, return compiled file
362
+		} elseif ($this->compilationEnforced !== true && $this->isValidCompiledFile($compiledFile)) {
363
+			// template is compiled
364
+			self::$cache['compiled'][$this->compileId] = true;
365
+		} else {
366
+			// compiles the template
367
+			$this->compilationEnforced = false;
368
+
369
+			if ($compiler === null) {
370
+				$compiler = $core->getDefaultCompilerFactory($this->getResourceName());
371
+
372
+				if ($compiler === null || $compiler === array('Dwoo\Compiler', 'compilerFactory')) {
373
+					$compiler = Compiler::compilerFactory();
374
+				} else {
375
+					$compiler = call_user_func($compiler);
376
+				}
377
+			}
378
+
379
+			$this->compiler = $compiler;
380
+
381
+			$compiler->setCustomPlugins($core->getCustomPlugins());
382
+			$compiler->setSecurityPolicy($core->getSecurityPolicy());
383
+			$this->makeDirectory(dirname($compiledFile), $core->getCompileDir());
384
+			file_put_contents($compiledFile, $compiler->compile($core, $this));
385
+			if ($this->chmod !== null) {
386
+				chmod($compiledFile, $this->chmod);
387
+			}
388
+
389
+			if (extension_loaded('Zend OPcache')) {
390
+				opcache_invalidate($compiledFile);
391
+			} elseif (extension_loaded('apc') && ini_get('apc.enabled')) {
392
+				apc_delete_file($compiledFile);
393
+			}
394
+
395
+			self::$cache['compiled'][$this->compileId] = true;
396
+		}
397
+
398
+		return $compiledFile;
399
+	}
400
+
401
+	/**
402
+	 * Checks if compiled file is valid (it exists).
403
+	 *
404
+	 * @param string $file
405
+	 *
406
+	 * @return bool True cache file existence
407
+	 */
408
+	protected function isValidCompiledFile($file)
409
+	{
410
+		return file_exists($file);
411
+	}
412
+
413
+	/**
414
+	 * Returns a new template string object with the resource id being the template source code.
415
+	 *
416
+	 * @param Core      $core           the dwoo instance requiring it
417
+	 * @param mixed     $resourceId     the filename (relative to this template's dir) of the template to include
418
+	 * @param int       $cacheTime      duration of the cache validity for this template, if null it defaults to the
419
+	 *                                  Dwoo instance that will render this template if null it defaults to the Dwoo
420
+	 *                                  instance that will render this template
421
+	 * @param string    $cacheId        the unique cache identifier of this page or anything else that makes this
422
+	 *                                  template's content unique, if null it defaults to the current url makes this
423
+	 *                                  template's content unique, if null it defaults to the current url
424
+	 * @param string    $compileId      the unique compiled identifier, which is used to distinguish this template from
425
+	 *                                  others, if null it defaults to the filename+bits of the path template from
426
+	 *                                  others, if null it defaults to the filename+bits of the path
427
+	 * @param ITemplate $parentTemplate the template that is requesting a new template object (through an include,
428
+	 *                                  extends or any other plugin) an include, extends or any other plugin)
429
+	 *
430
+	 * @return $this
431
+	 */
432
+	public static function templateFactory(Core $core, $resourceId, $cacheTime = null, $cacheId = null, $compileId = null, ITemplate $parentTemplate = null)
433
+	{
434
+		return new self($resourceId, $cacheTime, $cacheId, $compileId);
435
+	}
436
+
437
+	/**
438
+	 * Returns the full compiled file name and assigns a default value to it if
439
+	 * required.
440
+	 *
441
+	 * @param Core $core the dwoo instance that requests the file name
442
+	 *
443
+	 * @return string the full path to the compiled file
444
+	 */
445
+	protected function getCompiledFilename(Core $core)
446
+	{
447
+		// no compile id was provided, set default
448
+		if ($this->compileId === null) {
449
+			$this->compileId = $this->name;
450
+		}
451
+
452
+		return $core->getCompileDir() . $this->compileId . '.d' . Core::RELEASE_TAG . '.php';
453
+	}
454
+
455
+	/**
456
+	 * Returns the full cached file name and assigns a default value to it if
457
+	 * required.
458
+	 *
459
+	 * @param Core $core the dwoo instance that requests the file name
460
+	 *
461
+	 * @return string the full path to the cached file
462
+	 */
463
+	protected function getCacheFilename(Core $core)
464
+	{
465
+		// no cache id provided, use request_uri as default
466
+		if ($this->cacheId === null) {
467
+			if (isset($_SERVER['REQUEST_URI']) === true) {
468
+				$cacheId = $_SERVER['REQUEST_URI'];
469
+			} elseif (isset($_SERVER['SCRIPT_FILENAME']) && isset($_SERVER['argv'])) {
470
+				$cacheId = $_SERVER['SCRIPT_FILENAME'] . '-' . implode('-', $_SERVER['argv']);
471
+			} else {
472
+				$cacheId = '';
473
+			}
474
+			// force compiled id generation
475
+			$this->getCompiledFilename($core);
476
+
477
+			$this->cacheId = str_replace('../', '__', $this->compileId . strtr($cacheId, '\\%?=!:;' . PATH_SEPARATOR, '/-------'));
478
+		}
479
+
480
+		return $core->getCacheDir() . $this->cacheId . '.html';
481
+	}
482
+
483
+	/**
484
+	 * Returns some php code that will check if this template has been modified or not.
485
+	 * if the function returns null, the template will be instanciated and then the Uid checked
486
+	 *
487
+	 * @return string
488
+	 */
489
+	public function getIsModifiedCode()
490
+	{
491
+		return null;
492
+	}
493
+
494
+	/**
495
+	 * Ensures the given path exists.
496
+	 *
497
+	 * @param string $path    any path
498
+	 * @param string $baseDir the base directory where the directory is created
499
+	 *                        ($path must still contain the full path, $baseDir
500
+	 *                        is only used for unix permissions)
501
+	 *
502
+	 * @throws Exception
503
+	 */
504
+	protected function makeDirectory($path, $baseDir = null)
505
+	{
506
+		if (is_dir($path) === true) {
507
+			return;
508
+		}
509
+
510
+		if ($this->chmod === null) {
511
+			$chmod = 0777;
512
+		} else {
513
+			$chmod = $this->chmod;
514
+		}
515
+
516
+		$retries = 3;
517
+		while ($retries --) {
518
+			@mkdir($path, $chmod, true);
519
+			if (is_dir($path)) {
520
+				break;
521
+			}
522
+			usleep(20);
523
+		}
524
+
525
+		// enforce the correct mode for all directories created
526
+		if (strpos(PHP_OS, 'WIN') !== 0 && $baseDir !== null) {
527
+			$path    = strtr(str_replace($baseDir, '', $path), '\\', '/');
528
+			$folders = explode('/', trim($path, '/'));
529
+			foreach ($folders as $folder) {
530
+				$baseDir .= $folder . DIRECTORY_SEPARATOR;
531
+				if (!chmod($baseDir, $chmod)) {
532
+					throw new Exception('Unable to chmod ' . "$baseDir to $chmod: " . print_r(error_get_last(), true));
533
+				}
534
+			}
535
+		}
536
+	}
537 537
 }
Please login to merge, or discard this patch.
lib/Dwoo/Core.php 2 patches
Indentation   +1738 added lines, -1738 removed lines patch added patch discarded remove patch
@@ -44,1750 +44,1750 @@
 block discarded – undo
44 44
  */
45 45
 class Core
46 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) {
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 
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) {
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 334
             a valid path to a template file', E_USER_NOTICE
335
-            );
336
-        }
337
-
338
-        // save the current template, enters render mode at the same time
339
-        // if another rendering is requested it will be proxied to a new Core(instance
340
-        $this->template = $_tpl;
341
-
342
-        // load data
343
-        if ($data instanceof IDataProvider) {
344
-            $this->data = $data->getData();
345
-        } elseif (is_array($data)) {
346
-            $this->data = $data;
347
-        } elseif ($data instanceof ArrayAccess) {
348
-            $this->data = $data;
349
-        } else {
350
-            throw new Exception(
351
-                'Dwoo->get/Dwoo->output\'s data argument must be a IDataProvider object (i.e. Data) or
335
+			);
336
+		}
337
+
338
+		// save the current template, enters render mode at the same time
339
+		// if another rendering is requested it will be proxied to a new Core(instance
340
+		$this->template = $_tpl;
341
+
342
+		// load data
343
+		if ($data instanceof IDataProvider) {
344
+			$this->data = $data->getData();
345
+		} elseif (is_array($data)) {
346
+			$this->data = $data;
347
+		} elseif ($data instanceof ArrayAccess) {
348
+			$this->data = $data;
349
+		} else {
350
+			throw new Exception(
351
+				'Dwoo->get/Dwoo->output\'s data argument must be a IDataProvider object (i.e. Data) or
352 352
             an associative array', E_USER_NOTICE
353
-            );
354
-        }
355
-
356
-        $this->addGlobal('template', $_tpl->getName());
357
-        $this->initRuntimeVars($_tpl);
358
-
359
-        // try to get cached template
360
-        $file        = $_tpl->getCachedTemplate($this);
361
-        $doCache     = $file === true;
362
-        $cacheLoaded = is_string($file);
363
-
364
-        if ($cacheLoaded === true) {
365
-            // cache is present, run it
366
-            ob_start();
367
-            include $file;
368
-            $this->template = null;
369
-
370
-            return ob_get_clean();
371
-        } else {
372
-            $dynamicId = uniqid();
373
-
374
-            // render template
375
-            $compiledTemplate = $_tpl->getCompiledTemplate($this, $_compiler);
376
-            $out              = include $compiledTemplate;
377
-
378
-            // template returned false so it needs to be recompiled
379
-            if ($out === false) {
380
-                $_tpl->forceCompilation();
381
-                $compiledTemplate = $_tpl->getCompiledTemplate($this, $_compiler);
382
-                $out              = include $compiledTemplate;
383
-            }
384
-
385
-            if ($doCache === true) {
386
-                $out = preg_replace('/(<%|%>|<\?php|<\?|\?>)/', '<?php /*' . $dynamicId . '*/ echo \'$1\'; ?>', $out);
387
-                if (!class_exists(self::NAMESPACE_PLUGINS_BLOCKS . 'PluginDynamic')) {
388
-                    $this->getLoader()->loadPlugin('PluginDynamic');
389
-                }
390
-                $out = PluginDynamic::unescape($out, $dynamicId, $compiledTemplate);
391
-            }
392
-
393
-            // process filters
394
-            foreach ($this->filters as $filter) {
395
-                if (is_array($filter) && $filter[0] instanceof Filter) {
396
-                    $out = call_user_func($filter, $out);
397
-                } else {
398
-                    $out = call_user_func($filter, $this, $out);
399
-                }
400
-            }
401
-
402
-            if ($doCache === true) {
403
-                // building cache
404
-                $file = $_tpl->cache($this, $out);
405
-
406
-                // run it from the cache to be sure dynamics are rendered
407
-                ob_start();
408
-                include $file;
409
-                // exit render mode
410
-                $this->template = null;
411
-
412
-                return ob_get_clean();
413
-            } else {
414
-                // no need to build cache
415
-                // exit render mode
416
-                $this->template = null;
417
-
418
-                return $out;
419
-            }
420
-        }
421
-    }
422
-
423
-    /**
424
-     * Registers a Global.
425
-     * New globals can be added before compiling or rendering a template
426
-     * but after, you can only update existing globals.
427
-     *
428
-     * @param string $name
429
-     * @param mixed  $value
430
-     *
431
-     * @return $this
432
-     * @throws Exception
433
-     */
434
-    public function addGlobal($name, $value)
435
-    {
436
-        if (null === $this->globals) {
437
-            $this->initGlobals();
438
-        }
439
-
440
-        $this->globals[$name] = $value;
441
-
442
-        return $this;
443
-    }
444
-
445
-    /**
446
-     * Gets the registered Globals.
447
-     *
448
-     * @return array
449
-     */
450
-    public function getGlobals()
451
-    {
452
-        return $this->globals;
453
-    }
454
-
455
-    /**
456
-     * Re-initializes the globals array before each template run.
457
-     * this method is only callede once when the Dwoo object is created
458
-     *
459
-     * @return void
460
-     */
461
-    protected function initGlobals()
462
-    {
463
-        $this->globals = array(
464
-            'version' => self::VERSION,
465
-            'ad'      => '<a href="http://dwoo.org/">Powered by Dwoo</a>',
466
-            'now'     => $_SERVER['REQUEST_TIME'],
467
-            'charset' => $this->getCharset(),
468
-        );
469
-    }
470
-
471
-    /**
472
-     * Re-initializes the runtime variables before each template run.
473
-     * override this method to inject data in the globals array if needed, this
474
-     * method is called before each template execution
475
-     *
476
-     * @param ITemplate $tpl the template that is going to be rendered
477
-     *
478
-     * @return void
479
-     */
480
-    protected function initRuntimeVars(ITemplate $tpl)
481
-    {
482
-        $this->runtimePlugins = array();
483
-        $this->scope          = &$this->data;
484
-        $this->scopeTree      = array();
485
-        $this->stack          = array();
486
-        $this->curBlock       = null;
487
-        $this->buffer         = '';
488
-        $this->returnData     = array();
489
-    }
490
-
491
-    /**
492
-     * Adds a custom plugin that is not in one of the plugin directories.
493
-     *
494
-     * @param string   $name       the plugin name to be used in the templates
495
-     * @param callback $callback   the plugin callback, either a function name,
496
-     *                             a class name or an array containing an object
497
-     *                             or class name and a method name
498
-     * @param bool     $compilable if set to true, the plugin is assumed to be compilable
499
-     *
500
-     * @return void
501
-     * @throws Exception
502
-     */
503
-    public function addPlugin($name, $callback, $compilable = false)
504
-    {
505
-        $compilable = $compilable ? self::COMPILABLE_PLUGIN : 0;
506
-        if (is_array($callback)) {
507
-            if (is_subclass_of(is_object($callback[0]) ? get_class($callback[0]) : $callback[0], 'Dwoo\Block\Plugin')) {
508
-                $this->plugins[$name] = array(
509
-                    'type'     => self::BLOCK_PLUGIN | $compilable,
510
-                    'callback' => $callback,
511
-                    'class'    => (is_object($callback[0]) ? get_class($callback[0]) : $callback[0])
512
-                );
513
-            } else {
514
-                $this->plugins[$name] = array(
515
-                    'type'     => self::CLASS_PLUGIN | $compilable,
516
-                    'callback' => $callback,
517
-                    'class'    => (is_object($callback[0]) ? get_class($callback[0]) : $callback[0]),
518
-                    'function' => $callback[1]
519
-                );
520
-            }
521
-        } elseif (is_string($callback)) {
522
-            if (class_exists($callback)) {
523
-                if (is_subclass_of($callback, 'Dwoo\Block\Plugin')) {
524
-                    $this->plugins[$name] = array(
525
-                        'type'     => self::BLOCK_PLUGIN | $compilable,
526
-                        'callback' => $callback,
527
-                        'class'    => $callback
528
-                    );
529
-                } else {
530
-                    $this->plugins[$name] = array(
531
-                        'type'     => self::CLASS_PLUGIN | $compilable,
532
-                        'callback' => $callback,
533
-                        'class'    => $callback,
534
-                        'function' => ($compilable ? 'compile' : 'process')
535
-                    );
536
-                }
537
-            } elseif (function_exists($callback)) {
538
-                $this->plugins[$name] = array(
539
-                    'type'     => self::FUNC_PLUGIN | $compilable,
540
-                    'callback' => $callback
541
-                );
542
-            } else {
543
-                throw new Exception(
544
-                    'Callback could not be processed correctly, please check that the function/class 
353
+			);
354
+		}
355
+
356
+		$this->addGlobal('template', $_tpl->getName());
357
+		$this->initRuntimeVars($_tpl);
358
+
359
+		// try to get cached template
360
+		$file        = $_tpl->getCachedTemplate($this);
361
+		$doCache     = $file === true;
362
+		$cacheLoaded = is_string($file);
363
+
364
+		if ($cacheLoaded === true) {
365
+			// cache is present, run it
366
+			ob_start();
367
+			include $file;
368
+			$this->template = null;
369
+
370
+			return ob_get_clean();
371
+		} else {
372
+			$dynamicId = uniqid();
373
+
374
+			// render template
375
+			$compiledTemplate = $_tpl->getCompiledTemplate($this, $_compiler);
376
+			$out              = include $compiledTemplate;
377
+
378
+			// template returned false so it needs to be recompiled
379
+			if ($out === false) {
380
+				$_tpl->forceCompilation();
381
+				$compiledTemplate = $_tpl->getCompiledTemplate($this, $_compiler);
382
+				$out              = include $compiledTemplate;
383
+			}
384
+
385
+			if ($doCache === true) {
386
+				$out = preg_replace('/(<%|%>|<\?php|<\?|\?>)/', '<?php /*' . $dynamicId . '*/ echo \'$1\'; ?>', $out);
387
+				if (!class_exists(self::NAMESPACE_PLUGINS_BLOCKS . 'PluginDynamic')) {
388
+					$this->getLoader()->loadPlugin('PluginDynamic');
389
+				}
390
+				$out = PluginDynamic::unescape($out, $dynamicId, $compiledTemplate);
391
+			}
392
+
393
+			// process filters
394
+			foreach ($this->filters as $filter) {
395
+				if (is_array($filter) && $filter[0] instanceof Filter) {
396
+					$out = call_user_func($filter, $out);
397
+				} else {
398
+					$out = call_user_func($filter, $this, $out);
399
+				}
400
+			}
401
+
402
+			if ($doCache === true) {
403
+				// building cache
404
+				$file = $_tpl->cache($this, $out);
405
+
406
+				// run it from the cache to be sure dynamics are rendered
407
+				ob_start();
408
+				include $file;
409
+				// exit render mode
410
+				$this->template = null;
411
+
412
+				return ob_get_clean();
413
+			} else {
414
+				// no need to build cache
415
+				// exit render mode
416
+				$this->template = null;
417
+
418
+				return $out;
419
+			}
420
+		}
421
+	}
422
+
423
+	/**
424
+	 * Registers a Global.
425
+	 * New globals can be added before compiling or rendering a template
426
+	 * but after, you can only update existing globals.
427
+	 *
428
+	 * @param string $name
429
+	 * @param mixed  $value
430
+	 *
431
+	 * @return $this
432
+	 * @throws Exception
433
+	 */
434
+	public function addGlobal($name, $value)
435
+	{
436
+		if (null === $this->globals) {
437
+			$this->initGlobals();
438
+		}
439
+
440
+		$this->globals[$name] = $value;
441
+
442
+		return $this;
443
+	}
444
+
445
+	/**
446
+	 * Gets the registered Globals.
447
+	 *
448
+	 * @return array
449
+	 */
450
+	public function getGlobals()
451
+	{
452
+		return $this->globals;
453
+	}
454
+
455
+	/**
456
+	 * Re-initializes the globals array before each template run.
457
+	 * this method is only callede once when the Dwoo object is created
458
+	 *
459
+	 * @return void
460
+	 */
461
+	protected function initGlobals()
462
+	{
463
+		$this->globals = array(
464
+			'version' => self::VERSION,
465
+			'ad'      => '<a href="http://dwoo.org/">Powered by Dwoo</a>',
466
+			'now'     => $_SERVER['REQUEST_TIME'],
467
+			'charset' => $this->getCharset(),
468
+		);
469
+	}
470
+
471
+	/**
472
+	 * Re-initializes the runtime variables before each template run.
473
+	 * override this method to inject data in the globals array if needed, this
474
+	 * method is called before each template execution
475
+	 *
476
+	 * @param ITemplate $tpl the template that is going to be rendered
477
+	 *
478
+	 * @return void
479
+	 */
480
+	protected function initRuntimeVars(ITemplate $tpl)
481
+	{
482
+		$this->runtimePlugins = array();
483
+		$this->scope          = &$this->data;
484
+		$this->scopeTree      = array();
485
+		$this->stack          = array();
486
+		$this->curBlock       = null;
487
+		$this->buffer         = '';
488
+		$this->returnData     = array();
489
+	}
490
+
491
+	/**
492
+	 * Adds a custom plugin that is not in one of the plugin directories.
493
+	 *
494
+	 * @param string   $name       the plugin name to be used in the templates
495
+	 * @param callback $callback   the plugin callback, either a function name,
496
+	 *                             a class name or an array containing an object
497
+	 *                             or class name and a method name
498
+	 * @param bool     $compilable if set to true, the plugin is assumed to be compilable
499
+	 *
500
+	 * @return void
501
+	 * @throws Exception
502
+	 */
503
+	public function addPlugin($name, $callback, $compilable = false)
504
+	{
505
+		$compilable = $compilable ? self::COMPILABLE_PLUGIN : 0;
506
+		if (is_array($callback)) {
507
+			if (is_subclass_of(is_object($callback[0]) ? get_class($callback[0]) : $callback[0], 'Dwoo\Block\Plugin')) {
508
+				$this->plugins[$name] = array(
509
+					'type'     => self::BLOCK_PLUGIN | $compilable,
510
+					'callback' => $callback,
511
+					'class'    => (is_object($callback[0]) ? get_class($callback[0]) : $callback[0])
512
+				);
513
+			} else {
514
+				$this->plugins[$name] = array(
515
+					'type'     => self::CLASS_PLUGIN | $compilable,
516
+					'callback' => $callback,
517
+					'class'    => (is_object($callback[0]) ? get_class($callback[0]) : $callback[0]),
518
+					'function' => $callback[1]
519
+				);
520
+			}
521
+		} elseif (is_string($callback)) {
522
+			if (class_exists($callback)) {
523
+				if (is_subclass_of($callback, 'Dwoo\Block\Plugin')) {
524
+					$this->plugins[$name] = array(
525
+						'type'     => self::BLOCK_PLUGIN | $compilable,
526
+						'callback' => $callback,
527
+						'class'    => $callback
528
+					);
529
+				} else {
530
+					$this->plugins[$name] = array(
531
+						'type'     => self::CLASS_PLUGIN | $compilable,
532
+						'callback' => $callback,
533
+						'class'    => $callback,
534
+						'function' => ($compilable ? 'compile' : 'process')
535
+					);
536
+				}
537
+			} elseif (function_exists($callback)) {
538
+				$this->plugins[$name] = array(
539
+					'type'     => self::FUNC_PLUGIN | $compilable,
540
+					'callback' => $callback
541
+				);
542
+			} else {
543
+				throw new Exception(
544
+					'Callback could not be processed correctly, please check that the function/class 
545 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 
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 556
             used exists'
557
-            );
558
-        }
559
-    }
560
-
561
-    /**
562
-     * Removes a custom plugin.
563
-     *
564
-     * @param string $name the plugin name
565
-     *
566
-     * @return void
567
-     */
568
-    public function removePlugin($name)
569
-    {
570
-        if (isset($this->plugins[$name])) {
571
-            unset($this->plugins[$name]);
572
-        }
573
-    }
574
-
575
-    /**
576
-     * Adds a filter to this Dwoo instance, it will be used to filter the output of all the templates rendered by this
577
-     * instance.
578
-     *
579
-     * @param mixed $callback a callback or a filter name if it is autoloaded from a plugin directory
580
-     * @param bool  $autoload if true, the first parameter must be a filter name from one of the plugin directories
581
-     *
582
-     * @return void
583
-     * @throws Exception
584
-     */
585
-    public function addFilter($callback, $autoload = false)
586
-    {
587
-        if ($autoload) {
588
-            $class = self::NAMESPACE_PLUGINS_FILTERS . self::toCamelCase($callback);
589
-            if (!class_exists($class) && !function_exists($class)) {
590
-                try {
591
-                    $this->getLoader()->loadPlugin($callback);
592
-                }
593
-                catch (Exception $e) {
594
-                    if (strstr($callback, self::NAMESPACE_PLUGINS_FILTERS)) {
595
-                        throw new Exception(
596
-                            'Wrong filter name : ' . $callback . ', the "Filter" prefix should 
557
+			);
558
+		}
559
+	}
560
+
561
+	/**
562
+	 * Removes a custom plugin.
563
+	 *
564
+	 * @param string $name the plugin name
565
+	 *
566
+	 * @return void
567
+	 */
568
+	public function removePlugin($name)
569
+	{
570
+		if (isset($this->plugins[$name])) {
571
+			unset($this->plugins[$name]);
572
+		}
573
+	}
574
+
575
+	/**
576
+	 * Adds a filter to this Dwoo instance, it will be used to filter the output of all the templates rendered by this
577
+	 * instance.
578
+	 *
579
+	 * @param mixed $callback a callback or a filter name if it is autoloaded from a plugin directory
580
+	 * @param bool  $autoload if true, the first parameter must be a filter name from one of the plugin directories
581
+	 *
582
+	 * @return void
583
+	 * @throws Exception
584
+	 */
585
+	public function addFilter($callback, $autoload = false)
586
+	{
587
+		if ($autoload) {
588
+			$class = self::NAMESPACE_PLUGINS_FILTERS . self::toCamelCase($callback);
589
+			if (!class_exists($class) && !function_exists($class)) {
590
+				try {
591
+					$this->getLoader()->loadPlugin($callback);
592
+				}
593
+				catch (Exception $e) {
594
+					if (strstr($callback, self::NAMESPACE_PLUGINS_FILTERS)) {
595
+						throw new Exception(
596
+							'Wrong filter name : ' . $callback . ', the "Filter" prefix should 
597 597
                         not be used, please only use "' . str_replace('Filter', '', $callback) . '"'
598
-                        );
599
-                    } else {
600
-                        throw new Exception(
601
-                            'Wrong filter name : ' . $callback . ', when using autoload the filter must
598
+						);
599
+					} else {
600
+						throw new Exception(
601
+							'Wrong filter name : ' . $callback . ', when using autoload the filter must
602 602
                          be in one of your plugin dir as "name.php" containig a class or function named
603 603
                          "Filter<name>"'
604
-                        );
605
-                    }
606
-                }
607
-            }
608
-
609
-            if (class_exists($class)) {
610
-                $callback = array(new $class($this), 'process');
611
-            } elseif (function_exists($class)) {
612
-                $callback = $class;
613
-            } else {
614
-                throw new Exception(
615
-                    'Wrong filter name : ' . $callback . ', when using autoload the filter must be in
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 616
                 one of your plugin dir as "name.php" containig a class or function named "Filter<name>"'
617
-                );
618
-            }
619
-
620
-            $this->filters[] = $callback;
621
-        } else {
622
-            $this->filters[] = $callback;
623
-        }
624
-    }
625
-
626
-    /**
627
-     * Removes a filter.
628
-     *
629
-     * @param mixed $callback callback or filter name if it was autoloaded
630
-     *
631
-     * @return void
632
-     */
633
-    public function removeFilter($callback)
634
-    {
635
-        if (($index = array_search(self::NAMESPACE_PLUGINS_FILTERS. 'Filter' . self::toCamelCase($callback), $this->filters,
636
-                true)) !==
637
-            false) {
638
-            unset($this->filters[$index]);
639
-        } elseif (($index = array_search($callback, $this->filters, true)) !== false) {
640
-            unset($this->filters[$index]);
641
-        } else {
642
-            $class = self::NAMESPACE_PLUGINS_FILTERS . 'Filter' . $callback;
643
-            foreach ($this->filters as $index => $filter) {
644
-                if (is_array($filter) && $filter[0] instanceof $class) {
645
-                    unset($this->filters[$index]);
646
-                    break;
647
-                }
648
-            }
649
-        }
650
-    }
651
-
652
-    /**
653
-     * Adds a resource or overrides a default one.
654
-     *
655
-     * @param string   $name            the resource name
656
-     * @param string   $class           the resource class (which must implement ITemplate)
657
-     * @param callback $compilerFactory the compiler factory callback, a function that must return a compiler instance
658
-     *                                  used to compile this resource, if none is provided. by default it will produce
659
-     *                                  a Compiler object
660
-     *
661
-     * @return void
662
-     * @throws Exception
663
-     */
664
-    public function addResource($name, $class, $compilerFactory = null)
665
-    {
666
-        if (strlen($name) < 2) {
667
-            throw new Exception('Resource names must be at least two-character long to avoid conflicts with Windows paths');
668
-        }
669
-
670
-        if (!class_exists($class)) {
671
-            throw new Exception(sprintf('Resource class %s does not exist', $class));
672
-        }
673
-
674
-        $interfaces = class_implements($class);
675
-        if (in_array('Dwoo\ITemplate', $interfaces) === false) {
676
-            throw new Exception('Resource class must implement ITemplate');
677
-        }
678
-
679
-        $this->resources[$name] = array(
680
-            'class'    => $class,
681
-            'compiler' => $compilerFactory
682
-        );
683
-    }
684
-
685
-    /**
686
-     * Removes a custom resource.
687
-     *
688
-     * @param string $name the resource name
689
-     *
690
-     * @return void
691
-     */
692
-    public function removeResource($name)
693
-    {
694
-        unset($this->resources[$name]);
695
-        if ($name === 'file') {
696
-            $this->resources['file'] = array(
697
-                'class'    => 'Dwoo\Template\File',
698
-                'compiler' => null
699
-            );
700
-        }
701
-    }
702
-
703
-    /**
704
-     * Sets the loader object to use to load plugins.
705
-     *
706
-     * @param ILoader $loader loader
707
-     *
708
-     * @return void
709
-     */
710
-    public function setLoader(ILoader $loader)
711
-    {
712
-        $this->loader = $loader;
713
-    }
714
-
715
-    /**
716
-     * Returns the current loader object or a default one if none is currently found.
717
-     *
718
-     * @return ILoader|Loader
719
-     */
720
-    public function getLoader()
721
-    {
722
-        if ($this->loader === null) {
723
-            $this->loader = new Loader($this->getCompileDir());
724
-        }
725
-
726
-        return $this->loader;
727
-    }
728
-
729
-    /**
730
-     * Returns the custom plugins loaded.
731
-     * Used by the ITemplate classes to pass the custom plugins to their ICompiler instance.
732
-     *
733
-     * @return array
734
-     */
735
-    public function getCustomPlugins()
736
-    {
737
-        return $this->plugins;
738
-    }
739
-
740
-    /**
741
-     * Return a specified custom plugin loaded by his name.
742
-     * Used by the compiler, for executing a Closure.
743
-     *
744
-     * @param string $name
745
-     *
746
-     * @return mixed|null
747
-     */
748
-    public function getCustomPlugin($name)
749
-    {
750
-        if (isset($this->plugins[$name])) {
751
-            return $this->plugins[$name]['callback'];
752
-        }
753
-
754
-        return null;
755
-    }
756
-
757
-    /**
758
-     * Returns the cache directory with a trailing DIRECTORY_SEPARATOR.
759
-     *
760
-     * @return string
761
-     */
762
-    public function getCacheDir()
763
-    {
764
-        if ($this->cacheDir === null) {
765
-            $this->setCacheDir(dirname(__DIR__) . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR);
766
-        }
767
-
768
-        return $this->cacheDir;
769
-    }
770
-
771
-    /**
772
-     * Sets the cache directory and automatically appends a DIRECTORY_SEPARATOR.
773
-     *
774
-     * @param string $dir the cache directory
775
-     *
776
-     * @return void
777
-     * @throws Exception
778
-     */
779
-    public function setCacheDir($dir)
780
-    {
781
-        $this->cacheDir = rtrim($dir, '/\\') . DIRECTORY_SEPARATOR;
782
-        if (is_writable($this->cacheDir) === false) {
783
-            throw new Exception('The cache directory must be writable, chmod "' . $this->cacheDir . '" to make it writable');
784
-        }
785
-    }
786
-
787
-    /**
788
-     * Returns the compile directory with a trailing DIRECTORY_SEPARATOR.
789
-     *
790
-     * @return string
791
-     */
792
-    public function getCompileDir()
793
-    {
794
-        if ($this->compileDir === null) {
795
-            $this->setCompileDir(dirname(__DIR__) . DIRECTORY_SEPARATOR . 'compiled' . DIRECTORY_SEPARATOR);
796
-        }
797
-
798
-        return $this->compileDir;
799
-    }
800
-
801
-    /**
802
-     * Sets the compile directory and automatically appends a DIRECTORY_SEPARATOR.
803
-     *
804
-     * @param string $dir the compile directory
805
-     *
806
-     * @return void
807
-     * @throws Exception
808
-     */
809
-    public function setCompileDir($dir)
810
-    {
811
-        $this->compileDir = rtrim($dir, '/\\') . DIRECTORY_SEPARATOR;
812
-        if (is_writable($this->compileDir) === false) {
813
-            throw new Exception('The compile directory must be writable, chmod "' . $this->compileDir . '" to make it writable');
814
-        }
815
-    }
816
-
817
-    /**
818
-     * Returns an array of the template directory with a trailing DIRECTORY_SEPARATOR
819
-     *
820
-     * @return array
821
-     */
822
-    public function getTemplateDir()
823
-    {
824
-        return $this->templateDir;
825
-    }
826
-
827
-    /**
828
-     * sets the template directory and automatically appends a DIRECTORY_SEPARATOR
829
-     * template directory is stored in an array
830
-     *
831
-     * @param string $dir
832
-     *
833
-     * @throws Exception
834
-     */
835
-    public function setTemplateDir($dir)
836
-    {
837
-        $tmpDir = rtrim($dir, '/\\') . DIRECTORY_SEPARATOR;
838
-        if (is_dir($tmpDir) === false) {
839
-            throw new Exception('The template directory: "' . $tmpDir . '" does not exists, create the directory or specify an other location !');
840
-        }
841
-        $this->templateDir[] = $tmpDir;
842
-    }
843
-
844
-    /**
845
-     * Returns the default cache time that is used with templates that do not have a cache time set.
846
-     *
847
-     * @return int the duration in seconds
848
-     */
849
-    public function getCacheTime()
850
-    {
851
-        return $this->cacheTime;
852
-    }
853
-
854
-    /**
855
-     * Sets the default cache time to use with templates that do not have a cache time set.
856
-     *
857
-     * @param int $seconds the duration in seconds
858
-     *
859
-     * @return void
860
-     */
861
-    public function setCacheTime($seconds)
862
-    {
863
-        $this->cacheTime = (int)$seconds;
864
-    }
865
-
866
-    /**
867
-     * Returns the character set used by the string manipulation plugins.
868
-     * the charset is automatically lowercased
869
-     *
870
-     * @return string
871
-     */
872
-    public function getCharset()
873
-    {
874
-        return $this->charset;
875
-    }
876
-
877
-    /**
878
-     * Sets the character set used by the string manipulation plugins.
879
-     * the charset will be automatically lowercased
880
-     *
881
-     * @param string $charset the character set
882
-     *
883
-     * @return void
884
-     */
885
-    public function setCharset($charset)
886
-    {
887
-        $this->charset = strtolower((string)$charset);
888
-    }
889
-
890
-    /**
891
-     * Returns the current template being rendered, when applicable, or null.
892
-     *
893
-     * @return ITemplate|null
894
-     */
895
-    public function getTemplate()
896
-    {
897
-        return $this->template;
898
-    }
899
-
900
-    /**
901
-     * Sets the current template being rendered.
902
-     *
903
-     * @param ITemplate $tpl template object
904
-     *
905
-     * @return void
906
-     */
907
-    public function setTemplate(ITemplate $tpl)
908
-    {
909
-        $this->template = $tpl;
910
-    }
911
-
912
-    /**
913
-     * Sets the default compiler factory function for the given resource name.
914
-     * a compiler factory must return a ICompiler object pre-configured to fit your needs
915
-     *
916
-     * @param string   $resourceName    the resource name (i.e. file, string)
917
-     * @param callback $compilerFactory the compiler factory callback
918
-     *
919
-     * @return void
920
-     */
921
-    public function setDefaultCompilerFactory($resourceName, $compilerFactory)
922
-    {
923
-        $this->resources[$resourceName]['compiler'] = $compilerFactory;
924
-    }
925
-
926
-    /**
927
-     * Returns the default compiler factory function for the given resource name.
928
-     *
929
-     * @param string $resourceName the resource name
930
-     *
931
-     * @return callback the compiler factory callback
932
-     */
933
-    public function getDefaultCompilerFactory($resourceName)
934
-    {
935
-        return $this->resources[$resourceName]['compiler'];
936
-    }
937
-
938
-    /**
939
-     * Sets the security policy object to enforce some php security settings.
940
-     * use this if untrusted persons can modify templates
941
-     *
942
-     * @param SecurityPolicy $policy the security policy object
943
-     *
944
-     * @return void
945
-     */
946
-    public function setSecurityPolicy(SecurityPolicy $policy = null)
947
-    {
948
-        $this->securityPolicy = $policy;
949
-    }
950
-
951
-    /**
952
-     * Returns the current security policy object or null by default.
953
-     *
954
-     * @return SecurityPolicy|null the security policy object if any
955
-     */
956
-    public function getSecurityPolicy()
957
-    {
958
-        return $this->securityPolicy;
959
-    }
960
-
961
-    /**
962
-     * Sets the object that must be used as a plugin proxy when plugin can't be found
963
-     * by dwoo's loader.
964
-     *
965
-     * @param IPluginProxy $pluginProxy the proxy object
966
-     *
967
-     * @return void
968
-     */
969
-    public function setPluginProxy(IPluginProxy $pluginProxy)
970
-    {
971
-        $this->pluginProxy = $pluginProxy;
972
-    }
973
-
974
-    /**
975
-     * Returns the current plugin proxy object or null by default.
976
-     *
977
-     * @return IPluginProxy
978
-     */
979
-    public function getPluginProxy()
980
-    {
981
-        return $this->pluginProxy;
982
-    }
983
-
984
-    /**
985
-     * Checks whether the given template is cached or not.
986
-     *
987
-     * @param ITemplate $tpl the template object
988
-     *
989
-     * @return bool
990
-     */
991
-    public function isCached(ITemplate $tpl)
992
-    {
993
-        return is_string($tpl->getCachedTemplate($this));
994
-    }
995
-
996
-    /**
997
-     * Clear templates inside the compiled directory.
998
-     *
999
-     * @return int
1000
-     */
1001
-    public function clearCompiled()
1002
-    {
1003
-        $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->getCompileDir()), \RecursiveIteratorIterator::SELF_FIRST);
1004
-        $count    = 0;
1005
-        foreach ($iterator as $file) {
1006
-            if ($file->isFile()) {
1007
-                $count += unlink($file->__toString()) ? 1 : 0;
1008
-            }
1009
-        }
1010
-
1011
-        return $count;
1012
-    }
1013
-
1014
-    /**
1015
-     * Clears the cached templates if they are older than the given time.
1016
-     *
1017
-     * @param int $olderThan minimum time (in seconds) required for a cached template to be cleared
1018
-     *
1019
-     * @return int the amount of templates cleared
1020
-     */
1021
-    public function clearCache($olderThan = - 1)
1022
-    {
1023
-        $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->getCacheDir()), \RecursiveIteratorIterator::SELF_FIRST);
1024
-        $expired  = time() - $olderThan;
1025
-        $count    = 0;
1026
-        foreach ($iterator as $file) {
1027
-            if ($file->isFile() && $file->getCTime() < $expired) {
1028
-                $count += unlink((string)$file) ? 1 : 0;
1029
-            }
1030
-        }
1031
-
1032
-        return $count;
1033
-    }
1034
-
1035
-    /**
1036
-     * Fetches a template object of the given resource.
1037
-     *
1038
-     * @param string    $resourceName   the resource name (i.e. file, string)
1039
-     * @param string    $resourceId     the resource identifier (i.e. file path)
1040
-     * @param int       $cacheTime      the cache time setting for this resource
1041
-     * @param string    $cacheId        the unique cache identifier
1042
-     * @param string    $compileId      the unique compiler identifier
1043
-     * @param ITemplate $parentTemplate the parent template
1044
-     *
1045
-     * @return ITemplate
1046
-     * @throws Exception
1047
-     */
1048
-    public function templateFactory($resourceName, $resourceId, $cacheTime = null, $cacheId = null, $compileId = null, ITemplate $parentTemplate = null)
1049
-    {
1050
-        if (isset($this->resources[$resourceName])) {
1051
-            /**
1052
-             * Interface ITemplate
1053
-             *
1054
-             * @var ITemplate $class
1055
-             */
1056
-            $class = $this->resources[$resourceName]['class'];
1057
-
1058
-            return $class::templateFactory($this, $resourceId, $cacheTime, $cacheId, $compileId, $parentTemplate);
1059
-        }
1060
-
1061
-        throw new Exception('Unknown resource type : ' . $resourceName);
1062
-    }
1063
-
1064
-    /**
1065
-     * Checks if the input is an array or arrayaccess object, optionally it can also check if it's
1066
-     * empty.
1067
-     *
1068
-     * @param mixed $value        the variable to check
1069
-     * @param bool  $checkIsEmpty if true, the function will also check if the array|arrayaccess is empty,
1070
-     *                            and return true only if it's not empty
1071
-     *
1072
-     * @return int|bool true if it's an array|arrayaccess (or the item count if $checkIsEmpty is true) or false if it's
1073
-     *                  not an array|arrayaccess (or 0 if $checkIsEmpty is true)
1074
-     */
1075
-    public function isArray($value, $checkIsEmpty = false)
1076
-    {
1077
-        if (is_array($value) === true || $value instanceof ArrayAccess) {
1078
-            if ($checkIsEmpty === false) {
1079
-                return true;
1080
-            }
1081
-
1082
-            return $this->count($value);
1083
-        }
1084
-
1085
-        return false;
1086
-    }
1087
-
1088
-    /**
1089
-     * Checks if the input is an array or a traversable object, optionally it can also check if it's
1090
-     * empty.
1091
-     *
1092
-     * @param mixed $value        the variable to check
1093
-     * @param bool  $checkIsEmpty if true, the function will also check if the array|traversable is empty,
1094
-     *                            and return true only if it's not empty
1095
-     *
1096
-     * @return int|bool true if it's an array|traversable (or the item count if $checkIsEmpty is true) or false if it's
1097
-     *                  not an array|traversable (or 0 if $checkIsEmpty is true)
1098
-     */
1099
-    public function isTraversable($value, $checkIsEmpty = false)
1100
-    {
1101
-        if (is_array($value) === true) {
1102
-            if ($checkIsEmpty === false) {
1103
-                return true;
1104
-            } else {
1105
-                return count($value) > 0;
1106
-            }
1107
-        } elseif ($value instanceof Traversable) {
1108
-            if ($checkIsEmpty === false) {
1109
-                return true;
1110
-            } else {
1111
-                return $this->count($value);
1112
-            }
1113
-        }
1114
-
1115
-        return false;
1116
-    }
1117
-
1118
-    /**
1119
-     * Counts an array or arrayaccess/traversable object.
1120
-     *
1121
-     * @param mixed $value the value to count
1122
-     *
1123
-     * @return int|bool the count for arrays and objects that implement countable, true for other objects that don't,
1124
-     *                  and 0 for empty elements
1125
-     */
1126
-    public function count($value)
1127
-    {
1128
-        if (is_array($value) === true || $value instanceof Countable) {
1129
-            return count($value);
1130
-        } elseif ($value instanceof ArrayAccess) {
1131
-            if ($value->offsetExists(0)) {
1132
-                return true;
1133
-            }
1134
-        } elseif ($value instanceof Iterator) {
1135
-            $value->rewind();
1136
-            if ($value->valid()) {
1137
-                return true;
1138
-            }
1139
-        } elseif ($value instanceof Traversable) {
1140
-            foreach ($value as $dummy) {
1141
-                return true;
1142
-            }
1143
-        }
1144
-
1145
-        return 0;
1146
-    }
1147
-
1148
-    /**
1149
-     * Triggers a dwoo error.
1150
-     *
1151
-     * @param string $message the error message
1152
-     * @param int    $level   the error level, one of the PHP's E_* constants
1153
-     *
1154
-     * @return void
1155
-     */
1156
-    public function triggerError($message, $level = E_USER_NOTICE)
1157
-    {
1158
-        if (!($tplIdentifier = $this->template->getResourceIdentifier())) {
1159
-            $tplIdentifier = $this->template->getResourceName();
1160
-        }
1161
-        trigger_error('Dwoo error (in ' . $tplIdentifier . ') : ' . $message, $level);
1162
-    }
1163
-
1164
-    /**
1165
-     * Adds a block to the block stack.
1166
-     *
1167
-     * @param string $blockName the block name (without `Plugin` prefix)
1168
-     * @param array  $args      the arguments to be passed to the block's init() function
1169
-     *
1170
-     * @return BlockPlugin the newly created block
1171
-     */
1172
-    public function addStack($blockName, array $args = array())
1173
-    {
1174
-        if (isset($this->plugins[$blockName])) {
1175
-            $class = $this->plugins[$blockName]['class'];
1176
-        } else {
1177
-            $class = self::NAMESPACE_PLUGINS_BLOCKS . 'Plugin' . self::toCamelCase($blockName);
1178
-        }
1179
-
1180
-        if ($this->curBlock !== null) {
1181
-            $this->curBlock->buffer(ob_get_contents());
1182
-            ob_clean();
1183
-        } else {
1184
-            $this->buffer .= ob_get_contents();
1185
-            ob_clean();
1186
-        }
1187
-
1188
-        $block = new $class($this);
1189
-
1190
-        call_user_func_array(array($block, 'init'), $args);
1191
-
1192
-        $this->stack[] = $this->curBlock = $block;
1193
-
1194
-        return $block;
1195
-    }
1196
-
1197
-    /**
1198
-     * Removes the plugin at the top of the block stack.
1199
-     * Calls the block buffer() function, followed by a call to end() and finally a call to process()
1200
-     *
1201
-     * @return void
1202
-     */
1203
-    public function delStack()
1204
-    {
1205
-        $args = func_get_args();
1206
-
1207
-        $this->curBlock->buffer(ob_get_contents());
1208
-        ob_clean();
1209
-
1210
-        call_user_func_array(array($this->curBlock, 'end'), $args);
1211
-
1212
-        $tmp = array_pop($this->stack);
1213
-
1214
-        if (count($this->stack) > 0) {
1215
-            $this->curBlock = end($this->stack);
1216
-            $this->curBlock->buffer($tmp->process());
1217
-        } else {
1218
-            if ($this->buffer !== '') {
1219
-                echo $this->buffer;
1220
-                $this->buffer = '';
1221
-            }
1222
-            $this->curBlock = null;
1223
-            echo $tmp->process();
1224
-        }
1225
-
1226
-        unset($tmp);
1227
-    }
1228
-
1229
-    /**
1230
-     * Returns the parent block of the given block.
1231
-     *
1232
-     * @param BlockPlugin $block the block class plugin
1233
-     *
1234
-     * @return BlockPlugin|false if the given block isn't in the stack
1235
-     */
1236
-    public function getParentBlock(BlockPlugin $block)
1237
-    {
1238
-        $index = array_search($block, $this->stack, true);
1239
-        if ($index !== false && $index > 0) {
1240
-            return $this->stack[$index - 1];
1241
-        }
1242
-
1243
-        return false;
1244
-    }
1245
-
1246
-    /**
1247
-     * Finds the closest block of the given type, starting at the top of the stack.
1248
-     *
1249
-     * @param string $type the type of plugin you want to find
1250
-     *
1251
-     * @return BlockPlugin|false if no plugin of such type is in the stack
1252
-     */
1253
-    public function findBlock($type)
1254
-    {
1255
-        if (isset($this->plugins[$type])) {
1256
-            $type = $this->plugins[$type]['class'];
1257
-        } else {
1258
-            $type = self::NAMESPACE_PLUGINS_BLOCKS . 'Plugin_' . str_replace(self::NAMESPACE_PLUGINS_BLOCKS.'Plugin',
1259
-                    '', $type);
1260
-        }
1261
-
1262
-        $keys = array_keys($this->stack);
1263
-        while (($key = array_pop($keys)) !== false) {
1264
-            if ($this->stack[$key] instanceof $type) {
1265
-                return $this->stack[$key];
1266
-            }
1267
-        }
1268
-
1269
-        return false;
1270
-    }
1271
-
1272
-    /**
1273
-     * Returns a Plugin of the given class.
1274
-     * this is so a single instance of every class plugin is created at each template run,
1275
-     * allowing class plugins to have "per-template-run" static variables
1276
-     *
1277
-     * @param string $class the class name
1278
-     *
1279
-     * @return mixed an object of the given class
1280
-     */
1281
-    public function getObjectPlugin($class)
1282
-    {
1283
-        if (isset($this->runtimePlugins[$class])) {
1284
-            return $this->runtimePlugins[$class];
1285
-        }
1286
-
1287
-        return $this->runtimePlugins[$class] = new $class($this);
1288
-    }
1289
-
1290
-    /**
1291
-     * Calls the process() method of the given class-plugin name.
1292
-     *
1293
-     * @param string $plugName the class plugin name (without `Plugin` prefix)
1294
-     * @param array  $params   an array of parameters to send to the process() method
1295
-     *
1296
-     * @return string the process() return value
1297
-     */
1298
-    public function classCall($plugName, array $params = array())
1299
-    {
1300
-        $class  = self::toCamelCase($plugName);
1301
-        $plugin = $this->getObjectPlugin($class);
1302
-
1303
-        return call_user_func_array(array($plugin, 'process'), $params);
1304
-    }
1305
-
1306
-    /**
1307
-     * Calls a php function.
1308
-     *
1309
-     * @param string $callback the function to call
1310
-     * @param array  $params   an array of parameters to send to the function
1311
-     *
1312
-     * @return mixed the return value of the called function
1313
-     */
1314
-    public function arrayMap($callback, array $params)
1315
-    {
1316
-        if ($params[0] === $this) {
1317
-            $addThis = true;
1318
-            array_shift($params);
1319
-        }
1320
-        if ((is_array($params[0]) || ($params[0] instanceof Iterator && $params[0] instanceof ArrayAccess))) {
1321
-            if (empty($params[0])) {
1322
-                return $params[0];
1323
-            }
1324
-
1325
-            // array map
1326
-            $out = array();
1327
-            $cnt = count($params);
1328
-
1329
-            if (isset($addThis)) {
1330
-                array_unshift($params, $this);
1331
-                $items = $params[1];
1332
-                $keys  = array_keys($items);
1333
-
1334
-                if (is_string($callback) === false) {
1335
-                    while (($i = array_shift($keys)) !== null) {
1336
-                        $out[] = call_user_func_array($callback, array(1 => $items[$i]) + $params);
1337
-                    }
1338
-                } elseif ($cnt === 1) {
1339
-                    while (($i = array_shift($keys)) !== null) {
1340
-                        $out[] = $callback($this, $items[$i]);
1341
-                    }
1342
-                } elseif ($cnt === 2) {
1343
-                    while (($i = array_shift($keys)) !== null) {
1344
-                        $out[] = $callback($this, $items[$i], $params[2]);
1345
-                    }
1346
-                } elseif ($cnt === 3) {
1347
-                    while (($i = array_shift($keys)) !== null) {
1348
-                        $out[] = $callback($this, $items[$i], $params[2], $params[3]);
1349
-                    }
1350
-                } else {
1351
-                    while (($i = array_shift($keys)) !== null) {
1352
-                        $out[] = call_user_func_array($callback, array(1 => $items[$i]) + $params);
1353
-                    }
1354
-                }
1355
-            } else {
1356
-                $items = $params[0];
1357
-                $keys  = array_keys($items);
1358
-
1359
-                if (is_string($callback) === false) {
1360
-                    while (($i = array_shift($keys)) !== null) {
1361
-                        $out[] = call_user_func_array($callback, array($items[$i]) + $params);
1362
-                    }
1363
-                } elseif ($cnt === 1) {
1364
-                    while (($i = array_shift($keys)) !== null) {
1365
-                        $out[] = $callback($items[$i]);
1366
-                    }
1367
-                } elseif ($cnt === 2) {
1368
-                    while (($i = array_shift($keys)) !== null) {
1369
-                        $out[] = $callback($items[$i], $params[1]);
1370
-                    }
1371
-                } elseif ($cnt === 3) {
1372
-                    while (($i = array_shift($keys)) !== null) {
1373
-                        $out[] = $callback($items[$i], $params[1], $params[2]);
1374
-                    }
1375
-                } elseif ($cnt === 4) {
1376
-                    while (($i = array_shift($keys)) !== null) {
1377
-                        $out[] = $callback($items[$i], $params[1], $params[2], $params[3]);
1378
-                    }
1379
-                } else {
1380
-                    while (($i = array_shift($keys)) !== null) {
1381
-                        $out[] = call_user_func_array($callback, array($items[$i]) + $params);
1382
-                    }
1383
-                }
1384
-            }
1385
-
1386
-            return $out;
1387
-        } else {
1388
-            return $params[0];
1389
-        }
1390
-    }
1391
-
1392
-    /**
1393
-     * Reads a variable into the given data array.
1394
-     *
1395
-     * @param string $varstr   the variable string, using dwoo variable syntax (i.e. "var.subvar[subsubvar]->property")
1396
-     * @param mixed  $data     the data array or object to read from
1397
-     * @param bool   $safeRead if true, the function will check whether the index exists to prevent any notices from
1398
-     *                         being output
1399
-     *
1400
-     * @return mixed
1401
-     */
1402
-    public function readVarInto($varstr, $data, $safeRead = false)
1403
-    {
1404
-        if ($data === null) {
1405
-            return null;
1406
-        }
1407
-
1408
-        if (is_array($varstr) === false) {
1409
-            preg_match_all('#(\[|->|\.)?((?:[^.[\]-]|-(?!>))+)\]?#i', $varstr, $m);
1410
-        } else {
1411
-            $m = $varstr;
1412
-        }
1413
-        unset($varstr);
1414
-
1415
-        while (list($k, $sep) = each($m[1])) {
1416
-            if ($sep === '.' || $sep === '[' || $sep === '') {
1417
-                // strip enclosing quotes if present
1418
-                $m[2][$k] = preg_replace('#^(["\']?)(.*?)\1$#', '$2', $m[2][$k]);
1419
-
1420
-                if ((is_array($data) || $data instanceof ArrayAccess) && ($safeRead === false || isset($data[$m[2][$k]]))) {
1421
-                    $data = $data[$m[2][$k]];
1422
-                } else {
1423
-                    return null;
1424
-                }
1425
-            } else {
1426
-                if (is_object($data) && ($safeRead === false || isset($data->{$m[2][$k]}))) {
1427
-                    $data = $data->{$m[2][$k]};
1428
-                } else {
1429
-                    return null;
1430
-                }
1431
-            }
1432
-        }
1433
-
1434
-        return $data;
1435
-    }
1436
-
1437
-    /**
1438
-     * Reads a variable into the parent scope.
1439
-     *
1440
-     * @param int    $parentLevels the amount of parent levels to go from the current scope
1441
-     * @param string $varstr       the variable string, using dwoo variable syntax (i.e.
1442
-     *                             "var.subvar[subsubvar]->property")
1443
-     *
1444
-     * @return mixed
1445
-     */
1446
-    public function readParentVar($parentLevels, $varstr = null)
1447
-    {
1448
-        $tree = $this->scopeTree;
1449
-        $cur  = $this->data;
1450
-
1451
-        while ($parentLevels -- !== 0) {
1452
-            array_pop($tree);
1453
-        }
1454
-
1455
-        while (($i = array_shift($tree)) !== null) {
1456
-            if (is_object($cur)) {
1457
-                $cur = $cur->{$i};
1458
-            } else {
1459
-                $cur = $cur[$i];
1460
-            }
1461
-        }
1462
-
1463
-        if ($varstr !== null) {
1464
-            return $this->readVarInto($varstr, $cur);
1465
-        } else {
1466
-            return $cur;
1467
-        }
1468
-    }
1469
-
1470
-    /**
1471
-     * Reads a variable into the current scope.
1472
-     *
1473
-     * @param string $varstr the variable string, using dwoo variable syntax (i.e. "var.subvar[subsubvar]->property")
1474
-     *
1475
-     * @return mixed
1476
-     */
1477
-    public function readVar($varstr)
1478
-    {
1479
-        if (is_array($varstr) === true) {
1480
-            $m = $varstr;
1481
-            unset($varstr);
1482
-        } else {
1483
-            if (strstr($varstr, '.') === false && strstr($varstr, '[') === false && strstr($varstr, '->') === false) {
1484
-                if ($varstr === 'dwoo') {
1485
-                    return $this->getGlobals();
1486
-                } elseif ($varstr === '__' || $varstr === '_root') {
1487
-                    return $this->data;
1488
-                } elseif ($varstr === '_' || $varstr === '_parent') {
1489
-                    $varstr = '.' . $varstr;
1490
-                    $tree   = $this->scopeTree;
1491
-                    $cur    = $this->data;
1492
-                    array_pop($tree);
1493
-
1494
-                    while (($i = array_shift($tree)) !== null) {
1495
-                        if (is_object($cur)) {
1496
-                            $cur = $cur->{$i};
1497
-                        } else {
1498
-                            $cur = $cur[$i];
1499
-                        }
1500
-                    }
1501
-
1502
-                    return $cur;
1503
-                }
1504
-
1505
-                $cur = $this->scope;
1506
-
1507
-                if (isset($cur[$varstr])) {
1508
-                    return $cur[$varstr];
1509
-                } else {
1510
-                    return null;
1511
-                }
1512
-            }
1513
-
1514
-            if (substr($varstr, 0, 1) === '.') {
1515
-                $varstr = 'dwoo' . $varstr;
1516
-            }
1517
-
1518
-            preg_match_all('#(\[|->|\.)?((?:[^.[\]-]|-(?!>))+)\]?#i', $varstr, $m);
1519
-        }
1520
-
1521
-        $i = $m[2][0];
1522
-        if ($i === 'dwoo') {
1523
-            $cur = $this->getGlobals();
1524
-            array_shift($m[2]);
1525
-            array_shift($m[1]);
1526
-            switch ($m[2][0]) {
1527
-            case 'get':
1528
-                $cur = $_GET;
1529
-                break;
1530
-            case 'post':
1531
-                $cur = $_POST;
1532
-                break;
1533
-            case 'session':
1534
-                $cur = $_SESSION;
1535
-                break;
1536
-            case 'cookies':
1537
-            case 'cookie':
1538
-                $cur = $_COOKIE;
1539
-                break;
1540
-            case 'server':
1541
-                $cur = $_SERVER;
1542
-                break;
1543
-            case 'env':
1544
-                $cur = $_ENV;
1545
-                break;
1546
-            case 'request':
1547
-                $cur = $_REQUEST;
1548
-                break;
1549
-            case 'const':
1550
-                array_shift($m[2]);
1551
-                if (defined($m[2][0])) {
1552
-                    return constant($m[2][0]);
1553
-                } else {
1554
-                    return null;
1555
-                }
1556
-            }
1557
-            if ($cur !== $this->getGlobals()) {
1558
-                array_shift($m[2]);
1559
-                array_shift($m[1]);
1560
-            }
1561
-        } elseif ($i === '__' || $i === '_root') {
1562
-            $cur = $this->data;
1563
-            array_shift($m[2]);
1564
-            array_shift($m[1]);
1565
-        } elseif ($i === '_' || $i === '_parent') {
1566
-            $tree = $this->scopeTree;
1567
-            $cur  = $this->data;
1568
-
1569
-            while (true) {
1570
-                array_pop($tree);
1571
-                array_shift($m[2]);
1572
-                array_shift($m[1]);
1573
-                if (current($m[2]) === '_' || current($m[2]) === '_parent') {
1574
-                    continue;
1575
-                }
1576
-
1577
-                while (($i = array_shift($tree)) !== null) {
1578
-                    if (is_object($cur)) {
1579
-                        $cur = $cur->{$i};
1580
-                    } else {
1581
-                        $cur = $cur[$i];
1582
-                    }
1583
-                }
1584
-                break;
1585
-            }
1586
-        } else {
1587
-            $cur = $this->scope;
1588
-        }
1589
-
1590
-        while (list($k, $sep) = each($m[1])) {
1591
-            if ($sep === '.' || $sep === '[' || $sep === '') {
1592
-                if ((is_array($cur) || $cur instanceof ArrayAccess) && isset($cur[$m[2][$k]])) {
1593
-                    $cur = $cur[$m[2][$k]];
1594
-                } else {
1595
-                    return null;
1596
-                }
1597
-            } elseif ($sep === '->') {
1598
-                if (is_object($cur)) {
1599
-                    $cur = $cur->{$m[2][$k]};
1600
-                } else {
1601
-                    return null;
1602
-                }
1603
-            } else {
1604
-                return null;
1605
-            }
1606
-        }
1607
-
1608
-        return $cur;
1609
-    }
1610
-
1611
-    /**
1612
-     * Assign the value to the given variable.
1613
-     *
1614
-     * @param mixed  $value the value to assign
1615
-     * @param string $scope the variable string, using dwoo variable syntax (i.e. "var.subvar[subsubvar]->property")
1616
-     *
1617
-     * @return bool true if assigned correctly or false if a problem occured while parsing the var string
1618
-     */
1619
-    public function assignInScope($value, $scope)
1620
-    {
1621
-        if (!is_string($scope)) {
1622
-            $this->triggerError('Assignments must be done into strings, (' . gettype($scope) . ') ' . var_export($scope, true) . ' given', E_USER_ERROR);
1623
-        }
1624
-        if (strstr($scope, '.') === false && strstr($scope, '->') === false) {
1625
-            $this->scope[$scope] = $value;
1626
-        } else {
1627
-            // TODO handle _root/_parent scopes ?
1628
-            preg_match_all('#(\[|->|\.)?([^.[\]-]+)\]?#i', $scope, $m);
1629
-
1630
-            $cur  = &$this->scope;
1631
-            $last = array(
1632
-                array_pop($m[1]),
1633
-                array_pop($m[2])
1634
-            );
1635
-
1636
-            while (list($k, $sep) = each($m[1])) {
1637
-                if ($sep === '.' || $sep === '[' || $sep === '') {
1638
-                    if (is_array($cur) === false) {
1639
-                        $cur = array();
1640
-                    }
1641
-                    $cur = &$cur[$m[2][$k]];
1642
-                } elseif ($sep === '->') {
1643
-                    if (is_object($cur) === false) {
1644
-                        $cur = new stdClass();
1645
-                    }
1646
-                    $cur = &$cur->{$m[2][$k]};
1647
-                } else {
1648
-                    return false;
1649
-                }
1650
-            }
1651
-
1652
-            if ($last[0] === '.' || $last[0] === '[' || $last[0] === '') {
1653
-                if (is_array($cur) === false) {
1654
-                    $cur = array();
1655
-                }
1656
-                $cur[$last[1]] = $value;
1657
-            } elseif ($last[0] === '->') {
1658
-                if (is_object($cur) === false) {
1659
-                    $cur = new stdClass();
1660
-                }
1661
-                $cur->{$last[1]} = $value;
1662
-            } else {
1663
-                return false;
1664
-            }
1665
-        }
1666
-    }
1667
-
1668
-    /**
1669
-     * Sets the scope to the given scope string or array.
1670
-     *
1671
-     * @param mixed $scope    a string i.e. "level1.level2" or an array i.e. array("level1", "level2")
1672
-     * @param bool  $absolute if true, the scope is set from the top level scope and not from the current scope
1673
-     *
1674
-     * @return array the current scope tree
1675
-     */
1676
-    public function setScope($scope, $absolute = false)
1677
-    {
1678
-        $old = $this->scopeTree;
1679
-
1680
-        if (is_string($scope) === true) {
1681
-            $scope = explode('.', $scope);
1682
-        }
1683
-
1684
-        if ($absolute === true) {
1685
-            $this->scope     = &$this->data;
1686
-            $this->scopeTree = array();
1687
-        }
1688
-
1689
-        while (($bit = array_shift($scope)) !== null) {
1690
-            if ($bit === '_' || $bit === '_parent') {
1691
-                array_pop($this->scopeTree);
1692
-                $this->scope = &$this->data;
1693
-                $cnt         = count($this->scopeTree);
1694
-                for ($i = 0; $i < $cnt; ++ $i) {
1695
-                    $this->scope = &$this->scope[$this->scopeTree[$i]];
1696
-                }
1697
-            } elseif ($bit === '__' || $bit === '_root') {
1698
-                $this->scope     = &$this->data;
1699
-                $this->scopeTree = array();
1700
-            } elseif (isset($this->scope[$bit])) {
1701
-                if ($this->scope instanceof ArrayAccess) {
1702
-                    $tmp         = $this->scope[$bit];
1703
-                    $this->scope = &$tmp;
1704
-                } else {
1705
-                    $this->scope = &$this->scope[$bit];
1706
-                }
1707
-                $this->scopeTree[] = $bit;
1708
-            } else {
1709
-                unset($this->scope);
1710
-                $this->scope = null;
1711
-            }
1712
-        }
1713
-
1714
-        return $old;
1715
-    }
1716
-
1717
-    /**
1718
-     * Returns the entire data array.
1719
-     *
1720
-     * @return array
1721
-     */
1722
-    public function getData()
1723
-    {
1724
-        return $this->data;
1725
-    }
1726
-
1727
-    /**
1728
-     * Sets a return value for the currently running template.
1729
-     *
1730
-     * @param string $name  var name
1731
-     * @param mixed  $value var value
1732
-     *
1733
-     * @return void
1734
-     */
1735
-    public function setReturnValue($name, $value)
1736
-    {
1737
-        $this->returnData[$name] = $value;
1738
-    }
1739
-
1740
-    /**
1741
-     * Retrieves the return values set by the template.
1742
-     *
1743
-     * @return array
1744
-     */
1745
-    public function getReturnValues()
1746
-    {
1747
-        return $this->returnData;
1748
-    }
1749
-
1750
-    /**
1751
-     * Returns a reference to the current scope.
1752
-     *
1753
-     * @return mixed
1754
-     */
1755
-    public function &getScope()
1756
-    {
1757
-        return $this->scope;
1758
-    }
1759
-
1760
-    /**
1761
-     * Redirects all calls to unexisting to plugin proxy.
1762
-     *
1763
-     * @param string $method the method name
1764
-     * @param array  $args   array of arguments
1765
-     *
1766
-     * @return mixed
1767
-     * @throws Exception
1768
-     */
1769
-    public function __call($method, $args)
1770
-    {
1771
-        $proxy = $this->getPluginProxy();
1772
-        if (!$proxy) {
1773
-            throw new Exception('Call to undefined method ' . __CLASS__ . '::' . $method . '()');
1774
-        }
1775
-
1776
-        return call_user_func_array($proxy->getCallback($method), $args);
1777
-    }
1778
-
1779
-    /**
1780
-     * Convert plugin name from `auto_escape` to `AutoEscape`.
1781
-     * @param string $input
1782
-     * @param string $separator
1783
-     *
1784
-     * @return mixed
1785
-     */
1786
-    public static function toCamelCase($input, $separator = '_')
1787
-    {
1788
-        return join(array_map('ucfirst', explode($separator, $input)));
1789
-
1790
-        // TODO >= PHP5.4.32
1791
-        //return str_replace($separator, '', ucwords($input, $separator));
1792
-    }
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
+	public function setCacheDir($dir)
780
+	{
781
+		$this->cacheDir = rtrim($dir, '/\\') . DIRECTORY_SEPARATOR;
782
+		if (is_writable($this->cacheDir) === false) {
783
+			throw new Exception('The cache directory must be writable, chmod "' . $this->cacheDir . '" to make it writable');
784
+		}
785
+	}
786
+
787
+	/**
788
+	 * Returns the compile directory with a trailing DIRECTORY_SEPARATOR.
789
+	 *
790
+	 * @return string
791
+	 */
792
+	public function getCompileDir()
793
+	{
794
+		if ($this->compileDir === null) {
795
+			$this->setCompileDir(dirname(__DIR__) . DIRECTORY_SEPARATOR . 'compiled' . DIRECTORY_SEPARATOR);
796
+		}
797
+
798
+		return $this->compileDir;
799
+	}
800
+
801
+	/**
802
+	 * Sets the compile directory and automatically appends a DIRECTORY_SEPARATOR.
803
+	 *
804
+	 * @param string $dir the compile directory
805
+	 *
806
+	 * @return void
807
+	 * @throws Exception
808
+	 */
809
+	public function setCompileDir($dir)
810
+	{
811
+		$this->compileDir = rtrim($dir, '/\\') . DIRECTORY_SEPARATOR;
812
+		if (is_writable($this->compileDir) === false) {
813
+			throw new Exception('The compile directory must be writable, chmod "' . $this->compileDir . '" to make it writable');
814
+		}
815
+	}
816
+
817
+	/**
818
+	 * Returns an array of the template directory with a trailing DIRECTORY_SEPARATOR
819
+	 *
820
+	 * @return array
821
+	 */
822
+	public function getTemplateDir()
823
+	{
824
+		return $this->templateDir;
825
+	}
826
+
827
+	/**
828
+	 * sets the template directory and automatically appends a DIRECTORY_SEPARATOR
829
+	 * template directory is stored in an array
830
+	 *
831
+	 * @param string $dir
832
+	 *
833
+	 * @throws Exception
834
+	 */
835
+	public function setTemplateDir($dir)
836
+	{
837
+		$tmpDir = rtrim($dir, '/\\') . DIRECTORY_SEPARATOR;
838
+		if (is_dir($tmpDir) === false) {
839
+			throw new Exception('The template directory: "' . $tmpDir . '" does not exists, create the directory or specify an other location !');
840
+		}
841
+		$this->templateDir[] = $tmpDir;
842
+	}
843
+
844
+	/**
845
+	 * Returns the default cache time that is used with templates that do not have a cache time set.
846
+	 *
847
+	 * @return int the duration in seconds
848
+	 */
849
+	public function getCacheTime()
850
+	{
851
+		return $this->cacheTime;
852
+	}
853
+
854
+	/**
855
+	 * Sets the default cache time to use with templates that do not have a cache time set.
856
+	 *
857
+	 * @param int $seconds the duration in seconds
858
+	 *
859
+	 * @return void
860
+	 */
861
+	public function setCacheTime($seconds)
862
+	{
863
+		$this->cacheTime = (int)$seconds;
864
+	}
865
+
866
+	/**
867
+	 * Returns the character set used by the string manipulation plugins.
868
+	 * the charset is automatically lowercased
869
+	 *
870
+	 * @return string
871
+	 */
872
+	public function getCharset()
873
+	{
874
+		return $this->charset;
875
+	}
876
+
877
+	/**
878
+	 * Sets the character set used by the string manipulation plugins.
879
+	 * the charset will be automatically lowercased
880
+	 *
881
+	 * @param string $charset the character set
882
+	 *
883
+	 * @return void
884
+	 */
885
+	public function setCharset($charset)
886
+	{
887
+		$this->charset = strtolower((string)$charset);
888
+	}
889
+
890
+	/**
891
+	 * Returns the current template being rendered, when applicable, or null.
892
+	 *
893
+	 * @return ITemplate|null
894
+	 */
895
+	public function getTemplate()
896
+	{
897
+		return $this->template;
898
+	}
899
+
900
+	/**
901
+	 * Sets the current template being rendered.
902
+	 *
903
+	 * @param ITemplate $tpl template object
904
+	 *
905
+	 * @return void
906
+	 */
907
+	public function setTemplate(ITemplate $tpl)
908
+	{
909
+		$this->template = $tpl;
910
+	}
911
+
912
+	/**
913
+	 * Sets the default compiler factory function for the given resource name.
914
+	 * a compiler factory must return a ICompiler object pre-configured to fit your needs
915
+	 *
916
+	 * @param string   $resourceName    the resource name (i.e. file, string)
917
+	 * @param callback $compilerFactory the compiler factory callback
918
+	 *
919
+	 * @return void
920
+	 */
921
+	public function setDefaultCompilerFactory($resourceName, $compilerFactory)
922
+	{
923
+		$this->resources[$resourceName]['compiler'] = $compilerFactory;
924
+	}
925
+
926
+	/**
927
+	 * Returns the default compiler factory function for the given resource name.
928
+	 *
929
+	 * @param string $resourceName the resource name
930
+	 *
931
+	 * @return callback the compiler factory callback
932
+	 */
933
+	public function getDefaultCompilerFactory($resourceName)
934
+	{
935
+		return $this->resources[$resourceName]['compiler'];
936
+	}
937
+
938
+	/**
939
+	 * Sets the security policy object to enforce some php security settings.
940
+	 * use this if untrusted persons can modify templates
941
+	 *
942
+	 * @param SecurityPolicy $policy the security policy object
943
+	 *
944
+	 * @return void
945
+	 */
946
+	public function setSecurityPolicy(SecurityPolicy $policy = null)
947
+	{
948
+		$this->securityPolicy = $policy;
949
+	}
950
+
951
+	/**
952
+	 * Returns the current security policy object or null by default.
953
+	 *
954
+	 * @return SecurityPolicy|null the security policy object if any
955
+	 */
956
+	public function getSecurityPolicy()
957
+	{
958
+		return $this->securityPolicy;
959
+	}
960
+
961
+	/**
962
+	 * Sets the object that must be used as a plugin proxy when plugin can't be found
963
+	 * by dwoo's loader.
964
+	 *
965
+	 * @param IPluginProxy $pluginProxy the proxy object
966
+	 *
967
+	 * @return void
968
+	 */
969
+	public function setPluginProxy(IPluginProxy $pluginProxy)
970
+	{
971
+		$this->pluginProxy = $pluginProxy;
972
+	}
973
+
974
+	/**
975
+	 * Returns the current plugin proxy object or null by default.
976
+	 *
977
+	 * @return IPluginProxy
978
+	 */
979
+	public function getPluginProxy()
980
+	{
981
+		return $this->pluginProxy;
982
+	}
983
+
984
+	/**
985
+	 * Checks whether the given template is cached or not.
986
+	 *
987
+	 * @param ITemplate $tpl the template object
988
+	 *
989
+	 * @return bool
990
+	 */
991
+	public function isCached(ITemplate $tpl)
992
+	{
993
+		return is_string($tpl->getCachedTemplate($this));
994
+	}
995
+
996
+	/**
997
+	 * Clear templates inside the compiled directory.
998
+	 *
999
+	 * @return int
1000
+	 */
1001
+	public function clearCompiled()
1002
+	{
1003
+		$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->getCompileDir()), \RecursiveIteratorIterator::SELF_FIRST);
1004
+		$count    = 0;
1005
+		foreach ($iterator as $file) {
1006
+			if ($file->isFile()) {
1007
+				$count += unlink($file->__toString()) ? 1 : 0;
1008
+			}
1009
+		}
1010
+
1011
+		return $count;
1012
+	}
1013
+
1014
+	/**
1015
+	 * Clears the cached templates if they are older than the given time.
1016
+	 *
1017
+	 * @param int $olderThan minimum time (in seconds) required for a cached template to be cleared
1018
+	 *
1019
+	 * @return int the amount of templates cleared
1020
+	 */
1021
+	public function clearCache($olderThan = - 1)
1022
+	{
1023
+		$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->getCacheDir()), \RecursiveIteratorIterator::SELF_FIRST);
1024
+		$expired  = time() - $olderThan;
1025
+		$count    = 0;
1026
+		foreach ($iterator as $file) {
1027
+			if ($file->isFile() && $file->getCTime() < $expired) {
1028
+				$count += unlink((string)$file) ? 1 : 0;
1029
+			}
1030
+		}
1031
+
1032
+		return $count;
1033
+	}
1034
+
1035
+	/**
1036
+	 * Fetches a template object of the given resource.
1037
+	 *
1038
+	 * @param string    $resourceName   the resource name (i.e. file, string)
1039
+	 * @param string    $resourceId     the resource identifier (i.e. file path)
1040
+	 * @param int       $cacheTime      the cache time setting for this resource
1041
+	 * @param string    $cacheId        the unique cache identifier
1042
+	 * @param string    $compileId      the unique compiler identifier
1043
+	 * @param ITemplate $parentTemplate the parent template
1044
+	 *
1045
+	 * @return ITemplate
1046
+	 * @throws Exception
1047
+	 */
1048
+	public function templateFactory($resourceName, $resourceId, $cacheTime = null, $cacheId = null, $compileId = null, ITemplate $parentTemplate = null)
1049
+	{
1050
+		if (isset($this->resources[$resourceName])) {
1051
+			/**
1052
+			 * Interface ITemplate
1053
+			 *
1054
+			 * @var ITemplate $class
1055
+			 */
1056
+			$class = $this->resources[$resourceName]['class'];
1057
+
1058
+			return $class::templateFactory($this, $resourceId, $cacheTime, $cacheId, $compileId, $parentTemplate);
1059
+		}
1060
+
1061
+		throw new Exception('Unknown resource type : ' . $resourceName);
1062
+	}
1063
+
1064
+	/**
1065
+	 * Checks if the input is an array or arrayaccess object, optionally it can also check if it's
1066
+	 * empty.
1067
+	 *
1068
+	 * @param mixed $value        the variable to check
1069
+	 * @param bool  $checkIsEmpty if true, the function will also check if the array|arrayaccess is empty,
1070
+	 *                            and return true only if it's not empty
1071
+	 *
1072
+	 * @return int|bool true if it's an array|arrayaccess (or the item count if $checkIsEmpty is true) or false if it's
1073
+	 *                  not an array|arrayaccess (or 0 if $checkIsEmpty is true)
1074
+	 */
1075
+	public function isArray($value, $checkIsEmpty = false)
1076
+	{
1077
+		if (is_array($value) === true || $value instanceof ArrayAccess) {
1078
+			if ($checkIsEmpty === false) {
1079
+				return true;
1080
+			}
1081
+
1082
+			return $this->count($value);
1083
+		}
1084
+
1085
+		return false;
1086
+	}
1087
+
1088
+	/**
1089
+	 * Checks if the input is an array or a traversable object, optionally it can also check if it's
1090
+	 * empty.
1091
+	 *
1092
+	 * @param mixed $value        the variable to check
1093
+	 * @param bool  $checkIsEmpty if true, the function will also check if the array|traversable is empty,
1094
+	 *                            and return true only if it's not empty
1095
+	 *
1096
+	 * @return int|bool true if it's an array|traversable (or the item count if $checkIsEmpty is true) or false if it's
1097
+	 *                  not an array|traversable (or 0 if $checkIsEmpty is true)
1098
+	 */
1099
+	public function isTraversable($value, $checkIsEmpty = false)
1100
+	{
1101
+		if (is_array($value) === true) {
1102
+			if ($checkIsEmpty === false) {
1103
+				return true;
1104
+			} else {
1105
+				return count($value) > 0;
1106
+			}
1107
+		} elseif ($value instanceof Traversable) {
1108
+			if ($checkIsEmpty === false) {
1109
+				return true;
1110
+			} else {
1111
+				return $this->count($value);
1112
+			}
1113
+		}
1114
+
1115
+		return false;
1116
+	}
1117
+
1118
+	/**
1119
+	 * Counts an array or arrayaccess/traversable object.
1120
+	 *
1121
+	 * @param mixed $value the value to count
1122
+	 *
1123
+	 * @return int|bool the count for arrays and objects that implement countable, true for other objects that don't,
1124
+	 *                  and 0 for empty elements
1125
+	 */
1126
+	public function count($value)
1127
+	{
1128
+		if (is_array($value) === true || $value instanceof Countable) {
1129
+			return count($value);
1130
+		} elseif ($value instanceof ArrayAccess) {
1131
+			if ($value->offsetExists(0)) {
1132
+				return true;
1133
+			}
1134
+		} elseif ($value instanceof Iterator) {
1135
+			$value->rewind();
1136
+			if ($value->valid()) {
1137
+				return true;
1138
+			}
1139
+		} elseif ($value instanceof Traversable) {
1140
+			foreach ($value as $dummy) {
1141
+				return true;
1142
+			}
1143
+		}
1144
+
1145
+		return 0;
1146
+	}
1147
+
1148
+	/**
1149
+	 * Triggers a dwoo error.
1150
+	 *
1151
+	 * @param string $message the error message
1152
+	 * @param int    $level   the error level, one of the PHP's E_* constants
1153
+	 *
1154
+	 * @return void
1155
+	 */
1156
+	public function triggerError($message, $level = E_USER_NOTICE)
1157
+	{
1158
+		if (!($tplIdentifier = $this->template->getResourceIdentifier())) {
1159
+			$tplIdentifier = $this->template->getResourceName();
1160
+		}
1161
+		trigger_error('Dwoo error (in ' . $tplIdentifier . ') : ' . $message, $level);
1162
+	}
1163
+
1164
+	/**
1165
+	 * Adds a block to the block stack.
1166
+	 *
1167
+	 * @param string $blockName the block name (without `Plugin` prefix)
1168
+	 * @param array  $args      the arguments to be passed to the block's init() function
1169
+	 *
1170
+	 * @return BlockPlugin the newly created block
1171
+	 */
1172
+	public function addStack($blockName, array $args = array())
1173
+	{
1174
+		if (isset($this->plugins[$blockName])) {
1175
+			$class = $this->plugins[$blockName]['class'];
1176
+		} else {
1177
+			$class = self::NAMESPACE_PLUGINS_BLOCKS . 'Plugin' . self::toCamelCase($blockName);
1178
+		}
1179
+
1180
+		if ($this->curBlock !== null) {
1181
+			$this->curBlock->buffer(ob_get_contents());
1182
+			ob_clean();
1183
+		} else {
1184
+			$this->buffer .= ob_get_contents();
1185
+			ob_clean();
1186
+		}
1187
+
1188
+		$block = new $class($this);
1189
+
1190
+		call_user_func_array(array($block, 'init'), $args);
1191
+
1192
+		$this->stack[] = $this->curBlock = $block;
1193
+
1194
+		return $block;
1195
+	}
1196
+
1197
+	/**
1198
+	 * Removes the plugin at the top of the block stack.
1199
+	 * Calls the block buffer() function, followed by a call to end() and finally a call to process()
1200
+	 *
1201
+	 * @return void
1202
+	 */
1203
+	public function delStack()
1204
+	{
1205
+		$args = func_get_args();
1206
+
1207
+		$this->curBlock->buffer(ob_get_contents());
1208
+		ob_clean();
1209
+
1210
+		call_user_func_array(array($this->curBlock, 'end'), $args);
1211
+
1212
+		$tmp = array_pop($this->stack);
1213
+
1214
+		if (count($this->stack) > 0) {
1215
+			$this->curBlock = end($this->stack);
1216
+			$this->curBlock->buffer($tmp->process());
1217
+		} else {
1218
+			if ($this->buffer !== '') {
1219
+				echo $this->buffer;
1220
+				$this->buffer = '';
1221
+			}
1222
+			$this->curBlock = null;
1223
+			echo $tmp->process();
1224
+		}
1225
+
1226
+		unset($tmp);
1227
+	}
1228
+
1229
+	/**
1230
+	 * Returns the parent block of the given block.
1231
+	 *
1232
+	 * @param BlockPlugin $block the block class plugin
1233
+	 *
1234
+	 * @return BlockPlugin|false if the given block isn't in the stack
1235
+	 */
1236
+	public function getParentBlock(BlockPlugin $block)
1237
+	{
1238
+		$index = array_search($block, $this->stack, true);
1239
+		if ($index !== false && $index > 0) {
1240
+			return $this->stack[$index - 1];
1241
+		}
1242
+
1243
+		return false;
1244
+	}
1245
+
1246
+	/**
1247
+	 * Finds the closest block of the given type, starting at the top of the stack.
1248
+	 *
1249
+	 * @param string $type the type of plugin you want to find
1250
+	 *
1251
+	 * @return BlockPlugin|false if no plugin of such type is in the stack
1252
+	 */
1253
+	public function findBlock($type)
1254
+	{
1255
+		if (isset($this->plugins[$type])) {
1256
+			$type = $this->plugins[$type]['class'];
1257
+		} else {
1258
+			$type = self::NAMESPACE_PLUGINS_BLOCKS . 'Plugin_' . str_replace(self::NAMESPACE_PLUGINS_BLOCKS.'Plugin',
1259
+					'', $type);
1260
+		}
1261
+
1262
+		$keys = array_keys($this->stack);
1263
+		while (($key = array_pop($keys)) !== false) {
1264
+			if ($this->stack[$key] instanceof $type) {
1265
+				return $this->stack[$key];
1266
+			}
1267
+		}
1268
+
1269
+		return false;
1270
+	}
1271
+
1272
+	/**
1273
+	 * Returns a Plugin of the given class.
1274
+	 * this is so a single instance of every class plugin is created at each template run,
1275
+	 * allowing class plugins to have "per-template-run" static variables
1276
+	 *
1277
+	 * @param string $class the class name
1278
+	 *
1279
+	 * @return mixed an object of the given class
1280
+	 */
1281
+	public function getObjectPlugin($class)
1282
+	{
1283
+		if (isset($this->runtimePlugins[$class])) {
1284
+			return $this->runtimePlugins[$class];
1285
+		}
1286
+
1287
+		return $this->runtimePlugins[$class] = new $class($this);
1288
+	}
1289
+
1290
+	/**
1291
+	 * Calls the process() method of the given class-plugin name.
1292
+	 *
1293
+	 * @param string $plugName the class plugin name (without `Plugin` prefix)
1294
+	 * @param array  $params   an array of parameters to send to the process() method
1295
+	 *
1296
+	 * @return string the process() return value
1297
+	 */
1298
+	public function classCall($plugName, array $params = array())
1299
+	{
1300
+		$class  = self::toCamelCase($plugName);
1301
+		$plugin = $this->getObjectPlugin($class);
1302
+
1303
+		return call_user_func_array(array($plugin, 'process'), $params);
1304
+	}
1305
+
1306
+	/**
1307
+	 * Calls a php function.
1308
+	 *
1309
+	 * @param string $callback the function to call
1310
+	 * @param array  $params   an array of parameters to send to the function
1311
+	 *
1312
+	 * @return mixed the return value of the called function
1313
+	 */
1314
+	public function arrayMap($callback, array $params)
1315
+	{
1316
+		if ($params[0] === $this) {
1317
+			$addThis = true;
1318
+			array_shift($params);
1319
+		}
1320
+		if ((is_array($params[0]) || ($params[0] instanceof Iterator && $params[0] instanceof ArrayAccess))) {
1321
+			if (empty($params[0])) {
1322
+				return $params[0];
1323
+			}
1324
+
1325
+			// array map
1326
+			$out = array();
1327
+			$cnt = count($params);
1328
+
1329
+			if (isset($addThis)) {
1330
+				array_unshift($params, $this);
1331
+				$items = $params[1];
1332
+				$keys  = array_keys($items);
1333
+
1334
+				if (is_string($callback) === false) {
1335
+					while (($i = array_shift($keys)) !== null) {
1336
+						$out[] = call_user_func_array($callback, array(1 => $items[$i]) + $params);
1337
+					}
1338
+				} elseif ($cnt === 1) {
1339
+					while (($i = array_shift($keys)) !== null) {
1340
+						$out[] = $callback($this, $items[$i]);
1341
+					}
1342
+				} elseif ($cnt === 2) {
1343
+					while (($i = array_shift($keys)) !== null) {
1344
+						$out[] = $callback($this, $items[$i], $params[2]);
1345
+					}
1346
+				} elseif ($cnt === 3) {
1347
+					while (($i = array_shift($keys)) !== null) {
1348
+						$out[] = $callback($this, $items[$i], $params[2], $params[3]);
1349
+					}
1350
+				} else {
1351
+					while (($i = array_shift($keys)) !== null) {
1352
+						$out[] = call_user_func_array($callback, array(1 => $items[$i]) + $params);
1353
+					}
1354
+				}
1355
+			} else {
1356
+				$items = $params[0];
1357
+				$keys  = array_keys($items);
1358
+
1359
+				if (is_string($callback) === false) {
1360
+					while (($i = array_shift($keys)) !== null) {
1361
+						$out[] = call_user_func_array($callback, array($items[$i]) + $params);
1362
+					}
1363
+				} elseif ($cnt === 1) {
1364
+					while (($i = array_shift($keys)) !== null) {
1365
+						$out[] = $callback($items[$i]);
1366
+					}
1367
+				} elseif ($cnt === 2) {
1368
+					while (($i = array_shift($keys)) !== null) {
1369
+						$out[] = $callback($items[$i], $params[1]);
1370
+					}
1371
+				} elseif ($cnt === 3) {
1372
+					while (($i = array_shift($keys)) !== null) {
1373
+						$out[] = $callback($items[$i], $params[1], $params[2]);
1374
+					}
1375
+				} elseif ($cnt === 4) {
1376
+					while (($i = array_shift($keys)) !== null) {
1377
+						$out[] = $callback($items[$i], $params[1], $params[2], $params[3]);
1378
+					}
1379
+				} else {
1380
+					while (($i = array_shift($keys)) !== null) {
1381
+						$out[] = call_user_func_array($callback, array($items[$i]) + $params);
1382
+					}
1383
+				}
1384
+			}
1385
+
1386
+			return $out;
1387
+		} else {
1388
+			return $params[0];
1389
+		}
1390
+	}
1391
+
1392
+	/**
1393
+	 * Reads a variable into the given data array.
1394
+	 *
1395
+	 * @param string $varstr   the variable string, using dwoo variable syntax (i.e. "var.subvar[subsubvar]->property")
1396
+	 * @param mixed  $data     the data array or object to read from
1397
+	 * @param bool   $safeRead if true, the function will check whether the index exists to prevent any notices from
1398
+	 *                         being output
1399
+	 *
1400
+	 * @return mixed
1401
+	 */
1402
+	public function readVarInto($varstr, $data, $safeRead = false)
1403
+	{
1404
+		if ($data === null) {
1405
+			return null;
1406
+		}
1407
+
1408
+		if (is_array($varstr) === false) {
1409
+			preg_match_all('#(\[|->|\.)?((?:[^.[\]-]|-(?!>))+)\]?#i', $varstr, $m);
1410
+		} else {
1411
+			$m = $varstr;
1412
+		}
1413
+		unset($varstr);
1414
+
1415
+		while (list($k, $sep) = each($m[1])) {
1416
+			if ($sep === '.' || $sep === '[' || $sep === '') {
1417
+				// strip enclosing quotes if present
1418
+				$m[2][$k] = preg_replace('#^(["\']?)(.*?)\1$#', '$2', $m[2][$k]);
1419
+
1420
+				if ((is_array($data) || $data instanceof ArrayAccess) && ($safeRead === false || isset($data[$m[2][$k]]))) {
1421
+					$data = $data[$m[2][$k]];
1422
+				} else {
1423
+					return null;
1424
+				}
1425
+			} else {
1426
+				if (is_object($data) && ($safeRead === false || isset($data->{$m[2][$k]}))) {
1427
+					$data = $data->{$m[2][$k]};
1428
+				} else {
1429
+					return null;
1430
+				}
1431
+			}
1432
+		}
1433
+
1434
+		return $data;
1435
+	}
1436
+
1437
+	/**
1438
+	 * Reads a variable into the parent scope.
1439
+	 *
1440
+	 * @param int    $parentLevels the amount of parent levels to go from the current scope
1441
+	 * @param string $varstr       the variable string, using dwoo variable syntax (i.e.
1442
+	 *                             "var.subvar[subsubvar]->property")
1443
+	 *
1444
+	 * @return mixed
1445
+	 */
1446
+	public function readParentVar($parentLevels, $varstr = null)
1447
+	{
1448
+		$tree = $this->scopeTree;
1449
+		$cur  = $this->data;
1450
+
1451
+		while ($parentLevels -- !== 0) {
1452
+			array_pop($tree);
1453
+		}
1454
+
1455
+		while (($i = array_shift($tree)) !== null) {
1456
+			if (is_object($cur)) {
1457
+				$cur = $cur->{$i};
1458
+			} else {
1459
+				$cur = $cur[$i];
1460
+			}
1461
+		}
1462
+
1463
+		if ($varstr !== null) {
1464
+			return $this->readVarInto($varstr, $cur);
1465
+		} else {
1466
+			return $cur;
1467
+		}
1468
+	}
1469
+
1470
+	/**
1471
+	 * Reads a variable into the current scope.
1472
+	 *
1473
+	 * @param string $varstr the variable string, using dwoo variable syntax (i.e. "var.subvar[subsubvar]->property")
1474
+	 *
1475
+	 * @return mixed
1476
+	 */
1477
+	public function readVar($varstr)
1478
+	{
1479
+		if (is_array($varstr) === true) {
1480
+			$m = $varstr;
1481
+			unset($varstr);
1482
+		} else {
1483
+			if (strstr($varstr, '.') === false && strstr($varstr, '[') === false && strstr($varstr, '->') === false) {
1484
+				if ($varstr === 'dwoo') {
1485
+					return $this->getGlobals();
1486
+				} elseif ($varstr === '__' || $varstr === '_root') {
1487
+					return $this->data;
1488
+				} elseif ($varstr === '_' || $varstr === '_parent') {
1489
+					$varstr = '.' . $varstr;
1490
+					$tree   = $this->scopeTree;
1491
+					$cur    = $this->data;
1492
+					array_pop($tree);
1493
+
1494
+					while (($i = array_shift($tree)) !== null) {
1495
+						if (is_object($cur)) {
1496
+							$cur = $cur->{$i};
1497
+						} else {
1498
+							$cur = $cur[$i];
1499
+						}
1500
+					}
1501
+
1502
+					return $cur;
1503
+				}
1504
+
1505
+				$cur = $this->scope;
1506
+
1507
+				if (isset($cur[$varstr])) {
1508
+					return $cur[$varstr];
1509
+				} else {
1510
+					return null;
1511
+				}
1512
+			}
1513
+
1514
+			if (substr($varstr, 0, 1) === '.') {
1515
+				$varstr = 'dwoo' . $varstr;
1516
+			}
1517
+
1518
+			preg_match_all('#(\[|->|\.)?((?:[^.[\]-]|-(?!>))+)\]?#i', $varstr, $m);
1519
+		}
1520
+
1521
+		$i = $m[2][0];
1522
+		if ($i === 'dwoo') {
1523
+			$cur = $this->getGlobals();
1524
+			array_shift($m[2]);
1525
+			array_shift($m[1]);
1526
+			switch ($m[2][0]) {
1527
+			case 'get':
1528
+				$cur = $_GET;
1529
+				break;
1530
+			case 'post':
1531
+				$cur = $_POST;
1532
+				break;
1533
+			case 'session':
1534
+				$cur = $_SESSION;
1535
+				break;
1536
+			case 'cookies':
1537
+			case 'cookie':
1538
+				$cur = $_COOKIE;
1539
+				break;
1540
+			case 'server':
1541
+				$cur = $_SERVER;
1542
+				break;
1543
+			case 'env':
1544
+				$cur = $_ENV;
1545
+				break;
1546
+			case 'request':
1547
+				$cur = $_REQUEST;
1548
+				break;
1549
+			case 'const':
1550
+				array_shift($m[2]);
1551
+				if (defined($m[2][0])) {
1552
+					return constant($m[2][0]);
1553
+				} else {
1554
+					return null;
1555
+				}
1556
+			}
1557
+			if ($cur !== $this->getGlobals()) {
1558
+				array_shift($m[2]);
1559
+				array_shift($m[1]);
1560
+			}
1561
+		} elseif ($i === '__' || $i === '_root') {
1562
+			$cur = $this->data;
1563
+			array_shift($m[2]);
1564
+			array_shift($m[1]);
1565
+		} elseif ($i === '_' || $i === '_parent') {
1566
+			$tree = $this->scopeTree;
1567
+			$cur  = $this->data;
1568
+
1569
+			while (true) {
1570
+				array_pop($tree);
1571
+				array_shift($m[2]);
1572
+				array_shift($m[1]);
1573
+				if (current($m[2]) === '_' || current($m[2]) === '_parent') {
1574
+					continue;
1575
+				}
1576
+
1577
+				while (($i = array_shift($tree)) !== null) {
1578
+					if (is_object($cur)) {
1579
+						$cur = $cur->{$i};
1580
+					} else {
1581
+						$cur = $cur[$i];
1582
+					}
1583
+				}
1584
+				break;
1585
+			}
1586
+		} else {
1587
+			$cur = $this->scope;
1588
+		}
1589
+
1590
+		while (list($k, $sep) = each($m[1])) {
1591
+			if ($sep === '.' || $sep === '[' || $sep === '') {
1592
+				if ((is_array($cur) || $cur instanceof ArrayAccess) && isset($cur[$m[2][$k]])) {
1593
+					$cur = $cur[$m[2][$k]];
1594
+				} else {
1595
+					return null;
1596
+				}
1597
+			} elseif ($sep === '->') {
1598
+				if (is_object($cur)) {
1599
+					$cur = $cur->{$m[2][$k]};
1600
+				} else {
1601
+					return null;
1602
+				}
1603
+			} else {
1604
+				return null;
1605
+			}
1606
+		}
1607
+
1608
+		return $cur;
1609
+	}
1610
+
1611
+	/**
1612
+	 * Assign the value to the given variable.
1613
+	 *
1614
+	 * @param mixed  $value the value to assign
1615
+	 * @param string $scope the variable string, using dwoo variable syntax (i.e. "var.subvar[subsubvar]->property")
1616
+	 *
1617
+	 * @return bool true if assigned correctly or false if a problem occured while parsing the var string
1618
+	 */
1619
+	public function assignInScope($value, $scope)
1620
+	{
1621
+		if (!is_string($scope)) {
1622
+			$this->triggerError('Assignments must be done into strings, (' . gettype($scope) . ') ' . var_export($scope, true) . ' given', E_USER_ERROR);
1623
+		}
1624
+		if (strstr($scope, '.') === false && strstr($scope, '->') === false) {
1625
+			$this->scope[$scope] = $value;
1626
+		} else {
1627
+			// TODO handle _root/_parent scopes ?
1628
+			preg_match_all('#(\[|->|\.)?([^.[\]-]+)\]?#i', $scope, $m);
1629
+
1630
+			$cur  = &$this->scope;
1631
+			$last = array(
1632
+				array_pop($m[1]),
1633
+				array_pop($m[2])
1634
+			);
1635
+
1636
+			while (list($k, $sep) = each($m[1])) {
1637
+				if ($sep === '.' || $sep === '[' || $sep === '') {
1638
+					if (is_array($cur) === false) {
1639
+						$cur = array();
1640
+					}
1641
+					$cur = &$cur[$m[2][$k]];
1642
+				} elseif ($sep === '->') {
1643
+					if (is_object($cur) === false) {
1644
+						$cur = new stdClass();
1645
+					}
1646
+					$cur = &$cur->{$m[2][$k]};
1647
+				} else {
1648
+					return false;
1649
+				}
1650
+			}
1651
+
1652
+			if ($last[0] === '.' || $last[0] === '[' || $last[0] === '') {
1653
+				if (is_array($cur) === false) {
1654
+					$cur = array();
1655
+				}
1656
+				$cur[$last[1]] = $value;
1657
+			} elseif ($last[0] === '->') {
1658
+				if (is_object($cur) === false) {
1659
+					$cur = new stdClass();
1660
+				}
1661
+				$cur->{$last[1]} = $value;
1662
+			} else {
1663
+				return false;
1664
+			}
1665
+		}
1666
+	}
1667
+
1668
+	/**
1669
+	 * Sets the scope to the given scope string or array.
1670
+	 *
1671
+	 * @param mixed $scope    a string i.e. "level1.level2" or an array i.e. array("level1", "level2")
1672
+	 * @param bool  $absolute if true, the scope is set from the top level scope and not from the current scope
1673
+	 *
1674
+	 * @return array the current scope tree
1675
+	 */
1676
+	public function setScope($scope, $absolute = false)
1677
+	{
1678
+		$old = $this->scopeTree;
1679
+
1680
+		if (is_string($scope) === true) {
1681
+			$scope = explode('.', $scope);
1682
+		}
1683
+
1684
+		if ($absolute === true) {
1685
+			$this->scope     = &$this->data;
1686
+			$this->scopeTree = array();
1687
+		}
1688
+
1689
+		while (($bit = array_shift($scope)) !== null) {
1690
+			if ($bit === '_' || $bit === '_parent') {
1691
+				array_pop($this->scopeTree);
1692
+				$this->scope = &$this->data;
1693
+				$cnt         = count($this->scopeTree);
1694
+				for ($i = 0; $i < $cnt; ++ $i) {
1695
+					$this->scope = &$this->scope[$this->scopeTree[$i]];
1696
+				}
1697
+			} elseif ($bit === '__' || $bit === '_root') {
1698
+				$this->scope     = &$this->data;
1699
+				$this->scopeTree = array();
1700
+			} elseif (isset($this->scope[$bit])) {
1701
+				if ($this->scope instanceof ArrayAccess) {
1702
+					$tmp         = $this->scope[$bit];
1703
+					$this->scope = &$tmp;
1704
+				} else {
1705
+					$this->scope = &$this->scope[$bit];
1706
+				}
1707
+				$this->scopeTree[] = $bit;
1708
+			} else {
1709
+				unset($this->scope);
1710
+				$this->scope = null;
1711
+			}
1712
+		}
1713
+
1714
+		return $old;
1715
+	}
1716
+
1717
+	/**
1718
+	 * Returns the entire data array.
1719
+	 *
1720
+	 * @return array
1721
+	 */
1722
+	public function getData()
1723
+	{
1724
+		return $this->data;
1725
+	}
1726
+
1727
+	/**
1728
+	 * Sets a return value for the currently running template.
1729
+	 *
1730
+	 * @param string $name  var name
1731
+	 * @param mixed  $value var value
1732
+	 *
1733
+	 * @return void
1734
+	 */
1735
+	public function setReturnValue($name, $value)
1736
+	{
1737
+		$this->returnData[$name] = $value;
1738
+	}
1739
+
1740
+	/**
1741
+	 * Retrieves the return values set by the template.
1742
+	 *
1743
+	 * @return array
1744
+	 */
1745
+	public function getReturnValues()
1746
+	{
1747
+		return $this->returnData;
1748
+	}
1749
+
1750
+	/**
1751
+	 * Returns a reference to the current scope.
1752
+	 *
1753
+	 * @return mixed
1754
+	 */
1755
+	public function &getScope()
1756
+	{
1757
+		return $this->scope;
1758
+	}
1759
+
1760
+	/**
1761
+	 * Redirects all calls to unexisting to plugin proxy.
1762
+	 *
1763
+	 * @param string $method the method name
1764
+	 * @param array  $args   array of arguments
1765
+	 *
1766
+	 * @return mixed
1767
+	 * @throws Exception
1768
+	 */
1769
+	public function __call($method, $args)
1770
+	{
1771
+		$proxy = $this->getPluginProxy();
1772
+		if (!$proxy) {
1773
+			throw new Exception('Call to undefined method ' . __CLASS__ . '::' . $method . '()');
1774
+		}
1775
+
1776
+		return call_user_func_array($proxy->getCallback($method), $args);
1777
+	}
1778
+
1779
+	/**
1780
+	 * Convert plugin name from `auto_escape` to `AutoEscape`.
1781
+	 * @param string $input
1782
+	 * @param string $separator
1783
+	 *
1784
+	 * @return mixed
1785
+	 */
1786
+	public static function toCamelCase($input, $separator = '_')
1787
+	{
1788
+		return join(array_map('ucfirst', explode($separator, $input)));
1789
+
1790
+		// TODO >= PHP5.4.32
1791
+		//return str_replace($separator, '', ucwords($input, $separator));
1792
+	}
1793 1793
 }
Please login to merge, or discard this patch.
Spacing   +35 added lines, -35 removed lines patch added patch discarded remove patch
@@ -383,8 +383,8 @@  discard block
 block discarded – undo
383 383
             }
384 384
 
385 385
             if ($doCache === true) {
386
-                $out = preg_replace('/(<%|%>|<\?php|<\?|\?>)/', '<?php /*' . $dynamicId . '*/ echo \'$1\'; ?>', $out);
387
-                if (!class_exists(self::NAMESPACE_PLUGINS_BLOCKS . 'PluginDynamic')) {
386
+                $out = preg_replace('/(<%|%>|<\?php|<\?|\?>)/', '<?php /*'.$dynamicId.'*/ echo \'$1\'; ?>', $out);
387
+                if (!class_exists(self::NAMESPACE_PLUGINS_BLOCKS.'PluginDynamic')) {
388 388
                     $this->getLoader()->loadPlugin('PluginDynamic');
389 389
                 }
390 390
                 $out = PluginDynamic::unescape($out, $dynamicId, $compiledTemplate);
@@ -585,7 +585,7 @@  discard block
 block discarded – undo
585 585
     public function addFilter($callback, $autoload = false)
586 586
     {
587 587
         if ($autoload) {
588
-            $class = self::NAMESPACE_PLUGINS_FILTERS . self::toCamelCase($callback);
588
+            $class = self::NAMESPACE_PLUGINS_FILTERS.self::toCamelCase($callback);
589 589
             if (!class_exists($class) && !function_exists($class)) {
590 590
                 try {
591 591
                     $this->getLoader()->loadPlugin($callback);
@@ -593,12 +593,12 @@  discard block
 block discarded – undo
593 593
                 catch (Exception $e) {
594 594
                     if (strstr($callback, self::NAMESPACE_PLUGINS_FILTERS)) {
595 595
                         throw new Exception(
596
-                            'Wrong filter name : ' . $callback . ', the "Filter" prefix should 
597
-                        not be used, please only use "' . str_replace('Filter', '', $callback) . '"'
596
+                            'Wrong filter name : '.$callback.', the "Filter" prefix should 
597
+                        not be used, please only use "' . str_replace('Filter', '', $callback).'"'
598 598
                         );
599 599
                     } else {
600 600
                         throw new Exception(
601
-                            'Wrong filter name : ' . $callback . ', when using autoload the filter must
601
+                            'Wrong filter name : '.$callback.', when using autoload the filter must
602 602
                          be in one of your plugin dir as "name.php" containig a class or function named
603 603
                          "Filter<name>"'
604 604
                         );
@@ -612,7 +612,7 @@  discard block
 block discarded – undo
612 612
                 $callback = $class;
613 613
             } else {
614 614
                 throw new Exception(
615
-                    'Wrong filter name : ' . $callback . ', when using autoload the filter must be in
615
+                    'Wrong filter name : '.$callback.', when using autoload the filter must be in
616 616
                 one of your plugin dir as "name.php" containig a class or function named "Filter<name>"'
617 617
                 );
618 618
             }
@@ -632,14 +632,14 @@  discard block
 block discarded – undo
632 632
      */
633 633
     public function removeFilter($callback)
634 634
     {
635
-        if (($index = array_search(self::NAMESPACE_PLUGINS_FILTERS. 'Filter' . self::toCamelCase($callback), $this->filters,
635
+        if (($index = array_search(self::NAMESPACE_PLUGINS_FILTERS.'Filter'.self::toCamelCase($callback), $this->filters,
636 636
                 true)) !==
637 637
             false) {
638 638
             unset($this->filters[$index]);
639 639
         } elseif (($index = array_search($callback, $this->filters, true)) !== false) {
640 640
             unset($this->filters[$index]);
641 641
         } else {
642
-            $class = self::NAMESPACE_PLUGINS_FILTERS . 'Filter' . $callback;
642
+            $class = self::NAMESPACE_PLUGINS_FILTERS.'Filter'.$callback;
643 643
             foreach ($this->filters as $index => $filter) {
644 644
                 if (is_array($filter) && $filter[0] instanceof $class) {
645 645
                     unset($this->filters[$index]);
@@ -762,7 +762,7 @@  discard block
 block discarded – undo
762 762
     public function getCacheDir()
763 763
     {
764 764
         if ($this->cacheDir === null) {
765
-            $this->setCacheDir(dirname(__DIR__) . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR);
765
+            $this->setCacheDir(dirname(__DIR__).DIRECTORY_SEPARATOR.'cache'.DIRECTORY_SEPARATOR);
766 766
         }
767 767
 
768 768
         return $this->cacheDir;
@@ -778,9 +778,9 @@  discard block
 block discarded – undo
778 778
      */
779 779
     public function setCacheDir($dir)
780 780
     {
781
-        $this->cacheDir = rtrim($dir, '/\\') . DIRECTORY_SEPARATOR;
781
+        $this->cacheDir = rtrim($dir, '/\\').DIRECTORY_SEPARATOR;
782 782
         if (is_writable($this->cacheDir) === false) {
783
-            throw new Exception('The cache directory must be writable, chmod "' . $this->cacheDir . '" to make it writable');
783
+            throw new Exception('The cache directory must be writable, chmod "'.$this->cacheDir.'" to make it writable');
784 784
         }
785 785
     }
786 786
 
@@ -792,7 +792,7 @@  discard block
 block discarded – undo
792 792
     public function getCompileDir()
793 793
     {
794 794
         if ($this->compileDir === null) {
795
-            $this->setCompileDir(dirname(__DIR__) . DIRECTORY_SEPARATOR . 'compiled' . DIRECTORY_SEPARATOR);
795
+            $this->setCompileDir(dirname(__DIR__).DIRECTORY_SEPARATOR.'compiled'.DIRECTORY_SEPARATOR);
796 796
         }
797 797
 
798 798
         return $this->compileDir;
@@ -808,9 +808,9 @@  discard block
 block discarded – undo
808 808
      */
809 809
     public function setCompileDir($dir)
810 810
     {
811
-        $this->compileDir = rtrim($dir, '/\\') . DIRECTORY_SEPARATOR;
811
+        $this->compileDir = rtrim($dir, '/\\').DIRECTORY_SEPARATOR;
812 812
         if (is_writable($this->compileDir) === false) {
813
-            throw new Exception('The compile directory must be writable, chmod "' . $this->compileDir . '" to make it writable');
813
+            throw new Exception('The compile directory must be writable, chmod "'.$this->compileDir.'" to make it writable');
814 814
         }
815 815
     }
816 816
 
@@ -834,9 +834,9 @@  discard block
 block discarded – undo
834 834
      */
835 835
     public function setTemplateDir($dir)
836 836
     {
837
-        $tmpDir = rtrim($dir, '/\\') . DIRECTORY_SEPARATOR;
837
+        $tmpDir = rtrim($dir, '/\\').DIRECTORY_SEPARATOR;
838 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 !');
839
+            throw new Exception('The template directory: "'.$tmpDir.'" does not exists, create the directory or specify an other location !');
840 840
         }
841 841
         $this->templateDir[] = $tmpDir;
842 842
     }
@@ -860,7 +860,7 @@  discard block
 block discarded – undo
860 860
      */
861 861
     public function setCacheTime($seconds)
862 862
     {
863
-        $this->cacheTime = (int)$seconds;
863
+        $this->cacheTime = (int) $seconds;
864 864
     }
865 865
 
866 866
     /**
@@ -884,7 +884,7 @@  discard block
 block discarded – undo
884 884
      */
885 885
     public function setCharset($charset)
886 886
     {
887
-        $this->charset = strtolower((string)$charset);
887
+        $this->charset = strtolower((string) $charset);
888 888
     }
889 889
 
890 890
     /**
@@ -1021,11 +1021,11 @@  discard block
 block discarded – undo
1021 1021
     public function clearCache($olderThan = - 1)
1022 1022
     {
1023 1023
         $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->getCacheDir()), \RecursiveIteratorIterator::SELF_FIRST);
1024
-        $expired  = time() - $olderThan;
1024
+        $expired  = time()-$olderThan;
1025 1025
         $count    = 0;
1026 1026
         foreach ($iterator as $file) {
1027 1027
             if ($file->isFile() && $file->getCTime() < $expired) {
1028
-                $count += unlink((string)$file) ? 1 : 0;
1028
+                $count += unlink((string) $file) ? 1 : 0;
1029 1029
             }
1030 1030
         }
1031 1031
 
@@ -1058,7 +1058,7 @@  discard block
 block discarded – undo
1058 1058
             return $class::templateFactory($this, $resourceId, $cacheTime, $cacheId, $compileId, $parentTemplate);
1059 1059
         }
1060 1060
 
1061
-        throw new Exception('Unknown resource type : ' . $resourceName);
1061
+        throw new Exception('Unknown resource type : '.$resourceName);
1062 1062
     }
1063 1063
 
1064 1064
     /**
@@ -1158,7 +1158,7 @@  discard block
 block discarded – undo
1158 1158
         if (!($tplIdentifier = $this->template->getResourceIdentifier())) {
1159 1159
             $tplIdentifier = $this->template->getResourceName();
1160 1160
         }
1161
-        trigger_error('Dwoo error (in ' . $tplIdentifier . ') : ' . $message, $level);
1161
+        trigger_error('Dwoo error (in '.$tplIdentifier.') : '.$message, $level);
1162 1162
     }
1163 1163
 
1164 1164
     /**
@@ -1174,7 +1174,7 @@  discard block
 block discarded – undo
1174 1174
         if (isset($this->plugins[$blockName])) {
1175 1175
             $class = $this->plugins[$blockName]['class'];
1176 1176
         } else {
1177
-            $class = self::NAMESPACE_PLUGINS_BLOCKS . 'Plugin' . self::toCamelCase($blockName);
1177
+            $class = self::NAMESPACE_PLUGINS_BLOCKS.'Plugin'.self::toCamelCase($blockName);
1178 1178
         }
1179 1179
 
1180 1180
         if ($this->curBlock !== null) {
@@ -1237,7 +1237,7 @@  discard block
 block discarded – undo
1237 1237
     {
1238 1238
         $index = array_search($block, $this->stack, true);
1239 1239
         if ($index !== false && $index > 0) {
1240
-            return $this->stack[$index - 1];
1240
+            return $this->stack[$index-1];
1241 1241
         }
1242 1242
 
1243 1243
         return false;
@@ -1255,7 +1255,7 @@  discard block
 block discarded – undo
1255 1255
         if (isset($this->plugins[$type])) {
1256 1256
             $type = $this->plugins[$type]['class'];
1257 1257
         } else {
1258
-            $type = self::NAMESPACE_PLUGINS_BLOCKS . 'Plugin_' . str_replace(self::NAMESPACE_PLUGINS_BLOCKS.'Plugin',
1258
+            $type = self::NAMESPACE_PLUGINS_BLOCKS.'Plugin_'.str_replace(self::NAMESPACE_PLUGINS_BLOCKS.'Plugin',
1259 1259
                     '', $type);
1260 1260
         }
1261 1261
 
@@ -1333,7 +1333,7 @@  discard block
 block discarded – undo
1333 1333
 
1334 1334
                 if (is_string($callback) === false) {
1335 1335
                     while (($i = array_shift($keys)) !== null) {
1336
-                        $out[] = call_user_func_array($callback, array(1 => $items[$i]) + $params);
1336
+                        $out[] = call_user_func_array($callback, array(1 => $items[$i])+$params);
1337 1337
                     }
1338 1338
                 } elseif ($cnt === 1) {
1339 1339
                     while (($i = array_shift($keys)) !== null) {
@@ -1349,7 +1349,7 @@  discard block
 block discarded – undo
1349 1349
                     }
1350 1350
                 } else {
1351 1351
                     while (($i = array_shift($keys)) !== null) {
1352
-                        $out[] = call_user_func_array($callback, array(1 => $items[$i]) + $params);
1352
+                        $out[] = call_user_func_array($callback, array(1 => $items[$i])+$params);
1353 1353
                     }
1354 1354
                 }
1355 1355
             } else {
@@ -1358,7 +1358,7 @@  discard block
 block discarded – undo
1358 1358
 
1359 1359
                 if (is_string($callback) === false) {
1360 1360
                     while (($i = array_shift($keys)) !== null) {
1361
-                        $out[] = call_user_func_array($callback, array($items[$i]) + $params);
1361
+                        $out[] = call_user_func_array($callback, array($items[$i])+$params);
1362 1362
                     }
1363 1363
                 } elseif ($cnt === 1) {
1364 1364
                     while (($i = array_shift($keys)) !== null) {
@@ -1378,7 +1378,7 @@  discard block
 block discarded – undo
1378 1378
                     }
1379 1379
                 } else {
1380 1380
                     while (($i = array_shift($keys)) !== null) {
1381
-                        $out[] = call_user_func_array($callback, array($items[$i]) + $params);
1381
+                        $out[] = call_user_func_array($callback, array($items[$i])+$params);
1382 1382
                     }
1383 1383
                 }
1384 1384
             }
@@ -1448,7 +1448,7 @@  discard block
 block discarded – undo
1448 1448
         $tree = $this->scopeTree;
1449 1449
         $cur  = $this->data;
1450 1450
 
1451
-        while ($parentLevels -- !== 0) {
1451
+        while ($parentLevels-- !== 0) {
1452 1452
             array_pop($tree);
1453 1453
         }
1454 1454
 
@@ -1486,7 +1486,7 @@  discard block
 block discarded – undo
1486 1486
                 } elseif ($varstr === '__' || $varstr === '_root') {
1487 1487
                     return $this->data;
1488 1488
                 } elseif ($varstr === '_' || $varstr === '_parent') {
1489
-                    $varstr = '.' . $varstr;
1489
+                    $varstr = '.'.$varstr;
1490 1490
                     $tree   = $this->scopeTree;
1491 1491
                     $cur    = $this->data;
1492 1492
                     array_pop($tree);
@@ -1512,7 +1512,7 @@  discard block
 block discarded – undo
1512 1512
             }
1513 1513
 
1514 1514
             if (substr($varstr, 0, 1) === '.') {
1515
-                $varstr = 'dwoo' . $varstr;
1515
+                $varstr = 'dwoo'.$varstr;
1516 1516
             }
1517 1517
 
1518 1518
             preg_match_all('#(\[|->|\.)?((?:[^.[\]-]|-(?!>))+)\]?#i', $varstr, $m);
@@ -1619,7 +1619,7 @@  discard block
 block discarded – undo
1619 1619
     public function assignInScope($value, $scope)
1620 1620
     {
1621 1621
         if (!is_string($scope)) {
1622
-            $this->triggerError('Assignments must be done into strings, (' . gettype($scope) . ') ' . var_export($scope, true) . ' given', E_USER_ERROR);
1622
+            $this->triggerError('Assignments must be done into strings, ('.gettype($scope).') '.var_export($scope, true).' given', E_USER_ERROR);
1623 1623
         }
1624 1624
         if (strstr($scope, '.') === false && strstr($scope, '->') === false) {
1625 1625
             $this->scope[$scope] = $value;
@@ -1770,7 +1770,7 @@  discard block
 block discarded – undo
1770 1770
     {
1771 1771
         $proxy = $this->getPluginProxy();
1772 1772
         if (!$proxy) {
1773
-            throw new Exception('Call to undefined method ' . __CLASS__ . '::' . $method . '()');
1773
+            throw new Exception('Call to undefined method '.__CLASS__.'::'.$method.'()');
1774 1774
         }
1775 1775
 
1776 1776
         return call_user_func_array($proxy->getCallback($method), $args);
Please login to merge, or discard this patch.