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