Twig_Environment::getExtensions()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 4
rs 10
nc 1
cc 1
eloc 2
nop 0
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
 * @author Fabien Potencier <[email protected]>
16
 */
17
class Twig_Environment
18
{
19
    const VERSION = '1.18.0';
20
21
    protected $charset;
22
    protected $loader;
23
    protected $debug;
24
    protected $autoReload;
25
    protected $cache;
26
    protected $lexer;
27
    protected $parser;
28
    protected $compiler;
29
    protected $baseTemplateClass;
30
    protected $extensions;
31
    protected $parsers;
32
    protected $visitors;
33
    protected $filters;
34
    protected $tests;
35
    protected $functions;
36
    protected $globals;
37
    protected $runtimeInitialized;
38
    protected $extensionInitialized;
39
    protected $loadedTemplates;
40
    protected $strictVariables;
41
    protected $unaryOperators;
42
    protected $binaryOperators;
43
    protected $templateClassPrefix = '__TwigTemplate_';
44
    protected $functionCallbacks;
45
    protected $filterCallbacks;
46
    protected $staging;
47
48
    /**
49
     * Constructor.
50
     *
51
     * Available options:
52
     *
53
     *  * debug: When set to true, it automatically set "auto_reload" to true as
54
     *           well (default to false).
55
     *
56
     *  * charset: The charset used by the templates (default to UTF-8).
57
     *
58
     *  * base_template_class: The base template class to use for generated
59
     *                         templates (default to Twig_Template).
60
     *
61
     *  * cache: An absolute path where to store the compiled templates, or
62
     *           false to disable compilation cache (default).
63
     *
64
     *  * auto_reload: Whether to reload the template if the original source changed.
65
     *                 If you don't provide the auto_reload option, it will be
66
     *                 determined automatically based on the debug value.
67
     *
68
     *  * strict_variables: Whether to ignore invalid variables in templates
69
     *                      (default to false).
70
     *
71
     *  * autoescape: Whether to enable auto-escaping (default to html):
72
     *                  * false: disable auto-escaping
73
     *                  * true: equivalent to html
74
     *                  * html, js: set the autoescaping to one of the supported strategies
75
     *                  * filename: set the autoescaping strategy based on the template filename extension
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            = strtoupper($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 bool 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 bool 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 bool 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|false The cache file name or false when caching is disabled
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 int    $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.hash('sha256', $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
     * @throws Twig_Error_Loader  When the template cannot be found
288
     * @throws Twig_Error_Syntax  When an error occurred during compilation
289
     * @throws Twig_Error_Runtime When an error occurred during rendering
290
     */
291
    public function render($name, array $context = array())
292
    {
293
        return $this->loadTemplate($name)->render($context);
294
    }
295
296
    /**
297
     * Displays a template.
298
     *
299
     * @param string $name    The template name
300
     * @param array  $context An array of parameters to pass to the template
301
     *
302
     * @throws Twig_Error_Loader  When the template cannot be found
303
     * @throws Twig_Error_Syntax  When an error occurred during compilation
304
     * @throws Twig_Error_Runtime When an error occurred during rendering
305
     */
306
    public function display($name, array $context = array())
307
    {
308
        $this->loadTemplate($name)->display($context);
309
    }
310
311
    /**
312
     * Loads a template by name.
313
     *
314
     * @param string $name  The template name
315
     * @param int    $index The index if it is an embedded template
316
     *
317
     * @return Twig_TemplateInterface A template instance representing the given template name
318
     *
319
     * @throws Twig_Error_Loader When the template cannot be found
320
     * @throws Twig_Error_Syntax When an error occurred during compilation
321
     */
322
    public function loadTemplate($name, $index = null)
323
    {
324
        $cls = $this->getTemplateClass($name, $index);
325
326
        if (isset($this->loadedTemplates[$cls])) {
327
            return $this->loadedTemplates[$cls];
328
        }
329
330
        if (!class_exists($cls, false)) {
331
            if (false === $cache = $this->getCacheFilename($name)) {
332
                eval('?>'.$this->compileSource($this->getLoader()->getSource($name), $name));
0 ignored issues
show
Coding Style introduced by
It is generally not recommended to use eval unless absolutely required.

On one hand, eval might be exploited by malicious users if they somehow manage to inject dynamic content. On the other hand, with the emergence of faster PHP runtimes like the HHVM, eval prevents some optimization that they perform.

Loading history...
333
            } else {
334
                if (!is_file($cache) || ($this->isAutoReload() && !$this->isTemplateFresh($name, filemtime($cache)))) {
335
                    $this->writeCacheFile($cache, $this->compileSource($this->getLoader()->getSource($name), $name));
336
                }
337
338
                require_once $cache;
339
            }
340
        }
341
342
        if (!$this->runtimeInitialized) {
343
            $this->initRuntime();
344
        }
345
346
        return $this->loadedTemplates[$cls] = new $cls($this);
347
    }
348
349
    /**
350
     * Creates a template from source.
351
     *
352
     * This method should not be used as a generic way to load templates.
353
     *
354
     * @param string $name  The template name
0 ignored issues
show
Bug introduced by
There is no parameter named $name. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
355
     * @param int    $index The index if it is an embedded template
0 ignored issues
show
Bug introduced by
There is no parameter named $index. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
356
     *
357
     * @return Twig_Template A template instance representing the given template name
358
     *
359
     * @throws Twig_Error_Loader When the template cannot be found
360
     * @throws Twig_Error_Syntax When an error occurred during compilation
361
     */
362
    public function createTemplate($template)
363
    {
364
        $name = sprintf('__string_template__%s', hash('sha256', uniqid(mt_rand(), true), false));
365
366
        $loader = new Twig_Loader_Chain(array(
367
            new Twig_Loader_Array(array($name => $template)),
368
            $current = $this->getLoader(),
369
        ));
370
371
        $this->setLoader($loader);
372
        try {
373
            $template = $this->loadTemplate($name);
374
        } catch (Exception $e) {
375
            $this->setLoader($current);
376
377
            throw $e;
378
        }
379
        $this->setLoader($current);
380
381
        return $template;
382
    }
383
384
    /**
385
     * Returns true if the template is still fresh.
386
     *
387
     * Besides checking the loader for freshness information,
388
     * this method also checks if the enabled extensions have
389
     * not changed.
390
     *
391
     * @param string $name The template name
392
     * @param int    $time The last modification time of the cached template
393
     *
394
     * @return bool true if the template is fresh, false otherwise
395
     */
396
    public function isTemplateFresh($name, $time)
397
    {
398
        foreach ($this->extensions as $extension) {
399
            $r = new ReflectionObject($extension);
400
            if (filemtime($r->getFileName()) > $time) {
401
                return false;
402
            }
403
        }
404
405
        return $this->getLoader()->isFresh($name, $time);
0 ignored issues
show
Documentation introduced by
$time is of type integer, but the function expects a object<timestamp>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
406
    }
407
408
    /**
409
     * Tries to load a template consecutively from an array.
410
     *
411
     * Similar to loadTemplate() but it also accepts Twig_TemplateInterface instances and an array
412
     * of templates where each is tried to be loaded.
413
     *
414
     * @param string|Twig_Template|array $names A template or an array of templates to try consecutively
415
     *
416
     * @return Twig_Template
417
     *
418
     * @throws Twig_Error_Loader When none of the templates can be found
419
     * @throws Twig_Error_Syntax When an error occurred during compilation
420
     */
421
    public function resolveTemplate($names)
422
    {
423
        if (!is_array($names)) {
424
            $names = array($names);
425
        }
426
427
        foreach ($names as $name) {
428
            if ($name instanceof Twig_Template) {
429
                return $name;
430
            }
431
432
            try {
433
                return $this->loadTemplate($name);
434
            } catch (Twig_Error_Loader $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
435
            }
436
        }
437
438
        if (1 === count($names)) {
439
            throw $e;
440
        }
441
442
        throw new Twig_Error_Loader(sprintf('Unable to find one of the following templates: "%s".', implode('", "', $names)));
443
    }
444
445
    /**
446
     * Clears the internal template cache.
447
     */
448
    public function clearTemplateCache()
449
    {
450
        $this->loadedTemplates = array();
451
    }
452
453
    /**
454
     * Clears the template cache files on the filesystem.
455
     */
456
    public function clearCacheFiles()
457
    {
458
        if (false === $this->cache) {
459
            return;
460
        }
461
462
        foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->cache), RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
463
            if ($file->isFile()) {
464
                @unlink($file->getPathname());
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
465
            }
466
        }
