1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/* |
4
|
|
|
* This file is part of Twig. |
5
|
|
|
* |
6
|
|
|
* (c) 2009 Fabien Potencier |
7
|
|
|
* (c) 2009 Armin Ronacher |
8
|
|
|
* |
9
|
|
|
* For the full copyright and license information, please view the LICENSE |
10
|
|
|
* file that was distributed with this source code. |
11
|
|
|
*/ |
12
|
|
|
|
13
|
|
|
/** |
14
|
|
|
* Default base class for compiled templates. |
15
|
|
|
* |
16
|
|
|
* @author Fabien Potencier <[email protected]> |
17
|
|
|
*/ |
18
|
|
|
abstract class Twig_Template implements Twig_TemplateInterface |
|
|
|
|
19
|
|
|
{ |
20
|
|
|
protected static $cache = array(); |
21
|
|
|
|
22
|
|
|
protected $parent; |
23
|
|
|
protected $parents = array(); |
24
|
|
|
protected $env; |
25
|
|
|
protected $blocks; |
26
|
|
|
protected $traits; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* Constructor. |
30
|
|
|
* |
31
|
|
|
* @param Twig_Environment $env A Twig_Environment instance |
32
|
|
|
*/ |
33
|
|
|
public function __construct(Twig_Environment $env) |
34
|
|
|
{ |
35
|
|
|
$this->env = $env; |
36
|
|
|
$this->blocks = array(); |
37
|
|
|
$this->traits = array(); |
38
|
|
|
} |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* Returns the template name. |
42
|
|
|
* |
43
|
|
|
* @return string The template name |
44
|
|
|
*/ |
45
|
|
|
abstract public function getTemplateName(); |
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* {@inheritdoc} |
49
|
|
|
*/ |
50
|
|
|
public function getEnvironment() |
51
|
|
|
{ |
52
|
|
|
return $this->env; |
53
|
|
|
} |
54
|
|
|
|
55
|
|
|
/** |
56
|
|
|
* Returns the parent template. |
57
|
|
|
* |
58
|
|
|
* This method is for internal use only and should never be called |
59
|
|
|
* directly. |
60
|
|
|
* |
61
|
|
|
* @return Twig_TemplateInterface|false The parent template or false if there is no parent |
62
|
|
|
*/ |
63
|
|
|
public function getParent(array $context) |
64
|
|
|
{ |
65
|
|
|
if (null !== $this->parent) { |
66
|
|
|
return $this->parent; |
67
|
|
|
} |
68
|
|
|
|
69
|
|
|
try { |
70
|
|
|
$parent = $this->doGetParent($context); |
71
|
|
|
|
72
|
|
|
if (false === $parent) { |
73
|
|
|
return false; |
74
|
|
|
} |
75
|
|
|
|
76
|
|
|
if ($parent instanceof Twig_Template) { |
77
|
|
|
return $this->parents[$parent->getTemplateName()] = $parent; |
78
|
|
|
} |
79
|
|
|
|
80
|
|
|
if (!isset($this->parents[$parent])) { |
81
|
|
|
$this->parents[$parent] = $this->env->loadTemplate($parent); |
|
|
|
|
82
|
|
|
} |
83
|
|
|
} catch (Twig_Error_Loader $e) { |
84
|
|
|
$e->setTemplateFile(null); |
85
|
|
|
$e->guess(); |
86
|
|
|
|
87
|
|
|
throw $e; |
88
|
|
|
} |
89
|
|
|
|
90
|
|
|
return $this->parents[$parent]; |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
protected function doGetParent(array $context) |
|
|
|
|
94
|
|
|
{ |
95
|
|
|
return false; |
96
|
|
|
} |
97
|
|
|
|
98
|
|
|
public function isTraitable() |
99
|
|
|
{ |
100
|
|
|
return true; |
101
|
|
|
} |
102
|
|
|
|
103
|
|
|
/** |
104
|
|
|
* Displays a parent block. |
105
|
|
|
* |
106
|
|
|
* This method is for internal use only and should never be called |
107
|
|
|
* directly. |
108
|
|
|
* |
109
|
|
|
* @param string $name The block name to display from the parent |
110
|
|
|
* @param array $context The context |
111
|
|
|
* @param array $blocks The current set of blocks |
112
|
|
|
*/ |
113
|
|
|
public function displayParentBlock($name, array $context, array $blocks = array()) |
114
|
|
|
{ |
115
|
|
|
$name = (string) $name; |
116
|
|
|
|
117
|
|
|
if (isset($this->traits[$name])) { |
118
|
|
|
$this->traits[$name][0]->displayBlock($name, $context, $blocks, false); |
119
|
|
|
} elseif (false !== $parent = $this->getParent($context)) { |
120
|
|
|
$parent->displayBlock($name, $context, $blocks, false); |
|
|
|
|
121
|
|
|
} else { |
122
|
|
|
throw new Twig_Error_Runtime(sprintf('The template has no parent and no traits defining the "%s" block', $name), -1, $this->getTemplateName()); |
123
|
|
|
} |
124
|
|
|
} |
125
|
|
|
|
126
|
|
|
/** |
127
|
|
|
* Displays a block. |
128
|
|
|
* |
129
|
|
|
* This method is for internal use only and should never be called |
130
|
|
|
* directly. |
131
|
|
|
* |
132
|
|
|
* @param string $name The block name to display |
133
|
|
|
* @param array $context The context |
134
|
|
|
* @param array $blocks The current set of blocks |
135
|
|
|
* @param bool $useBlocks Whether to use the current set of blocks |
136
|
|
|
*/ |
137
|
|
|
public function displayBlock($name, array $context, array $blocks = array(), $useBlocks = true) |
138
|
|
|
{ |
139
|
|
|
$name = (string) $name; |
140
|
|
|
|
141
|
|
|
if ($useBlocks && isset($blocks[$name])) { |
142
|
|
|
$template = $blocks[$name][0]; |
143
|
|
|
$block = $blocks[$name][1]; |
144
|
|
|
} elseif (isset($this->blocks[$name])) { |
145
|
|
|
$template = $this->blocks[$name][0]; |
146
|
|
|
$block = $this->blocks[$name][1]; |
147
|
|
|
} else { |
148
|
|
|
$template = null; |
149
|
|
|
$block = null; |
150
|
|
|
} |
151
|
|
|
|
152
|
|
|
if (null !== $template) { |
153
|
|
|
try { |
154
|
|
|
$template->$block($context, $blocks); |
155
|
|
|
} catch (Twig_Error $e) { |
156
|
|
|
throw $e; |
157
|
|
|
} catch (Exception $e) { |
158
|
|
|
throw new Twig_Error_Runtime(sprintf('An exception has been thrown during the rendering of a template ("%s").', $e->getMessage()), -1, $template->getTemplateName(), $e); |
159
|
|
|
} |
160
|
|
|
} elseif (false !== $parent = $this->getParent($context)) { |
161
|
|
|
$parent->displayBlock($name, $context, array_merge($this->blocks, $blocks), false); |
|
|
|
|
162
|
|
|
} |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
/** |
166
|
|
|
* Renders a parent block. |
167
|
|
|
* |
168
|
|
|
* This method is for internal use only and should never be called |
169
|
|
|
* directly. |
170
|
|
|
* |
171
|
|
|
* @param string $name The block name to render from the parent |
172
|
|
|
* @param array $context The context |
173
|
|
|
* @param array $blocks The current set of blocks |
174
|
|
|
* |
175
|
|
|
* @return string The rendered block |
176
|
|
|
*/ |
177
|
|
|
public function renderParentBlock($name, array $context, array $blocks = array()) |
178
|
|
|
{ |
179
|
|
|
ob_start(); |
180
|
|
|
$this->displayParentBlock($name, $context, $blocks); |
181
|
|
|
|
182
|
|
|
return ob_get_clean(); |
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
/** |
186
|
|
|
* Renders a block. |
187
|
|
|
* |
188
|
|
|
* This method is for internal use only and should never be called |
189
|
|
|
* directly. |
190
|
|
|
* |
191
|
|
|
* @param string $name The block name to render |
192
|
|
|
* @param array $context The context |
193
|
|
|
* @param array $blocks The current set of blocks |
194
|
|
|
* @param bool $useBlocks Whether to use the current set of blocks |
195
|
|
|
* |
196
|
|
|
* @return string The rendered block |
197
|
|
|
*/ |
198
|
|
|
public function renderBlock($name, array $context, array $blocks = array(), $useBlocks = true) |
199
|
|
|
{ |
200
|
|
|
ob_start(); |
201
|
|
|
$this->displayBlock($name, $context, $blocks, $useBlocks); |
202
|
|
|
|
203
|
|
|
return ob_get_clean(); |
204
|
|
|
} |
205
|
|
|
|
206
|
|
|
/** |
207
|
|
|
* Returns whether a block exists or not. |
208
|
|
|
* |
209
|
|
|
* This method is for internal use only and should never be called |
210
|
|
|
* directly. |
211
|
|
|
* |
212
|
|
|
* This method does only return blocks defined in the current template |
213
|
|
|
* or defined in "used" traits. |
214
|
|
|
* |
215
|
|
|
* It does not return blocks from parent templates as the parent |
216
|
|
|
* template name can be dynamic, which is only known based on the |
217
|
|
|
* current context. |
218
|
|
|
* |
219
|
|
|
* @param string $name The block name |
220
|
|
|
* |
221
|
|
|
* @return bool true if the block exists, false otherwise |
222
|
|
|
*/ |
223
|
|
|
public function hasBlock($name) |
224
|
|
|
{ |
225
|
|
|
return isset($this->blocks[(string) $name]); |
226
|
|
|
} |
227
|
|
|
|
228
|
|
|
/** |
229
|
|
|
* Returns all block names. |
230
|
|
|
* |
231
|
|
|
* This method is for internal use only and should never be called |
232
|
|
|
* directly. |
233
|
|
|
* |
234
|
|
|
* @return array An array of block names |
235
|
|
|
* |
236
|
|
|
* @see hasBlock |
237
|
|
|
*/ |
238
|
|
|
public function getBlockNames() |
239
|
|
|
{ |
240
|
|
|
return array_keys($this->blocks); |
241
|
|
|
} |
242
|
|
|
|
243
|
|
|
/** |
244
|
|
|
* Returns all blocks. |
245
|
|
|
* |
246
|
|
|
* This method is for internal use only and should never be called |
247
|
|
|
* directly. |
248
|
|
|
* |
249
|
|
|
* @return array An array of blocks |
250
|
|
|
* |
251
|
|
|
* @see hasBlock |
252
|
|
|
*/ |
253
|
|
|
public function getBlocks() |
254
|
|
|
{ |
255
|
|
|
return $this->blocks; |
256
|
|
|
} |
257
|
|
|
|
258
|
|
|
/** |
259
|
|
|
* {@inheritdoc} |
260
|
|
|
*/ |
261
|
|
|
public function display(array $context, array $blocks = array()) |
262
|
|
|
{ |
263
|
|
|
$this->displayWithErrorHandling($this->env->mergeGlobals($context), array_merge($this->blocks, $blocks)); |
264
|
|
|
} |
265
|
|
|
|
266
|
|
|
/** |
267
|
|
|
* {@inheritdoc} |
268
|
|
|
*/ |
269
|
|
|
public function render(array $context) |
270
|
|
|
{ |
271
|
|
|
$level = ob_get_level(); |
272
|
|
|
ob_start(); |
273
|
|
|
try { |
274
|
|
|
$this->display($context); |
275
|
|
|
} catch (Exception $e) { |
276
|
|
|
while (ob_get_level() > $level) { |
277
|
|
|
ob_end_clean(); |
278
|
|
|
} |
279
|
|
|
|
280
|
|
|
throw $e; |
281
|
|
|
} |
282
|
|
|
|
283
|
|
|
return ob_get_clean(); |
284
|
|
|
} |
285
|
|
|
|
286
|
|
|
protected function displayWithErrorHandling(array $context, array $blocks = array()) |
287
|
|
|
{ |
288
|
|
|
try { |
289
|
|
|
$this->doDisplay($context, $blocks); |
290
|
|
|
} catch (Twig_Error $e) { |
291
|
|
|
if (!$e->getTemplateFile()) { |
|
|
|
|
292
|
|
|
$e->setTemplateFile($this->getTemplateName()); |
293
|
|
|
} |
294
|
|
|
|
295
|
|
|
// this is mostly useful for Twig_Error_Loader exceptions |
296
|
|
|
// see Twig_Error_Loader |
297
|
|
|
if (false === $e->getTemplateLine()) { |
298
|
|
|
$e->setTemplateLine(-1); |
299
|
|
|
$e->guess(); |
300
|
|
|
} |
301
|
|
|
|
302
|
|
|
throw $e; |
303
|
|
|
} catch (Exception $e) { |
304
|
|
|
throw new Twig_Error_Runtime(sprintf('An exception has been thrown during the rendering of a template ("%s").', $e->getMessage()), -1, $this->getTemplateName(), $e); |
305
|
|
|
} |
306
|
|
|
} |
307
|
|
|
|
308
|
|
|
/** |
309
|
|
|
* Auto-generated method to display the template with the given context. |
310
|
|
|
* |
311
|
|
|
* @param array $context An array of parameters to pass to the template |
312
|
|
|
* @param array $blocks An array of blocks to pass to the template |
313
|
|
|
*/ |
314
|
|
|
abstract protected function doDisplay(array $context, array $blocks = array()); |
|
|
|
|
315
|
|
|
|
316
|
|
|
/** |
317
|
|
|
* Returns a variable from the context. |
318
|
|
|
* |
319
|
|
|
* This method is for internal use only and should never be called |
320
|
|
|
* directly. |
321
|
|
|
* |
322
|
|
|
* This method should not be overridden in a sub-class as this is an |
323
|
|
|
* implementation detail that has been introduced to optimize variable |
324
|
|
|
* access for versions of PHP before 5.4. This is not a way to override |
325
|
|
|
* the way to get a variable value. |
326
|
|
|
* |
327
|
|
|
* @param array $context The context |
328
|
|
|
* @param string $item The variable to return from the context |
329
|
|
|
* @param bool $ignoreStrictCheck Whether to ignore the strict variable check or not |
330
|
|
|
* |
331
|
|
|
* @return The content of the context variable |
332
|
|
|
* |
333
|
|
|
* @throws Twig_Error_Runtime if the variable does not exist and Twig is running in strict mode |
334
|
|
|
*/ |
335
|
|
|
final protected function getContext($context, $item, $ignoreStrictCheck = false) |
336
|
|
|
{ |
337
|
|
|
if (!array_key_exists($item, $context)) { |
338
|
|
|
if ($ignoreStrictCheck || !$this->env->isStrictVariables()) { |
339
|
|
|
return; |
340
|
|
|
} |
341
|
|
|
|
342
|
|
|
throw new Twig_Error_Runtime(sprintf('Variable "%s" does not exist', $item), -1, $this->getTemplateName()); |
343
|
|
|
} |
344
|
|
|
|
345
|
|
|
return $context[$item]; |
346
|
|
|
} |
347
|
|
|
|
348
|
|
|
/** |
349
|
|
|
* Returns the attribute value for a given array/object. |
350
|
|
|
* |
351
|
|
|
* @param mixed $object The object or array from where to get the item |
352
|
|
|
* @param mixed $item The item to get from the array or object |
353
|
|
|
* @param array $arguments An array of arguments to pass if the item is an object method |
354
|
|
|
* @param string $type The type of attribute (@see Twig_Template constants) |
355
|
|
|
* @param bool $isDefinedTest Whether this is only a defined check |
356
|
|
|
* @param bool $ignoreStrictCheck Whether to ignore the strict attribute check or not |
357
|
|
|
* |
358
|
|
|
* @return mixed The attribute value, or a Boolean when $isDefinedTest is true, or null when the attribute is not set and $ignoreStrictCheck is true |
359
|
|
|
* |
360
|
|
|
* @throws Twig_Error_Runtime if the attribute does not exist and Twig is running in strict mode and $isDefinedTest is false |
361
|
|
|
*/ |
362
|
|
|
protected function getAttribute($object, $item, array $arguments = array(), $type = Twig_Template::ANY_CALL, $isDefinedTest = false, $ignoreStrictCheck = false) |
|
|
|
|
363
|
|
|
{ |
364
|
|
|
// array |
365
|
|
|
if (Twig_Template::METHOD_CALL !== $type) { |
|
|
|
|
366
|
|
|
$arrayItem = is_bool($item) || is_float($item) ? (int) $item : $item; |
367
|
|
|
|
368
|
|
|
if ((is_array($object) && array_key_exists($arrayItem, $object)) |
369
|
|
|
|| ($object instanceof ArrayAccess && isset($object[$arrayItem])) |
370
|
|
|
) { |
371
|
|
|
if ($isDefinedTest) { |
372
|
|
|
return true; |
373
|
|
|
} |
374
|
|
|
|
375
|
|
|
return $object[$arrayItem]; |
376
|
|
|
} |
377
|
|
|
|
378
|
|
|
if (Twig_Template::ARRAY_CALL === $type || !is_object($object)) { |
|
|
|
|
379
|
|
|
if ($isDefinedTest) { |
380
|
|
|
return false; |
381
|
|
|
} |
382
|
|
|
|
383
|
|
|
if ($ignoreStrictCheck || !$this->env->isStrictVariables()) { |
384
|
|
|
return; |
385
|
|
|
} |
386
|
|
|
|
387
|
|
|
if ($object instanceof ArrayAccess) { |
388
|
|
|
$message = sprintf('Key "%s" in object with ArrayAccess of class "%s" does not exist', $arrayItem, get_class($object)); |
389
|
|
|
} elseif (is_object($object)) { |
390
|
|
|
$message = sprintf('Impossible to access a key "%s" on an object of class "%s" that does not implement ArrayAccess interface', $item, get_class($object)); |
391
|
|
|
} elseif (is_array($object)) { |
392
|
|
|
if (empty($object)) { |
393
|
|
|
$message = sprintf('Key "%s" does not exist as the array is empty', $arrayItem); |
394
|
|
|
} else { |
395
|
|
|
$message = sprintf('Key "%s" for array with keys "%s" does not exist', $arrayItem, implode(', ', array_keys($object))); |
396
|
|
|
} |
397
|
|
|
} elseif (Twig_Template::ARRAY_CALL === $type) { |
|
|
|
|
398
|
|
|
$message = sprintf('Impossible to access a key ("%s") on a %s variable ("%s")', $item, gettype($object), $object); |
399
|
|
|
} else { |
400
|
|
|
$message = sprintf('Impossible to access an attribute ("%s") on a %s variable ("%s")', $item, gettype($object), $object); |
401
|
|
|
} |
402
|
|
|
|
403
|
|
|
throw new Twig_Error_Runtime($message, -1, $this->getTemplateName()); |
404
|
|
|
} |
405
|
|
|
} |
406
|
|
|
|
407
|
|
|
if (!is_object($object)) { |
408
|
|
|
if ($isDefinedTest) { |
409
|
|
|
return false; |
410
|
|
|
} |
411
|
|
|
|
412
|
|
|
if ($ignoreStrictCheck || !$this->env->isStrictVariables()) { |
413
|
|
|
return; |
414
|
|
|
} |
415
|
|
|
|
416
|
|
|
throw new Twig_Error_Runtime(sprintf('Impossible to invoke a method ("%s") on a %s variable ("%s")', $item, gettype($object), $object), -1, $this->getTemplateName()); |
417
|
|
|
} |
418
|
|
|
|
419
|
|
|
// object property |
420
|
|
|
if (Twig_Template::METHOD_CALL !== $type) { |
|
|
|
|
421
|
|
|
if (isset($object->$item) || array_key_exists((string) $item, $object)) { |
422
|
|
|
if ($isDefinedTest) { |
423
|
|
|
return true; |
424
|
|
|
} |
425
|
|
|
|
426
|
|
|
if ($this->env->hasExtension('sandbox')) { |
427
|
|
|
$this->env->getExtension('sandbox')->checkPropertyAllowed($object, $item); |
|
|
|
|
428
|
|
|
} |
429
|
|
|
|
430
|
|
|
return $object->$item; |
431
|
|
|
} |
432
|
|
|
} |
433
|
|
|
|
434
|
|
|
$class = get_class($object); |
435
|
|
|
|
436
|
|
|
// object method |
437
|
|
|
if (!isset(self::$cache[$class]['methods'])) { |
438
|
|
|
self::$cache[$class]['methods'] = array_change_key_case(array_flip(get_class_methods($object))); |
439
|
|
|
} |
440
|
|
|
|
441
|
|
|
$call = false; |
442
|
|
|
$lcItem = strtolower($item); |
443
|
|
|
if (isset(self::$cache[$class]['methods'][$lcItem])) { |
444
|
|
|
$method = (string) $item; |
445
|
|
|
} elseif (isset(self::$cache[$class]['methods']['get'.$lcItem])) { |
446
|
|
|
$method = 'get'.$item; |
447
|
|
|
} elseif (isset(self::$cache[$class]['methods']['is'.$lcItem])) { |
448
|
|
|
$method = 'is'.$item; |
449
|
|
|
} elseif (isset(self::$cache[$class]['methods']['__call'])) { |
450
|
|
|
$method = (string) $item; |
451
|
|
|
$call = true; |
452
|
|
|
} else { |
453
|
|
|
if ($isDefinedTest) { |
454
|
|
|
return false; |
455
|
|
|
} |
456
|
|
|
|
457
|
|
|
if ($ignoreStrictCheck || !$this->env->isStrictVariables()) { |
458
|
|
|
return; |
459
|
|
|
} |
460
|
|
|
|
461
|
|
|
throw new Twig_Error_Runtime(sprintf('Method "%s" for object "%s" does not exist', $item, get_class($object)), -1, $this->getTemplateName()); |
462
|
|
|
} |
463
|
|
|
|
464
|
|
|
if ($isDefinedTest) { |
465
|
|
|
return true; |
466
|
|
|
} |
467
|
|
|
|
468
|
|
|
if ($this->env->hasExtension('sandbox')) { |
469
|
|
|
$this->env->getExtension('sandbox')->checkMethodAllowed($object, $method); |
|
|
|
|
470
|
|
|
} |
471
|
|
|
|
472
|
|
|
// Some objects throw exceptions when they have __call, and the method we try |
473
|
|
|
// to call is not supported. If ignoreStrictCheck is true, we should return null. |
474
|
|
|
try { |
475
|
|
|
$ret = call_user_func_array(array($object, $method), $arguments); |
476
|
|
|
} catch (BadMethodCallException $e) { |
477
|
|
|
if ($call && ($ignoreStrictCheck || !$this->env->isStrictVariables())) { |
478
|
|
|
return; |
479
|
|
|
} |
480
|
|
|
throw $e; |
481
|
|
|
} |
482
|
|
|
|
483
|
|
|
// useful when calling a template method from a template |
484
|
|
|
// this is not supported but unfortunately heavily used in the Symfony profiler |
485
|
|
|
if ($object instanceof Twig_TemplateInterface) { |
486
|
|
|
return $ret === '' ? '' : new Twig_Markup($ret, $this->env->getCharset()); |
487
|
|
|
} |
488
|
|
|
|
489
|
|
|
return $ret; |
490
|
|
|
} |
491
|
|
|
} |
492
|
|
|
|
This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.