Dotclear

source: inc/libs/twig/Environment.php @ 1101:7273894e61b8

Revision 1101:7273894e61b8, 34.4 KB checked in by Dsls <dsls@…>, 12 years ago (diff)

Twig 1.12.2

Line 
1<?php
2
3/*
4 * This file is part of Twig.
5 *
6 * (c) 2009 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 * @package twig
16 * @author  Fabien Potencier <fabien@symfony.com>
17 */
18class Twig_Environment
19{
20    const VERSION = '1.12.2';
21
22    protected $charset;
23    protected $loader;
24    protected $debug;
25    protected $autoReload;
26    protected $cache;
27    protected $lexer;
28    protected $parser;
29    protected $compiler;
30    protected $baseTemplateClass;
31    protected $extensions;
32    protected $parsers;
33    protected $visitors;
34    protected $filters;
35    protected $tests;
36    protected $functions;
37    protected $globals;
38    protected $runtimeInitialized;
39    protected $extensionInitialized;
40    protected $loadedTemplates;
41    protected $strictVariables;
42    protected $unaryOperators;
43    protected $binaryOperators;
44    protected $templateClassPrefix = '__TwigTemplate_';
45    protected $functionCallbacks;
46    protected $filterCallbacks;
47    protected $staging;
48
49    /**
50     * Constructor.
51     *
52     * Available options:
53     *
54     *  * debug: When set to true, it automatically set "auto_reload" to true as
55     *           well (default to false).
56     *
57     *  * charset: The charset used by the templates (default to utf-8).
58     *
59     *  * base_template_class: The base template class to use for generated
60     *                         templates (default to Twig_Template).
61     *
62     *  * cache: An absolute path where to store the compiled templates, or
63     *           false to disable compilation cache (default).
64     *
65     *  * auto_reload: Whether to reload the template is the original source changed.
66     *                 If you don't provide the auto_reload option, it will be
67     *                 determined automatically base on the debug value.
68     *
69     *  * strict_variables: Whether to ignore invalid variables in templates
70     *                      (default to false).
71     *
72     *  * autoescape: Whether to enable auto-escaping (default to html):
73     *                  * false: disable auto-escaping
74     *                  * true: equivalent to html
75     *                  * html, js: set the autoescaping to one of the supported strategies
76     *                  * PHP callback: a PHP callback that returns an escaping strategy based on the template "filename"
77     *
78     *  * optimizations: A flag that indicates which optimizations to apply
79     *                   (default to -1 which means that all optimizations are enabled;
80     *                   set it to 0 to disable).
81     *
82     * @param Twig_LoaderInterface $loader  A Twig_LoaderInterface instance
83     * @param array                $options An array of options
84     */
85    public function __construct(Twig_LoaderInterface $loader = null, $options = array())
86    {
87        if (null !== $loader) {
88            $this->setLoader($loader);
89        }
90
91        $options = array_merge(array(
92            'debug'               => false,
93            'charset'             => 'UTF-8',
94            'base_template_class' => 'Twig_Template',
95            'strict_variables'    => false,
96            'autoescape'          => 'html',
97            'cache'               => false,
98            'auto_reload'         => null,
99            'optimizations'       => -1,
100        ), $options);
101
102        $this->debug              = (bool) $options['debug'];
103        $this->charset            = $options['charset'];
104        $this->baseTemplateClass  = $options['base_template_class'];
105        $this->autoReload         = null === $options['auto_reload'] ? $this->debug : (bool) $options['auto_reload'];
106        $this->strictVariables    = (bool) $options['strict_variables'];
107        $this->runtimeInitialized = false;
108        $this->setCache($options['cache']);
109        $this->functionCallbacks = array();
110        $this->filterCallbacks = array();
111
112        $this->addExtension(new Twig_Extension_Core());
113        $this->addExtension(new Twig_Extension_Escaper($options['autoescape']));
114        $this->addExtension(new Twig_Extension_Optimizer($options['optimizations']));
115        $this->extensionInitialized = false;
116        $this->staging = new Twig_Extension_Staging();
117    }
118
119    /**
120     * Gets the base template class for compiled templates.
121     *
122     * @return string The base template class name
123     */
124    public function getBaseTemplateClass()
125    {
126        return $this->baseTemplateClass;
127    }
128
129    /**
130     * Sets the base template class for compiled templates.
131     *
132     * @param string $class The base template class name
133     */
134    public function setBaseTemplateClass($class)
135    {
136        $this->baseTemplateClass = $class;
137    }
138
139    /**
140     * Enables debugging mode.
141     */
142    public function enableDebug()
143    {
144        $this->debug = true;
145    }
146
147    /**
148     * Disables debugging mode.
149     */
150    public function disableDebug()
151    {
152        $this->debug = false;
153    }
154
155    /**
156     * Checks if debug mode is enabled.
157     *
158     * @return Boolean true if debug mode is enabled, false otherwise
159     */
160    public function isDebug()
161    {
162        return $this->debug;
163    }
164
165    /**
166     * Enables the auto_reload option.
167     */
168    public function enableAutoReload()
169    {
170        $this->autoReload = true;
171    }
172
173    /**
174     * Disables the auto_reload option.
175     */
176    public function disableAutoReload()
177    {
178        $this->autoReload = false;
179    }
180
181    /**
182     * Checks if the auto_reload option is enabled.
183     *
184     * @return Boolean true if auto_reload is enabled, false otherwise
185     */
186    public function isAutoReload()
187    {
188        return $this->autoReload;
189    }
190
191    /**
192     * Enables the strict_variables option.
193     */
194    public function enableStrictVariables()
195    {
196        $this->strictVariables = true;
197    }
198
199    /**
200     * Disables the strict_variables option.
201     */
202    public function disableStrictVariables()
203    {
204        $this->strictVariables = false;
205    }
206
207    /**
208     * Checks if the strict_variables option is enabled.
209     *
210     * @return Boolean true if strict_variables is enabled, false otherwise
211     */
212    public function isStrictVariables()
213    {
214        return $this->strictVariables;
215    }
216
217    /**
218     * Gets the cache directory or false if cache is disabled.
219     *
220     * @return string|false
221     */
222    public function getCache()
223    {
224        return $this->cache;
225    }
226
227     /**
228      * Sets the cache directory or false if cache is disabled.
229      *
230      * @param string|false $cache The absolute path to the compiled templates,
231      *                            or false to disable cache
232      */
233    public function setCache($cache)
234    {
235        $this->cache = $cache ? $cache : false;
236    }
237
238    /**
239     * Gets the cache filename for a given template.
240     *
241     * @param string $name The template name
242     *
243     * @return string The cache file name
244     */
245    public function getCacheFilename($name)
246    {
247        if (false === $this->cache) {
248            return false;
249        }
250
251        $class = substr($this->getTemplateClass($name), strlen($this->templateClassPrefix));
252
253        return $this->getCache().'/'.substr($class, 0, 2).'/'.substr($class, 2, 2).'/'.substr($class, 4).'.php';
254    }
255
256    /**
257     * Gets the template class associated with the given string.
258     *
259     * @param string  $name  The name for which to calculate the template class name
260     * @param integer $index The index if it is an embedded template
261     *
262     * @return string The template class name
263     */
264    public function getTemplateClass($name, $index = null)
265    {
266        return $this->templateClassPrefix.md5($this->getLoader()->getCacheKey($name)).(null === $index ? '' : '_'.$index);
267    }
268
269    /**
270     * Gets the template class prefix.
271     *
272     * @return string The template class prefix
273     */
274    public function getTemplateClassPrefix()
275    {
276        return $this->templateClassPrefix;
277    }
278
279    /**
280     * Renders a template.
281     *
282     * @param string $name    The template name
283     * @param array  $context An array of parameters to pass to the template
284     *
285     * @return string The rendered template
286     */
287    public function render($name, array $context = array())
288    {
289        return $this->loadTemplate($name)->render($context);
290    }
291
292    /**
293     * Displays a template.
294     *
295     * @param string $name    The template name
296     * @param array  $context An array of parameters to pass to the template
297     */
298    public function display($name, array $context = array())
299    {
300        $this->loadTemplate($name)->display($context);
301    }
302
303    /**
304     * Loads a template by name.
305     *
306     * @param string  $name  The template name
307     * @param integer $index The index if it is an embedded template
308     *
309     * @return Twig_TemplateInterface A template instance representing the given template name
310     */
311    public function loadTemplate($name, $index = null)
312    {
313        $cls = $this->getTemplateClass($name, $index);
314
315        if (isset($this->loadedTemplates[$cls])) {
316            return $this->loadedTemplates[$cls];
317        }
318
319        if (!class_exists($cls, false)) {
320            if (false === $cache = $this->getCacheFilename($name)) {
321                eval('?>'.$this->compileSource($this->getLoader()->getSource($name), $name));
322            } else {
323                if (!is_file($cache) || ($this->isAutoReload() && !$this->isTemplateFresh($name, filemtime($cache)))) {
324                    $this->writeCacheFile($cache, $this->compileSource($this->getLoader()->getSource($name), $name));
325                }
326
327                require_once $cache;
328            }
329        }
330
331        if (!$this->runtimeInitialized) {
332            $this->initRuntime();
333        }
334
335        return $this->loadedTemplates[$cls] = new $cls($this);
336    }
337
338    /**
339     * Returns true if the template is still fresh.
340     *
341     * Besides checking the loader for freshness information,
342     * this method also checks if the enabled extensions have
343     * not changed.
344     *
345     * @param string    $name The template name
346     * @param timestamp $time The last modification time of the cached template
347     *
348     * @return Boolean true if the template is fresh, false otherwise
349     */
350    public function isTemplateFresh($name, $time)
351    {
352        foreach ($this->extensions as $extension) {
353            $r = new ReflectionObject($extension);
354            if (filemtime($r->getFileName()) > $time) {
355                return false;
356            }
357        }
358
359        return $this->getLoader()->isFresh($name, $time);
360    }
361
362    public function resolveTemplate($names)
363    {
364        if (!is_array($names)) {
365            $names = array($names);
366        }
367
368        foreach ($names as $name) {
369            if ($name instanceof Twig_Template) {
370                return $name;
371            }
372
373            try {
374                return $this->loadTemplate($name);
375            } catch (Twig_Error_Loader $e) {
376            }
377        }
378
379        if (1 === count($names)) {
380            throw $e;
381        }
382
383        throw new Twig_Error_Loader(sprintf('Unable to find one of the following templates: "%s".', implode('", "', $names)));
384    }
385
386    /**
387     * Clears the internal template cache.
388     */
389    public function clearTemplateCache()
390    {
391        $this->loadedTemplates = array();
392    }
393
394    /**
395     * Clears the template cache files on the filesystem.
396     */
397    public function clearCacheFiles()
398    {
399        if (false === $this->cache) {
400            return;
401        }
402
403        foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->cache), RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
404            if ($file->isFile()) {
405                @unlink($file->getPathname());
406            }
407        }
408    }
409
410    /**
411     * Gets the Lexer instance.
412     *
413     * @return Twig_LexerInterface A Twig_LexerInterface instance
414     */
415    public function getLexer()
416    {
417        if (null === $this->lexer) {
418            $this->lexer = new Twig_Lexer($this);
419        }
420
421        return $this->lexer;
422    }
423
424    /**
425     * Sets the Lexer instance.
426     *
427     * @param Twig_LexerInterface A Twig_LexerInterface instance
428     */
429    public function setLexer(Twig_LexerInterface $lexer)
430    {
431        $this->lexer = $lexer;
432    }
433
434    /**
435     * Tokenizes a source code.
436     *
437     * @param string $source The template source code
438     * @param string $name   The template name
439     *
440     * @return Twig_TokenStream A Twig_TokenStream instance
441     */
442    public function tokenize($source, $name = null)
443    {
444        return $this->getLexer()->tokenize($source, $name);
445    }
446
447    /**
448     * Gets the Parser instance.
449     *
450     * @return Twig_ParserInterface A Twig_ParserInterface instance
451     */
452    public function getParser()
453    {
454        if (null === $this->parser) {
455            $this->parser = new Twig_Parser($this);
456        }
457
458        return $this->parser;
459    }
460
461    /**
462     * Sets the Parser instance.
463     *
464     * @param Twig_ParserInterface A Twig_ParserInterface instance
465     */
466    public function setParser(Twig_ParserInterface $parser)
467    {
468        $this->parser = $parser;
469    }
470
471    /**
472     * Parses a token stream.
473     *
474     * @param Twig_TokenStream $tokens A Twig_TokenStream instance
475     *
476     * @return Twig_Node_Module A Node tree
477     */
478    public function parse(Twig_TokenStream $tokens)
479    {
480        return $this->getParser()->parse($tokens);
481    }
482
483    /**
484     * Gets the Compiler instance.
485     *
486     * @return Twig_CompilerInterface A Twig_CompilerInterface instance
487     */
488    public function getCompiler()
489    {
490        if (null === $this->compiler) {
491            $this->compiler = new Twig_Compiler($this);
492        }
493
494        return $this->compiler;
495    }
496
497    /**
498     * Sets the Compiler instance.
499     *
500     * @param Twig_CompilerInterface $compiler A Twig_CompilerInterface instance
501     */
502    public function setCompiler(Twig_CompilerInterface $compiler)
503    {
504        $this->compiler = $compiler;
505    }
506
507    /**
508     * Compiles a Node.
509     *
510     * @param Twig_NodeInterface $node A Twig_NodeInterface instance
511     *
512     * @return string The compiled PHP source code
513     */
514    public function compile(Twig_NodeInterface $node)
515    {
516        return $this->getCompiler()->compile($node)->getSource();
517    }
518
519    /**
520     * Compiles a template source code.
521     *
522     * @param string $source The template source code
523     * @param string $name   The template name
524     *
525     * @return string The compiled PHP source code
526     */
527    public function compileSource($source, $name = null)
528    {
529        try {
530            return $this->compile($this->parse($this->tokenize($source, $name)));
531        } catch (Twig_Error $e) {
532            $e->setTemplateFile($name);
533            throw $e;
534        } catch (Exception $e) {
535            throw new Twig_Error_Runtime(sprintf('An exception has been thrown during the compilation of a template ("%s").', $e->getMessage()), -1, $name, $e);
536        }
537    }
538
539    /**
540     * Sets the Loader instance.
541     *
542     * @param Twig_LoaderInterface $loader A Twig_LoaderInterface instance
543     */
544    public function setLoader(Twig_LoaderInterface $loader)
545    {
546        $this->loader = $loader;
547    }
548
549    /**
550     * Gets the Loader instance.
551     *
552     * @return Twig_LoaderInterface A Twig_LoaderInterface instance
553     */
554    public function getLoader()
555    {
556        if (null === $this->loader) {
557            throw new LogicException('You must set a loader first.');
558        }
559
560        return $this->loader;
561    }
562
563    /**
564     * Sets the default template charset.
565     *
566     * @param string $charset The default charset
567     */
568    public function setCharset($charset)
569    {
570        $this->charset = $charset;
571    }
572
573    /**
574     * Gets the default template charset.
575     *
576     * @return string The default charset
577     */
578    public function getCharset()
579    {
580        return $this->charset;
581    }
582
583    /**
584     * Initializes the runtime environment.
585     */
586    public function initRuntime()
587    {
588        $this->runtimeInitialized = true;
589
590        foreach ($this->getExtensions() as $extension) {
591            $extension->initRuntime($this);
592        }
593    }
594
595    /**
596     * Returns true if the given extension is registered.
597     *
598     * @param string $name The extension name
599     *
600     * @return Boolean Whether the extension is registered or not
601     */
602    public function hasExtension($name)
603    {
604        return isset($this->extensions[$name]);
605    }
606
607    /**
608     * Gets an extension by name.
609     *
610     * @param string $name The extension name
611     *
612     * @return Twig_ExtensionInterface A Twig_ExtensionInterface instance
613     */
614    public function getExtension($name)
615    {
616        if (!isset($this->extensions[$name])) {
617            throw new Twig_Error_Runtime(sprintf('The "%s" extension is not enabled.', $name));
618        }
619
620        return $this->extensions[$name];
621    }
622
623    /**
624     * Registers an extension.
625     *
626     * @param Twig_ExtensionInterface $extension A Twig_ExtensionInterface instance
627     */
628    public function addExtension(Twig_ExtensionInterface $extension)
629    {
630        if ($this->extensionInitialized) {
631            throw new LogicException(sprintf('Unable to register extension "%s" as extensions have already been initialized.', $extension->getName()));
632        }
633
634        $this->extensions[$extension->getName()] = $extension;
635    }
636
637    /**
638     * Removes an extension by name.
639     *
640     * This method is deprecated and you should not use it.
641     *
642     * @param string $name The extension name
643     *
644     * @deprecated since 1.12 (to be removed in 2.0)
645     */
646    public function removeExtension($name)
647    {
648        if ($this->extensionInitialized) {
649            throw new LogicException(sprintf('Unable to remove extension "%s" as extensions have already been initialized.', $name));
650        }
651
652        unset($this->extensions[$name]);
653    }
654
655    /**
656     * Registers an array of extensions.
657     *
658     * @param array $extensions An array of extensions
659     */
660    public function setExtensions(array $extensions)
661    {
662        foreach ($extensions as $extension) {
663            $this->addExtension($extension);
664        }
665    }
666
667    /**
668     * Returns all registered extensions.
669     *
670     * @return array An array of extensions
671     */
672    public function getExtensions()
673    {
674        return $this->extensions;
675    }
676
677    /**
678     * Registers a Token Parser.
679     *
680     * @param Twig_TokenParserInterface $parser A Twig_TokenParserInterface instance
681     */
682    public function addTokenParser(Twig_TokenParserInterface $parser)
683    {
684        if ($this->extensionInitialized) {
685            throw new LogicException('Unable to add a token parser as extensions have already been initialized.');
686        }
687
688        $this->staging->addTokenParser($parser);
689    }
690
691    /**
692     * Gets the registered Token Parsers.
693     *
694     * @return Twig_TokenParserBrokerInterface A broker containing token parsers
695     */
696    public function getTokenParsers()
697    {
698        if (!$this->extensionInitialized) {
699            $this->initExtensions();
700        }
701
702        return $this->parsers;
703    }
704
705    /**
706     * Gets registered tags.
707     *
708     * Be warned that this method cannot return tags defined by Twig_TokenParserBrokerInterface classes.
709     *
710     * @return Twig_TokenParserInterface[] An array of Twig_TokenParserInterface instances
711     */
712    public function getTags()
713    {
714        $tags = array();
715        foreach ($this->getTokenParsers()->getParsers() as $parser) {
716            if ($parser instanceof Twig_TokenParserInterface) {
717                $tags[$parser->getTag()] = $parser;
718            }
719        }
720
721        return $tags;
722    }
723
724    /**
725     * Registers a Node Visitor.
726     *
727     * @param Twig_NodeVisitorInterface $visitor A Twig_NodeVisitorInterface instance
728     */
729    public function addNodeVisitor(Twig_NodeVisitorInterface $visitor)
730    {
731        if ($this->extensionInitialized) {
732            throw new LogicException('Unable to add a node visitor as extensions have already been initialized.', $extension->getName());
733        }
734
735        $this->staging->addNodeVisitor($visitor);
736    }
737
738    /**
739     * Gets the registered Node Visitors.
740     *
741     * @return Twig_NodeVisitorInterface[] An array of Twig_NodeVisitorInterface instances
742     */
743    public function getNodeVisitors()
744    {
745        if (!$this->extensionInitialized) {
746            $this->initExtensions();
747        }
748
749        return $this->visitors;
750    }
751
752    /**
753     * Registers a Filter.
754     *
755     * @param string|Twig_SimpleFilter               $name   The filter name or a Twig_SimpleFilter instance
756     * @param Twig_FilterInterface|Twig_SimpleFilter $filter A Twig_FilterInterface instance or a Twig_SimpleFilter instance
757     */
758    public function addFilter($name, $filter = null)
759    {
760        if ($this->extensionInitialized) {
761            throw new LogicException(sprintf('Unable to add filter "%s" as extensions have already been initialized.', $name));
762        }
763
764        if (!$name instanceof Twig_SimpleFilter && !($filter instanceof Twig_SimpleFilter || $filter instanceof Twig_FilterInterface)) {
765            throw new LogicException('A filter must be an instance of Twig_FilterInterface or Twig_SimpleFilter');
766        }
767
768        if ($name instanceof Twig_SimpleFilter) {
769            $filter = $name;
770            $name = $filter->getName();
771        }
772
773        $this->staging->addFilter($name, $filter);
774    }
775
776    /**
777     * Get a filter by name.
778     *
779     * Subclasses may override this method and load filters differently;
780     * so no list of filters is available.
781     *
782     * @param string $name The filter name
783     *
784     * @return Twig_Filter|false A Twig_Filter instance or false if the filter does not exist
785     */
786    public function getFilter($name)
787    {
788        if (!$this->extensionInitialized) {
789            $this->initExtensions();
790        }
791
792        if (isset($this->filters[$name])) {
793            return $this->filters[$name];
794        }
795
796        foreach ($this->filters as $pattern => $filter) {
797            $pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count);
798
799            if ($count) {
800                if (preg_match('#^'.$pattern.'$#', $name, $matches)) {
801                    array_shift($matches);
802                    $filter->setArguments($matches);
803
804                    return $filter;
805                }
806            }
807        }
808
809        foreach ($this->filterCallbacks as $callback) {
810            if (false !== $filter = call_user_func($callback, $name)) {
811                return $filter;
812            }
813        }
814
815        return false;
816    }
817
818    public function registerUndefinedFilterCallback($callable)
819    {
820        $this->filterCallbacks[] = $callable;
821    }
822
823    /**
824     * Gets the registered Filters.
825     *
826     * Be warned that this method cannot return filters defined with registerUndefinedFunctionCallback.
827     *
828     * @return Twig_FilterInterface[] An array of Twig_FilterInterface instances
829     *
830     * @see registerUndefinedFilterCallback
831     */
832    public function getFilters()
833    {
834        if (!$this->extensionInitialized) {
835            $this->initExtensions();
836        }
837
838        return $this->filters;
839    }
840
841    /**
842     * Registers a Test.
843     *
844     * @param string|Twig_SimpleTest             $name The test name or a Twig_SimpleTest instance
845     * @param Twig_TestInterface|Twig_SimpleTest $test A Twig_TestInterface instance or a Twig_SimpleTest instance
846     */
847    public function addTest($name, $test = null)
848    {
849        if ($this->extensionInitialized) {
850            throw new LogicException(sprintf('Unable to add test "%s" as extensions have already been initialized.', $name));
851        }
852
853        if (!$name instanceof Twig_SimpleTest && !($test instanceof Twig_SimpleTest || $test instanceof Twig_TestInterface)) {
854            throw new LogicException('A test must be an instance of Twig_TestInterface or Twig_SimpleTest');
855        }
856
857        if ($name instanceof Twig_SimpleTest) {
858            $test = $name;
859            $name = $test->getName();
860        }
861
862        $this->staging->addTest($name, $test);
863    }
864
865    /**
866     * Gets the registered Tests.
867     *
868     * @return Twig_TestInterface[] An array of Twig_TestInterface instances
869     */
870    public function getTests()
871    {
872        if (!$this->extensionInitialized) {
873            $this->initExtensions();
874        }
875
876        return $this->tests;
877    }
878
879    /**
880     * Gets a test by name.
881     *
882     * @param string $name The test name
883     *
884     * @return Twig_Test|false A Twig_Test instance or false if the test does not exist
885     */
886    public function getTest($name)
887    {
888        if (!$this->extensionInitialized) {
889            $this->initExtensions();
890        }
891
892        if (isset($this->tests[$name])) {
893            return $this->tests[$name];
894        }
895
896        return false;
897    }
898
899    /**
900     * Registers a Function.
901     *
902     * @param string|Twig_SimpleFunction                 $name     The function name or a Twig_SimpleFunction instance
903     * @param Twig_FunctionInterface|Twig_SimpleFunction $function A Twig_FunctionInterface instance or a Twig_SimpleFunction instance
904     */
905    public function addFunction($name, $function = null)
906    {
907        if ($this->extensionInitialized) {
908            throw new LogicException(sprintf('Unable to add function "%s" as extensions have already been initialized.', $name));
909        }
910
911        if (!$name instanceof Twig_SimpleFunction && !($function instanceof Twig_SimpleFunction || $function instanceof Twig_FunctionInterface)) {
912            throw new LogicException('A function must be an instance of Twig_FunctionInterface or Twig_SimpleFunction');
913        }
914
915        if ($name instanceof Twig_SimpleFunction) {
916            $function = $name;
917            $name = $function->getName();
918        }
919
920        $this->staging->addFunction($name, $function);
921    }
922
923    /**
924     * Get a function by name.
925     *
926     * Subclasses may override this method and load functions differently;
927     * so no list of functions is available.
928     *
929     * @param string $name function name
930     *
931     * @return Twig_Function|false A Twig_Function instance or false if the function does not exist
932     */
933    public function getFunction($name)
934    {
935        if (!$this->extensionInitialized) {
936            $this->initExtensions();
937        }
938
939        if (isset($this->functions[$name])) {
940            return $this->functions[$name];
941        }
942
943        foreach ($this->functions as $pattern => $function) {
944            $pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count);
945
946            if ($count) {
947                if (preg_match('#^'.$pattern.'$#', $name, $matches)) {
948                    array_shift($matches);
949                    $function->setArguments($matches);
950
951                    return $function;
952                }
953            }
954        }
955
956        foreach ($this->functionCallbacks as $callback) {
957            if (false !== $function = call_user_func($callback, $name)) {
958                return $function;
959            }
960        }
961
962        return false;
963    }
964
965    public function registerUndefinedFunctionCallback($callable)
966    {
967        $this->functionCallbacks[] = $callable;
968    }
969
970    /**
971     * Gets registered functions.
972     *
973     * Be warned that this method cannot return functions defined with registerUndefinedFunctionCallback.
974     *
975     * @return Twig_FunctionInterface[] An array of Twig_FunctionInterface instances
976     *
977     * @see registerUndefinedFunctionCallback
978     */
979    public function getFunctions()
980    {
981        if (!$this->extensionInitialized) {
982            $this->initExtensions();
983        }
984
985        return $this->functions;
986    }
987
988    /**
989     * Registers a Global.
990     *
991     * New globals can be added before compiling or rendering a template;
992     * but after, you can only update existing globals.
993     *
994     * @param string $name  The global name
995     * @param mixed  $value The global value
996     */
997    public function addGlobal($name, $value)
998    {
999        if ($this->extensionInitialized || $this->runtimeInitialized) {
1000            if (null === $this->globals) {
1001                $this->globals = $this->initGlobals();
1002            }
1003
1004            /* This condition must be uncommented in Twig 2.0
1005            if (!array_key_exists($name, $this->globals)) {
1006                throw new LogicException(sprintf('Unable to add global "%s" as the runtime or the extensions have already been initialized.', $name));
1007            }
1008            */
1009        }
1010
1011        if ($this->extensionInitialized || $this->runtimeInitialized) {
1012            // update the value
1013            $this->globals[$name] = $value;
1014        } else {
1015            $this->staging->addGlobal($name, $value);
1016        }
1017    }
1018
1019    /**
1020     * Gets the registered Globals.
1021     *
1022     * @return array An array of globals
1023     */
1024    public function getGlobals()
1025    {
1026        if (!$this->runtimeInitialized && !$this->extensionInitialized) {
1027            return $this->initGlobals();
1028        }
1029
1030        if (null === $this->globals) {
1031            $this->globals = $this->initGlobals();
1032        }
1033
1034        return $this->globals;
1035    }
1036
1037    /**
1038     * Merges a context with the defined globals.
1039     *
1040     * @param array $context An array representing the context
1041     *
1042     * @return array The context merged with the globals
1043     */
1044    public function mergeGlobals(array $context)
1045    {
1046        // we don't use array_merge as the context being generally
1047        // bigger than globals, this code is faster.
1048        foreach ($this->getGlobals() as $key => $value) {
1049            if (!array_key_exists($key, $context)) {
1050                $context[$key] = $value;
1051            }
1052        }
1053
1054        return $context;
1055    }
1056
1057    /**
1058     * Gets the registered unary Operators.
1059     *
1060     * @return array An array of unary operators
1061     */
1062    public function getUnaryOperators()
1063    {
1064        if (!$this->extensionInitialized) {
1065            $this->initExtensions();
1066        }
1067
1068        return $this->unaryOperators;
1069    }
1070
1071    /**
1072     * Gets the registered binary Operators.
1073     *
1074     * @return array An array of binary operators
1075     */
1076    public function getBinaryOperators()
1077    {
1078        if (!$this->extensionInitialized) {
1079            $this->initExtensions();
1080        }
1081
1082        return $this->binaryOperators;
1083    }
1084
1085    public function computeAlternatives($name, $items)
1086    {
1087        $alternatives = array();
1088        foreach ($items as $item) {
1089            $lev = levenshtein($name, $item);
1090            if ($lev <= strlen($name) / 3 || false !== strpos($item, $name)) {
1091                $alternatives[$item] = $lev;
1092            }
1093        }
1094        asort($alternatives);
1095
1096        return array_keys($alternatives);
1097    }
1098
1099    protected function initGlobals()
1100    {
1101        $globals = array();
1102        foreach ($this->extensions as $extension) {
1103            $globals = array_merge($globals, $extension->getGlobals());
1104        }
1105
1106        return array_merge($globals, $this->staging->getGlobals());
1107    }
1108
1109    protected function initExtensions()
1110    {
1111        if ($this->extensionInitialized) {
1112            return;
1113        }
1114
1115        $this->extensionInitialized = true;
1116        $this->parsers = new Twig_TokenParserBroker();
1117        $this->filters = array();
1118        $this->functions = array();
1119        $this->tests = array();
1120        $this->visitors = array();
1121        $this->unaryOperators = array();
1122        $this->binaryOperators = array();
1123
1124        foreach ($this->extensions as $extension) {
1125            $this->initExtension($extension);
1126        }
1127        $this->initExtension($this->staging);
1128    }
1129
1130    protected function initExtension(Twig_ExtensionInterface $extension)
1131    {
1132        // filters
1133        foreach ($extension->getFilters() as $name => $filter) {
1134            if ($name instanceof Twig_SimpleFilter) {
1135                $filter = $name;
1136                $name = $filter->getName();
1137            } elseif ($filter instanceof Twig_SimpleFilter) {
1138                $name = $filter->getName();
1139            }
1140
1141            $this->filters[$name] = $filter;
1142        }
1143
1144        // functions
1145        foreach ($extension->getFunctions() as $name => $function) {
1146            if ($name instanceof Twig_SimpleFunction) {
1147                $function = $name;
1148                $name = $function->getName();
1149            } elseif ($function instanceof Twig_SimpleFunction) {
1150                $name = $function->getName();
1151            }
1152
1153            $this->functions[$name] = $function;
1154        }
1155
1156        // tests
1157        foreach ($extension->getTests() as $name => $test) {
1158            if ($name instanceof Twig_SimpleTest) {
1159                $test = $name;
1160                $name = $test->getName();
1161            } elseif ($test instanceof Twig_SimpleTest) {
1162                $name = $test->getName();
1163            }
1164
1165            $this->tests[$name] = $test;
1166        }
1167
1168        // token parsers
1169        foreach ($extension->getTokenParsers() as $parser) {
1170            if ($parser instanceof Twig_TokenParserInterface) {
1171                $this->parsers->addTokenParser($parser);
1172            } elseif ($parser instanceof Twig_TokenParserBrokerInterface) {
1173                $this->parsers->addTokenParserBroker($parser);
1174            } else {
1175                throw new LogicException('getTokenParsers() must return an array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances');
1176            }
1177        }
1178
1179        // node visitors
1180        foreach ($extension->getNodeVisitors() as $visitor) {
1181            $this->visitors[] = $visitor;
1182        }
1183
1184        // operators
1185        if ($operators = $extension->getOperators()) {
1186            if (2 !== count($operators)) {
1187                throw new InvalidArgumentException(sprintf('"%s::getOperators()" does not return a valid operators array.', get_class($extension)));
1188            }
1189
1190            $this->unaryOperators = array_merge($this->unaryOperators, $operators[0]);
1191            $this->binaryOperators = array_merge($this->binaryOperators, $operators[1]);
1192        }
1193    }
1194
1195    protected function writeCacheFile($file, $content)
1196    {
1197        $dir = dirname($file);
1198        if (!is_dir($dir)) {
1199            if (false === @mkdir($dir, 0777, true) && !is_dir($dir)) {
1200                throw new RuntimeException(sprintf("Unable to create the cache directory (%s).", $dir));
1201            }
1202        } elseif (!is_writable($dir)) {
1203            throw new RuntimeException(sprintf("Unable to write in the cache directory (%s).", $dir));
1204        }
1205
1206        $tmpFile = tempnam(dirname($file), basename($file));
1207        if (false !== @file_put_contents($tmpFile, $content)) {
1208            // rename does not work on Win32 before 5.2.6
1209            if (@rename($tmpFile, $file) || (@copy($tmpFile, $file) && unlink($tmpFile))) {
1210                @chmod($file, 0666 & ~umask());
1211
1212                return;
1213            }
1214        }
1215
1216        throw new RuntimeException(sprintf('Failed to write cache file "%s".', $file));
1217    }
1218}
Note: See TracBrowser for help on using the repository browser.

Sites map