467
    }
468
469
    /**
470
     * Gets the Lexer instance.
471
     *
472
     * @return Twig_LexerInterface A Twig_LexerInterface instance
473
     */
474
    public function getLexer()
475
    {
476
        if (null === $this->lexer) {
477
            $this->lexer = new Twig_Lexer($this);
478
        }
479
480
        return $this->lexer;
481
    }
482
483
    /**
484
     * Sets the Lexer instance.
485
     *
486
     * @param Twig_LexerInterface A Twig_LexerInterface instance
487
     */
488
    public function setLexer(Twig_LexerInterface $lexer)
489
    {
490
        $this->lexer = $lexer;
491
    }
492
493
    /**
494
     * Tokenizes a source code.
495
     *
496
     * @param string $source The template source code
497
     * @param string $name   The template name
498
     *
499
     * @return Twig_TokenStream A Twig_TokenStream instance
500
     *
501
     * @throws Twig_Error_Syntax When the code is syntactically wrong
502
     */
503
    public function tokenize($source, $name = null)
504
    {
505
        return $this->getLexer()->tokenize($source, $name);
506
    }
507
508
    /**
509
     * Gets the Parser instance.
510
     *
511
     * @return Twig_ParserInterface A Twig_ParserInterface instance
512
     */
513
    public function getParser()
