Project

General

Profile

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