Version 1
[yaffs-website] / vendor / twig / twig / lib / Twig / Environment.php
1 <?php
2
3 /*
4  * This file is part of Twig.
5  *
6  * (c) Fabien Potencier
7  *
8  * For the full copyright and license information, please view the LICENSE
9  * file that was distributed with this source code.
10  */
11
12 /**
13  * Stores the Twig configuration.
14  *
15  * @author Fabien Potencier <fabien@symfony.com>
16  */
17 class Twig_Environment
18 {
19     const VERSION = '1.33.0';
20     const VERSION_ID = 13300;
21     const MAJOR_VERSION = 1;
22     const MINOR_VERSION = 33;
23     const RELEASE_VERSION = 0;
24     const EXTRA_VERSION = '';
25
26     protected $charset;
27     protected $loader;
28     protected $debug;
29     protected $autoReload;
30     protected $cache;
31     protected $lexer;
32     protected $parser;
33     protected $compiler;
34     protected $baseTemplateClass;
35     protected $extensions;
36     protected $parsers;
37     protected $visitors;
38     protected $filters;
39     protected $tests;
40     protected $functions;
41     protected $globals;
42     protected $runtimeInitialized = false;
43     protected $extensionInitialized = false;
44     protected $loadedTemplates;
45     protected $strictVariables;
46     protected $unaryOperators;
47     protected $binaryOperators;
48     protected $templateClassPrefix = '__TwigTemplate_';
49     protected $functionCallbacks = array();
50     protected $filterCallbacks = array();
51     protected $staging;
52
53     private $originalCache;
54     private $bcWriteCacheFile = false;
55     private $bcGetCacheFilename = false;
56     private $lastModifiedExtension = 0;
57     private $extensionsByClass = array();
58     private $runtimeLoaders = array();
59     private $runtimes = array();
60     private $optionsHash;
61
62     /**
63      * Constructor.
64      *
65      * Available options:
66      *
67      *  * debug: When set to true, it automatically set "auto_reload" to true as
68      *           well (default to false).
69      *
70      *  * charset: The charset used by the templates (default to UTF-8).
71      *
72      *  * base_template_class: The base template class to use for generated
73      *                         templates (default to Twig_Template).
74      *
75      *  * cache: An absolute path where to store the compiled templates,
76      *           a Twig_Cache_Interface implementation,
77      *           or false to disable compilation cache (default).
78      *
79      *  * auto_reload: Whether to reload the template if the original source changed.
80      *                 If you don't provide the auto_reload option, it will be
81      *                 determined automatically based on the debug value.
82      *
83      *  * strict_variables: Whether to ignore invalid variables in templates
84      *                      (default to false).
85      *
86      *  * autoescape: Whether to enable auto-escaping (default to html):
87      *                  * false: disable auto-escaping
88      *                  * true: equivalent to html
89      *                  * html, js: set the autoescaping to one of the supported strategies
90      *                  * name: set the autoescaping strategy based on the template name extension
91      *                  * PHP callback: a PHP callback that returns an escaping strategy based on the template "name"
92      *
93      *  * optimizations: A flag that indicates which optimizations to apply
94      *                   (default to -1 which means that all optimizations are enabled;
95      *                   set it to 0 to disable).
96      *
97      * @param Twig_LoaderInterface $loader
98      * @param array                $options An array of options
99      */
100     public function __construct(Twig_LoaderInterface $loader = null, $options = array())
101     {
102         if (null !== $loader) {
103             $this->setLoader($loader);
104         } else {
105             @trigger_error('Not passing a Twig_LoaderInterface as the first constructor argument of Twig_Environment is deprecated since version 1.21.', E_USER_DEPRECATED);
106         }
107
108         $options = array_merge(array(
109             'debug' => false,
110             'charset' => 'UTF-8',
111             'base_template_class' => 'Twig_Template',
112             'strict_variables' => false,
113             'autoescape' => 'html',
114             'cache' => false,
115             'auto_reload' => null,
116             'optimizations' => -1,
117         ), $options);
118
119         $this->debug = (bool) $options['debug'];
120         $this->charset = strtoupper($options['charset']);
121         $this->baseTemplateClass = $options['base_template_class'];
122         $this->autoReload = null === $options['auto_reload'] ? $this->debug : (bool) $options['auto_reload'];
123         $this->strictVariables = (bool) $options['strict_variables'];
124         $this->setCache($options['cache']);
125
126         $this->addExtension(new Twig_Extension_Core());
127         $this->addExtension(new Twig_Extension_Escaper($options['autoescape']));
128         $this->addExtension(new Twig_Extension_Optimizer($options['optimizations']));
129         $this->staging = new Twig_Extension_Staging();
130
131         // For BC
132         if (is_string($this->originalCache)) {
133             $r = new ReflectionMethod($this, 'writeCacheFile');
134             if ($r->getDeclaringClass()->getName() !== __CLASS__) {
135                 @trigger_error('The Twig_Environment::writeCacheFile method is deprecated since version 1.22 and will be removed in Twig 2.0.', E_USER_DEPRECATED);
136
137                 $this->bcWriteCacheFile = true;
138             }
139
140             $r = new ReflectionMethod($this, 'getCacheFilename');
141             if ($r->getDeclaringClass()->getName() !== __CLASS__) {
142                 @trigger_error('The Twig_Environment::getCacheFilename method is deprecated since version 1.22 and will be removed in Twig 2.0.', E_USER_DEPRECATED);
143
144                 $this->bcGetCacheFilename = true;
145             }
146         }
147     }
148
149     /**
150      * Gets the base template class for compiled templates.
151      *
152      * @return string The base template class name
153      */
154     public function getBaseTemplateClass()
155     {
156         return $this->baseTemplateClass;
157     }
158
159     /**
160      * Sets the base template class for compiled templates.
161      *
162      * @param string $class The base template class name
163      */
164     public function setBaseTemplateClass($class)
165     {
166         $this->baseTemplateClass = $class;
167         $this->updateOptionsHash();
168     }
169
170     /**
171      * Enables debugging mode.
172      */
173     public function enableDebug()
174     {
175         $this->debug = true;
176         $this->updateOptionsHash();
177     }
178
179     /**
180      * Disables debugging mode.
181      */
182     public function disableDebug()
183     {
184         $this->debug = false;
185         $this->updateOptionsHash();
186     }
187
188     /**
189      * Checks if debug mode is enabled.
190      *
191      * @return bool true if debug mode is enabled, false otherwise
192      */
193     public function isDebug()
194     {
195         return $this->debug;
196     }
197
198     /**
199      * Enables the auto_reload option.
200      */
201     public function enableAutoReload()
202     {
203         $this->autoReload = true;
204     }
205
206     /**
207      * Disables the auto_reload option.
208      */
209     public function disableAutoReload()
210     {
211         $this->autoReload = false;
212     }
213
214     /**
215      * Checks if the auto_reload option is enabled.
216      *
217      * @return bool true if auto_reload is enabled, false otherwise
218      */
219     public function isAutoReload()
220     {
221         return $this->autoReload;
222     }
223
224     /**
225      * Enables the strict_variables option.
226      */
227     public function enableStrictVariables()
228     {
229         $this->strictVariables = true;
230         $this->updateOptionsHash();
231     }
232
233     /**
234      * Disables the strict_variables option.
235      */
236     public function disableStrictVariables()
237     {
238         $this->strictVariables = false;
239         $this->updateOptionsHash();
240     }
241
242     /**
243      * Checks if the strict_variables option is enabled.
244      *
245      * @return bool true if strict_variables is enabled, false otherwise
246      */
247     public function isStrictVariables()
248     {
249         return $this->strictVariables;
250     }
251
252     /**
253      * Gets the current cache implementation.
254      *
255      * @param bool $original Whether to return the original cache option or the real cache instance
256      *
257      * @return Twig_CacheInterface|string|false A Twig_CacheInterface implementation,
258      *                                          an absolute path to the compiled templates,
259      *                                          or false to disable cache
260      */
261     public function getCache($original = true)
262     {
263         return $original ? $this->originalCache : $this->cache;
264     }
265
266     /**
267      * Sets the current cache implementation.
268      *
269      * @param Twig_CacheInterface|string|false $cache A Twig_CacheInterface implementation,
270      *                                                an absolute path to the compiled templates,
271      *                                                or false to disable cache
272      */
273     public function setCache($cache)
274     {
275         if (is_string($cache)) {
276             $this->originalCache = $cache;
277             $this->cache = new Twig_Cache_Filesystem($cache);
278         } elseif (false === $cache) {
279             $this->originalCache = $cache;
280             $this->cache = new Twig_Cache_Null();
281         } elseif (null === $cache) {
282             @trigger_error('Using "null" as the cache strategy is deprecated since version 1.23 and will be removed in Twig 2.0.', E_USER_DEPRECATED);
283             $this->originalCache = false;
284             $this->cache = new Twig_Cache_Null();
285         } elseif ($cache instanceof Twig_CacheInterface) {
286             $this->originalCache = $this->cache = $cache;
287         } else {
288             throw new LogicException(sprintf('Cache can only be a string, false, or a Twig_CacheInterface implementation.'));
289         }
290     }
291
292     /**
293      * Gets the cache filename for a given template.
294      *
295      * @param string $name The template name
296      *
297      * @return string|false The cache file name or false when caching is disabled
298      *
299      * @deprecated since 1.22 (to be removed in 2.0)
300      */
301     public function getCacheFilename($name)
302     {
303         @trigger_error(sprintf('The %s method is deprecated since version 1.22 and will be removed in Twig 2.0.', __METHOD__), E_USER_DEPRECATED);
304
305         $key = $this->cache->generateKey($name, $this->getTemplateClass($name));
306
307         return !$key ? false : $key;
308     }
309
310     /**
311      * Gets the template class associated with the given string.
312      *
313      * The generated template class is based on the following parameters:
314      *
315      *  * The cache key for the given template;
316      *  * The currently enabled extensions;
317      *  * Whether the Twig C extension is available or not;
318      *  * PHP version;
319      *  * Twig version;
320      *  * Options with what environment was created.
321      *
322      * @param string   $name  The name for which to calculate the template class name
323      * @param int|null $index The index if it is an embedded template
324      *
325      * @return string The template class name
326      */
327     public function getTemplateClass($name, $index = null)
328     {
329         $key = $this->getLoader()->getCacheKey($name).$this->optionsHash;
330
331         return $this->templateClassPrefix.hash('sha256', $key).(null === $index ? '' : '_'.$index);
332     }
333
334     /**
335      * Gets the template class prefix.
336      *
337      * @return string The template class prefix
338      *
339      * @deprecated since 1.22 (to be removed in 2.0)
340      */
341     public function getTemplateClassPrefix()
342     {
343         @trigger_error(sprintf('The %s method is deprecated since version 1.22 and will be removed in Twig 2.0.', __METHOD__), E_USER_DEPRECATED);
344
345         return $this->templateClassPrefix;
346     }
347
348     /**
349      * Renders a template.
350      *
351      * @param string $name    The template name
352      * @param array  $context An array of parameters to pass to the template
353      *
354      * @return string The rendered template
355      *
356      * @throws Twig_Error_Loader  When the template cannot be found
357      * @throws Twig_Error_Syntax  When an error occurred during compilation
358      * @throws Twig_Error_Runtime When an error occurred during rendering
359      */
360     public function render($name, array $context = array())
361     {
362         return $this->loadTemplate($name)->render($context);
363     }
364
365     /**
366      * Displays a template.
367      *
368      * @param string $name    The template name
369      * @param array  $context An array of parameters to pass to the template
370      *
371      * @throws Twig_Error_Loader  When the template cannot be found
372      * @throws Twig_Error_Syntax  When an error occurred during compilation
373      * @throws Twig_Error_Runtime When an error occurred during rendering
374      */
375     public function display($name, array $context = array())
376     {
377         $this->loadTemplate($name)->display($context);
378     }
379
380     /**
381      * Loads a template.
382      *
383      * @param string|Twig_TemplateWrapper|Twig_Template $name The template name
384      *
385      * @return Twig_TemplateWrapper
386      */
387     public function load($name)
388     {
389         if ($name instanceof Twig_TemplateWrapper) {
390             return $name;
391         }
392
393         if ($name instanceof Twig_Template) {
394             return new Twig_TemplateWrapper($this, $name);
395         }
396
397         return new Twig_TemplateWrapper($this, $this->loadTemplate($name));
398     }
399
400     /**
401      * Loads a template internal representation.
402      *
403      * This method is for internal use only and should never be called
404      * directly.
405      *
406      * @param string $name  The template name
407      * @param int    $index The index if it is an embedded template
408      *
409      * @return Twig_TemplateInterface A template instance representing the given template name
410      *
411      * @throws Twig_Error_Loader  When the template cannot be found
412      * @throws Twig_Error_Runtime When a previously generated cache is corrupted
413      * @throws Twig_Error_Syntax  When an error occurred during compilation
414      *
415      * @internal
416      */
417     public function loadTemplate($name, $index = null)
418     {
419         $cls = $mainCls = $this->getTemplateClass($name);
420         if (null !== $index) {
421             $cls .= '_'.$index;
422         }
423
424         if (isset($this->loadedTemplates[$cls])) {
425             return $this->loadedTemplates[$cls];
426         }
427
428         if (!class_exists($cls, false)) {
429             if ($this->bcGetCacheFilename) {
430                 $key = $this->getCacheFilename($name);
431             } else {
432                 $key = $this->cache->generateKey($name, $mainCls);
433             }
434
435             if (!$this->isAutoReload() || $this->isTemplateFresh($name, $this->cache->getTimestamp($key))) {
436                 $this->cache->load($key);
437             }
438
439             if (!class_exists($cls, false)) {
440                 $loader = $this->getLoader();
441                 if (!$loader instanceof Twig_SourceContextLoaderInterface) {
442                     $source = new Twig_Source($loader->getSource($name), $name);
443                 } else {
444                     $source = $loader->getSourceContext($name);
445                 }
446
447                 $content = $this->compileSource($source);
448
449                 if ($this->bcWriteCacheFile) {
450                     $this->writeCacheFile($key, $content);
451                 } else {
452                     $this->cache->write($key, $content);
453                     $this->cache->load($key);
454                 }
455
456                 if (!class_exists($mainCls, false)) {
457                     /* Last line of defense if either $this->bcWriteCacheFile was used,
458                      * $this->cache is implemented as a no-op or we have a race condition
459                      * where the cache was cleared between the above calls to write to and load from
460                      * the cache.
461                      */
462                     eval('?>'.$content);
463                 }
464             }
465
466             if (!class_exists($cls, false)) {
467                 throw new Twig_Error_Runtime(sprintf('Failed to load Twig template "%s", index "%s": cache is corrupted.', $name, $index), -1, $source);
468             }
469         }
470
471         if (!$this->runtimeInitialized) {
472             $this->initRuntime();
473         }
474
475         return $this->loadedTemplates[$cls] = new $cls($this);
476     }
477
478     /**
479      * Creates a template from source.
480      *
481      * This method should not be used as a generic way to load templates.
482      *
483      * @param string $template The template name
484      *
485      * @return Twig_Template A template instance representing the given template name
486      *
487      * @throws Twig_Error_Loader When the template cannot be found
488      * @throws Twig_Error_Syntax When an error occurred during compilation
489      */
490     public function createTemplate($template)
491     {
492         $name = sprintf('__string_template__%s', hash('sha256', uniqid(mt_rand(), true), false));
493
494         $loader = new Twig_Loader_Chain(array(
495             new Twig_Loader_Array(array($name => $template)),
496             $current = $this->getLoader(),
497         ));
498
499         $this->setLoader($loader);
500         try {
501             $template = $this->loadTemplate($name);
502         } catch (Exception $e) {
503             $this->setLoader($current);
504
505             throw $e;
506         } catch (Throwable $e) {
507             $this->setLoader($current);
508
509             throw $e;
510         }
511         $this->setLoader($current);
512
513         return $template;
514     }
515
516     /**
517      * Returns true if the template is still fresh.
518      *
519      * Besides checking the loader for freshness information,
520      * this method also checks if the enabled extensions have
521      * not changed.
522      *
523      * @param string $name The template name
524      * @param int    $time The last modification time of the cached template
525      *
526      * @return bool true if the template is fresh, false otherwise
527      */
528     public function isTemplateFresh($name, $time)
529     {
530         if (0 === $this->lastModifiedExtension) {
531             foreach ($this->extensions as $extension) {
532                 $r = new ReflectionObject($extension);
533                 if (file_exists($r->getFileName()) && ($extensionTime = filemtime($r->getFileName())) > $this->lastModifiedExtension) {
534                     $this->lastModifiedExtension = $extensionTime;
535                 }
536             }
537         }
538
539         return $this->lastModifiedExtension <= $time && $this->getLoader()->isFresh($name, $time);
540     }
541
542     /**
543      * Tries to load a template consecutively from an array.
544      *
545      * Similar to loadTemplate() but it also accepts Twig_TemplateInterface instances and an array
546      * of templates where each is tried to be loaded.
547      *
548      * @param string|Twig_Template|array $names A template or an array of templates to try consecutively
549      *
550      * @return Twig_Template
551      *
552      * @throws Twig_Error_Loader When none of the templates can be found
553      * @throws Twig_Error_Syntax When an error occurred during compilation
554      */
555     public function resolveTemplate($names)
556     {
557         if (!is_array($names)) {
558             $names = array($names);
559         }
560
561         foreach ($names as $name) {
562             if ($name instanceof Twig_Template) {
563                 return $name;
564             }
565
566             try {
567                 return $this->loadTemplate($name);
568             } catch (Twig_Error_Loader $e) {
569             }
570         }
571
572         if (1 === count($names)) {
573             throw $e;
574         }
575
576         throw new Twig_Error_Loader(sprintf('Unable to find one of the following templates: "%s".', implode('", "', $names)));
577     }
578
579     /**
580      * Clears the internal template cache.
581      *
582      * @deprecated since 1.18.3 (to be removed in 2.0)
583      */
584     public function clearTemplateCache()
585     {
586         @trigger_error(sprintf('The %s method is deprecated since version 1.18.3 and will be removed in Twig 2.0.', __METHOD__), E_USER_DEPRECATED);
587
588         $this->loadedTemplates = array();
589     }
590
591     /**
592      * Clears the template cache files on the filesystem.
593      *
594      * @deprecated since 1.22 (to be removed in 2.0)
595      */
596     public function clearCacheFiles()
597     {
598         @trigger_error(sprintf('The %s method is deprecated since version 1.22 and will be removed in Twig 2.0.', __METHOD__), E_USER_DEPRECATED);
599
600         if (is_string($this->originalCache)) {
601             foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->originalCache), RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
602                 if ($file->isFile()) {
603                     @unlink($file->getPathname());
604                 }
605             }
606         }
607     }
608
609     /**
610      * Gets the Lexer instance.
611      *
612      * @return Twig_LexerInterface
613      *
614      * @deprecated since 1.25 (to be removed in 2.0)
615      */
616     public function getLexer()
617     {
618         @trigger_error(sprintf('The %s() method is deprecated since version 1.25 and will be removed in 2.0.', __FUNCTION__), E_USER_DEPRECATED);
619
620         if (null === $this->lexer) {
621             $this->lexer = new Twig_Lexer($this);
622         }
623
624         return $this->lexer;
625     }
626
627     public function setLexer(Twig_LexerInterface $lexer)
628     {
629         $this->lexer = $lexer;
630     }
631
632     /**
633      * Tokenizes a source code.
634      *
635      * @param string|Twig_Source $source The template source code
636      * @param string             $name   The template name (deprecated)
637      *
638      * @return Twig_TokenStream
639      *
640      * @throws Twig_Error_Syntax When the code is syntactically wrong
641      */
642     public function tokenize($source, $name = null)
643     {
644         if (!$source instanceof Twig_Source) {
645             @trigger_error(sprintf('Passing a string as the $source argument of %s() is deprecated since version 1.27. Pass a Twig_Source instance instead.', __METHOD__), E_USER_DEPRECATED);
646             $source = new Twig_Source($source, $name);
647         }
648
649         if (null === $this->lexer) {
650             $this->lexer = new Twig_Lexer($this);
651         }
652
653         return $this->lexer->tokenize($source);
654     }
655
656     /**
657      * Gets the Parser instance.
658      *
659      * @return Twig_ParserInterface
660      *
661      * @deprecated since 1.25 (to be removed in 2.0)
662      */
663     public function getParser()
664     {
665         @trigger_error(sprintf('The %s() method is deprecated since version 1.25 and will be removed in 2.0.', __FUNCTION__), E_USER_DEPRECATED);
666
667         if (null === $this->parser) {
668             $this->parser = new Twig_Parser($this);
669         }
670
671         return $this->parser;
672     }
673
674     public function setParser(Twig_ParserInterface $parser)
675     {
676         $this->parser = $parser;
677     }
678
679     /**
680      * Converts a token stream to a node tree.
681      *
682      * @return Twig_Node_Module
683      *
684      * @throws Twig_Error_Syntax When the token stream is syntactically or semantically wrong
685      */
686     public function parse(Twig_TokenStream $stream)
687     {
688         if (null === $this->parser) {
689             $this->parser = new Twig_Parser($this);
690         }
691
692         return $this->parser->parse($stream);
693     }
694
695     /**
696      * Gets the Compiler instance.
697      *
698      * @return Twig_CompilerInterface
699      *
700      * @deprecated since 1.25 (to be removed in 2.0)
701      */
702     public function getCompiler()
703     {
704         @trigger_error(sprintf('The %s() method is deprecated since version 1.25 and will be removed in 2.0.', __FUNCTION__), E_USER_DEPRECATED);
705
706         if (null === $this->compiler) {
707             $this->compiler = new Twig_Compiler($this);
708         }
709
710         return $this->compiler;
711     }
712
713     public function setCompiler(Twig_CompilerInterface $compiler)
714     {
715         $this->compiler = $compiler;
716     }
717
718     /**
719      * Compiles a node and returns the PHP code.
720      *
721      * @return string The compiled PHP source code
722      */
723     public function compile(Twig_NodeInterface $node)
724     {
725         if (null === $this->compiler) {
726             $this->compiler = new Twig_Compiler($this);
727         }
728
729         return $this->compiler->compile($node)->getSource();
730     }
731
732     /**
733      * Compiles a template source code.
734      *
735      * @param string|Twig_Source $source The template source code
736      * @param string             $name   The template name (deprecated)
737      *
738      * @return string The compiled PHP source code
739      *
740      * @throws Twig_Error_Syntax When there was an error during tokenizing, parsing or compiling
741      */
742     public function compileSource($source, $name = null)
743     {
744         if (!$source instanceof Twig_Source) {
745             @trigger_error(sprintf('Passing a string as the $source argument of %s() is deprecated since version 1.27. Pass a Twig_Source instance instead.', __METHOD__), E_USER_DEPRECATED);
746             $source = new Twig_Source($source, $name);
747         }
748
749         try {
750             return $this->compile($this->parse($this->tokenize($source)));
751         } catch (Twig_Error $e) {
752             $e->setSourceContext($source);
753             throw $e;
754         } catch (Exception $e) {
755             throw new Twig_Error_Syntax(sprintf('An exception has been thrown during the compilation of a template ("%s").', $e->getMessage()), -1, $source, $e);
756         }
757     }
758
759     public function setLoader(Twig_LoaderInterface $loader)
760     {
761         if (!$loader instanceof Twig_SourceContextLoaderInterface && 0 !== strpos(get_class($loader), 'Mock_Twig_LoaderInterface')) {
762             @trigger_error(sprintf('Twig loader "%s" should implement Twig_SourceContextLoaderInterface since version 1.27.', get_class($loader)), E_USER_DEPRECATED);
763         }
764
765         $this->loader = $loader;
766     }
767
768     /**
769      * Gets the Loader instance.
770      *
771      * @return Twig_LoaderInterface
772      */
773     public function getLoader()
774     {
775         if (null === $this->loader) {
776             throw new LogicException('You must set a loader first.');
777         }
778
779         return $this->loader;
780     }
781
782     /**
783      * Sets the default template charset.
784      *
785      * @param string $charset The default charset
786      */
787     public function setCharset($charset)
788     {
789         $this->charset = strtoupper($charset);
790     }
791
792     /**
793      * Gets the default template charset.
794      *
795      * @return string The default charset
796      */
797     public function getCharset()
798     {
799         return $this->charset;
800     }
801
802     /**
803      * Initializes the runtime environment.
804      *
805      * @deprecated since 1.23 (to be removed in 2.0)
806      */
807     public function initRuntime()
808     {
809         $this->runtimeInitialized = true;
810
811         foreach ($this->getExtensions() as $name => $extension) {
812             if (!$extension instanceof Twig_Extension_InitRuntimeInterface) {
813                 $m = new ReflectionMethod($extension, 'initRuntime');
814
815                 if ('Twig_Extension' !== $m->getDeclaringClass()->getName()) {
816                     @trigger_error(sprintf('Defining the initRuntime() method in the "%s" extension is deprecated since version 1.23. Use the `needs_environment` option to get the Twig_Environment instance in filters, functions, or tests; or explicitly implement Twig_Extension_InitRuntimeInterface if needed (not recommended).', $name), E_USER_DEPRECATED);
817                 }
818             }
819
820             $extension->initRuntime($this);
821         }
822     }
823
824     /**
825      * Returns true if the given extension is registered.
826      *
827      * @param string $class The extension class name
828      *
829      * @return bool Whether the extension is registered or not
830      */
831     public function hasExtension($class)
832     {
833         $class = ltrim($class, '\\');
834         if (isset($this->extensions[$class])) {
835             if ($class !== get_class($this->extensions[$class])) {
836                 @trigger_error(sprintf('Referencing the "%s" extension by its name (defined by getName()) is deprecated since 1.26 and will be removed in Twig 2.0. Use the Fully Qualified Extension Class Name instead.', $class), E_USER_DEPRECATED);
837             }
838
839             return true;
840         }
841
842         return isset($this->extensionsByClass[$class]);
843     }
844
845     /**
846      * Adds a runtime loader.
847      */
848     public function addRuntimeLoader(Twig_RuntimeLoaderInterface $loader)
849     {
850         $this->runtimeLoaders[] = $loader;
851     }
852
853     /**
854      * Gets an extension by class name.
855      *
856      * @param string $class The extension class name
857      *
858      * @return Twig_ExtensionInterface
859      */
860     public function getExtension($class)
861     {
862         $class = ltrim($class, '\\');
863
864         if (isset($this->extensions[$class])) {
865             if ($class !== get_class($this->extensions[$class])) {
866                 @trigger_error(sprintf('Referencing the "%s" extension by its name (defined by getName()) is deprecated since 1.26 and will be removed in Twig 2.0. Use the Fully Qualified Extension Class Name instead.', $class), E_USER_DEPRECATED);
867             }
868
869             return $this->extensions[$class];
870         }
871
872         if (!isset($this->extensionsByClass[$class])) {
873             throw new Twig_Error_Runtime(sprintf('The "%s" extension is not enabled.', $class));
874         }
875
876         return $this->extensionsByClass[$class];
877     }
878
879     /**
880      * Returns the runtime implementation of a Twig element (filter/function/test).
881      *
882      * @param string $class A runtime class name
883      *
884      * @return object The runtime implementation
885      *
886      * @throws Twig_Error_Runtime When the template cannot be found
887      */
888     public function getRuntime($class)
889     {
890         if (isset($this->runtimes[$class])) {
891             return $this->runtimes[$class];
892         }
893
894         foreach ($this->runtimeLoaders as $loader) {
895             if (null !== $runtime = $loader->load($class)) {
896                 return $this->runtimes[$class] = $runtime;
897             }
898         }
899
900         throw new Twig_Error_Runtime(sprintf('Unable to load the "%s" runtime.', $class));
901     }
902
903     public function addExtension(Twig_ExtensionInterface $extension)
904     {
905         if ($this->extensionInitialized) {
906             throw new LogicException(sprintf('Unable to register extension "%s" as extensions have already been initialized.', $extension->getName()));
907         }
908
909         $class = get_class($extension);
910         if ($class !== $extension->getName()) {
911             if (isset($this->extensions[$extension->getName()])) {
912                 unset($this->extensions[$extension->getName()], $this->extensionsByClass[$class]);
913                 @trigger_error(sprintf('The possibility to register the same extension twice ("%s") is deprecated since version 1.23 and will be removed in Twig 2.0. Use proper PHP inheritance instead.', $extension->getName()), E_USER_DEPRECATED);
914             }
915         }
916
917         $this->lastModifiedExtension = 0;
918         $this->extensionsByClass[$class] = $extension;
919         $this->extensions[$extension->getName()] = $extension;
920         $this->updateOptionsHash();
921     }
922
923     /**
924      * Removes an extension by name.
925      *
926      * This method is deprecated and you should not use it.
927      *
928      * @param string $name The extension name
929      *
930      * @deprecated since 1.12 (to be removed in 2.0)
931      */
932     public function removeExtension($name)
933     {
934         @trigger_error(sprintf('The %s method is deprecated since version 1.12 and will be removed in Twig 2.0.', __METHOD__), E_USER_DEPRECATED);
935
936         if ($this->extensionInitialized) {
937             throw new LogicException(sprintf('Unable to remove extension "%s" as extensions have already been initialized.', $name));
938         }
939
940         $class = ltrim($name, '\\');
941         if (isset($this->extensions[$class])) {
942             if ($class !== get_class($this->extensions[$class])) {
943                 @trigger_error(sprintf('Referencing the "%s" extension by its name (defined by getName()) is deprecated since 1.26 and will be removed in Twig 2.0. Use the Fully Qualified Extension Class Name instead.', $class), E_USER_DEPRECATED);
944             }
945
946             unset($this->extensions[$class]);
947         }
948
949         unset($this->extensions[$class]);
950         $this->updateOptionsHash();
951     }
952
953     /**
954      * Registers an array of extensions.
955      *
956      * @param array $extensions An array of extensions
957      */
958     public function setExtensions(array $extensions)
959     {
960         foreach ($extensions as $extension) {
961             $this->addExtension($extension);
962         }
963     }
964
965     /**
966      * Returns all registered extensions.
967      *
968      * @return Twig_ExtensionInterface[] An array of extensions (keys are for internal usage only and should not be relied on)
969      */
970     public function getExtensions()
971     {
972         return $this->extensions;
973     }
974
975     public function addTokenParser(Twig_TokenParserInterface $parser)
976     {
977         if ($this->extensionInitialized) {
978             throw new LogicException('Unable to add a token parser as extensions have already been initialized.');
979         }
980
981         $this->staging->addTokenParser($parser);
982     }
983
984     /**
985      * Gets the registered Token Parsers.
986      *
987      * @return Twig_TokenParserBrokerInterface
988      *
989      * @internal
990      */
991     public function getTokenParsers()
992     {
993         if (!$this->extensionInitialized) {
994             $this->initExtensions();
995         }
996
997         return $this->parsers;
998     }
999
1000     /**
1001      * Gets registered tags.
1002      *
1003      * Be warned that this method cannot return tags defined by Twig_TokenParserBrokerInterface classes.
1004      *
1005      * @return Twig_TokenParserInterface[]
1006      *
1007      * @internal
1008      */
1009     public function getTags()
1010     {
1011         $tags = array();
1012         foreach ($this->getTokenParsers()->getParsers() as $parser) {
1013             if ($parser instanceof Twig_TokenParserInterface) {
1014                 $tags[$parser->getTag()] = $parser;
1015             }
1016         }
1017
1018         return $tags;
1019     }
1020
1021     public function addNodeVisitor(Twig_NodeVisitorInterface $visitor)
1022     {
1023         if ($this->extensionInitialized) {
1024             throw new LogicException('Unable to add a node visitor as extensions have already been initialized.');
1025         }
1026
1027         $this->staging->addNodeVisitor($visitor);
1028     }
1029
1030     /**
1031      * Gets the registered Node Visitors.
1032      *
1033      * @return Twig_NodeVisitorInterface[]
1034      *
1035      * @internal
1036      */
1037     public function getNodeVisitors()
1038     {
1039         if (!$this->extensionInitialized) {
1040             $this->initExtensions();
1041         }
1042
1043         return $this->visitors;
1044     }
1045
1046     /**
1047      * Registers a Filter.
1048      *
1049      * @param string|Twig_SimpleFilter               $name   The filter name or a Twig_SimpleFilter instance
1050      * @param Twig_FilterInterface|Twig_SimpleFilter $filter
1051      */
1052     public function addFilter($name, $filter = null)
1053     {
1054         if (!$name instanceof Twig_SimpleFilter && !($filter instanceof Twig_SimpleFilter || $filter instanceof Twig_FilterInterface)) {
1055             throw new LogicException('A filter must be an instance of Twig_FilterInterface or Twig_SimpleFilter.');
1056         }
1057
1058         if ($name instanceof Twig_SimpleFilter) {
1059             $filter = $name;
1060             $name = $filter->getName();
1061         } else {
1062             @trigger_error(sprintf('Passing a name as a first argument to the %s method is deprecated since version 1.21. Pass an instance of "Twig_SimpleFilter" instead when defining filter "%s".', __METHOD__, $name), E_USER_DEPRECATED);
1063         }
1064
1065         if ($this->extensionInitialized) {
1066             throw new LogicException(sprintf('Unable to add filter "%s" as extensions have already been initialized.', $name));
1067         }
1068
1069         $this->staging->addFilter($name, $filter);
1070     }
1071
1072     /**
1073      * Get a filter by name.
1074      *
1075      * Subclasses may override this method and load filters differently;
1076      * so no list of filters is available.
1077      *
1078      * @param string $name The filter name
1079      *
1080      * @return Twig_Filter|false A Twig_Filter instance or false if the filter does not exist
1081      *
1082      * @internal
1083      */
1084     public function getFilter($name)
1085     {
1086         if (!$this->extensionInitialized) {
1087             $this->initExtensions();
1088         }
1089
1090         if (isset($this->filters[$name])) {
1091             return $this->filters[$name];
1092         }
1093
1094         foreach ($this->filters as $pattern => $filter) {
1095             $pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count);
1096
1097             if ($count) {
1098                 if (preg_match('#^'.$pattern.'$#', $name, $matches)) {
1099                     array_shift($matches);
1100                     $filter->setArguments($matches);
1101
1102                     return $filter;
1103                 }
1104             }
1105         }
1106
1107         foreach ($this->filterCallbacks as $callback) {
1108             if (false !== $filter = call_user_func($callback, $name)) {
1109                 return $filter;
1110             }
1111         }
1112
1113         return false;
1114     }
1115
1116     public function registerUndefinedFilterCallback($callable)
1117     {
1118         $this->filterCallbacks[] = $callable;
1119     }
1120
1121     /**
1122      * Gets the registered Filters.
1123      *
1124      * Be warned that this method cannot return filters defined with registerUndefinedFilterCallback.
1125      *
1126      * @return Twig_FilterInterface[]
1127      *
1128      * @see registerUndefinedFilterCallback
1129      *
1130      * @internal
1131      */
1132     public function getFilters()
1133     {
1134         if (!$this->extensionInitialized) {
1135             $this->initExtensions();
1136         }
1137
1138         return $this->filters;
1139     }
1140
1141     /**
1142      * Registers a Test.
1143      *
1144      * @param string|Twig_SimpleTest             $name The test name or a Twig_SimpleTest instance
1145      * @param Twig_TestInterface|Twig_SimpleTest $test A Twig_TestInterface instance or a Twig_SimpleTest instance
1146      */
1147     public function addTest($name, $test = null)
1148     {
1149         if (!$name instanceof Twig_SimpleTest && !($test instanceof Twig_SimpleTest || $test instanceof Twig_TestInterface)) {
1150             throw new LogicException('A test must be an instance of Twig_TestInterface or Twig_SimpleTest.');
1151         }
1152
1153         if ($name instanceof Twig_SimpleTest) {
1154             $test = $name;
1155             $name = $test->getName();
1156         } else {
1157             @trigger_error(sprintf('Passing a name as a first argument to the %s method is deprecated since version 1.21. Pass an instance of "Twig_SimpleTest" instead when defining test "%s".', __METHOD__, $name), E_USER_DEPRECATED);
1158         }
1159
1160         if ($this->extensionInitialized) {
1161             throw new LogicException(sprintf('Unable to add test "%s" as extensions have already been initialized.', $name));
1162         }
1163
1164         $this->staging->addTest($name, $test);
1165     }
1166
1167     /**
1168      * Gets the registered Tests.
1169      *
1170      * @return Twig_TestInterface[]
1171      *
1172      * @internal
1173      */
1174     public function getTests()
1175     {
1176         if (!$this->extensionInitialized) {
1177             $this->initExtensions();
1178         }
1179
1180         return $this->tests;
1181     }
1182
1183     /**
1184      * Gets a test by name.
1185      *
1186      * @param string $name The test name
1187      *
1188      * @return Twig_Test|false A Twig_Test instance or false if the test does not exist
1189      *
1190      * @internal
1191      */
1192     public function getTest($name)
1193     {
1194         if (!$this->extensionInitialized) {
1195             $this->initExtensions();
1196         }
1197
1198         if (isset($this->tests[$name])) {
1199             return $this->tests[$name];
1200         }
1201
1202         return false;
1203     }
1204
1205     /**
1206      * Registers a Function.
1207      *
1208      * @param string|Twig_SimpleFunction                 $name     The function name or a Twig_SimpleFunction instance
1209      * @param Twig_FunctionInterface|Twig_SimpleFunction $function
1210      */
1211     public function addFunction($name, $function = null)
1212     {
1213         if (!$name instanceof Twig_SimpleFunction && !($function instanceof Twig_SimpleFunction || $function instanceof Twig_FunctionInterface)) {
1214             throw new LogicException('A function must be an instance of Twig_FunctionInterface or Twig_SimpleFunction.');
1215         }
1216
1217         if ($name instanceof Twig_SimpleFunction) {
1218             $function = $name;
1219             $name = $function->getName();
1220         } else {
1221             @trigger_error(sprintf('Passing a name as a first argument to the %s method is deprecated since version 1.21. Pass an instance of "Twig_SimpleFunction" instead when defining function "%s".', __METHOD__, $name), E_USER_DEPRECATED);
1222         }
1223
1224         if ($this->extensionInitialized) {
1225             throw new LogicException(sprintf('Unable to add function "%s" as extensions have already been initialized.', $name));
1226         }
1227
1228         $this->staging->addFunction($name, $function);
1229     }
1230
1231     /**
1232      * Get a function by name.
1233      *
1234      * Subclasses may override this method and load functions differently;
1235      * so no list of functions is available.
1236      *
1237      * @param string $name function name
1238      *
1239      * @return Twig_Function|false A Twig_Function instance or false if the function does not exist
1240      *
1241      * @internal
1242      */
1243     public function getFunction($name)
1244     {
1245         if (!$this->extensionInitialized) {
1246             $this->initExtensions();
1247         }
1248
1249         if (isset($this->functions[$name])) {
1250             return $this->functions[$name];
1251         }
1252
1253         foreach ($this->functions as $pattern => $function) {
1254             $pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count);
1255
1256             if ($count) {
1257                 if (preg_match('#^'.$pattern.'$#', $name, $matches)) {
1258                     array_shift($matches);
1259                     $function->setArguments($matches);
1260
1261                     return $function;
1262                 }
1263             }
1264         }
1265
1266         foreach ($this->functionCallbacks as $callback) {
1267             if (false !== $function = call_user_func($callback, $name)) {
1268                 return $function;
1269             }
1270         }
1271
1272         return false;
1273     }
1274
1275     public function registerUndefinedFunctionCallback($callable)
1276     {
1277         $this->functionCallbacks[] = $callable;
1278     }
1279
1280     /**
1281      * Gets registered functions.
1282      *
1283      * Be warned that this method cannot return functions defined with registerUndefinedFunctionCallback.
1284      *
1285      * @return Twig_FunctionInterface[]
1286      *
1287      * @see registerUndefinedFunctionCallback
1288      *
1289      * @internal
1290      */
1291     public function getFunctions()
1292     {
1293         if (!$this->extensionInitialized) {
1294             $this->initExtensions();
1295         }
1296
1297         return $this->functions;
1298     }
1299
1300     /**
1301      * Registers a Global.
1302      *
1303      * New globals can be added before compiling or rendering a template;
1304      * but after, you can only update existing globals.
1305      *
1306      * @param string $name  The global name
1307      * @param mixed  $value The global value
1308      */
1309     public function addGlobal($name, $value)
1310     {
1311         if ($this->extensionInitialized || $this->runtimeInitialized) {
1312             if (null === $this->globals) {
1313                 $this->globals = $this->initGlobals();
1314             }
1315
1316             if (!array_key_exists($name, $this->globals)) {
1317                 // The deprecation notice must be turned into the following exception in Twig 2.0
1318                 @trigger_error(sprintf('Registering global variable "%s" at runtime or when the extensions have already been initialized is deprecated since version 1.21.', $name), E_USER_DEPRECATED);
1319                 //throw new LogicException(sprintf('Unable to add global "%s" as the runtime or the extensions have already been initialized.', $name));
1320             }
1321         }
1322
1323         if ($this->extensionInitialized || $this->runtimeInitialized) {
1324             // update the value
1325             $this->globals[$name] = $value;
1326         } else {
1327             $this->staging->addGlobal($name, $value);
1328         }
1329     }
1330
1331     /**
1332      * Gets the registered Globals.
1333      *
1334      * @return array An array of globals
1335      *
1336      * @internal
1337      */
1338     public function getGlobals()
1339     {
1340         if (!$this->runtimeInitialized && !$this->extensionInitialized) {
1341             return $this->initGlobals();
1342         }
1343
1344         if (null === $this->globals) {
1345             $this->globals = $this->initGlobals();
1346         }
1347
1348         return $this->globals;
1349     }
1350
1351     /**
1352      * Merges a context with the defined globals.
1353      *
1354      * @param array $context An array representing the context
1355      *
1356      * @return array The context merged with the globals
1357      */
1358     public function mergeGlobals(array $context)
1359     {
1360         // we don't use array_merge as the context being generally
1361         // bigger than globals, this code is faster.
1362         foreach ($this->getGlobals() as $key => $value) {
1363             if (!array_key_exists($key, $context)) {
1364                 $context[$key] = $value;
1365             }
1366         }
1367
1368         return $context;
1369     }
1370
1371     /**
1372      * Gets the registered unary Operators.
1373      *
1374      * @return array An array of unary operators
1375      *
1376      * @internal
1377      */
1378     public function getUnaryOperators()
1379     {
1380         if (!$this->extensionInitialized) {
1381             $this->initExtensions();
1382         }
1383
1384         return $this->unaryOperators;
1385     }
1386
1387     /**
1388      * Gets the registered binary Operators.
1389      *
1390      * @return array An array of binary operators
1391      *
1392      * @internal
1393      */
1394     public function getBinaryOperators()
1395     {
1396         if (!$this->extensionInitialized) {
1397             $this->initExtensions();
1398         }
1399
1400         return $this->binaryOperators;
1401     }
1402
1403     /**
1404      * @deprecated since 1.23 (to be removed in 2.0)
1405      */
1406     public function computeAlternatives($name, $items)
1407     {
1408         @trigger_error(sprintf('The %s method is deprecated since version 1.23 and will be removed in Twig 2.0.', __METHOD__), E_USER_DEPRECATED);
1409
1410         return Twig_Error_Syntax::computeAlternatives($name, $items);
1411     }
1412
1413     /**
1414      * @internal
1415      */
1416     protected function initGlobals()
1417     {
1418         $globals = array();
1419         foreach ($this->extensions as $name => $extension) {
1420             if (!$extension instanceof Twig_Extension_GlobalsInterface) {
1421                 $m = new ReflectionMethod($extension, 'getGlobals');
1422
1423                 if ('Twig_Extension' !== $m->getDeclaringClass()->getName()) {
1424                     @trigger_error(sprintf('Defining the getGlobals() method in the "%s" extension without explicitly implementing Twig_Extension_GlobalsInterface is deprecated since version 1.23.', $name), E_USER_DEPRECATED);
1425                 }
1426             }
1427
1428             $extGlob = $extension->getGlobals();
1429             if (!is_array($extGlob)) {
1430                 throw new UnexpectedValueException(sprintf('"%s::getGlobals()" must return an array of globals.', get_class($extension)));
1431             }
1432
1433             $globals[] = $extGlob;
1434         }
1435
1436         $globals[] = $this->staging->getGlobals();
1437
1438         return call_user_func_array('array_merge', $globals);
1439     }
1440
1441     /**
1442      * @internal
1443      */
1444     protected function initExtensions()
1445     {
1446         if ($this->extensionInitialized) {
1447             return;
1448         }
1449
1450         $this->parsers = new Twig_TokenParserBroker(array(), array(), false);
1451         $this->filters = array();
1452         $this->functions = array();
1453         $this->tests = array();
1454         $this->visitors = array();
1455         $this->unaryOperators = array();
1456         $this->binaryOperators = array();
1457
1458         foreach ($this->extensions as $extension) {
1459             $this->initExtension($extension);
1460         }
1461         $this->initExtension($this->staging);
1462         // Done at the end only, so that an exception during initialization does not mark the environment as initialized when catching the exception
1463         $this->extensionInitialized = true;
1464     }
1465
1466     /**
1467      * @internal
1468      */
1469     protected function initExtension(Twig_ExtensionInterface $extension)
1470     {
1471         // filters
1472         foreach ($extension->getFilters() as $name => $filter) {
1473             if ($filter instanceof Twig_SimpleFilter) {
1474                 $name = $filter->getName();
1475             } else {
1476                 @trigger_error(sprintf('Using an instance of "%s" for filter "%s" is deprecated since version 1.21. Use Twig_SimpleFilter instead.', get_class($filter), $name), E_USER_DEPRECATED);
1477             }
1478
1479             $this->filters[$name] = $filter;
1480         }
1481
1482         // functions
1483         foreach ($extension->getFunctions() as $name => $function) {
1484             if ($function instanceof Twig_SimpleFunction) {
1485                 $name = $function->getName();
1486             } else {
1487                 @trigger_error(sprintf('Using an instance of "%s" for function "%s" is deprecated since version 1.21. Use Twig_SimpleFunction instead.', get_class($function), $name), E_USER_DEPRECATED);
1488             }
1489
1490             $this->functions[$name] = $function;
1491         }
1492
1493         // tests
1494         foreach ($extension->getTests() as $name => $test) {
1495             if ($test instanceof Twig_SimpleTest) {
1496                 $name = $test->getName();
1497             } else {
1498                 @trigger_error(sprintf('Using an instance of "%s" for test "%s" is deprecated since version 1.21. Use Twig_SimpleTest instead.', get_class($test), $name), E_USER_DEPRECATED);
1499             }
1500
1501             $this->tests[$name] = $test;
1502         }
1503
1504         // token parsers
1505         foreach ($extension->getTokenParsers() as $parser) {
1506             if ($parser instanceof Twig_TokenParserInterface) {
1507                 $this->parsers->addTokenParser($parser);
1508             } elseif ($parser instanceof Twig_TokenParserBrokerInterface) {
1509                 @trigger_error('Registering a Twig_TokenParserBrokerInterface instance is deprecated since version 1.21.', E_USER_DEPRECATED);
1510
1511                 $this->parsers->addTokenParserBroker($parser);
1512             } else {
1513                 throw new LogicException('getTokenParsers() must return an array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances.');
1514             }
1515         }
1516
1517         // node visitors
1518         foreach ($extension->getNodeVisitors() as $visitor) {
1519             $this->visitors[] = $visitor;
1520         }
1521
1522         // operators
1523         if ($operators = $extension->getOperators()) {
1524             if (!is_array($operators)) {
1525                 throw new InvalidArgumentException(sprintf('"%s::getOperators()" must return an array with operators, got "%s".', get_class($extension), is_object($operators) ? get_class($operators) : gettype($operators).(is_resource($operators) ? '' : '#'.$operators)));
1526             }
1527
1528             if (2 !== count($operators)) {
1529                 throw new InvalidArgumentException(sprintf('"%s::getOperators()" must return an array of 2 elements, got %d.', get_class($extension), count($operators)));
1530             }
1531
1532             $this->unaryOperators = array_merge($this->unaryOperators, $operators[0]);
1533             $this->binaryOperators = array_merge($this->binaryOperators, $operators[1]);
1534         }
1535     }
1536
1537     /**
1538      * @deprecated since 1.22 (to be removed in 2.0)
1539      */
1540     protected function writeCacheFile($file, $content)
1541     {
1542         $this->cache->write($file, $content);
1543     }
1544
1545     private function updateOptionsHash()
1546     {
1547         $hashParts = array_merge(
1548             array_keys($this->extensions),
1549             array(
1550                 (int) function_exists('twig_template_get_attributes'),
1551                 PHP_MAJOR_VERSION,
1552                 PHP_MINOR_VERSION,
1553                 self::VERSION,
1554                 (int) $this->debug,
1555                 $this->baseTemplateClass,
1556                 (int) $this->strictVariables,
1557             )
1558         );
1559         $this->optionsHash = implode(':', $hashParts);
1560     }
1561 }