514
    {
515
        if (null === $this->parser) {
516
            $this->parser = new Twig_Parser($this);
517
        }
518
519
        return $this->parser;
520
    }
521
522
    /**
523
     * Sets the Parser instance.
524
     *
525
     * @param Twig_ParserInterface A Twig_ParserInterface instance
526
     */
527
    public function setParser(Twig_ParserInterface $parser)
528
    {
529
        $this->parser = $parser;
530
    }
531
532
    /**
533
     * Converts a token stream to a node tree.
534
     *
535
     * @param Twig_TokenStream $stream A token stream instance
536
     *
537
     * @return Twig_Node_Module A node tree
538
     *
539
     * @throws Twig_Error_Syntax When the token stream is syntactically or semantically wrong
540
     */
541
    public function parse(Twig_TokenStream $stream)
542
    {
543
        return $this->getParser()->parse($stream);
544
    }
545
546
    /**
547
     * Gets the Compiler instance.
548
     *
549
     * @return Twig_CompilerInterface A Twig_CompilerInterface instance
550
     */
551
    public function getCompiler()
552
    {
553
        if (null === $this->compiler) {
554
            $this->compiler = new Twig_Compiler($this);
555
        }
556
557
        return $this->compiler;
558
    }
559
560
    /**
561
     * Sets the Compiler instance.
562
     *
563
     * @param Twig_CompilerInterface $compiler A Twig_CompilerInterface instance
564
     */
565
    public function setCompiler(Twig_CompilerInterface $compiler)
566
    {
567
        $this->compiler = $compiler;
568
    }
569
570
    /**
571
     * Compiles a node and returns the PHP code.
572
     *
573
     * @param Twig_NodeInterface $node A Twig_NodeInterface instance
574
     *
575
     * @return string The compiled PHP source code
576
     */
577
    public function compile(Twig_NodeInterface $node)
578
    {
579
        return $this->getCompiler()->compile($node)->getSource();
580
    }
581
582
    /**
583
     * Compiles a template source code.
584
     *
585
     * @param string $source The template source code
586
     * @param string $name   The template name
587
     *
588
     * @return string The compiled PHP source code
589
     *
590
     * @throws Twig_Error_Syntax When there was an error during tokenizing, parsing or compiling
591
     */
592
    public function compileSource($source, $name = null)
593
    {
594
        try {
595
            return $this->compile($this->parse($this->tokenize($source, $name)));
596
        } catch (Twig_Error $e) {
597
            $e->setTemplateFile($name);
598
            throw $e;
599
        } catch (Exception $e) {
600
            throw new Twig_Error_Syntax(sprintf('An exception has been thrown during the compilation of a template ("%s").', $e->getMessage()), -1, $name, $e);
601
        }
602
    }
603
604
    /**
605
     * Sets the Loader instance.
606
     *
607
     * @param Twig_LoaderInterface $loader A Twig_LoaderInterface instance
608
     */
609
    public function setLoader(Twig_LoaderInterface $loader)
610
    {
611
        $this->loader = $loader;
612
    }
613
614
    /**
615
     * Gets the Loader instance.
616
     *
617
     * @return Twig_LoaderInterface A Twig_LoaderInterface instance
618
     */
