Project

General

Profile

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 <fabien@symfony.com>
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 = array();
26
    protected $traits = array();
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
    }
37

    
38
    /**
39
     * Returns the template name.
40
     *
41
     * @return string The template name
42
     */
43
    abstract public function getTemplateName();
44

    
45
    /**
46
     * @deprecated since 1.20 (to be removed in 2.0)
47
     */
48
    public function getEnvironment()
49
    {
50
        @trigger_error('The '.__METHOD__.' method is deprecated since version 1.20 and will be removed in 2.0.', E_USER_DEPRECATED);
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
     * @param array $context
62
     *
63
     * @return Twig_TemplateInterface|false The parent template or false if there is no parent
64
     *
65
     * @internal
66
     */
67
    public function getParent(array $context)
68
    {
69
        if (null !== $this->parent) {
70
            return $this->parent;
71
        }
72

    
73
        try {
74
            $parent = $this->doGetParent($context);
75

    
76
            if (false === $parent) {
77
                return false;
78
            }
79

    
80
            if ($parent instanceof self) {
81
                return $this->parents[$parent->getTemplateName()] = $parent;
82
            }
83

    
84
            if (!isset($this->parents[$parent])) {
85
                $this->parents[$parent] = $this->loadTemplate($parent);
86
            }
87
        } catch (Twig_Error_Loader $e) {
88
            $e->setTemplateFile(null);
89
            $e->guess();
90

    
91
            throw $e;
92
        }
93

    
94
        return $this->parents[$parent];
95
    }
96

    
97
    protected function doGetParent(array $context)
98
    {
99
        return false;
100
    }
101

    
102
    public function isTraitable()
103
    {
104
        return true;
105
    }
106

    
107
    /**
108
     * Displays a parent block.
109
     *
110
     * This method is for internal use only and should never be called
111
     * directly.
112
     *
113
     * @param string $name    The block name to display from the parent
114
     * @param array  $context The context
115
     * @param array  $blocks  The current set of blocks
116
     *
117
     * @internal
118
     */
119
    public function displayParentBlock($name, array $context, array $blocks = array())
120
    {
121
        $name = (string) $name;
122

    
123
        if (isset($this->traits[$name])) {
124
            $this->traits[$name][0]->displayBlock($name, $context, $blocks, false);
125
        } elseif (false !== $parent = $this->getParent($context)) {
126
            $parent->displayBlock($name, $context, $blocks, false);
127
        } else {
128
            throw new Twig_Error_Runtime(sprintf('The template has no parent and no traits defining the "%s" block', $name), -1, $this->getTemplateName());
129
        }
130
    }
131

    
132
    /**
133
     * Displays a block.
134
     *
135
     * This method is for internal use only and should never be called
136
     * directly.
137
     *
138
     * @param string $name      The block name to display
139
     * @param array  $context   The context
140
     * @param array  $blocks    The current set of blocks
141
     * @param bool   $useBlocks Whether to use the current set of blocks
142
     *
143
     * @internal
144
     */
145
    public function displayBlock($name, array $context, array $blocks = array(), $useBlocks = true)
146
    {
147
        $name = (string) $name;
148

    
149
        if ($useBlocks && isset($blocks[$name])) {
150
            $template = $blocks[$name][0];
151
            $block = $blocks[$name][1];
152
        } elseif (isset($this->blocks[$name])) {
153
            $template = $this->blocks[$name][0];
154
            $block = $this->blocks[$name][1];
155
        } else {
156
            $template = null;
157
            $block = null;
158
        }
159

    
160
        if (null !== $template) {
161
            // avoid RCEs when sandbox is enabled
162
            if (!$template instanceof self) {
163
                throw new LogicException('A block must be a method on a Twig_Template instance.');
164
            }
165

    
166
            try {
167
                $template->$block($context, $blocks);
168
            } catch (Twig_Error $e) {
169
                if (!$e->getTemplateFile()) {
170
                    $e->setTemplateFile($template->getTemplateName());
171
                }
172

    
173
                // this is mostly useful for Twig_Error_Loader exceptions
174
                // see Twig_Error_Loader
175
                if (false === $e->getTemplateLine()) {
176
                    $e->setTemplateLine(-1);
177
                    $e->guess();
178
                }
179

    
180
                throw $e;
181
            } catch (Exception $e) {
182
                throw new Twig_Error_Runtime(sprintf('An exception has been thrown during the rendering of a template ("%s").', $e->getMessage()), -1, $template->getTemplateName(), $e);
183
            }
184
        } elseif (false !== $parent = $this->getParent($context)) {
185
            $parent->displayBlock($name, $context, array_merge($this->blocks, $blocks), false);
186
        }
187
    }
188

    
189
    /**
190
     * Renders a parent block.
191
     *
192
     * This method is for internal use only and should never be called
193
     * directly.
194
     *
195
     * @param string $name    The block name to render from the parent
196
     * @param array  $context The context
197
     * @param array  $blocks  The current set of blocks
198
     *
199
     * @return string The rendered block
200
     *
201
     * @internal
202
     */
203
    public function renderParentBlock($name, array $context, array $blocks = array())
204
    {
205
        ob_start();
206
        $this->displayParentBlock($name, $context, $blocks);
207

    
208
        return ob_get_clean();
209
    }
210

    
211
    /**
212
     * Renders a block.
213
     *
214
     * This method is for internal use only and should never be called
215
     * directly.
216
     *
217
     * @param string $name      The block name to render
218
     * @param array  $context   The context
219
     * @param array  $blocks    The current set of blocks
220
     * @param bool   $useBlocks Whether to use the current set of blocks
221
     *
222
     * @return string The rendered block
223
     *
224
     * @internal
225
     */
226
    public function renderBlock($name, array $context, array $blocks = array(), $useBlocks = true)
227
    {
228
        ob_start();
229
        $this->displayBlock($name, $context, $blocks, $useBlocks);
230

    
231
        return ob_get_clean();
232
    }
233

    
234
    /**
235
     * Returns whether a block exists or not.
236
     *
237
     * This method is for internal use only and should never be called
238
     * directly.
239
     *
240
     * This method does only return blocks defined in the current template
241
     * or defined in "used" traits.
242
     *
243
     * It does not return blocks from parent templates as the parent
244
     * template name can be dynamic, which is only known based on the
245
     * current context.
246
     *
247
     * @param string $name The block name
248
     *
249
     * @return bool true if the block exists, false otherwise
250
     *
251
     * @internal
252
     */
253
    public function hasBlock($name)
254
    {
255
        return isset($this->blocks[(string) $name]);
256
    }
257

    
258
    /**
259
     * Returns all block names.
260
     *
261
     * This method is for internal use only and should never be called
262
     * directly.
263
     *
264
     * @return array An array of block names
265
     *
266
     * @see hasBlock
267
     *
268
     * @internal
269
     */
270
    public function getBlockNames()
271
    {
272
        return array_keys($this->blocks);
273
    }
274

    
275
    protected function loadTemplate($template, $templateName = null, $line = null, $index = null)
276
    {
277
        try {
278
            if (is_array($template)) {
279
                return $this->env->resolveTemplate($template);
280
            }
281

    
282
            if ($template instanceof self) {
283
                return $template;
284
            }
285

    
286
            return $this->env->loadTemplate($template, $index);
287
        } catch (Twig_Error $e) {
288
            if (!$e->getTemplateFile()) {
289
                $e->setTemplateFile($templateName ? $templateName : $this->getTemplateName());
290
            }
291

    
292
            if ($e->getTemplateLine()) {
293
                throw $e;
294
            }
295

    
296
            if (!$line) {
297
                $e->guess();
298
            } else {
299
                $e->setTemplateLine($line);
300
            }
301

    
302
            throw $e;
303
        }
304
    }
305

    
306
    /**
307
     * Returns all blocks.
308
     *
309
     * This method is for internal use only and should never be called
310
     * directly.
311
     *
312
     * @return array An array of blocks
313
     *
314
     * @see hasBlock
315
     *
316
     * @internal
317
     */
318
    public function getBlocks()
319
    {
320
        return $this->blocks;
321
    }
322

    
323
    /**
324
     * Returns the template source code.
325
     *
326
     * @return string|null The template source code or null if it is not available
327
     */
328
    public function getSource()
329
    {
330
        $reflector = new ReflectionClass($this);
331
        $file = $reflector->getFileName();
332

    
333
        if (!file_exists($file)) {
334
            return;
335
        }
336

    
337
        $source = file($file, FILE_IGNORE_NEW_LINES);
338
        array_splice($source, 0, $reflector->getEndLine());
339

    
340
        $i = 0;
341
        while (isset($source[$i]) && '/* */' === substr_replace($source[$i], '', 3, -2)) {
342
            $source[$i] = str_replace('*//* ', '*/', substr($source[$i], 3, -2));
343
            ++$i;
344
        }
345
        array_splice($source, $i);
346

    
347
        return implode("\n", $source);
348
    }
349

    
350
    /**
351
     * {@inheritdoc}
352
     */
353
    public function display(array $context, array $blocks = array())
354
    {
355
        $this->displayWithErrorHandling($this->env->mergeGlobals($context), array_merge($this->blocks, $blocks));
356
    }
357

    
358
    /**
359
     * {@inheritdoc}
360
     */
361
    public function render(array $context)
362
    {
363
        $level = ob_get_level();
364
        ob_start();
365
        try {
366
            $this->display($context);
367
        } catch (Exception $e) {
368
            while (ob_get_level() > $level) {
369
                ob_end_clean();
370
            }
371

    
372
            throw $e;
373
        }
374

    
375
        return ob_get_clean();
376
    }
377

    
378
    protected function displayWithErrorHandling(array $context, array $blocks = array())
379
    {
380
        try {
381
            $this->doDisplay($context, $blocks);
382
        } catch (Twig_Error $e) {
383
            if (!$e->getTemplateFile()) {
384
                $e->setTemplateFile($this->getTemplateName());
385
            }
386

    
387
            // this is mostly useful for Twig_Error_Loader exceptions
388
            // see Twig_Error_Loader
389
            if (false === $e->getTemplateLine()) {
390
                $e->setTemplateLine(-1);
391
                $e->guess();
392
            }
393

    
394
            throw $e;
395
        } catch (Exception $e) {
396
            throw new Twig_Error_Runtime(sprintf('An exception has been thrown during the rendering of a template ("%s").', $e->getMessage()), -1, $this->getTemplateName(), $e);
397
        }
398
    }
399

    
400
    /**
401
     * Auto-generated method to display the template with the given context.
402
     *
403
     * @param array $context An array of parameters to pass to the template
404
     * @param array $blocks  An array of blocks to pass to the template
405
     */
406
    abstract protected function doDisplay(array $context, array $blocks = array());
407

    
408
    /**
409
     * Returns a variable from the context.
410
     *
411
     * This method is for internal use only and should never be called
412
     * directly.
413
     *
414
     * This method should not be overridden in a sub-class as this is an
415
     * implementation detail that has been introduced to optimize variable
416
     * access for versions of PHP before 5.4. This is not a way to override
417
     * the way to get a variable value.
418
     *
419
     * @param array  $context           The context
420
     * @param string $item              The variable to return from the context
421
     * @param bool   $ignoreStrictCheck Whether to ignore the strict variable check or not
422
     *
423
     * @return mixed The content of the context variable
424
     *
425
     * @throws Twig_Error_Runtime if the variable does not exist and Twig is running in strict mode
426
     *
427
     * @internal
428
     */
429
    final protected function getContext($context, $item, $ignoreStrictCheck = false)
430
    {
431
        if (!array_key_exists($item, $context)) {
432
            if ($ignoreStrictCheck || !$this->env->isStrictVariables()) {
433
                return;
434
            }
435

    
436
            throw new Twig_Error_Runtime(sprintf('Variable "%s" does not exist', $item), -1, $this->getTemplateName());
437
        }
438

    
439
        return $context[$item];
440
    }
441

    
442
    /**
443
     * Returns the attribute value for a given array/object.
444
     *
445
     * @param mixed  $object            The object or array from where to get the item
446
     * @param mixed  $item              The item to get from the array or object
447
     * @param array  $arguments         An array of arguments to pass if the item is an object method
448
     * @param string $type              The type of attribute (@see Twig_Template constants)
449
     * @param bool   $isDefinedTest     Whether this is only a defined check
450
     * @param bool   $ignoreStrictCheck Whether to ignore the strict attribute check or not
451
     *
452
     * @return mixed The attribute value, or a Boolean when $isDefinedTest is true, or null when the attribute is not set and $ignoreStrictCheck is true
453
     *
454
     * @throws Twig_Error_Runtime if the attribute does not exist and Twig is running in strict mode and $isDefinedTest is false
455
     */
456
    protected function getAttribute($object, $item, array $arguments = array(), $type = self::ANY_CALL, $isDefinedTest = false, $ignoreStrictCheck = false)
457
    {
458
        // array
459
        if (self::METHOD_CALL !== $type) {
460
            $arrayItem = is_bool($item) || is_float($item) ? (int) $item : $item;
461

    
462
            if ((is_array($object) && array_key_exists($arrayItem, $object))
463
                || ($object instanceof ArrayAccess && isset($object[$arrayItem]))
464
            ) {
465
                if ($isDefinedTest) {
466
                    return true;
467
                }
468

    
469
                return $object[$arrayItem];
470
            }
471

    
472
            if (self::ARRAY_CALL === $type || !is_object($object)) {
473
                if ($isDefinedTest) {
474
                    return false;
475
                }
476

    
477
                if ($ignoreStrictCheck || !$this->env->isStrictVariables()) {
478
                    return;
479
                }
480

    
481
                if ($object instanceof ArrayAccess) {
482
                    $message = sprintf('Key "%s" in object with ArrayAccess of class "%s" does not exist', $arrayItem, get_class($object));
483
                } elseif (is_object($object)) {
484
                    $message = sprintf('Impossible to access a key "%s" on an object of class "%s" that does not implement ArrayAccess interface', $item, get_class($object));
485
                } elseif (is_array($object)) {
486
                    if (empty($object)) {
487
                        $message = sprintf('Key "%s" does not exist as the array is empty', $arrayItem);
488
                    } else {
489
                        $message = sprintf('Key "%s" for array with keys "%s" does not exist', $arrayItem, implode(', ', array_keys($object)));
490
                    }
491
                } elseif (self::ARRAY_CALL === $type) {
492
                    if (null === $object) {
493
                        $message = sprintf('Impossible to access a key ("%s") on a null variable', $item);
494
                    } else {
495
                        $message = sprintf('Impossible to access a key ("%s") on a %s variable ("%s")', $item, gettype($object), $object);
496
                    }
497
                } elseif (null === $object) {
498
                    $message = sprintf('Impossible to access an attribute ("%s") on a null variable', $item);
499
                } else {
500
                    $message = sprintf('Impossible to access an attribute ("%s") on a %s variable ("%s")', $item, gettype($object), $object);
501
                }
502

    
503
                throw new Twig_Error_Runtime($message, -1, $this->getTemplateName());
504
            }
505
        }
506

    
507
        if (!is_object($object)) {
508
            if ($isDefinedTest) {
509
                return false;
510
            }
511

    
512
            if ($ignoreStrictCheck || !$this->env->isStrictVariables()) {
513
                return;
514
            }
515

    
516
            if (null === $object) {
517
                $message = sprintf('Impossible to invoke a method ("%s") on a null variable', $item);
518
            } else {
519
                $message = sprintf('Impossible to invoke a method ("%s") on a %s variable ("%s")', $item, gettype($object), $object);
520
            }
521

    
522
            throw new Twig_Error_Runtime($message, -1, $this->getTemplateName());
523
        }
524

    
525
        // object property
526
        if (self::METHOD_CALL !== $type && !$object instanceof self) { // Twig_Template does not have public properties, and we don't want to allow access to internal ones
527
            if (isset($object->$item) || array_key_exists((string) $item, $object)) {
528
                if ($isDefinedTest) {
529
                    return true;
530
                }
531

    
532
                if ($this->env->hasExtension('sandbox')) {
533
                    $this->env->getExtension('sandbox')->checkPropertyAllowed($object, $item);
534
                }
535

    
536
                return $object->$item;
537
            }
538
        }
539

    
540
        $class = get_class($object);
541

    
542
        // object method
543
        if (!isset(self::$cache[$class]['methods'])) {
544
            // get_class_methods returns all methods accessible in the scope, but we only want public ones to be accessible in templates
545
            if ($object instanceof self) {
546
                $ref = new ReflectionClass($class);
547
                $methods = array();
548

    
549
                foreach ($ref->getMethods(ReflectionMethod::IS_PUBLIC) as $refMethod) {
550
                    $methodName = strtolower($refMethod->name);
551

    
552
                    // Accessing the environment from templates is forbidden to prevent untrusted changes to the environment
553
                    if ('getenvironment' !== $methodName) {
554
                        $methods[$methodName] = true;
555
                    }
556
                }
557

    
558
                self::$cache[$class]['methods'] = $methods;
559
            } else {
560
                self::$cache[$class]['methods'] = array_change_key_case(array_flip(get_class_methods($object)));
561
            }
562
        }
563

    
564
        $call = false;
565
        $lcItem = strtolower($item);
566
        if (isset(self::$cache[$class]['methods'][$lcItem])) {
567
            $method = (string) $item;
568
        } elseif (isset(self::$cache[$class]['methods']['get'.$lcItem])) {
569
            $method = 'get'.$item;
570
        } elseif (isset(self::$cache[$class]['methods']['is'.$lcItem])) {
571
            $method = 'is'.$item;
572
        } elseif (isset(self::$cache[$class]['methods']['__call'])) {
573
            $method = (string) $item;
574
            $call = true;
575
        } else {
576
            if ($isDefinedTest) {
577
                return false;
578
            }
579

    
580
            if ($ignoreStrictCheck || !$this->env->isStrictVariables()) {
581
                return;
582
            }
583

    
584
            throw new Twig_Error_Runtime(sprintf('Method "%s" for object "%s" does not exist', $item, get_class($object)), -1, $this->getTemplateName());
585
        }
586

    
587
        if ($isDefinedTest) {
588
            return true;
589
        }
590

    
591
        if ($this->env->hasExtension('sandbox')) {
592
            $this->env->getExtension('sandbox')->checkMethodAllowed($object, $method);
593
        }
594

    
595
        // Some objects throw exceptions when they have __call, and the method we try
596
        // to call is not supported. If ignoreStrictCheck is true, we should return null.
597
        try {
598
            $ret = call_user_func_array(array($object, $method), $arguments);
599
        } catch (BadMethodCallException $e) {
600
            if ($call && ($ignoreStrictCheck || !$this->env->isStrictVariables())) {
601
                return;
602
            }
603
            throw $e;
604
        }
605

    
606
        // useful when calling a template method from a template
607
        // this is not supported but unfortunately heavily used in the Symfony profiler
608
        if ($object instanceof Twig_TemplateInterface) {
609
            return $ret === '' ? '' : new Twig_Markup($ret, $this->env->getCharset());
610
        }
611

    
612
        return $ret;
613
    }
614
}
(33-33/43)