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