619
    public function getLoader()
620
    {
621
        if (null === $this->loader) {
622
            throw new LogicException('You must set a loader first.');
623
        }
624
625
        return $this->loader;
626
    }
627
628
    /**
629
     * Sets the default template charset.
630
     *
631
     * @param string $charset The default charset
632
     */
633
    public function setCharset($charset)
634
    {
635
        $this->charset = strtoupper($charset);
636
    }
637
638
    /**
639
     * Gets the default template charset.
640
     *
641
     * @return string The default charset
642
     */
643
    public function getCharset()
644
    {
645
        return $this->charset;
646
    }
647
648
    /**
649
     * Initializes the runtime environment.
650
     */
651
    public function initRuntime()
652
    {
653
        $this->runtimeInitialized = true;
654
655
        foreach ($this->getExtensions() as $extension) {
656
            $extension->initRuntime($this);
657
        }
658
    }
659
660
    /**
661
     * Returns true if the given extension is registered.
662
     *
663
     * @param string $name The extension name
664
     *
665
     * @return bool Whether the extension is registered or not
666
     */
667
    public function hasExtension($name)
668
    {
669
        return isset($this->extensions[$name]);
670
    }
671
672
    /**
673
     * Gets an extension by name.
674
     *
675
     * @param string $name The extension name
676
     *
677
     * @return Twig_ExtensionInterface A Twig_ExtensionInterface instance
678
     */
679
    public function getExtension($name)
680
    {
681
        if (!isset($this->extensions[$name])) {
682
            throw new Twig_Error_Runtime(sprintf('The "%s" extension is not enabled.', $name));
683
        }
684
685
        return $this->extensions[$name];
686
    }
687
688
    /**
689
     * Registers an extension.
690
     *
691
     * @param Twig_ExtensionInterface $extension A Twig_ExtensionInterface instance
692
     */
693
    public function addExtension(Twig_ExtensionInterface $extension)
694
    {
695
        if ($this->extensionInitialized) {
696
            throw new LogicException(sprintf('Unable to register extension "%s" as extensions have already been initialized.', $extension->getName()));
697
        }
698
699
        $this->extensions[$extension->getName()] = $extension;
700
    }
701
702
    /**
703
     * Removes an extension by name.
704
     *
705
     * This method is deprecated and you should not use it.
706
     *
707
     * @param string $name The extension name
708
     *
709
     * @deprecated since 1.12 (to be removed in 2.0)
710
     */
711
    public function removeExtension($name)
712
    {
713
        if ($this->extensionInitialized) {
714
            throw new LogicException(sprintf('Unable to remove extension "%s" as extensions have already been initialized.', $name));
715
        }
716
717
        unset($this->extensions[$name]);
718
    }
719
720
    /**
721
     * Registers an array of extensions.
722
     *
723
     * @param array $extensions An array of extensions
724
     */
725
    public function setExtensions(array $extensions)
726
    {
727
        foreach ($extensions as $extension) {
728
            $this->addExtension($extension);
729
        }
730
    }
731
732
    /**
733
     * Returns all registered extensions.
734
     *
735
     * @return array An array of extensions
736
     */
737
    public function getExtensions()
738
    {
739
        return $this->extensions;
740
    }
741
742
    /**
743
     * Registers a Token Parser.
744
     *
745
     * @param Twig_TokenParserInterface $parser A Twig_TokenParserInterface instance
746
     */
747
    public function addTokenParser(Twig_TokenParserInterface $parser)
748
    {
749
        if ($this->extensionInitialized) {
750
            throw new LogicException('Unable to add a token parser as extensions have already been initialized.');
751
        }
752
753
        $this->staging->addTokenParser($parser);
754
    }
755
756
    /**
757
     * Gets the registered Token Parsers.
758
     *
759
     * @return Twig_TokenParserBrokerInterface A broker containing token parsers
760
     */
761
    public function getTokenParsers()
762
    {
763
        if (!$this->extensionInitialized) {
764
            $this->initExtensions();
765
        }
766
767
        return $this->parsers;
768
    }
769
770
    /**
771
     * Gets registered tags.
772
     *
773
     * Be warned that this method cannot return tags defined by Twig_TokenParserBrokerInterface classes.
774
     *
775
     * @return Twig_TokenParserInterface[] An array of Twig_TokenParserInterface instances
776
     */
777
    public function getTags()
