Project

General

Profile

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
 * Twig_NodeVisitor_Escaper implements output escaping.
14
 *
15
 * @package    twig
16
 * @author     Fabien Potencier <fabien@symfony.com>
17
 */
18
class Twig_NodeVisitor_Escaper implements Twig_NodeVisitorInterface
19
{
20
    protected $statusStack = array();
21
    protected $blocks = array();
22
    protected $safeAnalysis;
23
    protected $traverser;
24
    protected $defaultStrategy = false;
25
    protected $safeVars = array();
26

    
27
    public function __construct()
28
    {
29
        $this->safeAnalysis = new Twig_NodeVisitor_SafeAnalysis();
30
    }
31

    
32
    /**
33
     * Called before child nodes are visited.
34
     *
35
     * @param Twig_NodeInterface $node The node to visit
36
     * @param Twig_Environment   $env  The Twig environment instance
37
     *
38
     * @return Twig_NodeInterface The modified node
39
     */
40
    public function enterNode(Twig_NodeInterface $node, Twig_Environment $env)
41
    {
42
        if ($node instanceof Twig_Node_Module) {
43
            if ($env->hasExtension('escaper') && $defaultStrategy = $env->getExtension('escaper')->getDefaultStrategy($node->getAttribute('filename'))) {
44
                $this->defaultStrategy = $defaultStrategy;
45
            }
46
            $this->safeVars = array();
47
        } elseif ($node instanceof Twig_Node_AutoEscape) {
48
            $this->statusStack[] = $node->getAttribute('value');
49
        } elseif ($node instanceof Twig_Node_Block) {
50
            $this->statusStack[] = isset($this->blocks[$node->getAttribute('name')]) ? $this->blocks[$node->getAttribute('name')] : $this->needEscaping($env);
51
        } elseif ($node instanceof Twig_Node_Import) {
52
            $this->safeVars[] = $node->getNode('var')->getAttribute('name');
53
        }
54

    
55
        return $node;
56
    }
57

    
58
    /**
59
     * Called after child nodes are visited.
60
     *
61
     * @param Twig_NodeInterface $node The node to visit
62
     * @param Twig_Environment   $env  The Twig environment instance
63
     *
64
     * @return Twig_NodeInterface The modified node
65
     */
66
    public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env)
67
    {
68
        if ($node instanceof Twig_Node_Module) {
69
            $this->defaultStrategy = false;
70
            $this->safeVars = array();
71
        } elseif ($node instanceof Twig_Node_Expression_Filter) {
72
            return $this->preEscapeFilterNode($node, $env);
73
        } elseif ($node instanceof Twig_Node_Print) {
74
            return $this->escapePrintNode($node, $env, $this->needEscaping($env));
75
        }
76

    
77
        if ($node instanceof Twig_Node_AutoEscape || $node instanceof Twig_Node_Block) {
78
            array_pop($this->statusStack);
79
        } elseif ($node instanceof Twig_Node_BlockReference) {
80
            $this->blocks[$node->getAttribute('name')] = $this->needEscaping($env);
81
        }
82

    
83
        return $node;
84
    }
85

    
86
    protected function escapePrintNode(Twig_Node_Print $node, Twig_Environment $env, $type)
87
    {
88
        if (false === $type) {
89
            return $node;
90
        }
91

    
92
        $expression = $node->getNode('expr');
93

    
94
        if ($this->isSafeFor($type, $expression, $env)) {
95
            return $node;
96
        }
97

    
98
        $class = get_class($node);
99

    
100
        return new $class(
101
            $this->getEscaperFilter($type, $expression),
102
            $node->getLine()
103
        );
104
    }
105

    
106
    protected function preEscapeFilterNode(Twig_Node_Expression_Filter $filter, Twig_Environment $env)
107
    {
108
        $name = $filter->getNode('filter')->getAttribute('value');
109

    
110
        if (false !== $f = $env->getFilter($name)) {
111
            $type = $f->getPreEscape();
112
            if (null === $type) {
113
                return $filter;
114
            }
115

    
116
            $node = $filter->getNode('node');
117
            if ($this->isSafeFor($type, $node, $env)) {
118
                return $filter;
119
            }
120

    
121
            $filter->setNode('node', $this->getEscaperFilter($type, $node));
122

    
123
            return $filter;
124
        }
125

    
126
        return $filter;
127
    }
128

    
129
    protected function isSafeFor($type, Twig_NodeInterface $expression, $env)
130
    {
131
        $safe = $this->safeAnalysis->getSafe($expression);
132

    
133
        if (null === $safe) {
134
            if (null === $this->traverser) {
135
                $this->traverser = new Twig_NodeTraverser($env, array($this->safeAnalysis));
136
            }
137

    
138
            $this->safeAnalysis->setSafeVars($this->safeVars);
139

    
140
            $this->traverser->traverse($expression);
141
            $safe = $this->safeAnalysis->getSafe($expression);
142
        }
143

    
144
        return in_array($type, $safe) || in_array('all', $safe);
145
    }
146

    
147
    protected function needEscaping(Twig_Environment $env)
148
    {
149
        if (count($this->statusStack)) {
150
            return $this->statusStack[count($this->statusStack) - 1];
151
        }
152

    
153
        return $this->defaultStrategy ? $this->defaultStrategy : false;
154
    }
155

    
156
    protected function getEscaperFilter($type, Twig_NodeInterface $node)
157
    {
158
        $line = $node->getLine();
159
        $name = new Twig_Node_Expression_Constant('escape', $line);
160
        $args = new Twig_Node(array(new Twig_Node_Expression_Constant((string) $type, $line), new Twig_Node_Expression_Constant(null, $line), new Twig_Node_Expression_Constant(true, $line)));
161

    
162
        return new Twig_Node_Expression_Filter($node, $name, $args, $line);
163
    }
164

    
165
    /**
166
     * {@inheritdoc}
167
     */
168
    public function getPriority()
169
    {
170
        return 0;
171
    }
172
}
(1-1/5)