]>
Commit | Line | Data |
---|---|---|
a4565e88 NL |
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 | * @author Fabien Potencier <fabien@symfony.com> | |
16 | */ | |
17 | class Twig_NodeVisitor_Escaper implements Twig_NodeVisitorInterface | |
18 | { | |
19 | protected $statusStack = array(); | |
20 | protected $blocks = array(); | |
21 | protected $safeAnalysis; | |
22 | protected $traverser; | |
23 | protected $defaultStrategy = false; | |
24 | protected $safeVars = array(); | |
25 | ||
26 | public function __construct() | |
27 | { | |
28 | $this->safeAnalysis = new Twig_NodeVisitor_SafeAnalysis(); | |
29 | } | |
30 | ||
31 | /** | |
32 | * Called before child nodes are visited. | |
33 | * | |
34 | * @param Twig_NodeInterface $node The node to visit | |
35 | * @param Twig_Environment $env The Twig environment instance | |
36 | * | |
37 | * @return Twig_NodeInterface The modified node | |
38 | */ | |
39 | public function enterNode(Twig_NodeInterface $node, Twig_Environment $env) | |
40 | { | |
41 | if ($node instanceof Twig_Node_Module) { | |
42 | if ($env->hasExtension('escaper') && $defaultStrategy = $env->getExtension('escaper')->getDefaultStrategy($node->getAttribute('filename'))) { | |
43 | $this->defaultStrategy = $defaultStrategy; | |
44 | } | |
45 | $this->safeVars = array(); | |
46 | } elseif ($node instanceof Twig_Node_AutoEscape) { | |
47 | $this->statusStack[] = $node->getAttribute('value'); | |
48 | } elseif ($node instanceof Twig_Node_Block) { | |
49 | $this->statusStack[] = isset($this->blocks[$node->getAttribute('name')]) ? $this->blocks[$node->getAttribute('name')] : $this->needEscaping($env); | |
50 | } elseif ($node instanceof Twig_Node_Import) { | |
51 | $this->safeVars[] = $node->getNode('var')->getAttribute('name'); | |
52 | } | |
53 | ||
54 | return $node; | |
55 | } | |
56 | ||
57 | /** | |
58 | * Called after child nodes are visited. | |
59 | * | |
60 | * @param Twig_NodeInterface $node The node to visit | |
61 | * @param Twig_Environment $env The Twig environment instance | |
62 | * | |
63 | * @return Twig_NodeInterface The modified node | |
64 | */ | |
65 | public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env) | |
66 | { | |
67 | if ($node instanceof Twig_Node_Module) { | |
68 | $this->defaultStrategy = false; | |
69 | $this->safeVars = array(); | |
70 | } elseif ($node instanceof Twig_Node_Expression_Filter) { | |
71 | return $this->preEscapeFilterNode($node, $env); | |
72 | } elseif ($node instanceof Twig_Node_Print) { | |
73 | return $this->escapePrintNode($node, $env, $this->needEscaping($env)); | |
74 | } | |
75 | ||
76 | if ($node instanceof Twig_Node_AutoEscape || $node instanceof Twig_Node_Block) { | |
77 | array_pop($this->statusStack); | |
78 | } elseif ($node instanceof Twig_Node_BlockReference) { | |
79 | $this->blocks[$node->getAttribute('name')] = $this->needEscaping($env); | |
80 | } | |
81 | ||
82 | return $node; | |
83 | } | |
84 | ||
85 | protected function escapePrintNode(Twig_Node_Print $node, Twig_Environment $env, $type) | |
86 | { | |
87 | if (false === $type) { | |
88 | return $node; | |
89 | } | |
90 | ||
91 | $expression = $node->getNode('expr'); | |
92 | ||
93 | if ($this->isSafeFor($type, $expression, $env)) { | |
94 | return $node; | |
95 | } | |
96 | ||
97 | $class = get_class($node); | |
98 | ||
99 | return new $class( | |
100 | $this->getEscaperFilter($type, $expression), | |
101 | $node->getLine() | |
102 | ); | |
103 | } | |
104 | ||
105 | protected function preEscapeFilterNode(Twig_Node_Expression_Filter $filter, Twig_Environment $env) | |
106 | { | |
107 | $name = $filter->getNode('filter')->getAttribute('value'); | |
108 | ||
109 | $type = $env->getFilter($name)->getPreEscape(); | |
110 | if (null === $type) { | |
111 | return $filter; | |
112 | } | |
113 | ||
114 | $node = $filter->getNode('node'); | |
115 | if ($this->isSafeFor($type, $node, $env)) { | |
116 | return $filter; | |
117 | } | |
118 | ||
119 | $filter->setNode('node', $this->getEscaperFilter($type, $node)); | |
120 | ||
121 | return $filter; | |
122 | } | |
123 | ||
124 | protected function isSafeFor($type, Twig_NodeInterface $expression, $env) | |
125 | { | |
126 | $safe = $this->safeAnalysis->getSafe($expression); | |
127 | ||
128 | if (null === $safe) { | |
129 | if (null === $this->traverser) { | |
130 | $this->traverser = new Twig_NodeTraverser($env, array($this->safeAnalysis)); | |
131 | } | |
132 | ||
133 | $this->safeAnalysis->setSafeVars($this->safeVars); | |
134 | ||
135 | $this->traverser->traverse($expression); | |
136 | $safe = $this->safeAnalysis->getSafe($expression); | |
137 | } | |
138 | ||
139 | return in_array($type, $safe) || in_array('all', $safe); | |
140 | } | |
141 | ||
142 | protected function needEscaping(Twig_Environment $env) | |
143 | { | |
144 | if (count($this->statusStack)) { | |
145 | return $this->statusStack[count($this->statusStack) - 1]; | |
146 | } | |
147 | ||
148 | return $this->defaultStrategy ? $this->defaultStrategy : false; | |
149 | } | |
150 | ||
151 | protected function getEscaperFilter($type, Twig_NodeInterface $node) | |
152 | { | |
153 | $line = $node->getLine(); | |
154 | $name = new Twig_Node_Expression_Constant('escape', $line); | |
155 | $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))); | |
156 | ||
157 | return new Twig_Node_Expression_Filter($node, $name, $args, $line); | |
158 | } | |
159 | ||
160 | /** | |
161 | * {@inheritdoc} | |
162 | */ | |
163 | public function getPriority() | |
164 | { | |
165 | return 0; | |
166 | } | |
167 | } |