778
    {
779
        $tags = array();
780
        foreach ($this->getTokenParsers()->getParsers() as $parser) {
781
            if ($parser instanceof Twig_TokenParserInterface) {
782
                $tags[$parser->getTag()] = $parser;
783
            }
784
        }
785
786
        return $tags;
787
    }
788
789
    /**
790
     * Registers a Node Visitor.
791
     *
792
     * @param Twig_NodeVisitorInterface $visitor A Twig_NodeVisitorInterface instance
793
     */
794
    public function addNodeVisitor(Twig_NodeVisitorInterface $visitor)
795
    {
796
        if ($this->extensionInitialized) {
797
            throw new LogicException('Unable to add a node visitor as extensions have already been initialized.');
798
        }
799
800
        $this->staging->addNodeVisitor($visitor);
801
    }
802
803
    /**
804
     * Gets the registered Node Visitors.
805
     *
806
     * @return Twig_NodeVisitorInterface[] An array of Twig_NodeVisitorInterface instances
807
     */
808
    public function getNodeVisitors()
809
    {
810
        if (!$this->extensionInitialized) {
811
            $this->initExtensions();
812
        }
813
814
        return $this->visitors;
815
    }
816
817
    /**
818
     * Registers a Filter.
819
     *
820
     * @param string|Twig_SimpleFilter               $name   The filter name or a Twig_SimpleFilter instance
821
     * @param Twig_FilterInterface|Twig_SimpleFilter $filter A Twig_FilterInterface instance or a Twig_SimpleFilter instance
822
     */
823
    public function addFilter($name, $filter = null)
824
    {
825
        if (!$name instanceof Twig_SimpleFilter && !($filter instanceof Twig_SimpleFilter || $filter instanceof Twig_FilterInterface)) {
826
            throw new LogicException('A filter must be an instance of Twig_FilterInterface or Twig_SimpleFilter');
827
        }
828
829
        if ($name instanceof Twig_SimpleFilter) {
830
            $filter = $name;
831
            $name = $filter->getName();
832
        }
833
834
        if ($this->extensionInitialized) {
835
            throw new LogicException(sprintf('Unable to add filter "%s" as extensions have already been initialized.', $name));
836
        }
837
838
        $this->staging->addFilter($name, $filter);
839
    }
840
841
    /**
842
     * Get a filter by name.
843
     *
844
     * Subclasses may override this method and load filters differently;
845
     * so no list of filters is available.
846
     *
847
     * @param string $name The filter name
848
     *
849
     * @return Twig_Filter|false A Twig_Filter instance or false if the filter does not exist
850
     */
851
    public function getFilter($name)
852
    {
853
        if (!$this->extensionInitialized) {
854
            $this->initExtensions();
855
        }
856
857
        if (isset($this->filters[$name])) {
858
            return $this->filters[$name];
859
        }
860
861
        foreach ($this->filters as $pattern => $filter) {
862
            $pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count);
863
864
            if ($count) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $count of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
865
                if (preg_match('#^'.$pattern.'$#', $name, $matches)) {
866
                    array_shift($matches);
867
                    $filter->setArguments($matches);
868
869
                    return $filter;
870
                }
871
            }
872
        }
873
874
        foreach ($this->filterCallbacks as $callback) {
875
            if (false !== $filter = call_user_func($callback, $name)) {
876
                return $filter;
877
            }
878
        }
879
880
        return false;
881
    }
882
883
    public function registerUndefinedFilterCallback($callable)
884
    {
885
        $this->filterCallbacks[] = $callable;
886
    }
887
888
    /**
889
     * Gets the registered Filters.
890
     *
891
     * Be warned that this method cannot return filters defined with registerUndefinedFunctionCallback.
892
     *
893
     * @return Twig_FilterInterface[] An array of Twig_FilterInterface instances
894
     *
895
     * @see registerUndefinedFilterCallback
896
     */
897
    public function getFilters()
898
    {
899
        if (!$this->extensionInitialized) {
900
            $this->initExtensions();
901
        }
902
903
        return $this->filters;
904
    }
905
906
    /**
907
     * Registers a Test.
908
     *
909
     * @param string|Twig_SimpleTest             $name The test name or a Twig_SimpleTest instance
910
     * @param Twig_TestInterface|Twig_SimpleTest $test A Twig_TestInterface instance or a Twig_SimpleTest instance
911
     */
912
    public function addTest($name, $test = null)
913
    {
914
        if (!$name instanceof Twig_SimpleTest && !($test instanceof Twig_SimpleTest || $test instanceof Twig_TestInterface)) {
915
            throw new LogicException('A test must be an instance of Twig_TestInterface or Twig_SimpleTest');
916
        }
917
918
        if ($name instanceof Twig_SimpleTest) {
919
            $test = $name;
920
            $name = $test->getName();
921
        }
922
923
        if ($this->extensionInitialized) {
924
            throw new LogicException(sprintf('Unable to add test "%s" as extensions have already been initialized.', $name));
925
        }
926
927
        $this->staging->addTest($name, $test);
928
    }
929
930
    /**
931
     * Gets the registered Tests.
932
     *
933
     * @return Twig_TestInterface[] An array of Twig_TestInterface instances
934
     */
935
    public function getTests()
936
    {
937
        if (!$this->extensionInitialized) {
938
            $this->initExtensions();
939
        }
940
941
        return $this->tests;
942
    }
943
944
    /**
945
     * Gets a test by name.
946
     *
947
     * @param string $name The test name
948
     *
949
     * @return Twig_Test|false A Twig_Test instance or false if the test does not exist
950
     */
951
    public function getTest($name)
952
    {
953
        if (!$this->extensionInitialized) {
954
            $this->initExtensions();
955
        }
956
957
        if (isset($this->tests[$name])) {
958
            return $this->tests[$name];
959
        }
960
961
        return false;
962
    }
963
964
    /**
965
     * Registers a Function.
966
     *
967
     * @param string|Twig_SimpleFunction                 $name     The function name or a Twig_SimpleFunction instance
968
     * @param Twig_FunctionInterface|Twig_SimpleFunction $function A Twig_FunctionInterface instance or a Twig_SimpleFunction instance
969
     */
970
    public function addFunction($name, $function = null)
971
    {
972
        if (!$name instanceof Twig_SimpleFunction && !($function instanceof Twig_SimpleFunction || $function instanceof Twig_FunctionInterface)) {
973
            throw new LogicException('A function must be an instance of Twig_FunctionInterface or Twig_SimpleFunction');
974
        }
975
976
        if ($name instanceof Twig_SimpleFunction) {
977
            $function = $name;
978
            $name = $function->getName();
979
        }
980
981
        if ($this->extensionInitialized) {
982
            throw new LogicException(sprintf('Unable to add function "%s" as extensions have already been initialized.', $name));
983
        }
984
985
        $this->staging->addFunction($name, $function);
986
    }
987
988
    /**
989
     * Get a function by name.
990
     *
991
     * Subclasses may override this method and load functions differently;
992
     * so no list of functions is available.
993
     *
994
     * @param string $name function name
995
     *
996
     * @return Twig_Function|false A Twig_Function instance or false if the function does not exist
997
     */
998
    public function getFunction($name)
999
    {
1000
        if (!$this->extensionInitialized) {
1001
            $this->initExtensions();
1002
        }
1003
1004
        if (isset($this->functions[$name])) {
1005
            return $this->functions[$name];
1006
        }
1007
1008
        foreach ($this->functions as $pattern => $function) {
1009
            $pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count);
1010
1011
            if ($count) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $count of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1012
                if (preg_match('#^'.$pattern.'$#', $name, $matches)) {
1013
                    array_shift($matches);
1014
                    $function->setArguments($matches);
1015
1016
                    return $function;
1017
                }
1018
            }
1019
        }
1020
1021
        foreach ($this->functionCallbacks as $callback) {
1022
            if (false !== $function = call_user_func($callback, $name)) {
1023
                return $function;
1024
            }
1025
        }
1026
1027
        return false;
1028
    }
1029
1030
    public function registerUndefinedFunctionCallback($callable)
1031
    {
1032
        $this->functionCallbacks[] = $callable;
1033
    }
1034
1035
    /**
1036
     * Gets registered functions.
1037
     *
1038
     * Be warned that this method cannot return functions defined with registerUndefinedFunctionCallback.
1039
     *
1040
     * @return Twig_FunctionInterface[] An array of Twig_FunctionInterface instances
1041
     *
1042
     * @see registerUndefinedFunctionCallback
1043
     */
1044
    public function getFunctions()
1045
    {
1046
        if (!$this->extensionInitialized) {
1047
            $this->initExtensions();
1048
        }
1049
1050
        return $this->functions;
1051
    }
1052
1053
    /**
1054
     * Registers a Global.
1055
     *
1056
     * New globals can be added before compiling or rendering a template;
1057
     * but after, you can only update existing globals.
1058
     *
1059
     * @param string $name  The global name
1060
     * @param mixed  $value The global value
1061
     */
1062
    public function addGlobal($name, $value)
1063
    {
1064
        if ($this->extensionInitialized || $this->runtimeInitialized) {
1065
            if (null === $this->globals) {
1066
                $this->globals = $this->initGlobals();
1067
            }
1068
1069
            /* This condition must be uncommented in Twig 2.0
1070
            if (!array_key_exists($name, $this->globals)) {
1071
                throw new LogicException(sprintf('Unable to add global "%s" as the runtime or the extensions have already been initialized.', $name));
1072
            }
1073
            */
1074
        }
1075
1076
        if ($this->extensionInitialized || $this->runtimeInitialized) {
1077
            // update the value
1078
            $this->globals[$name] = $value;
1079
        } else {
1080
            $this->staging->addGlobal($name, $value);
1081
        }
1082
    }
1083
1084
    /**
1085
     * Gets the registered Globals.
1086
     *
1087
     * @return array An array of globals
1088
     */
1089
    public function getGlobals()
1090
    {
1091
        if (!$this->runtimeInitialized && !$this->extensionInitialized) {
1092
            return $this->initGlobals();
1093
        }
1094
1095
        if (null === $this->globals) {
1096
            $this->globals = $this->initGlobals();
1097
        }
1098
1099
        return $this->globals;
1100
    }
1101
1102
    /**
1103
     * Merges a context with the defined globals.
1104
     *
1105
     * @param array $context An array representing the context
1106
     *
1107
     * @return array The context merged with the globals
1108
     */
1109
    public function mergeGlobals(array $context)
1110
    {
1111
        // we don't use array_merge as the context being generally
1112
        // bigger than globals, this code is faster.
1113
        foreach ($this->getGlobals() as $key => $value) {
1114
            if (!array_key_exists($key, $context)) {
1115
                $context[$key] = $value;
1116
            }
1117
        }
1118
1119
        return $context;
1120
    }
1121
1122
    /**
1123
     * Gets the registered unary Operators.
1124
     *
1125
     * @return array An array of unary operators
1126
     */
1127
    public function getUnaryOperators()
1128
    {
1129
        if (!$this->extensionInitialized) {
1130
            $this->initExtensions();
1131
        }
1132
1133
        return $this->unaryOperators;
1134
    }
1135
1136
    /**
1137
     * Gets the registered binary Operators.
1138
     *
1139
     * @return array An array of binary operators
1140
     */
1141
    public function getBinaryOperators()
1142
    {
1143
        if (!$this->extensionInitialized) {
1144
            $this->initExtensions();
1145
        }
1146
1147
        return $this->binaryOperators;
1148
    }
1149
1150
    public function computeAlternatives($name, $items)
1151
    {
1152
        $alternatives = array();
1153
        foreach ($items as $item) {
1154
            $lev = levenshtein($name, $item);
1155
            if ($lev <= strlen($name) / 3 || false !== strpos($item, $name)) {
1156
                $alternatives[$item] = $lev;
1157
            }
1158
        }
1159
        asort($alternatives);
1160
1161
        return array_keys($alternatives);
1162
    }
1163
1164
    protected function initGlobals()
1165
    {
1166
        $globals = array();
1167
        foreach ($this->extensions as $extension) {
1168
            $extGlob = $extension->getGlobals();
1169
            if (!is_array($extGlob)) {
1170
                throw new UnexpectedValueException(sprintf('"%s::getGlobals()" must return an array of globals.', get_class($extension)));
1171
            }
1172
1173
            $globals[] = $extGlob;
1174
        }
1175
1176
        $globals[] = $this->staging->getGlobals();
1177
1178
        return call_user_func_array('array_merge', $globals);
1179
    }
1180
1181
    protected function initExtensions()
1182
    {
1183
        if ($this->extensionInitialized) {
1184
            return;
1185
        }
1186
1187
        $this->extensionInitialized = true;
1188
        $this->parsers = new Twig_TokenParserBroker();
0 ignored issues
show
Deprecated Code introduced by
The class Twig_TokenParserBroker has been deprecated with message: since 1.12 (to be removed in 2.0)

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
1189
        $this->filters = array();
1190
        $this->functions = array();
1191
        $this->tests = array();
1192
        $this->visitors = array();
1193
        $this->unaryOperators = array();
1194
        $this->binaryOperators = array();
1195
1196
        foreach ($this->extensions as $extension) {
1197
            $this->initExtension($extension);
1198
        }
1199
        $this->initExtension($this->staging);
1200
    }
1201
1202
    protected function initExtension(Twig_ExtensionInterface $extension)
1203
    {
1204
        // filters
1205
        foreach ($extension->getFilters() as $name => $filter) {
1206
            if ($name instanceof Twig_SimpleFilter) {
1207
                $filter = $name;
1208
                $name = $filter->getName();
1209
            } elseif ($filter instanceof Twig_SimpleFilter) {
1210
                $name = $filter->getName();
1211
            }
1212
1213
            $this->filters[$name] = $filter;
1214
        }
1215
1216
        // functions
1217
        foreach ($extension->getFunctions() as $name => $function) {
1218
            if ($name instanceof Twig_SimpleFunction) {
1219
                $function = $name;
1220
                $name = $function->getName();
1221
            } elseif ($function instanceof Twig_SimpleFunction) {
1222
                $name = $function->getName();
1223
            }
1224
1225
            $this->functions[$name] = $function;
1226
        }
1227
1228
        // tests
1229
        foreach ($extension->getTests() as $name => $test) {
1230
            if ($name instanceof Twig_SimpleTest) {
1231
                $test = $name;
1232
                $name = $test->getName();
1233
            } elseif ($test instanceof Twig_SimpleTest) {
1234
                $name = $test->getName();
1235
            }
1236
1237
            $this->tests[$name] = $test;
1238
        }
1239
1240
        // token parsers
1241
        foreach ($extension->getTokenParsers() as $parser) {
1242
            if ($parser instanceof Twig_TokenParserInterface) {
1243
                $this->parsers->addTokenParser($parser);
1244
            } elseif ($parser instanceof Twig_TokenParserBrokerInterface) {
1245
                $this->parsers->addTokenParserBroker($parser);
0 ignored issues
show
Compatibility introduced by
$parser of type object<Twig_TokenParserBrokerInterface> is not a sub-type of object<Twig_TokenParserBroker>. It seems like you assume a concrete implementation of the interface Twig_TokenParserBrokerInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
1246
            } else {
1247
                throw new LogicException('getTokenParsers() must return an array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances');
1248
            }
1249
        }
1250
1251
        // node visitors
1252
        foreach ($extension->getNodeVisitors() as $visitor) {
1253
            $this->visitors[] = $visitor;
1254
        }
1255
1256
        // operators
1257
        if ($operators = $extension->getOperators()) {
1258
            if (2 !== count($operators)) {
1259
                throw new InvalidArgumentException(sprintf('"%s::getOperators()" does not return a valid operators array.', get_class($extension)));
1260
            }
1261
1262
            $this->unaryOperators = array_merge($this->unaryOperators, $operators[0]);
1263
            $this->binaryOperators = array_merge($this->binaryOperators, $operators[1]);
1264
        }
1265
    }
1266
1267
    protected function writeCacheFile($file, $content)
1268
    {
1269
        $dir = dirname($file);
1270
        if (!is_dir($dir)) {
1271
            if (false === @mkdir($dir, 0777, true)) {
1272
                clearstatcache(false, $dir);
1273
                if (!is_dir($dir)) {
1274
                    throw new RuntimeException(sprintf("Unable to create the cache directory (%s).", $dir));
1275
                }
1276
            }
1277
        } elseif (!is_writable($dir)) {
1278
            throw new RuntimeException(sprintf("Unable to write in the cache directory (%s).", $dir));
1279
        }
1280
1281
        $tmpFile = tempnam($dir, basename($file));
1282
        if (false !== @file_put_contents($tmpFile, $content)) {
1283
            // rename does not work on Win32 before 5.2.6
1284
            if (@rename($tmpFile, $file) || (@copy($tmpFile, $file) && unlink($tmpFile))) {
1285
                @chmod($file, 0666 & ~umask());
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1286
1287
                return;
1288
            }
1289
        }
1290
1291
        throw new RuntimeException(sprintf('Failed to write cache file "%s".', $file));
1292
    }
1293
}
1294