diff options
author | Nicolas Lœuillet <nicolas.loeuillet@gmail.com> | 2013-08-02 22:40:51 +0200 |
---|---|---|
committer | Nicolas Lœuillet <nicolas.loeuillet@gmail.com> | 2013-08-02 22:40:51 +0200 |
commit | a4565e88edbc8e3bd092a475469769c86a4c350c (patch) | |
tree | a6a3c935b03a23ff87575c8c315cf8ba78fe68c2 | |
parent | f6c9baab3efeec1d0efa151e276fc08d5b58f9e9 (diff) | |
download | wallabag-a4565e88edbc8e3bd092a475469769c86a4c350c.tar.gz wallabag-a4565e88edbc8e3bd092a475469769c86a4c350c.tar.zst wallabag-a4565e88edbc8e3bd092a475469769c86a4c350c.zip |
add Twig & refactor poche
218 files changed, 16371 insertions, 1554 deletions
diff --git a/inc/Encoding.php b/inc/3rdparty/Encoding.php index 577763b4..577763b4 100644 --- a/inc/Encoding.php +++ b/inc/3rdparty/Encoding.php | |||
diff --git a/inc/JSLikeHTMLElement.php b/inc/3rdparty/JSLikeHTMLElement.php index 238ba8a8..238ba8a8 100644 --- a/inc/JSLikeHTMLElement.php +++ b/inc/3rdparty/JSLikeHTMLElement.php | |||
diff --git a/inc/Readability.php b/inc/3rdparty/Readability.php index e1e8738b..e1e8738b 100644 --- a/inc/Readability.php +++ b/inc/3rdparty/Readability.php | |||
diff --git a/inc/Session.class.php b/inc/3rdparty/Session.class.php index eff924cc..eff924cc 100644 --- a/inc/Session.class.php +++ b/inc/3rdparty/Session.class.php | |||
diff --git a/inc/simple_html_dom.php b/inc/3rdparty/simple_html_dom.php index 43b94e57..43b94e57 100644 --- a/inc/simple_html_dom.php +++ b/inc/3rdparty/simple_html_dom.php | |||
diff --git a/inc/Twig/Autoloader.php b/inc/Twig/Autoloader.php new file mode 100644 index 00000000..7007d315 --- /dev/null +++ b/inc/Twig/Autoloader.php | |||
@@ -0,0 +1,48 @@ | |||
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 | * Autoloads Twig classes. | ||
14 | * | ||
15 | * @author Fabien Potencier <fabien@symfony.com> | ||
16 | */ | ||
17 | class Twig_Autoloader | ||
18 | { | ||
19 | /** | ||
20 | * Registers Twig_Autoloader as an SPL autoloader. | ||
21 | * | ||
22 | * @param Boolean $prepend Whether to prepend the autoloader or not. | ||
23 | */ | ||
24 | public static function register($prepend = false) | ||
25 | { | ||
26 | if (version_compare(phpversion(), '5.3.0', '>=')) { | ||
27 | spl_autoload_register(array(new self, 'autoload'), true, $prepend); | ||
28 | } else { | ||
29 | spl_autoload_register(array(new self, 'autoload')); | ||
30 | } | ||
31 | } | ||
32 | |||
33 | /** | ||
34 | * Handles autoloading of classes. | ||
35 | * | ||
36 | * @param string $class A class name. | ||
37 | */ | ||
38 | public static function autoload($class) | ||
39 | { | ||
40 | if (0 !== strpos($class, 'Twig')) { | ||
41 | return; | ||
42 | } | ||
43 | |||
44 | if (is_file($file = dirname(__FILE__).'/../'.str_replace(array('_', "\0"), array('/', ''), $class).'.php')) { | ||
45 | require $file; | ||
46 | } | ||
47 | } | ||
48 | } | ||
diff --git a/inc/Twig/Compiler.php b/inc/Twig/Compiler.php new file mode 100644 index 00000000..99aecbcc --- /dev/null +++ b/inc/Twig/Compiler.php | |||
@@ -0,0 +1,267 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2009 Fabien Potencier | ||
7 | * (c) 2009 Armin Ronacher | ||
8 | * | ||
9 | * For the full copyright and license information, please view the LICENSE | ||
10 | * file that was distributed with this source code. | ||
11 | */ | ||
12 | |||
13 | /** | ||
14 | * Compiles a node to PHP code. | ||
15 | * | ||
16 | * @author Fabien Potencier <fabien@symfony.com> | ||
17 | */ | ||
18 | class Twig_Compiler implements Twig_CompilerInterface | ||
19 | { | ||
20 | protected $lastLine; | ||
21 | protected $source; | ||
22 | protected $indentation; | ||
23 | protected $env; | ||
24 | protected $debugInfo; | ||
25 | protected $sourceOffset; | ||
26 | protected $sourceLine; | ||
27 | protected $filename; | ||
28 | |||
29 | /** | ||
30 | * Constructor. | ||
31 | * | ||
32 | * @param Twig_Environment $env The twig environment instance | ||
33 | */ | ||
34 | public function __construct(Twig_Environment $env) | ||
35 | { | ||
36 | $this->env = $env; | ||
37 | $this->debugInfo = array(); | ||
38 | } | ||
39 | |||
40 | public function getFilename() | ||
41 | { | ||
42 | return $this->filename; | ||
43 | } | ||
44 | |||
45 | /** | ||
46 | * Returns the environment instance related to this compiler. | ||
47 | * | ||
48 | * @return Twig_Environment The environment instance | ||
49 | */ | ||
50 | public function getEnvironment() | ||
51 | { | ||
52 | return $this->env; | ||
53 | } | ||
54 | |||
55 | /** | ||
56 | * Gets the current PHP code after compilation. | ||
57 | * | ||
58 | * @return string The PHP code | ||
59 | */ | ||
60 | public function getSource() | ||
61 | { | ||
62 | return $this->source; | ||
63 | } | ||
64 | |||
65 | /** | ||
66 | * Compiles a node. | ||
67 | * | ||
68 | * @param Twig_NodeInterface $node The node to compile | ||
69 | * @param integer $indentation The current indentation | ||
70 | * | ||
71 | * @return Twig_Compiler The current compiler instance | ||
72 | */ | ||
73 | public function compile(Twig_NodeInterface $node, $indentation = 0) | ||
74 | { | ||
75 | $this->lastLine = null; | ||
76 | $this->source = ''; | ||
77 | $this->sourceOffset = 0; | ||
78 | // source code starts at 1 (as we then increment it when we encounter new lines) | ||
79 | $this->sourceLine = 1; | ||
80 | $this->indentation = $indentation; | ||
81 | |||
82 | if ($node instanceof Twig_Node_Module) { | ||
83 | $this->filename = $node->getAttribute('filename'); | ||
84 | } | ||
85 | |||
86 | $node->compile($this); | ||
87 | |||
88 | return $this; | ||
89 | } | ||
90 | |||
91 | public function subcompile(Twig_NodeInterface $node, $raw = true) | ||
92 | { | ||
93 | if (false === $raw) { | ||
94 | $this->addIndentation(); | ||
95 | } | ||
96 | |||
97 | $node->compile($this); | ||
98 | |||
99 | return $this; | ||
100 | } | ||
101 | |||
102 | /** | ||
103 | * Adds a raw string to the compiled code. | ||
104 | * | ||
105 | * @param string $string The string | ||
106 | * | ||
107 | * @return Twig_Compiler The current compiler instance | ||
108 | */ | ||
109 | public function raw($string) | ||
110 | { | ||
111 | $this->source .= $string; | ||
112 | |||
113 | return $this; | ||
114 | } | ||
115 | |||
116 | /** | ||
117 | * Writes a string to the compiled code by adding indentation. | ||
118 | * | ||
119 | * @return Twig_Compiler The current compiler instance | ||
120 | */ | ||
121 | public function write() | ||
122 | { | ||
123 | $strings = func_get_args(); | ||
124 | foreach ($strings as $string) { | ||
125 | $this->addIndentation(); | ||
126 | $this->source .= $string; | ||
127 | } | ||
128 | |||
129 | return $this; | ||
130 | } | ||
131 | |||
132 | /** | ||
133 | * Appends an indentation to the current PHP code after compilation. | ||
134 | * | ||
135 | * @return Twig_Compiler The current compiler instance | ||
136 | */ | ||
137 | public function addIndentation() | ||
138 | { | ||
139 | $this->source .= str_repeat(' ', $this->indentation * 4); | ||
140 | |||
141 | return $this; | ||
142 | } | ||
143 | |||
144 | /** | ||
145 | * Adds a quoted string to the compiled code. | ||
146 | * | ||
147 | * @param string $value The string | ||
148 | * | ||
149 | * @return Twig_Compiler The current compiler instance | ||
150 | */ | ||
151 | public function string($value) | ||
152 | { | ||
153 | $this->source .= sprintf('"%s"', addcslashes($value, "\0\t\"\$\\")); | ||
154 | |||
155 | return $this; | ||
156 | } | ||
157 | |||
158 | /** | ||
159 | * Returns a PHP representation of a given value. | ||
160 | * | ||
161 | * @param mixed $value The value to convert | ||
162 | * | ||
163 | * @return Twig_Compiler The current compiler instance | ||
164 | */ | ||
165 | public function repr($value) | ||
166 | { | ||
167 | if (is_int($value) || is_float($value)) { | ||
168 | if (false !== $locale = setlocale(LC_NUMERIC, 0)) { | ||
169 | setlocale(LC_NUMERIC, 'C'); | ||
170 | } | ||
171 | |||
172 | $this->raw($value); | ||
173 | |||
174 | if (false !== $locale) { | ||
175 | setlocale(LC_NUMERIC, $locale); | ||
176 | } | ||
177 | } elseif (null === $value) { | ||
178 | $this->raw('null'); | ||
179 | } elseif (is_bool($value)) { | ||
180 | $this->raw($value ? 'true' : 'false'); | ||
181 | } elseif (is_array($value)) { | ||
182 | $this->raw('array('); | ||
183 | $i = 0; | ||
184 | foreach ($value as $key => $value) { | ||
185 | if ($i++) { | ||
186 | $this->raw(', '); | ||
187 | } | ||
188 | $this->repr($key); | ||
189 | $this->raw(' => '); | ||
190 | $this->repr($value); | ||
191 | } | ||
192 | $this->raw(')'); | ||
193 | } else { | ||
194 | $this->string($value); | ||
195 | } | ||
196 | |||
197 | return $this; | ||
198 | } | ||
199 | |||
200 | /** | ||
201 | * Adds debugging information. | ||
202 | * | ||
203 | * @param Twig_NodeInterface $node The related twig node | ||
204 | * | ||
205 | * @return Twig_Compiler The current compiler instance | ||
206 | */ | ||
207 | public function addDebugInfo(Twig_NodeInterface $node) | ||
208 | { | ||
209 | if ($node->getLine() != $this->lastLine) { | ||
210 | $this->write("// line {$node->getLine()}\n"); | ||
211 | |||
212 | // when mbstring.func_overload is set to 2 | ||
213 | // mb_substr_count() replaces substr_count() | ||
214 | // but they have different signatures! | ||
215 | if (((int) ini_get('mbstring.func_overload')) & 2) { | ||
216 | // this is much slower than the "right" version | ||
217 | $this->sourceLine += mb_substr_count(mb_substr($this->source, $this->sourceOffset), "\n"); | ||
218 | } else { | ||
219 | $this->sourceLine += substr_count($this->source, "\n", $this->sourceOffset); | ||
220 | } | ||
221 | $this->sourceOffset = strlen($this->source); | ||
222 | $this->debugInfo[$this->sourceLine] = $node->getLine(); | ||
223 | |||
224 | $this->lastLine = $node->getLine(); | ||
225 | } | ||
226 | |||
227 | return $this; | ||
228 | } | ||
229 | |||
230 | public function getDebugInfo() | ||
231 | { | ||
232 | return $this->debugInfo; | ||
233 | } | ||
234 | |||
235 | /** | ||
236 | * Indents the generated code. | ||
237 | * | ||
238 | * @param integer $step The number of indentation to add | ||
239 | * | ||
240 | * @return Twig_Compiler The current compiler instance | ||
241 | */ | ||
242 | public function indent($step = 1) | ||
243 | { | ||
244 | $this->indentation += $step; | ||
245 | |||
246 | return $this; | ||
247 | } | ||
248 | |||
249 | /** | ||
250 | * Outdents the generated code. | ||
251 | * | ||
252 | * @param integer $step The number of indentation to remove | ||
253 | * | ||
254 | * @return Twig_Compiler The current compiler instance | ||
255 | */ | ||
256 | public function outdent($step = 1) | ||
257 | { | ||
258 | // can't outdent by more steps than the current indentation level | ||
259 | if ($this->indentation < $step) { | ||
260 | throw new LogicException('Unable to call outdent() as the indentation would become negative'); | ||
261 | } | ||
262 | |||
263 | $this->indentation -= $step; | ||
264 | |||
265 | return $this; | ||
266 | } | ||
267 | } | ||
diff --git a/inc/Twig/CompilerInterface.php b/inc/Twig/CompilerInterface.php new file mode 100644 index 00000000..e293ec91 --- /dev/null +++ b/inc/Twig/CompilerInterface.php | |||
@@ -0,0 +1,35 @@ | |||
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 | * Interface implemented by compiler classes. | ||
14 | * | ||
15 | * @author Fabien Potencier <fabien@symfony.com> | ||
16 | * @deprecated since 1.12 (to be removed in 2.0) | ||
17 | */ | ||
18 | interface Twig_CompilerInterface | ||
19 | { | ||
20 | /** | ||
21 | * Compiles a node. | ||
22 | * | ||
23 | * @param Twig_NodeInterface $node The node to compile | ||
24 | * | ||
25 | * @return Twig_CompilerInterface The current compiler instance | ||
26 | */ | ||
27 | public function compile(Twig_NodeInterface $node); | ||
28 | |||
29 | /** | ||
30 | * Gets the current PHP code after compilation. | ||
31 | * | ||
32 | * @return string The PHP code | ||
33 | */ | ||
34 | public function getSource(); | ||
35 | } | ||
diff --git a/inc/Twig/Environment.php b/inc/Twig/Environment.php new file mode 100644 index 00000000..3afa73d6 --- /dev/null +++ b/inc/Twig/Environment.php | |||
@@ -0,0 +1,1224 @@ | |||
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 <fabien@symfony.com> | ||
16 | */ | ||
17 | class Twig_Environment | ||
18 | { | ||
19 | const VERSION = '1.13.1'; | ||
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 is the original source changed. | ||
65 | * If you don't provide the auto_reload option, it will be | ||
66 | * determined automatically base 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 | * * PHP callback: a PHP callback that returns an escaping strategy based on the template "filename" | ||
76 | * | ||
77 | * * optimizations: A flag that indicates which optimizations to apply | ||
78 | * (default to -1 which means that all optimizations are enabled; | ||
79 | * set it to 0 to disable). | ||
80 | * | ||
81 | * @param Twig_LoaderInterface $loader A Twig_LoaderInterface instance | ||
82 | * @param array $options An array of options | ||
83 | */ | ||
84 | public function __construct(Twig_LoaderInterface $loader = null, $options = array()) | ||
85 | { | ||
86 | if (null !== $loader) { | ||
87 | $this->setLoader($loader); | ||
88 | } | ||
89 | |||
90 | $options = array_merge(array( | ||
91 | 'debug' => false, | ||
92 | 'charset' => 'UTF-8', | ||
93 | 'base_template_class' => 'Twig_Template', | ||
94 | 'strict_variables' => false, | ||
95 | 'autoescape' => 'html', | ||
96 | 'cache' => false, | ||
97 | 'auto_reload' => null, | ||
98 | 'optimizations' => -1, | ||
99 | ), $options); | ||
100 | |||
101 | $this->debug = (bool) $options['debug']; | ||
102 | $this->charset = strtoupper($options['charset']); | ||
103 | $this->baseTemplateClass = $options['base_template_class']; | ||
104 | $this->autoReload = null === $options['auto_reload'] ? $this->debug : (bool) $options['auto_reload']; | ||
105 | $this->strictVariables = (bool) $options['strict_variables']; | ||
106 | $this->runtimeInitialized = false; | ||
107 | $this->setCache($options['cache']); | ||
108 | $this->functionCallbacks = array(); | ||
109 | $this->filterCallbacks = array(); | ||
110 | |||
111 | $this->addExtension(new Twig_Extension_Core()); | ||
112 | $this->addExtension(new Twig_Extension_Escaper($options['autoescape'])); | ||
113 | $this->addExtension(new Twig_Extension_Optimizer($options['optimizations'])); | ||
114 | $this->extensionInitialized = false; | ||
115 | $this->staging = new Twig_Extension_Staging(); | ||
116 | } | ||
117 | |||
118 | /** | ||
119 | * Gets the base template class for compiled templates. | ||
120 | * | ||
121 | * @return string The base template class name | ||
122 | */ | ||
123 | public function getBaseTemplateClass() | ||
124 | { | ||
125 | return $this->baseTemplateClass; | ||
126 | } | ||
127 | |||
128 | /** | ||
129 | * Sets the base template class for compiled templates. | ||
130 | * | ||
131 | * @param string $class The base template class name | ||
132 | */ | ||
133 | public function setBaseTemplateClass($class) | ||
134 | { | ||
135 | $this->baseTemplateClass = $class; | ||
136 | } | ||
137 | |||
138 | /** | ||
139 | * Enables debugging mode. | ||
140 | */ | ||
141 | public function enableDebug() | ||
142 | { | ||
143 | $this->debug = true; | ||
144 | } | ||
145 | |||
146 | /** | ||
147 | * Disables debugging mode. | ||
148 | */ | ||
149 | public function disableDebug() | ||
150 | { | ||
151 | $this->debug = false; | ||
152 | } | ||
153 | |||
154 | /** | ||
155 | * Checks if debug mode is enabled. | ||
156 | * | ||
157 | * @return Boolean true if debug mode is enabled, false otherwise | ||
158 | */ | ||
159 | public function isDebug() | ||
160 | { | ||
161 | return $this->debug; | ||
162 | } | ||
163 | |||
164 | /** | ||
165 | * Enables the auto_reload option. | ||
166 | */ | ||
167 | public function enableAutoReload() | ||
168 | { | ||
169 | $this->autoReload = true; | ||
170 | } | ||
171 | |||
172 | /** | ||
173 | * Disables the auto_reload option. | ||
174 | */ | ||
175 | public function disableAutoReload() | ||
176 | { | ||
177 | $this->autoReload = false; | ||
178 | } | ||
179 | |||
180 | /** | ||
181 | * Checks if the auto_reload option is enabled. | ||
182 | * | ||
183 | * @return Boolean true if auto_reload is enabled, false otherwise | ||
184 | */ | ||
185 | public function isAutoReload() | ||
186 | { | ||
187 | return $this->autoReload; | ||
188 | } | ||
189 | |||
190 | /** | ||
191 | * Enables the strict_variables option. | ||
192 | */ | ||
193 | public function enableStrictVariables() | ||
194 | { | ||
195 | $this->strictVariables = true; | ||
196 | } | ||
197 | |||
198 | /** | ||
199 | * Disables the strict_variables option. | ||
200 | */ | ||
201 | public function disableStrictVariables() | ||
202 | { | ||
203 | $this->strictVariables = false; | ||
204 | } | ||
205 | |||
206 | /** | ||
207 | * Checks if the strict_variables option is enabled. | ||
208 | * | ||
209 | * @return Boolean true if strict_variables is enabled, false otherwise | ||
210 | */ | ||
211 | public function isStrictVariables() | ||
212 | { | ||
213 | return $this->strictVariables; | ||
214 | } | ||
215 | |||
216 | /** | ||
217 | * Gets the cache directory or false if cache is disabled. | ||
218 | * | ||
219 | * @return string|false | ||
220 | */ | ||
221 | public function getCache() | ||
222 | { | ||
223 | return $this->cache; | ||
224 | } | ||
225 | |||
226 | /** | ||
227 | * Sets the cache directory or false if cache is disabled. | ||
228 | * | ||
229 | * @param string|false $cache The absolute path to the compiled templates, | ||
230 | * or false to disable cache | ||
231 | */ | ||
232 | public function setCache($cache) | ||
233 | { | ||
234 | $this->cache = $cache ? $cache : false; | ||
235 | } | ||
236 | |||
237 | /** | ||
238 | * Gets the cache filename for a given template. | ||
239 | * | ||
240 | * @param string $name The template name | ||
241 | * | ||
242 | * @return string The cache file name | ||
243 | */ | ||
244 | public function getCacheFilename($name) | ||
245 | { | ||
246 | if (false === $this->cache) { | ||
247 | return false; | ||
248 | } | ||
249 | |||
250 | $class = substr($this->getTemplateClass($name), strlen($this->templateClassPrefix)); | ||
251 | |||
252 | return $this->getCache().'/'.substr($class, 0, 2).'/'.substr($class, 2, 2).'/'.substr($class, 4).'.php'; | ||
253 | } | ||
254 | |||
255 | /** | ||
256 | * Gets the template class associated with the given string. | ||
257 | * | ||
258 | * @param string $name The name for which to calculate the template class name | ||
259 | * @param integer $index The index if it is an embedded template | ||
260 | * | ||
261 | * @return string The template class name | ||
262 | */ | ||
263 | public function getTemplateClass($name, $index = null) | ||
264 | { | ||
265 | return $this->templateClassPrefix.md5($this->getLoader()->getCacheKey($name)).(null === $index ? '' : '_'.$index); | ||
266 | } | ||
267 | |||
268 | /** | ||
269 | * Gets the template class prefix. | ||
270 | * | ||
271 | * @return string The template class prefix | ||
272 | */ | ||
273 | public function getTemplateClassPrefix() | ||
274 | { | ||
275 | return $this->templateClassPrefix; | ||
276 | } | ||
277 | |||
278 | /** | ||
279 | * Renders a template. | ||
280 | * | ||
281 | * @param string $name The template name | ||
282 | * @param array $context An array of parameters to pass to the template | ||
283 | * | ||
284 | * @return string The rendered template | ||
285 | */ | ||
286 | public function render($name, array $context = array()) | ||
287 | { | ||
288 | return $this->loadTemplate($name)->render($context); | ||
289 | } | ||
290 | |||
291 | /** | ||
292 | * Displays a template. | ||
293 | * | ||
294 | * @param string $name The template name | ||
295 | * @param array $context An array of parameters to pass to the template | ||
296 | */ | ||
297 | public function display($name, array $context = array()) | ||
298 | { | ||
299 | $this->loadTemplate($name)->display($context); | ||
300 | } | ||
301 | |||
302 | /** | ||
303 | * Loads a template by name. | ||
304 | * | ||
305 | * @param string $name The template name | ||
306 | * @param integer $index The index if it is an embedded template | ||
307 | * | ||
308 | * @return Twig_TemplateInterface A template instance representing the given template name | ||
309 | */ | ||
310 | public function loadTemplate($name, $index = null) | ||
311 | { | ||
312 | $cls = $this->getTemplateClass($name, $index); | ||
313 | |||
314 | if (isset($this->loadedTemplates[$cls])) { | ||
315 | return $this->loadedTemplates[$cls]; | ||
316 | } | ||
317 | |||
318 | if (!class_exists($cls, false)) { | ||
319 | if (false === $cache = $this->getCacheFilename($name)) { | ||
320 | eval('?>'.$this->compileSource($this->getLoader()->getSource($name), $name)); | ||
321 | } else { | ||
322 | if (!is_file($cache) || ($this->isAutoReload() && !$this->isTemplateFresh($name, filemtime($cache)))) { | ||
323 | $this->writeCacheFile($cache, $this->compileSource($this->getLoader()->getSource($name), $name)); | ||
324 | } | ||
325 | |||
326 | require_once $cache; | ||
327 | } | ||
328 | } | ||
329 | |||
330 | if (!$this->runtimeInitialized) { | ||
331 | $this->initRuntime(); | ||
332 | } | ||
333 | |||
334 | return $this->loadedTemplates[$cls] = new $cls($this); | ||
335 | } | ||
336 | |||
337 | /** | ||
338 | * Returns true if the template is still fresh. | ||
339 | * | ||
340 | * Besides checking the loader for freshness information, | ||
341 | * this method also checks if the enabled extensions have | ||
342 | * not changed. | ||
343 | * | ||
344 | * @param string $name The template name | ||
345 | * @param timestamp $time The last modification time of the cached template | ||
346 | * | ||
347 | * @return Boolean true if the template is fresh, false otherwise | ||
348 | */ | ||
349 | public function isTemplateFresh($name, $time) | ||
350 | { | ||
351 | foreach ($this->extensions as $extension) { | ||
352 | $r = new ReflectionObject($extension); | ||
353 | if (filemtime($r->getFileName()) > $time) { | ||
354 | return false; | ||
355 | } | ||
356 | } | ||
357 | |||
358 | return $this->getLoader()->isFresh($name, $time); | ||
359 | } | ||
360 | |||
361 | public function resolveTemplate($names) | ||
362 | { | ||
363 | if (!is_array($names)) { | ||
364 | $names = array($names); | ||
365 | } | ||
366 | |||
367 | foreach ($names as $name) { | ||
368 | if ($name instanceof Twig_Template) { | ||
369 | return $name; | ||
370 | } | ||
371 | |||
372 | try { | ||
373 | return $this->loadTemplate($name); | ||
374 | } catch (Twig_Error_Loader $e) { | ||
375 | } | ||
376 | } | ||
377 | |||
378 | if (1 === count($names)) { | ||
379 | throw $e; | ||
380 | } | ||
381 | |||
382 | throw new Twig_Error_Loader(sprintf('Unable to find one of the following templates: "%s".', implode('", "', $names))); | ||
383 | } | ||
384 | |||
385 | /** | ||
386 | * Clears the internal template cache. | ||
387 | */ | ||
388 | public function clearTemplateCache() | ||
389 | { | ||
390 | $this->loadedTemplates = array(); | ||
391 | } | ||
392 | |||
393 | /** | ||
394 | * Clears the template cache files on the filesystem. | ||
395 | */ | ||
396 | public function clearCacheFiles() | ||
397 | { | ||
398 | if (false === $this->cache) { | ||
399 | return; | ||
400 | } | ||
401 | |||
402 | foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->cache), RecursiveIteratorIterator::LEAVES_ONLY) as $file) { | ||
403 | if ($file->isFile()) { | ||
404 | @unlink($file->getPathname()); | ||
405 | } | ||
406 | } | ||
407 | } | ||
408 | |||
409 | /** | ||
410 | * Gets the Lexer instance. | ||
411 | * | ||
412 | * @return Twig_LexerInterface A Twig_LexerInterface instance | ||
413 | */ | ||
414 | public function getLexer() | ||
415 | { | ||
416 | if (null === $this->lexer) { | ||
417 | $this->lexer = new Twig_Lexer($this); | ||
418 | } | ||
419 | |||
420 | return $this->lexer; | ||
421 | } | ||
422 | |||
423 | /** | ||
424 | * Sets the Lexer instance. | ||
425 | * | ||
426 | * @param Twig_LexerInterface A Twig_LexerInterface instance | ||
427 | */ | ||
428 | public function setLexer(Twig_LexerInterface $lexer) | ||
429 | { | ||
430 | $this->lexer = $lexer; | ||
431 | } | ||
432 | |||
433 | /** | ||
434 | * Tokenizes a source code. | ||
435 | * | ||
436 | * @param string $source The template source code | ||
437 | * @param string $name The template name | ||
438 | * | ||
439 | * @return Twig_TokenStream A Twig_TokenStream instance | ||
440 | */ | ||
441 | public function tokenize($source, $name = null) | ||
442 | { | ||
443 | return $this->getLexer()->tokenize($source, $name); | ||
444 | } | ||
445 | |||
446 | /** | ||
447 | * Gets the Parser instance. | ||
448 | * | ||
449 | * @return Twig_ParserInterface A Twig_ParserInterface instance | ||
450 | */ | ||
451 | public function getParser() | ||
452 | { | ||
453 | if (null === $this->parser) { | ||
454 | $this->parser = new Twig_Parser($this); | ||
455 | } | ||
456 | |||
457 | return $this->parser; | ||
458 | } | ||
459 | |||
460 | /** | ||
461 | * Sets the Parser instance. | ||
462 | * | ||
463 | * @param Twig_ParserInterface A Twig_ParserInterface instance | ||
464 | */ | ||
465 | public function setParser(Twig_ParserInterface $parser) | ||
466 | { | ||
467 | $this->parser = $parser; | ||
468 | } | ||
469 | |||
470 | /** | ||
471 | * Parses a token stream. | ||
472 | * | ||
473 | * @param Twig_TokenStream $tokens A Twig_TokenStream instance | ||
474 | * | ||
475 | * @return Twig_Node_Module A Node tree | ||
476 | */ | ||
477 | public function parse(Twig_TokenStream $tokens) | ||
478 | { | ||
479 | return $this->getParser()->parse($tokens); | ||
480 | } | ||
481 | |||
482 | /** | ||
483 | * Gets the Compiler instance. | ||
484 | * | ||
485 | * @return Twig_CompilerInterface A Twig_CompilerInterface instance | ||
486 | */ | ||
487 | public function getCompiler() | ||
488 | { | ||
489 | if (null === $this->compiler) { | ||
490 | $this->compiler = new Twig_Compiler($this); | ||
491 | } | ||
492 | |||
493 | return $this->compiler; | ||
494 | } | ||
495 | |||
496 | /** | ||
497 | * Sets the Compiler instance. | ||
498 | * | ||
499 | * @param Twig_CompilerInterface $compiler A Twig_CompilerInterface instance | ||
500 | */ | ||
501 | public function setCompiler(Twig_CompilerInterface $compiler) | ||
502 | { | ||
503 | $this->compiler = $compiler; | ||
504 | } | ||
505 | |||
506 | /** | ||
507 | * Compiles a Node. | ||
508 | * | ||
509 | * @param Twig_NodeInterface $node A Twig_NodeInterface instance | ||
510 | * | ||
511 | * @return string The compiled PHP source code | ||
512 | */ | ||
513 | public function compile(Twig_NodeInterface $node) | ||
514 | { | ||
515 | return $this->getCompiler()->compile($node)->getSource(); | ||
516 | } | ||
517 | |||
518 | /** | ||
519 | * Compiles a template source code. | ||
520 | * | ||
521 | * @param string $source The template source code | ||
522 | * @param string $name The template name | ||
523 | * | ||
524 | * @return string The compiled PHP source code | ||
525 | */ | ||
526 | public function compileSource($source, $name = null) | ||
527 | { | ||
528 | try { | ||
529 | return $this->compile($this->parse($this->tokenize($source, $name))); | ||
530 | } catch (Twig_Error $e) { | ||
531 | $e->setTemplateFile($name); | ||
532 | throw $e; | ||
533 | } catch (Exception $e) { | ||
534 | throw new Twig_Error_Runtime(sprintf('An exception has been thrown during the compilation of a template ("%s").', $e->getMessage()), -1, $name, $e); | ||
535 | } | ||
536 | } | ||
537 | |||
538 | /** | ||
539 | * Sets the Loader instance. | ||
540 | * | ||
541 | * @param Twig_LoaderInterface $loader A Twig_LoaderInterface instance | ||
542 | */ | ||
543 | public function setLoader(Twig_LoaderInterface $loader) | ||
544 | { | ||
545 | $this->loader = $loader; | ||
546 | } | ||
547 | |||
548 | /** | ||
549 | * Gets the Loader instance. | ||
550 | * | ||
551 | * @return Twig_LoaderInterface A Twig_LoaderInterface instance | ||
552 | */ | ||
553 | public function getLoader() | ||
554 | { | ||
555 | if (null === $this->loader) { | ||
556 | throw new LogicException('You must set a loader first.'); | ||
557 | } | ||
558 | |||
559 | return $this->loader; | ||
560 | } | ||
561 | |||
562 | /** | ||
563 | * Sets the default template charset. | ||
564 | * | ||
565 | * @param string $charset The default charset | ||
566 | */ | ||
567 | public function setCharset($charset) | ||
568 | { | ||
569 | $this->charset = strtoupper($charset); | ||
570 | } | ||
571 | |||
572 | /** | ||
573 | * Gets the default template charset. | ||
574 | * | ||
575 | * @return string The default charset | ||
576 | */ | ||
577 | public function getCharset() | ||
578 | { | ||
579 | return $this->charset; | ||
580 | } | ||
581 | |||
582 | /** | ||
583 | * Initializes the runtime environment. | ||
584 | */ | ||
585 | public function initRuntime() | ||
586 | { | ||
587 | $this->runtimeInitialized = true; | ||
588 | |||
589 | foreach ($this->getExtensions() as $extension) { | ||
590 | $extension->initRuntime($this); | ||
591 | } | ||
592 | } | ||
593 | |||
594 | /** | ||
595 | * Returns true if the given extension is registered. | ||
596 | * | ||
597 | * @param string $name The extension name | ||
598 | * | ||
599 | * @return Boolean Whether the extension is registered or not | ||
600 | */ | ||
601 | public function hasExtension($name) | ||
602 | { | ||
603 | return isset($this->extensions[$name]); | ||
604 | } | ||
605 | |||
606 | /** | ||
607 | * Gets an extension by name. | ||
608 | * | ||
609 | * @param string $name The extension name | ||
610 | * | ||
611 | * @return Twig_ExtensionInterface A Twig_ExtensionInterface instance | ||
612 | */ | ||
613 | public function getExtension($name) | ||
614 | { | ||
615 | if (!isset($this->extensions[$name])) { | ||
616 | throw new Twig_Error_Runtime(sprintf('The "%s" extension is not enabled.', $name)); | ||
617 | } | ||
618 | |||
619 | return $this->extensions[$name]; | ||
620 | } | ||
621 | |||
622 | /** | ||
623 | * Registers an extension. | ||
624 | * | ||
625 | * @param Twig_ExtensionInterface $extension A Twig_ExtensionInterface instance | ||
626 | */ | ||
627 | public function addExtension(Twig_ExtensionInterface $extension) | ||
628 | { | ||
629 | if ($this->extensionInitialized) { | ||
630 | throw new LogicException(sprintf('Unable to register extension "%s" as extensions have already been initialized.', $extension->getName())); | ||
631 | } | ||
632 | |||
633 | $this->extensions[$extension->getName()] = $extension; | ||
634 | } | ||
635 | |||
636 | /** | ||
637 | * Removes an extension by name. | ||
638 | * | ||
639 | * This method is deprecated and you should not use it. | ||
640 | * | ||
641 | * @param string $name The extension name | ||
642 | * | ||
643 | * @deprecated since 1.12 (to be removed in 2.0) | ||
644 | */ | ||
645 | public function removeExtension($name) | ||
646 | { | ||
647 | if ($this->extensionInitialized) { | ||
648 | throw new LogicException(sprintf('Unable to remove extension "%s" as extensions have already been initialized.', $name)); | ||
649 | } | ||
650 | |||
651 | unset($this->extensions[$name]); | ||
652 | } | ||
653 | |||
654 | /** | ||
655 | * Registers an array of extensions. | ||
656 | * | ||
657 | * @param array $extensions An array of extensions | ||
658 | */ | ||
659 | public function setExtensions(array $extensions) | ||
660 | { | ||
661 | foreach ($extensions as $extension) { | ||
662 | $this->addExtension($extension); | ||
663 | } | ||
664 | } | ||
665 | |||
666 | /** | ||
667 | * Returns all registered extensions. | ||
668 | * | ||
669 | * @return array An array of extensions | ||
670 | */ | ||
671 | public function getExtensions() | ||
672 | { | ||
673 | return $this->extensions; | ||
674 | } | ||
675 | |||
676 | /** | ||
677 | * Registers a Token Parser. | ||
678 | * | ||
679 | * @param Twig_TokenParserInterface $parser A Twig_TokenParserInterface instance | ||
680 | */ | ||
681 | public function addTokenParser(Twig_TokenParserInterface $parser) | ||
682 | { | ||
683 | if ($this->extensionInitialized) { | ||
684 | throw new LogicException('Unable to add a token parser as extensions have already been initialized.'); | ||
685 | } | ||
686 | |||
687 | $this->staging->addTokenParser($parser); | ||
688 | } | ||
689 | |||
690 | /** | ||
691 | * Gets the registered Token Parsers. | ||
692 | * | ||
693 | * @return Twig_TokenParserBrokerInterface A broker containing token parsers | ||
694 | */ | ||
695 | public function getTokenParsers() | ||
696 | { | ||
697 | if (!$this->extensionInitialized) { | ||
698 | $this->initExtensions(); | ||
699 | } | ||
700 | |||
701 | return $this->parsers; | ||
702 | } | ||
703 | |||
704 | /** | ||
705 | * Gets registered tags. | ||
706 | * | ||
707 | * Be warned that this method cannot return tags defined by Twig_TokenParserBrokerInterface classes. | ||
708 | * | ||
709 | * @return Twig_TokenParserInterface[] An array of Twig_TokenParserInterface instances | ||
710 | */ | ||
711 | public function getTags() | ||
712 | { | ||
713 | $tags = array(); | ||
714 | foreach ($this->getTokenParsers()->getParsers() as $parser) { | ||
715 | if ($parser instanceof Twig_TokenParserInterface) { | ||
716 | $tags[$parser->getTag()] = $parser; | ||
717 | } | ||
718 | } | ||
719 | |||
720 | return $tags; | ||
721 | } | ||
722 | |||
723 | /** | ||
724 | * Registers a Node Visitor. | ||
725 | * | ||
726 | * @param Twig_NodeVisitorInterface $visitor A Twig_NodeVisitorInterface instance | ||
727 | */ | ||
728 | public function addNodeVisitor(Twig_NodeVisitorInterface $visitor) | ||
729 | { | ||
730 | if ($this->extensionInitialized) { | ||
731 | throw new LogicException('Unable to add a node visitor as extensions have already been initialized.', $extension->getName()); | ||
732 | } | ||
733 | |||
734 | $this->staging->addNodeVisitor($visitor); | ||
735 | } | ||
736 | |||
737 | /** | ||
738 | * Gets the registered Node Visitors. | ||
739 | * | ||
740 | * @return Twig_NodeVisitorInterface[] An array of Twig_NodeVisitorInterface instances | ||
741 | */ | ||
742 | public function getNodeVisitors() | ||
743 | { | ||
744 | if (!$this->extensionInitialized) { | ||
745 | $this->initExtensions(); | ||
746 | } | ||
747 | |||
748 | return $this->visitors; | ||
749 | } | ||
750 | |||
751 | /** | ||
752 | * Registers a Filter. | ||
753 | * | ||
754 | * @param string|Twig_SimpleFilter $name The filter name or a Twig_SimpleFilter instance | ||
755 | * @param Twig_FilterInterface|Twig_SimpleFilter $filter A Twig_FilterInterface instance or a Twig_SimpleFilter instance | ||
756 | */ | ||
757 | public function addFilter($name, $filter = null) | ||
758 | { | ||
759 | if (!$name instanceof Twig_SimpleFilter && !($filter instanceof Twig_SimpleFilter || $filter instanceof Twig_FilterInterface)) { | ||
760 | throw new LogicException('A filter must be an instance of Twig_FilterInterface or Twig_SimpleFilter'); | ||
761 | } | ||
762 | |||
763 | if ($name instanceof Twig_SimpleFilter) { | ||
764 | $filter = $name; | ||
765 | $name = $filter->getName(); | ||
766 | } | ||
767 | |||
768 | if ($this->extensionInitialized) { | ||
769 | throw new LogicException(sprintf('Unable to add filter "%s" as extensions have already been initialized.', $name)); | ||
770 | } | ||
771 | |||
772 | $this->staging->addFilter($name, $filter); | ||
773 | } | ||
774 | |||
775 | /** | ||
776 | * Get a filter by name. | ||
777 | * | ||
778 | * Subclasses may override this method and load filters differently; | ||
779 | * so no list of filters is available. | ||
780 | * | ||
781 | * @param string $name The filter name | ||
782 | * | ||
783 | * @return Twig_Filter|false A Twig_Filter instance or false if the filter does not exist | ||
784 | */ | ||
785 | public function getFilter($name) | ||
786 | { | ||
787 | if (!$this->extensionInitialized) { | ||
788 | $this->initExtensions(); | ||
789 | } | ||
790 | |||
791 | if (isset($this->filters[$name])) { | ||
792 | return $this->filters[$name]; | ||
793 | } | ||
794 | |||
795 | foreach ($this->filters as $pattern => $filter) { | ||
796 | $pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count); | ||
797 | |||
798 | if ($count) { | ||
799 | if (preg_match('#^'.$pattern.'$#', $name, $matches)) { | ||
800 | array_shift($matches); | ||
801 | $filter->setArguments($matches); | ||
802 | |||
803 | return $filter; | ||
804 | } | ||
805 | } | ||
806 | } | ||
807 | |||
808 | foreach ($this->filterCallbacks as $callback) { | ||
809 | if (false !== $filter = call_user_func($callback, $name)) { | ||
810 | return $filter; | ||
811 | } | ||
812 | } | ||
813 | |||
814 | return false; | ||
815 | } | ||
816 | |||
817 | public function registerUndefinedFilterCallback($callable) | ||
818 | { | ||
819 | $this->filterCallbacks[] = $callable; | ||
820 | } | ||
821 | |||
822 | /** | ||
823 | * Gets the registered Filters. | ||
824 | * | ||
825 | * Be warned that this method cannot return filters defined with registerUndefinedFunctionCallback. | ||
826 | * | ||
827 | * @return Twig_FilterInterface[] An array of Twig_FilterInterface instances | ||
828 | * | ||
829 | * @see registerUndefinedFilterCallback | ||
830 | */ | ||
831 | public function getFilters() | ||
832 | { | ||
833 | if (!$this->extensionInitialized) { | ||
834 | $this->initExtensions(); | ||
835 | } | ||
836 | |||
837 | return $this->filters; | ||
838 | } | ||
839 | |||
840 | /** | ||
841 | * Registers a Test. | ||
842 | * | ||
843 | * @param string|Twig_SimpleTest $name The test name or a Twig_SimpleTest instance | ||
844 | * @param Twig_TestInterface|Twig_SimpleTest $test A Twig_TestInterface instance or a Twig_SimpleTest instance | ||
845 | */ | ||
846 | public function addTest($name, $test = null) | ||
847 | { | ||
848 | if (!$name instanceof Twig_SimpleTest && !($test instanceof Twig_SimpleTest || $test instanceof Twig_TestInterface)) { | ||
849 | throw new LogicException('A test must be an instance of Twig_TestInterface or Twig_SimpleTest'); | ||
850 | } | ||
851 | |||
852 | if ($name instanceof Twig_SimpleTest) { | ||
853 | $test = $name; | ||
854 | $name = $test->getName(); | ||
855 | } | ||
856 | |||
857 | if ($this->extensionInitialized) { | ||
858 | throw new LogicException(sprintf('Unable to add test "%s" as extensions have already been initialized.', $name)); | ||
859 | } | ||
860 | |||
861 | $this->staging->addTest($name, $test); | ||
862 | } | ||
863 | |||
864 | /** | ||
865 | * Gets the registered Tests. | ||
866 | * | ||
867 | * @return Twig_TestInterface[] An array of Twig_TestInterface instances | ||
868 | */ | ||
869 | public function getTests() | ||
870 | { | ||
871 | if (!$this->extensionInitialized) { | ||
872 | $this->initExtensions(); | ||
873 | } | ||
874 | |||
875 | return $this->tests; | ||
876 | } | ||
877 | |||
878 | /** | ||
879 | * Gets a test by name. | ||
880 | * | ||
881 | * @param string $name The test name | ||
882 | * | ||
883 | * @return Twig_Test|false A Twig_Test instance or false if the test does not exist | ||
884 | */ | ||
885 | public function getTest($name) | ||
886 | { | ||
887 | if (!$this->extensionInitialized) { | ||
888 | $this->initExtensions(); | ||
889 | } | ||
890 | |||
891 | if (isset($this->tests[$name])) { | ||
892 | return $this->tests[$name]; | ||
893 | } | ||
894 | |||
895 | return false; | ||
896 | } | ||
897 | |||
898 | /** | ||
899 | * Registers a Function. | ||
900 | * | ||
901 | * @param string|Twig_SimpleFunction $name The function name or a Twig_SimpleFunction instance | ||
902 | * @param Twig_FunctionInterface|Twig_SimpleFunction $function A Twig_FunctionInterface instance or a Twig_SimpleFunction instance | ||
903 | */ | ||
904 | public function addFunction($name, $function = null) | ||
905 | { | ||
906 | if (!$name instanceof Twig_SimpleFunction && !($function instanceof Twig_SimpleFunction || $function instanceof Twig_FunctionInterface)) { | ||
907 | throw new LogicException('A function must be an instance of Twig_FunctionInterface or Twig_SimpleFunction'); | ||
908 | } | ||
909 | |||
910 | if ($name instanceof Twig_SimpleFunction) { | ||
911 | $function = $name; | ||
912 | $name = $function->getName(); | ||
913 | } | ||
914 | |||
915 | if ($this->extensionInitialized) { | ||
916 | throw new LogicException(sprintf('Unable to add function "%s" as extensions have already been initialized.', $name)); | ||
917 | } | ||
918 | |||
919 | $this->staging->addFunction($name, $function); | ||
920 | } | ||
921 | |||
922 | /** | ||
923 | * Get a function by name. | ||
924 | * | ||
925 | * Subclasses may override this method and load functions differently; | ||
926 | * so no list of functions is available. | ||
927 | * | ||
928 | * @param string $name function name | ||
929 | * | ||
930 | * @return Twig_Function|false A Twig_Function instance or false if the function does not exist | ||
931 | */ | ||
932 | public function getFunction($name) | ||
933 | { | ||
934 | if (!$this->extensionInitialized) { | ||
935 | $this->initExtensions(); | ||
936 | } | ||
937 | |||
938 | if (isset($this->functions[$name])) { | ||
939 | return $this->functions[$name]; | ||
940 | } | ||
941 | |||
942 | foreach ($this->functions as $pattern => $function) { | ||
943 | $pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count); | ||
944 | |||
945 | if ($count) { | ||
946 | if (preg_match('#^'.$pattern.'$#', $name, $matches)) { | ||
947 | array_shift($matches); | ||
948 | $function->setArguments($matches); | ||
949 | |||
950 | return $function; | ||
951 | } | ||
952 | } | ||
953 | } | ||
954 | |||
955 | foreach ($this->functionCallbacks as $callback) { | ||
956 | if (false !== $function = call_user_func($callback, $name)) { | ||
957 | return $function; | ||
958 | } | ||
959 | } | ||
960 | |||
961 | return false; | ||
962 | } | ||
963 | |||
964 | public function registerUndefinedFunctionCallback($callable) | ||
965 | { | ||
966 | $this->functionCallbacks[] = $callable; | ||
967 | } | ||
968 | |||
969 | /** | ||
970 | * Gets registered functions. | ||
971 | * | ||
972 | * Be warned that this method cannot return functions defined with registerUndefinedFunctionCallback. | ||
973 | * | ||
974 | * @return Twig_FunctionInterface[] An array of Twig_FunctionInterface instances | ||
975 | * | ||
976 | * @see registerUndefinedFunctionCallback | ||
977 | */ | ||
978 | public function getFunctions() | ||
979 | { | ||
980 | if (!$this->extensionInitialized) { | ||
981 | $this->initExtensions(); | ||
982 | } | ||
983 | |||
984 | return $this->functions; | ||
985 | } | ||
986 | |||
987 | /** | ||
988 | * Registers a Global. | ||
989 | * | ||
990 | * New globals can be added before compiling or rendering a template; | ||
991 | * but after, you can only update existing globals. | ||
992 | * | ||
993 | * @param string $name The global name | ||
994 | * @param mixed $value The global value | ||
995 | */ | ||
996 | public function addGlobal($name, $value) | ||
997 | { | ||
998 | if ($this->extensionInitialized || $this->runtimeInitialized) { | ||
999 | if (null === $this->globals) { | ||
1000 | $this->globals = $this->initGlobals(); | ||
1001 | } | ||
1002 | |||
1003 | /* This condition must be uncommented in Twig 2.0 | ||
1004 | if (!array_key_exists($name, $this->globals)) { | ||
1005 | throw new LogicException(sprintf('Unable to add global "%s" as the runtime or the extensions have already been initialized.', $name)); | ||
1006 | } | ||
1007 | */ | ||
1008 | } | ||
1009 | |||
1010 | if ($this->extensionInitialized || $this->runtimeInitialized) { | ||
1011 | // update the value | ||
1012 | $this->globals[$name] = $value; | ||
1013 | } else { | ||
1014 | $this->staging->addGlobal($name, $value); | ||
1015 | } | ||
1016 | } | ||
1017 | |||
1018 | /** | ||
1019 | * Gets the registered Globals. | ||
1020 | * | ||
1021 | * @return array An array of globals | ||
1022 | */ | ||
1023 | public function getGlobals() | ||
1024 | { | ||
1025 | if (!$this->runtimeInitialized && !$this->extensionInitialized) { | ||
1026 | return $this->initGlobals(); | ||
1027 | } | ||
1028 | |||
1029 | if (null === $this->globals) { | ||
1030 | $this->globals = $this->initGlobals(); | ||
1031 | } | ||
1032 | |||
1033 | return $this->globals; | ||
1034 | } | ||
1035 | |||
1036 | /** | ||
1037 | * Merges a context with the defined globals. | ||
1038 | * | ||
1039 | * @param array $context An array representing the context | ||
1040 | * | ||
1041 | * @return array The context merged with the globals | ||
1042 | */ | ||
1043 | public function mergeGlobals(array $context) | ||
1044 | { | ||
1045 | // we don't use array_merge as the context being generally | ||
1046 | // bigger than globals, this code is faster. | ||
1047 | foreach ($this->getGlobals() as $key => $value) { | ||
1048 | if (!array_key_exists($key, $context)) { | ||
1049 | $context[$key] = $value; | ||
1050 | } | ||
1051 | } | ||
1052 | |||
1053 | return $context; | ||
1054 | } | ||
1055 | |||
1056 | /** | ||
1057 | * Gets the registered unary Operators. | ||
1058 | * | ||
1059 | * @return array An array of unary operators | ||
1060 | */ | ||
1061 | public function getUnaryOperators() | ||
1062 | { | ||
1063 | if (!$this->extensionInitialized) { | ||
1064 | $this->initExtensions(); | ||
1065 | } | ||
1066 | |||
1067 | return $this->unaryOperators; | ||
1068 | } | ||
1069 | |||
1070 | /** | ||
1071 | * Gets the registered binary Operators. | ||
1072 | * | ||
1073 | * @return array An array of binary operators | ||
1074 | */ | ||
1075 | public function getBinaryOperators() | ||
1076 | { | ||
1077 | if (!$this->extensionInitialized) { | ||
1078 | $this->initExtensions(); | ||
1079 | } | ||
1080 | |||
1081 | return $this->binaryOperators; | ||
1082 | } | ||
1083 | |||
1084 | public function computeAlternatives($name, $items) | ||
1085 | { | ||
1086 | $alternatives = array(); | ||
1087 | foreach ($items as $item) { | ||
1088 | $lev = levenshtein($name, $item); | ||
1089 | if ($lev <= strlen($name) / 3 || false !== strpos($item, $name)) { | ||
1090 | $alternatives[$item] = $lev; | ||
1091 | } | ||
1092 | } | ||
1093 | asort($alternatives); | ||
1094 | |||
1095 | return array_keys($alternatives); | ||
1096 | } | ||
1097 | |||
1098 | protected function initGlobals() | ||
1099 | { | ||
1100 | $globals = array(); | ||
1101 | foreach ($this->extensions as $extension) { | ||
1102 | $extGlob = $extension->getGlobals(); | ||
1103 | if (!is_array($extGlob)) { | ||
1104 | throw new UnexpectedValueException(sprintf('"%s::getGlobals()" must return an array of globals.', get_class($extension))); | ||
1105 | } | ||
1106 | |||
1107 | $globals[] = $extGlob; | ||
1108 | } | ||
1109 | |||
1110 | $globals[] = $this->staging->getGlobals(); | ||
1111 | |||
1112 | return call_user_func_array('array_merge', $globals); | ||
1113 | } | ||
1114 | |||
1115 | protected function initExtensions() | ||
1116 | { | ||
1117 | if ($this->extensionInitialized) { | ||
1118 | return; | ||
1119 | } | ||
1120 | |||
1121 | $this->extensionInitialized = true; | ||
1122 | $this->parsers = new Twig_TokenParserBroker(); | ||
1123 | $this->filters = array(); | ||
1124 | $this->functions = array(); | ||
1125 | $this->tests = array(); | ||
1126 | $this->visitors = array(); | ||
1127 | $this->unaryOperators = array(); | ||
1128 | $this->binaryOperators = array(); | ||
1129 | |||
1130 | foreach ($this->extensions as $extension) { | ||
1131 | $this->initExtension($extension); | ||
1132 | } | ||
1133 | $this->initExtension($this->staging); | ||
1134 | } | ||
1135 | |||
1136 | protected function initExtension(Twig_ExtensionInterface $extension) | ||
1137 | { | ||
1138 | // filters | ||
1139 | foreach ($extension->getFilters() as $name => $filter) { | ||
1140 | if ($name instanceof Twig_SimpleFilter) { | ||
1141 | $filter = $name; | ||
1142 | $name = $filter->getName(); | ||
1143 | } elseif ($filter instanceof Twig_SimpleFilter) { | ||
1144 | $name = $filter->getName(); | ||
1145 | } | ||
1146 | |||
1147 | $this->filters[$name] = $filter; | ||
1148 | } | ||
1149 | |||
1150 | // functions | ||
1151 | foreach ($extension->getFunctions() as $name => $function) { | ||
1152 | if ($name instanceof Twig_SimpleFunction) { | ||
1153 | $function = $name; | ||
1154 | $name = $function->getName(); | ||
1155 | } elseif ($function instanceof Twig_SimpleFunction) { | ||
1156 | $name = $function->getName(); | ||
1157 | } | ||
1158 | |||
1159 | $this->functions[$name] = $function; | ||
1160 | } | ||
1161 | |||
1162 | // tests | ||
1163 | foreach ($extension->getTests() as $name => $test) { | ||
1164 | if ($name instanceof Twig_SimpleTest) { | ||
1165 | $test = $name; | ||
1166 | $name = $test->getName(); | ||
1167 | } elseif ($test instanceof Twig_SimpleTest) { | ||
1168 | $name = $test->getName(); | ||
1169 | } | ||
1170 | |||
1171 | $this->tests[$name] = $test; | ||
1172 | } | ||
1173 | |||
1174 | // token parsers | ||
1175 | foreach ($extension->getTokenParsers() as $parser) { | ||
1176 | if ($parser instanceof Twig_TokenParserInterface) { | ||
1177 | $this->parsers->addTokenParser($parser); | ||
1178 | } elseif ($parser instanceof Twig_TokenParserBrokerInterface) { | ||
1179 | $this->parsers->addTokenParserBroker($parser); | ||
1180 | } else { | ||
1181 | throw new LogicException('getTokenParsers() must return an array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances'); | ||
1182 | } | ||
1183 | } | ||
1184 | |||
1185 | // node visitors | ||
1186 | foreach ($extension->getNodeVisitors() as $visitor) { | ||
1187 | $this->visitors[] = $visitor; | ||
1188 | } | ||
1189 | |||
1190 | // operators | ||
1191 | if ($operators = $extension->getOperators()) { | ||
1192 | if (2 !== count($operators)) { | ||
1193 | throw new InvalidArgumentException(sprintf('"%s::getOperators()" does not return a valid operators array.', get_class($extension))); | ||
1194 | } | ||
1195 | |||
1196 | $this->unaryOperators = array_merge($this->unaryOperators, $operators[0]); | ||
1197 | $this->binaryOperators = array_merge($this->binaryOperators, $operators[1]); | ||
1198 | } | ||
1199 | } | ||
1200 | |||
1201 | protected function writeCacheFile($file, $content) | ||
1202 | { | ||
1203 | $dir = dirname($file); | ||
1204 | if (!is_dir($dir)) { | ||
1205 | if (false === @mkdir($dir, 0777, true) && !is_dir($dir)) { | ||
1206 | throw new RuntimeException(sprintf("Unable to create the cache directory (%s).", $dir)); | ||
1207 | } | ||
1208 | } elseif (!is_writable($dir)) { | ||
1209 | throw new RuntimeException(sprintf("Unable to write in the cache directory (%s).", $dir)); | ||
1210 | } | ||
1211 | |||
1212 | $tmpFile = tempnam(dirname($file), basename($file)); | ||
1213 | if (false !== @file_put_contents($tmpFile, $content)) { | ||
1214 | // rename does not work on Win32 before 5.2.6 | ||
1215 | if (@rename($tmpFile, $file) || (@copy($tmpFile, $file) && unlink($tmpFile))) { | ||
1216 | @chmod($file, 0666 & ~umask()); | ||
1217 | |||
1218 | return; | ||
1219 | } | ||
1220 | } | ||
1221 | |||
1222 | throw new RuntimeException(sprintf('Failed to write cache file "%s".', $file)); | ||
1223 | } | ||
1224 | } | ||
diff --git a/inc/Twig/Error.php b/inc/Twig/Error.php new file mode 100644 index 00000000..72d91a98 --- /dev/null +++ b/inc/Twig/Error.php | |||
@@ -0,0 +1,239 @@ | |||
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 base exception. | ||
14 | * | ||
15 | * This exception class and its children must only be used when | ||
16 | * an error occurs during the loading of a template, when a syntax error | ||
17 | * is detected in a template, or when rendering a template. Other | ||
18 | * errors must use regular PHP exception classes (like when the template | ||
19 | * cache directory is not writable for instance). | ||
20 | * | ||
21 | * To help debugging template issues, this class tracks the original template | ||
22 | * name and line where the error occurred. | ||
23 | * | ||
24 | * Whenever possible, you must set these information (original template name | ||
25 | * and line number) yourself by passing them to the constructor. If some or all | ||
26 | * these information are not available from where you throw the exception, then | ||
27 | * this class will guess them automatically (when the line number is set to -1 | ||
28 | * and/or the filename is set to null). As this is a costly operation, this | ||
29 | * can be disabled by passing false for both the filename and the line number | ||
30 | * when creating a new instance of this class. | ||
31 | * | ||
32 | * @author Fabien Potencier <fabien@symfony.com> | ||
33 | */ | ||
34 | class Twig_Error extends Exception | ||
35 | { | ||
36 | protected $lineno; | ||
37 | protected $filename; | ||
38 | protected $rawMessage; | ||
39 | protected $previous; | ||
40 | |||
41 | /** | ||
42 | * Constructor. | ||
43 | * | ||
44 | * Set both the line number and the filename to false to | ||
45 | * disable automatic guessing of the original template name | ||
46 | * and line number. | ||
47 | * | ||
48 | * Set the line number to -1 to enable its automatic guessing. | ||
49 | * Set the filename to null to enable its automatic guessing. | ||
50 | * | ||
51 | * By default, automatic guessing is enabled. | ||
52 | * | ||
53 | * @param string $message The error message | ||
54 | * @param integer $lineno The template line where the error occurred | ||
55 | * @param string $filename The template file name where the error occurred | ||
56 | * @param Exception $previous The previous exception | ||
57 | */ | ||
58 | public function __construct($message, $lineno = -1, $filename = null, Exception $previous = null) | ||
59 | { | ||
60 | if (version_compare(PHP_VERSION, '5.3.0', '<')) { | ||
61 | $this->previous = $previous; | ||
62 | parent::__construct(''); | ||
63 | } else { | ||
64 | parent::__construct('', 0, $previous); | ||
65 | } | ||
66 | |||
67 | $this->lineno = $lineno; | ||
68 | $this->filename = $filename; | ||
69 | |||
70 | if (-1 === $this->lineno || null === $this->filename) { | ||
71 | $this->guessTemplateInfo(); | ||
72 | } | ||
73 | |||
74 | $this->rawMessage = $message; | ||
75 | |||
76 | $this->updateRepr(); | ||
77 | } | ||
78 | |||
79 | /** | ||
80 | * Gets the raw message. | ||
81 | * | ||
82 | * @return string The raw message | ||
83 | */ | ||
84 | public function getRawMessage() | ||
85 | { | ||
86 | return $this->rawMessage; | ||
87 | } | ||
88 | |||
89 | /** | ||
90 | * Gets the filename where the error occurred. | ||
91 | * | ||
92 | * @return string The filename | ||
93 | */ | ||
94 | public function getTemplateFile() | ||
95 | { | ||
96 | return $this->filename; | ||
97 | } | ||
98 | |||
99 | /** | ||
100 | * Sets the filename where the error occurred. | ||
101 | * | ||
102 | * @param string $filename The filename | ||
103 | */ | ||
104 | public function setTemplateFile($filename) | ||
105 | { | ||
106 | $this->filename = $filename; | ||
107 | |||
108 | $this->updateRepr(); | ||
109 | } | ||
110 | |||
111 | /** | ||
112 | * Gets the template line where the error occurred. | ||
113 | * | ||
114 | * @return integer The template line | ||
115 | */ | ||
116 | public function getTemplateLine() | ||
117 | { | ||
118 | return $this->lineno; | ||
119 | } | ||
120 | |||
121 | /** | ||
122 | * Sets the template line where the error occurred. | ||
123 | * | ||
124 | * @param integer $lineno The template line | ||
125 | */ | ||
126 | public function setTemplateLine($lineno) | ||
127 | { | ||
128 | $this->lineno = $lineno; | ||
129 | |||
130 | $this->updateRepr(); | ||
131 | } | ||
132 | |||
133 | public function guess() | ||
134 | { | ||
135 | $this->guessTemplateInfo(); | ||
136 | $this->updateRepr(); | ||
137 | } | ||
138 | |||
139 | /** | ||
140 | * For PHP < 5.3.0, provides access to the getPrevious() method. | ||
141 | * | ||
142 | * @param string $method The method name | ||
143 | * @param array $arguments The parameters to be passed to the method | ||
144 | * | ||
145 | * @return Exception The previous exception or null | ||
146 | * | ||
147 | * @throws BadMethodCallException | ||
148 | */ | ||
149 | public function __call($method, $arguments) | ||
150 | { | ||
151 | if ('getprevious' == strtolower($method)) { | ||
152 | return $this->previous; | ||
153 | } | ||
154 | |||
155 | throw new BadMethodCallException(sprintf('Method "Twig_Error::%s()" does not exist.', $method)); | ||
156 | } | ||
157 | |||
158 | protected function updateRepr() | ||
159 | { | ||
160 | $this->message = $this->rawMessage; | ||
161 | |||
162 | $dot = false; | ||
163 | if ('.' === substr($this->message, -1)) { | ||
164 | $this->message = substr($this->message, 0, -1); | ||
165 | $dot = true; | ||
166 | } | ||
167 | |||
168 | if ($this->filename) { | ||
169 | if (is_string($this->filename) || (is_object($this->filename) && method_exists($this->filename, '__toString'))) { | ||
170 | $filename = sprintf('"%s"', $this->filename); | ||
171 | } else { | ||
172 | $filename = json_encode($this->filename); | ||
173 | } | ||
174 | $this->message .= sprintf(' in %s', $filename); | ||
175 | } | ||
176 | |||
177 | if ($this->lineno && $this->lineno >= 0) { | ||
178 | $this->message .= sprintf(' at line %d', $this->lineno); | ||
179 | } | ||
180 | |||
181 | if ($dot) { | ||
182 | $this->message .= '.'; | ||
183 | } | ||
184 | } | ||
185 | |||
186 | protected function guessTemplateInfo() | ||
187 | { | ||
188 | $template = null; | ||
189 | |||
190 | if (version_compare(phpversion(), '5.3.6', '>=')) { | ||
191 | $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS | DEBUG_BACKTRACE_PROVIDE_OBJECT); | ||
192 | } else { | ||
193 | $backtrace = debug_backtrace(); | ||
194 | } | ||
195 | |||
196 | foreach ($backtrace as $trace) { | ||
197 | if (isset($trace['object']) && $trace['object'] instanceof Twig_Template && 'Twig_Template' !== get_class($trace['object'])) { | ||
198 | if (null === $this->filename || $this->filename == $trace['object']->getTemplateName()) { | ||
199 | $template = $trace['object']; | ||
200 | } | ||
201 | } | ||
202 | } | ||
203 | |||
204 | // update template filename | ||
205 | if (null !== $template && null === $this->filename) { | ||
206 | $this->filename = $template->getTemplateName(); | ||
207 | } | ||
208 | |||
209 | if (null === $template || $this->lineno > -1) { | ||
210 | return; | ||
211 | } | ||
212 | |||
213 | $r = new ReflectionObject($template); | ||
214 | $file = $r->getFileName(); | ||
215 | |||
216 | $exceptions = array($e = $this); | ||
217 | while (($e instanceof self || method_exists($e, 'getPrevious')) && $e = $e->getPrevious()) { | ||
218 | $exceptions[] = $e; | ||
219 | } | ||
220 | |||
221 | while ($e = array_pop($exceptions)) { | ||
222 | $traces = $e->getTrace(); | ||
223 | while ($trace = array_shift($traces)) { | ||
224 | if (!isset($trace['file']) || !isset($trace['line']) || $file != $trace['file']) { | ||
225 | continue; | ||
226 | } | ||
227 | |||
228 | foreach ($template->getDebugInfo() as $codeLine => $templateLine) { | ||
229 | if ($codeLine <= $trace['line']) { | ||
230 | // update template line | ||
231 | $this->lineno = $templateLine; | ||
232 | |||
233 | return; | ||
234 | } | ||
235 | } | ||
236 | } | ||
237 | } | ||
238 | } | ||
239 | } | ||
diff --git a/inc/Twig/Error/Loader.php b/inc/Twig/Error/Loader.php new file mode 100644 index 00000000..68efb574 --- /dev/null +++ b/inc/Twig/Error/Loader.php | |||
@@ -0,0 +1,31 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 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 | * Exception thrown when an error occurs during template loading. | ||
14 | * | ||
15 | * Automatic template information guessing is always turned off as | ||
16 | * if a template cannot be loaded, there is nothing to guess. | ||
17 | * However, when a template is loaded from another one, then, we need | ||
18 | * to find the current context and this is automatically done by | ||
19 | * Twig_Template::displayWithErrorHandling(). | ||
20 | * | ||
21 | * This strategy makes Twig_Environment::resolveTemplate() much faster. | ||
22 | * | ||
23 | * @author Fabien Potencier <fabien@symfony.com> | ||
24 | */ | ||
25 | class Twig_Error_Loader extends Twig_Error | ||
26 | { | ||
27 | public function __construct($message, $lineno = -1, $filename = null, Exception $previous = null) | ||
28 | { | ||
29 | parent::__construct($message, false, false, $previous); | ||
30 | } | ||
31 | } | ||
diff --git a/inc/Twig/Error/Runtime.php b/inc/Twig/Error/Runtime.php new file mode 100644 index 00000000..8b6ceddb --- /dev/null +++ b/inc/Twig/Error/Runtime.php | |||
@@ -0,0 +1,20 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2009 Fabien Potencier | ||
7 | * (c) 2009 Armin Ronacher | ||
8 | * | ||
9 | * For the full copyright and license information, please view the LICENSE | ||
10 | * file that was distributed with this source code. | ||
11 | */ | ||
12 | |||
13 | /** | ||
14 | * Exception thrown when an error occurs at runtime. | ||
15 | * | ||
16 | * @author Fabien Potencier <fabien@symfony.com> | ||
17 | */ | ||
18 | class Twig_Error_Runtime extends Twig_Error | ||
19 | { | ||
20 | } | ||
diff --git a/inc/Twig/Error/Syntax.php b/inc/Twig/Error/Syntax.php new file mode 100644 index 00000000..0f5c5792 --- /dev/null +++ b/inc/Twig/Error/Syntax.php | |||
@@ -0,0 +1,20 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2009 Fabien Potencier | ||
7 | * (c) 2009 Armin Ronacher | ||
8 | * | ||
9 | * For the full copyright and license information, please view the LICENSE | ||
10 | * file that was distributed with this source code. | ||
11 | */ | ||
12 | |||
13 | /** | ||
14 | * Exception thrown when a syntax error occurs during lexing or parsing of a template. | ||
15 | * | ||
16 | * @author Fabien Potencier <fabien@symfony.com> | ||
17 | */ | ||
18 | class Twig_Error_Syntax extends Twig_Error | ||
19 | { | ||
20 | } | ||
diff --git a/inc/Twig/ExistsLoaderInterface.php b/inc/Twig/ExistsLoaderInterface.php new file mode 100644 index 00000000..ce434765 --- /dev/null +++ b/inc/Twig/ExistsLoaderInterface.php | |||
@@ -0,0 +1,28 @@ | |||
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 | * Adds an exists() method for loaders. | ||
14 | * | ||
15 | * @author Florin Patan <florinpatan@gmail.com> | ||
16 | * @deprecated since 1.12 (to be removed in 2.0) | ||
17 | */ | ||
18 | interface Twig_ExistsLoaderInterface | ||
19 | { | ||
20 | /** | ||
21 | * Check if we have the source code of a template, given its name. | ||
22 | * | ||
23 | * @param string $name The name of the template to check if we can load | ||
24 | * | ||
25 | * @return boolean If the template source code is handled by this loader or not | ||
26 | */ | ||
27 | public function exists($name); | ||
28 | } | ||
diff --git a/inc/Twig/ExpressionParser.php b/inc/Twig/ExpressionParser.php new file mode 100644 index 00000000..9cf19344 --- /dev/null +++ b/inc/Twig/ExpressionParser.php | |||
@@ -0,0 +1,600 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2009 Fabien Potencier | ||
7 | * (c) 2009 Armin Ronacher | ||
8 | * | ||
9 | * For the full copyright and license information, please view the LICENSE | ||
10 | * file that was distributed with this source code. | ||
11 | */ | ||
12 | |||
13 | /** | ||
14 | * Parses expressions. | ||
15 | * | ||
16 | * This parser implements a "Precedence climbing" algorithm. | ||
17 | * | ||
18 | * @see http://www.engr.mun.ca/~theo/Misc/exp_parsing.htm | ||
19 | * @see http://en.wikipedia.org/wiki/Operator-precedence_parser | ||
20 | * | ||
21 | * @author Fabien Potencier <fabien@symfony.com> | ||
22 | */ | ||
23 | class Twig_ExpressionParser | ||
24 | { | ||
25 | const OPERATOR_LEFT = 1; | ||
26 | const OPERATOR_RIGHT = 2; | ||
27 | |||
28 | protected $parser; | ||
29 | protected $unaryOperators; | ||
30 | protected $binaryOperators; | ||
31 | |||
32 | public function __construct(Twig_Parser $parser, array $unaryOperators, array $binaryOperators) | ||
33 | { | ||
34 | $this->parser = $parser; | ||
35 | $this->unaryOperators = $unaryOperators; | ||
36 | $this->binaryOperators = $binaryOperators; | ||
37 | } | ||
38 | |||
39 | public function parseExpression($precedence = 0) | ||
40 | { | ||
41 | $expr = $this->getPrimary(); | ||
42 | $token = $this->parser->getCurrentToken(); | ||
43 | while ($this->isBinary($token) && $this->binaryOperators[$token->getValue()]['precedence'] >= $precedence) { | ||
44 | $op = $this->binaryOperators[$token->getValue()]; | ||
45 | $this->parser->getStream()->next(); | ||
46 | |||
47 | if (isset($op['callable'])) { | ||
48 | $expr = call_user_func($op['callable'], $this->parser, $expr); | ||
49 | } else { | ||
50 | $expr1 = $this->parseExpression(self::OPERATOR_LEFT === $op['associativity'] ? $op['precedence'] + 1 : $op['precedence']); | ||
51 | $class = $op['class']; | ||
52 | $expr = new $class($expr, $expr1, $token->getLine()); | ||
53 | } | ||
54 | |||
55 | $token = $this->parser->getCurrentToken(); | ||
56 | } | ||
57 | |||
58 | if (0 === $precedence) { | ||
59 | return $this->parseConditionalExpression($expr); | ||
60 | } | ||
61 | |||
62 | return $expr; | ||
63 | } | ||
64 | |||
65 | protected function getPrimary() | ||
66 | { | ||
67 | $token = $this->parser->getCurrentToken(); | ||
68 | |||
69 | if ($this->isUnary($token)) { | ||
70 | $operator = $this->unaryOperators[$token->getValue()]; | ||
71 | $this->parser->getStream()->next(); | ||
72 | $expr = $this->parseExpression($operator['precedence']); | ||
73 | $class = $operator['class']; | ||
74 | |||
75 | return $this->parsePostfixExpression(new $class($expr, $token->getLine())); | ||
76 | } elseif ($token->test(Twig_Token::PUNCTUATION_TYPE, '(')) { | ||
77 | $this->parser->getStream()->next(); | ||
78 | $expr = $this->parseExpression(); | ||
79 | $this->parser->getStream()->expect(Twig_Token::PUNCTUATION_TYPE, ')', 'An opened parenthesis is not properly closed'); | ||
80 | |||
81 | return $this->parsePostfixExpression($expr); | ||
82 | } | ||
83 | |||
84 | return $this->parsePrimaryExpression(); | ||
85 | } | ||
86 | |||
87 | protected function parseConditionalExpression($expr) | ||
88 | { | ||
89 | while ($this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '?')) { | ||
90 | $this->parser->getStream()->next(); | ||
91 | if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, ':')) { | ||
92 | $expr2 = $this->parseExpression(); | ||
93 | if ($this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, ':')) { | ||
94 | $this->parser->getStream()->next(); | ||
95 | $expr3 = $this->parseExpression(); | ||
96 | } else { | ||
97 | $expr3 = new Twig_Node_Expression_Constant('', $this->parser->getCurrentToken()->getLine()); | ||
98 | } | ||
99 | } else { | ||
100 | $this->parser->getStream()->next(); | ||
101 | $expr2 = $expr; | ||
102 | $expr3 = $this->parseExpression(); | ||
103 | } | ||
104 | |||
105 | $expr = new Twig_Node_Expression_Conditional($expr, $expr2, $expr3, $this->parser->getCurrentToken()->getLine()); | ||
106 | } | ||
107 | |||
108 | return $expr; | ||
109 | } | ||
110 | |||
111 | protected function isUnary(Twig_Token $token) | ||
112 | { | ||
113 | return $token->test(Twig_Token::OPERATOR_TYPE) && isset($this->unaryOperators[$token->getValue()]); | ||
114 | } | ||
115 | |||
116 | protected function isBinary(Twig_Token $token) | ||
117 | { | ||
118 | return $token->test(Twig_Token::OPERATOR_TYPE) && isset($this->binaryOperators[$token->getValue()]); | ||
119 | } | ||
120 | |||
121 | public function parsePrimaryExpression() | ||
122 | { | ||
123 | $token = $this->parser->getCurrentToken(); | ||
124 | switch ($token->getType()) { | ||
125 | case Twig_Token::NAME_TYPE: | ||
126 | $this->parser->getStream()->next(); | ||
127 | switch ($token->getValue()) { | ||
128 | case 'true': | ||
129 | case 'TRUE': | ||
130 | $node = new Twig_Node_Expression_Constant(true, $token->getLine()); | ||
131 | break; | ||
132 | |||
133 | case 'false': | ||
134 | case 'FALSE': | ||
135 | $node = new Twig_Node_Expression_Constant(false, $token->getLine()); | ||
136 | break; | ||
137 | |||
138 | case 'none': | ||
139 | case 'NONE': | ||
140 | case 'null': | ||
141 | case 'NULL': | ||
142 | $node = new Twig_Node_Expression_Constant(null, $token->getLine()); | ||
143 | break; | ||
144 | |||
145 | default: | ||
146 | if ('(' === $this->parser->getCurrentToken()->getValue()) { | ||
147 | $node = $this->getFunctionNode($token->getValue(), $token->getLine()); | ||
148 | } else { | ||
149 | $node = new Twig_Node_Expression_Name($token->getValue(), $token->getLine()); | ||
150 | } | ||
151 | } | ||
152 | break; | ||
153 | |||
154 | case Twig_Token::NUMBER_TYPE: | ||
155 | $this->parser->getStream()->next(); | ||
156 | $node = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine()); | ||
157 | break; | ||
158 | |||
159 | case Twig_Token::STRING_TYPE: | ||
160 | case Twig_Token::INTERPOLATION_START_TYPE: | ||
161 | $node = $this->parseStringExpression(); | ||
162 | break; | ||
163 | |||
164 | default: | ||
165 | if ($token->test(Twig_Token::PUNCTUATION_TYPE, '[')) { | ||
166 | $node = $this->parseArrayExpression(); | ||
167 | } elseif ($token->test(Twig_Token::PUNCTUATION_TYPE, '{')) { | ||
168 | $node = $this->parseHashExpression(); | ||
169 | } else { | ||
170 | throw new Twig_Error_Syntax(sprintf('Unexpected token "%s" of value "%s"', Twig_Token::typeToEnglish($token->getType(), $token->getLine()), $token->getValue()), $token->getLine(), $this->parser->getFilename()); | ||
171 | } | ||
172 | } | ||
173 | |||
174 | return $this->parsePostfixExpression($node); | ||
175 | } | ||
176 | |||
177 | public function parseStringExpression() | ||
178 | { | ||
179 | $stream = $this->parser->getStream(); | ||
180 | |||
181 | $nodes = array(); | ||
182 | // a string cannot be followed by another string in a single expression | ||
183 | $nextCanBeString = true; | ||
184 | while (true) { | ||
185 | if ($stream->test(Twig_Token::STRING_TYPE) && $nextCanBeString) { | ||
186 | $token = $stream->next(); | ||
187 | $nodes[] = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine()); | ||
188 | $nextCanBeString = false; | ||
189 | } elseif ($stream->test(Twig_Token::INTERPOLATION_START_TYPE)) { | ||
190 | $stream->next(); | ||
191 | $nodes[] = $this->parseExpression(); | ||
192 | $stream->expect(Twig_Token::INTERPOLATION_END_TYPE); | ||
193 | $nextCanBeString = true; | ||
194 | } else { | ||
195 | break; | ||
196 | } | ||
197 | } | ||
198 | |||
199 | $expr = array_shift($nodes); | ||
200 | foreach ($nodes as $node) { | ||
201 | $expr = new Twig_Node_Expression_Binary_Concat($expr, $node, $node->getLine()); | ||
202 | } | ||
203 | |||
204 | return $expr; | ||
205 | } | ||
206 | |||
207 | public function parseArrayExpression() | ||
208 | { | ||
209 | $stream = $this->parser->getStream(); | ||
210 | $stream->expect(Twig_Token::PUNCTUATION_TYPE, '[', 'An array element was expected'); | ||
211 | |||
212 | $node = new Twig_Node_Expression_Array(array(), $stream->getCurrent()->getLine()); | ||
213 | $first = true; | ||
214 | while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, ']')) { | ||
215 | if (!$first) { | ||
216 | $stream->expect(Twig_Token::PUNCTUATION_TYPE, ',', 'An array element must be followed by a comma'); | ||
217 | |||
218 | // trailing ,? | ||
219 | if ($stream->test(Twig_Token::PUNCTUATION_TYPE, ']')) { | ||
220 | break; | ||
221 | } | ||
222 | } | ||
223 | $first = false; | ||
224 | |||
225 | $node->addElement($this->parseExpression()); | ||
226 | } | ||
227 | $stream->expect(Twig_Token::PUNCTUATION_TYPE, ']', 'An opened array is not properly closed'); | ||
228 | |||
229 | return $node; | ||
230 | } | ||
231 | |||
232 | public function parseHashExpression() | ||
233 | { | ||
234 | $stream = $this->parser->getStream(); | ||
235 | $stream->expect(Twig_Token::PUNCTUATION_TYPE, '{', 'A hash element was expected'); | ||
236 | |||
237 | $node = new Twig_Node_Expression_Array(array(), $stream->getCurrent()->getLine()); | ||
238 | $first = true; | ||
239 | while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, '}')) { | ||
240 | if (!$first) { | ||
241 | $stream->expect(Twig_Token::PUNCTUATION_TYPE, ',', 'A hash value must be followed by a comma'); | ||
242 | |||
243 | // trailing ,? | ||
244 | if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '}')) { | ||
245 | break; | ||
246 | } | ||
247 | } | ||
248 | $first = false; | ||
249 | |||
250 | // a hash key can be: | ||
251 | // | ||
252 | // * a number -- 12 | ||
253 | // * a string -- 'a' | ||
254 | // * a name, which is equivalent to a string -- a | ||
255 | // * an expression, which must be enclosed in parentheses -- (1 + 2) | ||
256 | if ($stream->test(Twig_Token::STRING_TYPE) || $stream->test(Twig_Token::NAME_TYPE) || $stream->test(Twig_Token::NUMBER_TYPE)) { | ||
257 | $token = $stream->next(); | ||
258 | $key = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine()); | ||
259 | } elseif ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) { | ||
260 | $key = $this->parseExpression(); | ||
261 | } else { | ||
262 | $current = $stream->getCurrent(); | ||
263 | |||
264 | throw new Twig_Error_Syntax(sprintf('A hash key must be a quoted string, a number, a name, or an expression enclosed in parentheses (unexpected token "%s" of value "%s"', Twig_Token::typeToEnglish($current->getType(), $current->getLine()), $current->getValue()), $current->getLine(), $this->parser->getFilename()); | ||
265 | } | ||
266 | |||
267 | $stream->expect(Twig_Token::PUNCTUATION_TYPE, ':', 'A hash key must be followed by a colon (:)'); | ||
268 | $value = $this->parseExpression(); | ||
269 | |||
270 | $node->addElement($value, $key); | ||
271 | } | ||
272 | $stream->expect(Twig_Token::PUNCTUATION_TYPE, '}', 'An opened hash is not properly closed'); | ||
273 | |||
274 | return $node; | ||
275 | } | ||
276 | |||
277 | public function parsePostfixExpression($node) | ||
278 | { | ||
279 | while (true) { | ||
280 | $token = $this->parser->getCurrentToken(); | ||
281 | if ($token->getType() == Twig_Token::PUNCTUATION_TYPE) { | ||
282 | if ('.' == $token->getValue() || '[' == $token->getValue()) { | ||
283 | $node = $this->parseSubscriptExpression($node); | ||
284 | } elseif ('|' == $token->getValue()) { | ||
285 | $node = $this->parseFilterExpression($node); | ||
286 | } else { | ||
287 | break; | ||
288 | } | ||
289 | } else { | ||
290 | break; | ||
291 | } | ||
292 | } | ||
293 | |||
294 | return $node; | ||
295 | } | ||
296 | |||
297 | public function getFunctionNode($name, $line) | ||
298 | { | ||
299 | switch ($name) { | ||
300 | case 'parent': | ||
301 | $args = $this->parseArguments(); | ||
302 | if (!count($this->parser->getBlockStack())) { | ||
303 | throw new Twig_Error_Syntax('Calling "parent" outside a block is forbidden', $line, $this->parser->getFilename()); | ||
304 | } | ||
305 | |||
306 | if (!$this->parser->getParent() && !$this->parser->hasTraits()) { | ||
307 | throw new Twig_Error_Syntax('Calling "parent" on a template that does not extend nor "use" another template is forbidden', $line, $this->parser->getFilename()); | ||
308 | } | ||
309 | |||
310 | return new Twig_Node_Expression_Parent($this->parser->peekBlockStack(), $line); | ||
311 | case 'block': | ||
312 | return new Twig_Node_Expression_BlockReference($this->parseArguments()->getNode(0), false, $line); | ||
313 | case 'attribute': | ||
314 | $args = $this->parseArguments(); | ||
315 | if (count($args) < 2) { | ||
316 | throw new Twig_Error_Syntax('The "attribute" function takes at least two arguments (the variable and the attributes)', $line, $this->parser->getFilename()); | ||
317 | } | ||
318 | |||
319 | return new Twig_Node_Expression_GetAttr($args->getNode(0), $args->getNode(1), count($args) > 2 ? $args->getNode(2) : new Twig_Node_Expression_Array(array(), $line), Twig_TemplateInterface::ANY_CALL, $line); | ||
320 | default: | ||
321 | if (null !== $alias = $this->parser->getImportedSymbol('function', $name)) { | ||
322 | $arguments = new Twig_Node_Expression_Array(array(), $line); | ||
323 | foreach ($this->parseArguments() as $n) { | ||
324 | $arguments->addElement($n); | ||
325 | } | ||
326 | |||
327 | $node = new Twig_Node_Expression_MethodCall($alias['node'], $alias['name'], $arguments, $line); | ||
328 | $node->setAttribute('safe', true); | ||
329 | |||
330 | return $node; | ||
331 | } | ||
332 | |||
333 | $args = $this->parseArguments(true); | ||
334 | $class = $this->getFunctionNodeClass($name, $line); | ||
335 | |||
336 | return new $class($name, $args, $line); | ||
337 | } | ||
338 | } | ||
339 | |||
340 | public function parseSubscriptExpression($node) | ||
341 | { | ||
342 | $stream = $this->parser->getStream(); | ||
343 | $token = $stream->next(); | ||
344 | $lineno = $token->getLine(); | ||
345 | $arguments = new Twig_Node_Expression_Array(array(), $lineno); | ||
346 | $type = Twig_TemplateInterface::ANY_CALL; | ||
347 | if ($token->getValue() == '.') { | ||
348 | $token = $stream->next(); | ||
349 | if ( | ||
350 | $token->getType() == Twig_Token::NAME_TYPE | ||
351 | || | ||
352 | $token->getType() == Twig_Token::NUMBER_TYPE | ||
353 | || | ||
354 | ($token->getType() == Twig_Token::OPERATOR_TYPE && preg_match(Twig_Lexer::REGEX_NAME, $token->getValue())) | ||
355 | ) { | ||
356 | $arg = new Twig_Node_Expression_Constant($token->getValue(), $lineno); | ||
357 | |||
358 | if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) { | ||
359 | $type = Twig_TemplateInterface::METHOD_CALL; | ||
360 | foreach ($this->parseArguments() as $n) { | ||
361 | $arguments->addElement($n); | ||
362 | } | ||
363 | } | ||
364 | } else { | ||
365 | throw new Twig_Error_Syntax('Expected name or number', $lineno, $this->parser->getFilename()); | ||
366 | } | ||
367 | |||
368 | if ($node instanceof Twig_Node_Expression_Name && null !== $this->parser->getImportedSymbol('template', $node->getAttribute('name'))) { | ||
369 | if (!$arg instanceof Twig_Node_Expression_Constant) { | ||
370 | throw new Twig_Error_Syntax(sprintf('Dynamic macro names are not supported (called on "%s")', $node->getAttribute('name')), $token->getLine(), $this->parser->getFilename()); | ||
371 | } | ||
372 | |||
373 | $node = new Twig_Node_Expression_MethodCall($node, 'get'.$arg->getAttribute('value'), $arguments, $lineno); | ||
374 | $node->setAttribute('safe', true); | ||
375 | |||
376 | return $node; | ||
377 | } | ||
378 | } else { | ||
379 | $type = Twig_TemplateInterface::ARRAY_CALL; | ||
380 | |||
381 | // slice? | ||
382 | $slice = false; | ||
383 | if ($stream->test(Twig_Token::PUNCTUATION_TYPE, ':')) { | ||
384 | $slice = true; | ||
385 | $arg = new Twig_Node_Expression_Constant(0, $token->getLine()); | ||
386 | } else { | ||
387 | $arg = $this->parseExpression(); | ||
388 | } | ||
389 | |||
390 | if ($stream->test(Twig_Token::PUNCTUATION_TYPE, ':')) { | ||
391 | $slice = true; | ||
392 | $stream->next(); | ||
393 | } | ||
394 | |||
395 | if ($slice) { | ||
396 | if ($stream->test(Twig_Token::PUNCTUATION_TYPE, ']')) { | ||
397 | $length = new Twig_Node_Expression_Constant(null, $token->getLine()); | ||
398 | } else { | ||
399 | $length = $this->parseExpression(); | ||
400 | } | ||
401 | |||
402 | $class = $this->getFilterNodeClass('slice', $token->getLine()); | ||
403 | $arguments = new Twig_Node(array($arg, $length)); | ||
404 | $filter = new $class($node, new Twig_Node_Expression_Constant('slice', $token->getLine()), $arguments, $token->getLine()); | ||
405 | |||
406 | $stream->expect(Twig_Token::PUNCTUATION_TYPE, ']'); | ||
407 | |||
408 | return $filter; | ||
409 | } | ||
410 | |||
411 | $stream->expect(Twig_Token::PUNCTUATION_TYPE, ']'); | ||
412 | } | ||
413 | |||
414 | return new Twig_Node_Expression_GetAttr($node, $arg, $arguments, $type, $lineno); | ||
415 | } | ||
416 | |||
417 | public function parseFilterExpression($node) | ||
418 | { | ||
419 | $this->parser->getStream()->next(); | ||
420 | |||
421 | return $this->parseFilterExpressionRaw($node); | ||
422 | } | ||
423 | |||
424 | public function parseFilterExpressionRaw($node, $tag = null) | ||
425 | { | ||
426 | while (true) { | ||
427 | $token = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE); | ||
428 | |||
429 | $name = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine()); | ||
430 | if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '(')) { | ||
431 | $arguments = new Twig_Node(); | ||
432 | } else { | ||
433 | $arguments = $this->parseArguments(true); | ||
434 | } | ||
435 | |||
436 | $class = $this->getFilterNodeClass($name->getAttribute('value'), $token->getLine()); | ||
437 | |||
438 | $node = new $class($node, $name, $arguments, $token->getLine(), $tag); | ||
439 | |||
440 | if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '|')) { | ||
441 | break; | ||
442 | } | ||
443 | |||
444 | $this->parser->getStream()->next(); | ||
445 | } | ||
446 | |||
447 | return $node; | ||
448 | } | ||
449 | |||
450 | /** | ||
451 | * Parses arguments. | ||
452 | * | ||
453 | * @param Boolean $namedArguments Whether to allow named arguments or not | ||
454 | * @param Boolean $definition Whether we are parsing arguments for a function definition | ||
455 | */ | ||
456 | public function parseArguments($namedArguments = false, $definition = false) | ||
457 | { | ||
458 | $args = array(); | ||
459 | $stream = $this->parser->getStream(); | ||
460 | |||
461 | $stream->expect(Twig_Token::PUNCTUATION_TYPE, '(', 'A list of arguments must begin with an opening parenthesis'); | ||
462 | while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, ')')) { | ||
463 | if (!empty($args)) { | ||
464 | $stream->expect(Twig_Token::PUNCTUATION_TYPE, ',', 'Arguments must be separated by a comma'); | ||
465 | } | ||
466 | |||
467 | if ($definition) { | ||
468 | $token = $stream->expect(Twig_Token::NAME_TYPE, null, 'An argument must be a name'); | ||
469 | $value = new Twig_Node_Expression_Name($token->getValue(), $this->parser->getCurrentToken()->getLine()); | ||
470 | } else { | ||
471 | $value = $this->parseExpression(); | ||
472 | } | ||
473 | |||
474 | $name = null; | ||
475 | if ($namedArguments && $stream->test(Twig_Token::OPERATOR_TYPE, '=')) { | ||
476 | $token = $stream->next(); | ||
477 | if (!$value instanceof Twig_Node_Expression_Name) { | ||
478 | throw new Twig_Error_Syntax(sprintf('A parameter name must be a string, "%s" given', get_class($value)), $token->getLine(), $this->parser->getFilename()); | ||
479 | } | ||
480 | $name = $value->getAttribute('name'); | ||
481 | |||
482 | if ($definition) { | ||
483 | $value = $this->parsePrimaryExpression(); | ||
484 | |||
485 | if (!$this->checkConstantExpression($value)) { | ||
486 | throw new Twig_Error_Syntax(sprintf('A default value for an argument must be a constant (a boolean, a string, a number, or an array).'), $token->getLine(), $this->parser->getFilename()); | ||
487 | } | ||
488 | } else { | ||
489 | $value = $this->parseExpression(); | ||
490 | } | ||
491 | } | ||
492 | |||
493 | if ($definition) { | ||
494 | if (null === $name) { | ||
495 | $name = $value->getAttribute('name'); | ||
496 | $value = new Twig_Node_Expression_Constant(null, $this->parser->getCurrentToken()->getLine()); | ||
497 | } | ||
498 | $args[$name] = $value; | ||
499 | } else { | ||
500 | if (null === $name) { | ||
501 | $args[] = $value; | ||
502 | } else { | ||
503 | $args[$name] = $value; | ||
504 | } | ||
505 | } | ||
506 | } | ||
507 | $stream->expect(Twig_Token::PUNCTUATION_TYPE, ')', 'A list of arguments must be closed by a parenthesis'); | ||
508 | |||
509 | return new Twig_Node($args); | ||
510 | } | ||
511 | |||
512 | public function parseAssignmentExpression() | ||
513 | { | ||
514 | $targets = array(); | ||
515 | while (true) { | ||
516 | $token = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE, null, 'Only variables can be assigned to'); | ||
517 | if (in_array($token->getValue(), array('true', 'false', 'none'))) { | ||
518 | throw new Twig_Error_Syntax(sprintf('You cannot assign a value to "%s"', $token->getValue()), $token->getLine(), $this->parser->getFilename()); | ||
519 | } | ||
520 | $targets[] = new Twig_Node_Expression_AssignName($token->getValue(), $token->getLine()); | ||
521 | |||
522 | if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, ',')) { | ||
523 | break; | ||
524 | } | ||
525 | $this->parser->getStream()->next(); | ||
526 | } | ||
527 | |||
528 | return new Twig_Node($targets); | ||
529 | } | ||
530 | |||
531 | public function parseMultitargetExpression() | ||
532 | { | ||
533 | $targets = array(); | ||
534 | while (true) { | ||
535 | $targets[] = $this->parseExpression(); | ||
536 | if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, ',')) { | ||
537 | break; | ||
538 | } | ||
539 | $this->parser->getStream()->next(); | ||
540 | } | ||
541 | |||
542 | return new Twig_Node($targets); | ||
543 | } | ||
544 | |||
545 | protected function getFunctionNodeClass($name, $line) | ||
546 | { | ||
547 | $env = $this->parser->getEnvironment(); | ||
548 | |||
549 | if (false === $function = $env->getFunction($name)) { | ||
550 | $message = sprintf('The function "%s" does not exist', $name); | ||
551 | if ($alternatives = $env->computeAlternatives($name, array_keys($env->getFunctions()))) { | ||
552 | $message = sprintf('%s. Did you mean "%s"', $message, implode('", "', $alternatives)); | ||
553 | } | ||
554 | |||
555 | throw new Twig_Error_Syntax($message, $line, $this->parser->getFilename()); | ||
556 | } | ||
557 | |||
558 | if ($function instanceof Twig_SimpleFunction) { | ||
559 | return $function->getNodeClass(); | ||
560 | } | ||
561 | |||
562 | return $function instanceof Twig_Function_Node ? $function->getClass() : 'Twig_Node_Expression_Function'; | ||
563 | } | ||
564 | |||
565 | protected function getFilterNodeClass($name, $line) | ||
566 | { | ||
567 | $env = $this->parser->getEnvironment(); | ||
568 | |||
569 | if (false === $filter = $env->getFilter($name)) { | ||
570 | $message = sprintf('The filter "%s" does not exist', $name); | ||
571 | if ($alternatives = $env->computeAlternatives($name, array_keys($env->getFilters()))) { | ||
572 | $message = sprintf('%s. Did you mean "%s"', $message, implode('", "', $alternatives)); | ||
573 | } | ||
574 | |||
575 | throw new Twig_Error_Syntax($message, $line, $this->parser->getFilename()); | ||
576 | } | ||
577 | |||
578 | if ($filter instanceof Twig_SimpleFilter) { | ||
579 | return $filter->getNodeClass(); | ||
580 | } | ||
581 | |||
582 | return $filter instanceof Twig_Filter_Node ? $filter->getClass() : 'Twig_Node_Expression_Filter'; | ||
583 | } | ||
584 | |||
585 | // checks that the node only contains "constant" elements | ||
586 | protected function checkConstantExpression(Twig_NodeInterface $node) | ||
587 | { | ||
588 | if (!($node instanceof Twig_Node_Expression_Constant || $node instanceof Twig_Node_Expression_Array)) { | ||
589 | return false; | ||
590 | } | ||
591 | |||
592 | foreach ($node as $n) { | ||
593 | if (!$this->checkConstantExpression($n)) { | ||
594 | return false; | ||
595 | } | ||
596 | } | ||
597 | |||
598 | return true; | ||
599 | } | ||
600 | } | ||
diff --git a/inc/Twig/Extension.php b/inc/Twig/Extension.php new file mode 100644 index 00000000..931fc033 --- /dev/null +++ b/inc/Twig/Extension.php | |||
@@ -0,0 +1,93 @@ | |||
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 | abstract class Twig_Extension implements Twig_ExtensionInterface | ||
12 | { | ||
13 | /** | ||
14 | * Initializes the runtime environment. | ||
15 | * | ||
16 | * This is where you can load some file that contains filter functions for instance. | ||
17 | * | ||
18 | * @param Twig_Environment $environment The current Twig_Environment instance | ||
19 | */ | ||
20 | public function initRuntime(Twig_Environment $environment) | ||
21 | { | ||
22 | } | ||
23 | |||
24 | /** | ||
25 | * Returns the token parser instances to add to the existing list. | ||
26 | * | ||
27 | * @return array An array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances | ||
28 | */ | ||
29 | public function getTokenParsers() | ||
30 | { | ||
31 | return array(); | ||
32 | } | ||
33 | |||
34 | /** | ||
35 | * Returns the node visitor instances to add to the existing list. | ||
36 | * | ||
37 | * @return array An array of Twig_NodeVisitorInterface instances | ||
38 | */ | ||
39 | public function getNodeVisitors() | ||
40 | { | ||
41 | return array(); | ||
42 | } | ||
43 | |||
44 | /** | ||
45 | * Returns a list of filters to add to the existing list. | ||
46 | * | ||
47 | * @return array An array of filters | ||
48 | */ | ||
49 | public function getFilters() | ||
50 | { | ||
51 | return array(); | ||
52 | } | ||
53 | |||
54 | /** | ||
55 | * Returns a list of tests to add to the existing list. | ||
56 | * | ||
57 | * @return array An array of tests | ||
58 | */ | ||
59 | public function getTests() | ||
60 | { | ||
61 | return array(); | ||
62 | } | ||
63 | |||
64 | /** | ||
65 | * Returns a list of functions to add to the existing list. | ||
66 | * | ||
67 | * @return array An array of functions | ||
68 | */ | ||
69 | public function getFunctions() | ||
70 | { | ||
71 | return array(); | ||
72 | } | ||
73 | |||
74 | /** | ||
75 | * Returns a list of operators to add to the existing list. | ||
76 | * | ||
77 | * @return array An array of operators | ||
78 | */ | ||
79 | public function getOperators() | ||
80 | { | ||
81 | return array(); | ||
82 | } | ||
83 | |||
84 | /** | ||
85 | * Returns a list of global variables to add to the existing list. | ||
86 | * | ||
87 | * @return array An array of global variables | ||
88 | */ | ||
89 | public function getGlobals() | ||
90 | { | ||
91 | return array(); | ||
92 | } | ||
93 | } | ||
diff --git a/inc/Twig/Extension/Core.php b/inc/Twig/Extension/Core.php new file mode 100644 index 00000000..e68687b4 --- /dev/null +++ b/inc/Twig/Extension/Core.php | |||
@@ -0,0 +1,1355 @@ | |||
1 | <?php | ||
2 | |||
3 | if (!defined('ENT_SUBSTITUTE')) { | ||
4 | define('ENT_SUBSTITUTE', 8); | ||
5 | } | ||
6 | |||
7 | /* | ||
8 | * This file is part of Twig. | ||
9 | * | ||
10 | * (c) 2009 Fabien Potencier | ||
11 | * | ||
12 | * For the full copyright and license information, please view the LICENSE | ||
13 | * file that was distributed with this source code. | ||
14 | */ | ||
15 | class Twig_Extension_Core extends Twig_Extension | ||
16 | { | ||
17 | protected $dateFormats = array('F j, Y H:i', '%d days'); | ||
18 | protected $numberFormat = array(0, '.', ','); | ||
19 | protected $timezone = null; | ||
20 | |||
21 | /** | ||
22 | * Sets the default format to be used by the date filter. | ||
23 | * | ||
24 | * @param string $format The default date format string | ||
25 | * @param string $dateIntervalFormat The default date interval format string | ||
26 | */ | ||
27 | public function setDateFormat($format = null, $dateIntervalFormat = null) | ||
28 | { | ||
29 | if (null !== $format) { | ||
30 | $this->dateFormats[0] = $format; | ||
31 | } | ||
32 | |||
33 | if (null !== $dateIntervalFormat) { | ||
34 | $this->dateFormats[1] = $dateIntervalFormat; | ||
35 | } | ||
36 | } | ||
37 | |||
38 | /** | ||
39 | * Gets the default format to be used by the date filter. | ||
40 | * | ||
41 | * @return array The default date format string and the default date interval format string | ||
42 | */ | ||
43 | public function getDateFormat() | ||
44 | { | ||
45 | return $this->dateFormats; | ||
46 | } | ||
47 | |||
48 | /** | ||
49 | * Sets the default timezone to be used by the date filter. | ||
50 | * | ||
51 | * @param DateTimeZone|string $timezone The default timezone string or a DateTimeZone object | ||
52 | */ | ||
53 | public function setTimezone($timezone) | ||
54 | { | ||
55 | $this->timezone = $timezone instanceof DateTimeZone ? $timezone : new DateTimeZone($timezone); | ||
56 | } | ||
57 | |||
58 | /** | ||
59 | * Gets the default timezone to be used by the date filter. | ||
60 | * | ||
61 | * @return DateTimeZone The default timezone currently in use | ||
62 | */ | ||
63 | public function getTimezone() | ||
64 | { | ||
65 | if (null === $this->timezone) { | ||
66 | $this->timezone = new DateTimeZone(date_default_timezone_get()); | ||
67 | } | ||
68 | |||
69 | return $this->timezone; | ||
70 | } | ||
71 | |||
72 | /** | ||
73 | * Sets the default format to be used by the number_format filter. | ||
74 | * | ||
75 | * @param integer $decimal The number of decimal places to use. | ||
76 | * @param string $decimalPoint The character(s) to use for the decimal point. | ||
77 | * @param string $thousandSep The character(s) to use for the thousands separator. | ||
78 | */ | ||
79 | public function setNumberFormat($decimal, $decimalPoint, $thousandSep) | ||
80 | { | ||
81 | $this->numberFormat = array($decimal, $decimalPoint, $thousandSep); | ||
82 | } | ||
83 | |||
84 | /** | ||
85 | * Get the default format used by the number_format filter. | ||
86 | * | ||
87 | * @return array The arguments for number_format() | ||
88 | */ | ||
89 | public function getNumberFormat() | ||
90 | { | ||
91 | return $this->numberFormat; | ||
92 | } | ||
93 | |||
94 | /** | ||
95 | * Returns the token parser instance to add to the existing list. | ||
96 | * | ||
97 | * @return array An array of Twig_TokenParser instances | ||
98 | */ | ||
99 | public function getTokenParsers() | ||
100 | { | ||
101 | return array( | ||
102 | new Twig_TokenParser_For(), | ||
103 | new Twig_TokenParser_If(), | ||
104 | new Twig_TokenParser_Extends(), | ||
105 | new Twig_TokenParser_Include(), | ||
106 | new Twig_TokenParser_Block(), | ||
107 | new Twig_TokenParser_Use(), | ||
108 | new Twig_TokenParser_Filter(), | ||
109 | new Twig_TokenParser_Macro(), | ||
110 | new Twig_TokenParser_Import(), | ||
111 | new Twig_TokenParser_From(), | ||
112 | new Twig_TokenParser_Set(), | ||
113 | new Twig_TokenParser_Spaceless(), | ||
114 | new Twig_TokenParser_Flush(), | ||
115 | new Twig_TokenParser_Do(), | ||
116 | new Twig_TokenParser_Embed(), | ||
117 | ); | ||
118 | } | ||
119 | |||
120 | /** | ||
121 | * Returns a list of filters to add to the existing list. | ||
122 | * | ||
123 | * @return array An array of filters | ||
124 | */ | ||
125 | public function getFilters() | ||
126 | { | ||
127 | $filters = array( | ||
128 | // formatting filters | ||
129 | new Twig_SimpleFilter('date', 'twig_date_format_filter', array('needs_environment' => true)), | ||
130 | new Twig_SimpleFilter('date_modify', 'twig_date_modify_filter', array('needs_environment' => true)), | ||
131 | new Twig_SimpleFilter('format', 'sprintf'), | ||
132 | new Twig_SimpleFilter('replace', 'strtr'), | ||
133 | new Twig_SimpleFilter('number_format', 'twig_number_format_filter', array('needs_environment' => true)), | ||
134 | new Twig_SimpleFilter('abs', 'abs'), | ||
135 | |||
136 | // encoding | ||
137 | new Twig_SimpleFilter('url_encode', 'twig_urlencode_filter'), | ||
138 | new Twig_SimpleFilter('json_encode', 'twig_jsonencode_filter'), | ||
139 | new Twig_SimpleFilter('convert_encoding', 'twig_convert_encoding'), | ||
140 | |||
141 | // string filters | ||
142 | new Twig_SimpleFilter('title', 'twig_title_string_filter', array('needs_environment' => true)), | ||
143 | new Twig_SimpleFilter('capitalize', 'twig_capitalize_string_filter', array('needs_environment' => true)), | ||
144 | new Twig_SimpleFilter('upper', 'strtoupper'), | ||
145 | new Twig_SimpleFilter('lower', 'strtolower'), | ||
146 | new Twig_SimpleFilter('striptags', 'strip_tags'), | ||
147 | new Twig_SimpleFilter('trim', 'trim'), | ||
148 | new Twig_SimpleFilter('nl2br', 'nl2br', array('pre_escape' => 'html', 'is_safe' => array('html'))), | ||
149 | |||
150 | // array helpers | ||
151 | new Twig_SimpleFilter('join', 'twig_join_filter'), | ||
152 | new Twig_SimpleFilter('split', 'twig_split_filter'), | ||
153 | new Twig_SimpleFilter('sort', 'twig_sort_filter'), | ||
154 | new Twig_SimpleFilter('merge', 'twig_array_merge'), | ||
155 | new Twig_SimpleFilter('batch', 'twig_array_batch'), | ||
156 | |||
157 | // string/array filters | ||
158 | new Twig_SimpleFilter('reverse', 'twig_reverse_filter', array('needs_environment' => true)), | ||
159 | new Twig_SimpleFilter('length', 'twig_length_filter', array('needs_environment' => true)), | ||
160 | new Twig_SimpleFilter('slice', 'twig_slice', array('needs_environment' => true)), | ||
161 | new Twig_SimpleFilter('first', 'twig_first', array('needs_environment' => true)), | ||
162 | new Twig_SimpleFilter('last', 'twig_last', array('needs_environment' => true)), | ||
163 | |||
164 | // iteration and runtime | ||
165 | new Twig_SimpleFilter('default', '_twig_default_filter', array('node_class' => 'Twig_Node_Expression_Filter_Default')), | ||
166 | new Twig_SimpleFilter('keys', 'twig_get_array_keys_filter'), | ||
167 | |||
168 | // escaping | ||
169 | new Twig_SimpleFilter('escape', 'twig_escape_filter', array('needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe')), | ||
170 | new Twig_SimpleFilter('e', 'twig_escape_filter', array('needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe')), | ||
171 | ); | ||
172 | |||
173 | if (function_exists('mb_get_info')) { | ||
174 | $filters[] = new Twig_SimpleFilter('upper', 'twig_upper_filter', array('needs_environment' => true)); | ||
175 | $filters[] = new Twig_SimpleFilter('lower', 'twig_lower_filter', array('needs_environment' => true)); | ||
176 | } | ||
177 | |||
178 | return $filters; | ||
179 | } | ||
180 | |||
181 | /** | ||
182 | * Returns a list of global functions to add to the existing list. | ||
183 | * | ||
184 | * @return array An array of global functions | ||
185 | */ | ||
186 | public function getFunctions() | ||
187 | { | ||
188 | return array( | ||
189 | new Twig_SimpleFunction('range', 'range'), | ||
190 | new Twig_SimpleFunction('constant', 'twig_constant'), | ||
191 | new Twig_SimpleFunction('cycle', 'twig_cycle'), | ||
192 | new Twig_SimpleFunction('random', 'twig_random', array('needs_environment' => true)), | ||
193 | new Twig_SimpleFunction('date', 'twig_date_converter', array('needs_environment' => true)), | ||
194 | new Twig_SimpleFunction('include', 'twig_include', array('needs_environment' => true, 'needs_context' => true, 'is_safe' => array('all'))), | ||
195 | ); | ||
196 | } | ||
197 | |||
198 | /** | ||
199 | * Returns a list of tests to add to the existing list. | ||
200 | * | ||
201 | * @return array An array of tests | ||
202 | */ | ||
203 | public function getTests() | ||
204 | { | ||
205 | return array( | ||
206 | new Twig_SimpleTest('even', null, array('node_class' => 'Twig_Node_Expression_Test_Even')), | ||
207 | new Twig_SimpleTest('odd', null, array('node_class' => 'Twig_Node_Expression_Test_Odd')), | ||
208 | new Twig_SimpleTest('defined', null, array('node_class' => 'Twig_Node_Expression_Test_Defined')), | ||
209 | new Twig_SimpleTest('sameas', null, array('node_class' => 'Twig_Node_Expression_Test_Sameas')), | ||
210 | new Twig_SimpleTest('none', null, array('node_class' => 'Twig_Node_Expression_Test_Null')), | ||
211 | new Twig_SimpleTest('null', null, array('node_class' => 'Twig_Node_Expression_Test_Null')), | ||
212 | new Twig_SimpleTest('divisibleby', null, array('node_class' => 'Twig_Node_Expression_Test_Divisibleby')), | ||
213 | new Twig_SimpleTest('constant', null, array('node_class' => 'Twig_Node_Expression_Test_Constant')), | ||
214 | new Twig_SimpleTest('empty', 'twig_test_empty'), | ||
215 | new Twig_SimpleTest('iterable', 'twig_test_iterable'), | ||
216 | ); | ||
217 | } | ||
218 | |||
219 | /** | ||
220 | * Returns a list of operators to add to the existing list. | ||
221 | * | ||
222 | * @return array An array of operators | ||
223 | */ | ||
224 | public function getOperators() | ||
225 | { | ||
226 | return array( | ||
227 | array( | ||
228 | 'not' => array('precedence' => 50, 'class' => 'Twig_Node_Expression_Unary_Not'), | ||
229 | '-' => array('precedence' => 500, 'class' => 'Twig_Node_Expression_Unary_Neg'), | ||
230 | '+' => array('precedence' => 500, 'class' => 'Twig_Node_Expression_Unary_Pos'), | ||
231 | ), | ||
232 | array( | ||
233 | 'or' => array('precedence' => 10, 'class' => 'Twig_Node_Expression_Binary_Or', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), | ||
234 | 'and' => array('precedence' => 15, 'class' => 'Twig_Node_Expression_Binary_And', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), | ||
235 | 'b-or' => array('precedence' => 16, 'class' => 'Twig_Node_Expression_Binary_BitwiseOr', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), | ||
236 | 'b-xor' => array('precedence' => 17, 'class' => 'Twig_Node_Expression_Binary_BitwiseXor', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), | ||
237 | 'b-and' => array('precedence' => 18, 'class' => 'Twig_Node_Expression_Binary_BitwiseAnd', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), | ||
238 | '==' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Equal', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), | ||
239 | '!=' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_NotEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), | ||
240 | '<' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Less', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), | ||
241 | '>' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Greater', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), | ||
242 | '>=' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_GreaterEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), | ||
243 | '<=' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_LessEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), | ||
244 | 'not in' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_NotIn', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), | ||
245 | 'in' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_In', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), | ||
246 | '..' => array('precedence' => 25, 'class' => 'Twig_Node_Expression_Binary_Range', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), | ||
247 | '+' => array('precedence' => 30, 'class' => 'Twig_Node_Expression_Binary_Add', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), | ||
248 | '-' => array('precedence' => 30, 'class' => 'Twig_Node_Expression_Binary_Sub', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), | ||
249 | '~' => array('precedence' => 40, 'class' => 'Twig_Node_Expression_Binary_Concat', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), | ||
250 | '*' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Mul', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), | ||
251 | '/' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Div', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), | ||
252 | '//' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_FloorDiv', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), | ||
253 | '%' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Mod', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), | ||
254 | 'is' => array('precedence' => 100, 'callable' => array($this, 'parseTestExpression'), 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), | ||
255 | 'is not' => array('precedence' => 100, 'callable' => array($this, 'parseNotTestExpression'), 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), | ||
256 | '**' => array('precedence' => 200, 'class' => 'Twig_Node_Expression_Binary_Power', 'associativity' => Twig_ExpressionParser::OPERATOR_RIGHT), | ||
257 | ), | ||
258 | ); | ||
259 | } | ||
260 | |||
261 | public function parseNotTestExpression(Twig_Parser $parser, $node) | ||
262 | { | ||
263 | return new Twig_Node_Expression_Unary_Not($this->parseTestExpression($parser, $node), $parser->getCurrentToken()->getLine()); | ||
264 | } | ||
265 | |||
266 | public function parseTestExpression(Twig_Parser $parser, $node) | ||
267 | { | ||
268 | $stream = $parser->getStream(); | ||
269 | $name = $stream->expect(Twig_Token::NAME_TYPE)->getValue(); | ||
270 | $arguments = null; | ||
271 | if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) { | ||
272 | $arguments = $parser->getExpressionParser()->parseArguments(true); | ||
273 | } | ||
274 | |||
275 | $class = $this->getTestNodeClass($parser, $name, $node->getLine()); | ||
276 | |||
277 | return new $class($node, $name, $arguments, $parser->getCurrentToken()->getLine()); | ||
278 | } | ||
279 | |||
280 | protected function getTestNodeClass(Twig_Parser $parser, $name, $line) | ||
281 | { | ||
282 | $env = $parser->getEnvironment(); | ||
283 | $testMap = $env->getTests(); | ||
284 | if (!isset($testMap[$name])) { | ||
285 | $message = sprintf('The test "%s" does not exist', $name); | ||
286 | if ($alternatives = $env->computeAlternatives($name, array_keys($env->getTests()))) { | ||
287 | $message = sprintf('%s. Did you mean "%s"', $message, implode('", "', $alternatives)); | ||
288 | } | ||
289 | |||
290 | throw new Twig_Error_Syntax($message, $line, $parser->getFilename()); | ||
291 | } | ||
292 | |||
293 | if ($testMap[$name] instanceof Twig_SimpleTest) { | ||
294 | return $testMap[$name]->getNodeClass(); | ||
295 | } | ||
296 | |||
297 | return $testMap[$name] instanceof Twig_Test_Node ? $testMap[$name]->getClass() : 'Twig_Node_Expression_Test'; | ||
298 | } | ||
299 | |||
300 | /** | ||
301 | * Returns the name of the extension. | ||
302 | * | ||
303 | * @return string The extension name | ||
304 | */ | ||
305 | public function getName() | ||
306 | { | ||
307 | return 'core'; | ||
308 | } | ||
309 | } | ||
310 | |||
311 | /** | ||
312 | * Cycles over a value. | ||
313 | * | ||
314 | * @param ArrayAccess|array $values An array or an ArrayAccess instance | ||
315 | * @param integer $position The cycle position | ||
316 | * | ||
317 | * @return string The next value in the cycle | ||
318 | */ | ||
319 | function twig_cycle($values, $position) | ||
320 | { | ||
321 | if (!is_array($values) && !$values instanceof ArrayAccess) { | ||
322 | return $values; | ||
323 | } | ||
324 | |||
325 | return $values[$position % count($values)]; | ||
326 | } | ||
327 | |||
328 | /** | ||
329 | * Returns a random value depending on the supplied parameter type: | ||
330 | * - a random item from a Traversable or array | ||
331 | * - a random character from a string | ||
332 | * - a random integer between 0 and the integer parameter | ||
333 | * | ||
334 | * @param Twig_Environment $env A Twig_Environment instance | ||
335 | * @param Traversable|array|integer|string $values The values to pick a random item from | ||
336 | * | ||
337 | * @throws Twig_Error_Runtime When $values is an empty array (does not apply to an empty string which is returned as is). | ||
338 | * | ||
339 | * @return mixed A random value from the given sequence | ||
340 | */ | ||
341 | function twig_random(Twig_Environment $env, $values = null) | ||
342 | { | ||
343 | if (null === $values) { | ||
344 | return mt_rand(); | ||
345 | } | ||
346 | |||
347 | if (is_int($values) || is_float($values)) { | ||
348 | return $values < 0 ? mt_rand($values, 0) : mt_rand(0, $values); | ||
349 | } | ||
350 | |||
351 | if ($values instanceof Traversable) { | ||
352 | $values = iterator_to_array($values); | ||
353 | } elseif (is_string($values)) { | ||
354 | if ('' === $values) { | ||
355 | return ''; | ||
356 | } | ||
357 | if (null !== $charset = $env->getCharset()) { | ||
358 | if ('UTF-8' != $charset) { | ||
359 | $values = twig_convert_encoding($values, 'UTF-8', $charset); | ||
360 | } | ||
361 | |||
362 | // unicode version of str_split() | ||
363 | // split at all positions, but not after the start and not before the end | ||
364 | $values = preg_split('/(?<!^)(?!$)/u', $values); | ||
365 | |||
366 | if ('UTF-8' != $charset) { | ||
367 | foreach ($values as $i => $value) { | ||
368 | $values[$i] = twig_convert_encoding($value, $charset, 'UTF-8'); | ||
369 | } | ||
370 | } | ||
371 | } else { | ||
372 | return $values[mt_rand(0, strlen($values) - 1)]; | ||
373 | } | ||
374 | } | ||
375 | |||
376 | if (!is_array($values)) { | ||
377 | return $values; | ||
378 | } | ||
379 | |||
380 | if (0 === count($values)) { | ||
381 | throw new Twig_Error_Runtime('The random function cannot pick from an empty array.'); | ||
382 | } | ||
383 | |||
384 | return $values[array_rand($values, 1)]; | ||
385 | } | ||
386 | |||
387 | /** | ||
388 | * Converts a date to the given format. | ||
389 | * | ||
390 | * <pre> | ||
391 | * {{ post.published_at|date("m/d/Y") }} | ||
392 | * </pre> | ||
393 | * | ||
394 | * @param Twig_Environment $env A Twig_Environment instance | ||
395 | * @param DateTime|DateInterval|string $date A date | ||
396 | * @param string $format A format | ||
397 | * @param DateTimeZone|string $timezone A timezone | ||
398 | * | ||
399 | * @return string The formatted date | ||
400 | */ | ||
401 | function twig_date_format_filter(Twig_Environment $env, $date, $format = null, $timezone = null) | ||
402 | { | ||
403 | if (null === $format) { | ||
404 | $formats = $env->getExtension('core')->getDateFormat(); | ||
405 | $format = $date instanceof DateInterval ? $formats[1] : $formats[0]; | ||
406 | } | ||
407 | |||
408 | if ($date instanceof DateInterval) { | ||
409 | return $date->format($format); | ||
410 | } | ||
411 | |||
412 | return twig_date_converter($env, $date, $timezone)->format($format); | ||
413 | } | ||
414 | |||
415 | /** | ||
416 | * Returns a new date object modified | ||
417 | * | ||
418 | * <pre> | ||
419 | * {{ post.published_at|date_modify("-1day")|date("m/d/Y") }} | ||
420 | * </pre> | ||
421 | * | ||
422 | * @param Twig_Environment $env A Twig_Environment instance | ||
423 | * @param DateTime|string $date A date | ||
424 | * @param string $modifier A modifier string | ||
425 | * | ||
426 | * @return DateTime A new date object | ||
427 | */ | ||
428 | function twig_date_modify_filter(Twig_Environment $env, $date, $modifier) | ||
429 | { | ||
430 | $date = twig_date_converter($env, $date, false); | ||
431 | $date->modify($modifier); | ||
432 | |||
433 | return $date; | ||
434 | } | ||
435 | |||
436 | /** | ||
437 | * Converts an input to a DateTime instance. | ||
438 | * | ||
439 | * <pre> | ||
440 | * {% if date(user.created_at) < date('+2days') %} | ||
441 | * {# do something #} | ||
442 | * {% endif %} | ||
443 | * </pre> | ||
444 | * | ||
445 | * @param Twig_Environment $env A Twig_Environment instance | ||
446 | * @param DateTime|string $date A date | ||
447 | * @param DateTimeZone|string $timezone A timezone | ||
448 | * | ||
449 | * @return DateTime A DateTime instance | ||
450 | */ | ||
451 | function twig_date_converter(Twig_Environment $env, $date = null, $timezone = null) | ||
452 | { | ||
453 | // determine the timezone | ||
454 | if (!$timezone) { | ||
455 | $defaultTimezone = $env->getExtension('core')->getTimezone(); | ||
456 | } elseif (!$timezone instanceof DateTimeZone) { | ||
457 | $defaultTimezone = new DateTimeZone($timezone); | ||
458 | } else { | ||
459 | $defaultTimezone = $timezone; | ||
460 | } | ||
461 | |||
462 | if ($date instanceof DateTime) { | ||
463 | $date = clone $date; | ||
464 | if (false !== $timezone) { | ||
465 | $date->setTimezone($defaultTimezone); | ||
466 | } | ||
467 | |||
468 | return $date; | ||
469 | } | ||
470 | |||
471 | $asString = (string) $date; | ||
472 | if (ctype_digit($asString) || (!empty($asString) && '-' === $asString[0] && ctype_digit(substr($asString, 1)))) { | ||
473 | $date = '@'.$date; | ||
474 | } | ||
475 | |||
476 | $date = new DateTime($date, $defaultTimezone); | ||
477 | if (false !== $timezone) { | ||
478 | $date->setTimezone($defaultTimezone); | ||
479 | } | ||
480 | |||
481 | return $date; | ||
482 | } | ||
483 | |||
484 | /** | ||
485 | * Number format filter. | ||
486 | * | ||
487 | * All of the formatting options can be left null, in that case the defaults will | ||
488 | * be used. Supplying any of the parameters will override the defaults set in the | ||
489 | * environment object. | ||
490 | * | ||
491 | * @param Twig_Environment $env A Twig_Environment instance | ||
492 | * @param mixed $number A float/int/string of the number to format | ||
493 | * @param integer $decimal The number of decimal points to display. | ||
494 | * @param string $decimalPoint The character(s) to use for the decimal point. | ||
495 | * @param string $thousandSep The character(s) to use for the thousands separator. | ||
496 | * | ||
497 | * @return string The formatted number | ||
498 | */ | ||
499 | function twig_number_format_filter(Twig_Environment $env, $number, $decimal = null, $decimalPoint = null, $thousandSep = null) | ||
500 | { | ||
501 | $defaults = $env->getExtension('core')->getNumberFormat(); | ||
502 | if (null === $decimal) { | ||
503 | $decimal = $defaults[0]; | ||
504 | } | ||
505 | |||
506 | if (null === $decimalPoint) { | ||
507 | $decimalPoint = $defaults[1]; | ||
508 | } | ||
509 | |||
510 | if (null === $thousandSep) { | ||
511 | $thousandSep = $defaults[2]; | ||
512 | } | ||
513 | |||
514 | return number_format((float) $number, $decimal, $decimalPoint, $thousandSep); | ||
515 | } | ||
516 | |||
517 | /** | ||
518 | * URL encodes a string as a path segment or an array as a query string. | ||
519 | * | ||
520 | * @param string|array $url A URL or an array of query parameters | ||
521 | * @param bool $raw true to use rawurlencode() instead of urlencode | ||
522 | * | ||
523 | * @return string The URL encoded value | ||
524 | */ | ||
525 | function twig_urlencode_filter($url, $raw = false) | ||
526 | { | ||
527 | if (is_array($url)) { | ||
528 | return http_build_query($url, '', '&'); | ||
529 | } | ||
530 | |||
531 | if ($raw) { | ||
532 | return rawurlencode($url); | ||
533 | } | ||
534 | |||
535 | return urlencode($url); | ||
536 | } | ||
537 | |||
538 | if (version_compare(PHP_VERSION, '5.3.0', '<')) { | ||
539 | /** | ||
540 | * JSON encodes a variable. | ||
541 | * | ||
542 | * @param mixed $value The value to encode. | ||
543 | * @param integer $options Not used on PHP 5.2.x | ||
544 | * | ||
545 | * @return mixed The JSON encoded value | ||
546 | */ | ||
547 | function twig_jsonencode_filter($value, $options = 0) | ||
548 | { | ||
549 | if ($value instanceof Twig_Markup) { | ||
550 | $value = (string) $value; | ||
551 | } elseif (is_array($value)) { | ||
552 | array_walk_recursive($value, '_twig_markup2string'); | ||
553 | } | ||
554 | |||
555 | return json_encode($value); | ||
556 | } | ||
557 | } else { | ||
558 | /** | ||
559 | * JSON encodes a variable. | ||
560 | * | ||
561 | * @param mixed $value The value to encode. | ||
562 | * @param integer $options Bitmask consisting of JSON_HEX_QUOT, JSON_HEX_TAG, JSON_HEX_AMP, JSON_HEX_APOS, JSON_NUMERIC_CHECK, JSON_PRETTY_PRINT, JSON_UNESCAPED_SLASHES, JSON_FORCE_OBJECT | ||
563 | * | ||
564 | * @return mixed The JSON encoded value | ||
565 | */ | ||
566 | function twig_jsonencode_filter($value, $options = 0) | ||
567 | { | ||
568 | if ($value instanceof Twig_Markup) { | ||
569 | $value = (string) $value; | ||
570 | } elseif (is_array($value)) { | ||
571 | array_walk_recursive($value, '_twig_markup2string'); | ||
572 | } | ||
573 | |||
574 | return json_encode($value, $options); | ||
575 | } | ||
576 | } | ||
577 | |||
578 | function _twig_markup2string(&$value) | ||
579 | { | ||
580 | if ($value instanceof Twig_Markup) { | ||
581 | $value = (string) $value; | ||
582 | } | ||
583 | } | ||
584 | |||
585 | /** | ||
586 | * Merges an array with another one. | ||
587 | * | ||
588 | * <pre> | ||
589 | * {% set items = { 'apple': 'fruit', 'orange': 'fruit' } %} | ||
590 | * | ||
591 | * {% set items = items|merge({ 'peugeot': 'car' }) %} | ||
592 | * | ||
593 | * {# items now contains { 'apple': 'fruit', 'orange': 'fruit', 'peugeot': 'car' } #} | ||
594 | * </pre> | ||
595 | * | ||
596 | * @param array $arr1 An array | ||
597 | * @param array $arr2 An array | ||
598 | * | ||
599 | * @return array The merged array | ||
600 | */ | ||
601 | function twig_array_merge($arr1, $arr2) | ||
602 | { | ||
603 | if (!is_array($arr1) || !is_array($arr2)) { | ||
604 | throw new Twig_Error_Runtime('The merge filter only works with arrays or hashes.'); | ||
605 | } | ||
606 | |||
607 | return array_merge($arr1, $arr2); | ||
608 | } | ||
609 | |||
610 | /** | ||
611 | * Slices a variable. | ||
612 | * | ||
613 | * @param Twig_Environment $env A Twig_Environment instance | ||
614 | * @param mixed $item A variable | ||
615 | * @param integer $start Start of the slice | ||
616 | * @param integer $length Size of the slice | ||
617 | * @param Boolean $preserveKeys Whether to preserve key or not (when the input is an array) | ||
618 | * | ||
619 | * @return mixed The sliced variable | ||
620 | */ | ||
621 | function twig_slice(Twig_Environment $env, $item, $start, $length = null, $preserveKeys = false) | ||
622 | { | ||
623 | if ($item instanceof Traversable) { | ||
624 | $item = iterator_to_array($item, false); | ||
625 | } | ||
626 | |||
627 | if (is_array($item)) { | ||
628 | return array_slice($item, $start, $length, $preserveKeys); | ||
629 | } | ||
630 | |||
631 | $item = (string) $item; | ||
632 | |||
633 | if (function_exists('mb_get_info') && null !== $charset = $env->getCharset()) { | ||
634 | return mb_substr($item, $start, null === $length ? mb_strlen($item, $charset) - $start : $length, $charset); | ||
635 | } | ||
636 | |||
637 | return null === $length ? substr($item, $start) : substr($item, $start, $length); | ||
638 | } | ||
639 | |||
640 | /** | ||
641 | * Returns the first element of the item. | ||
642 | * | ||
643 | * @param Twig_Environment $env A Twig_Environment instance | ||
644 | * @param mixed $item A variable | ||
645 | * | ||
646 | * @return mixed The first element of the item | ||
647 | */ | ||
648 | function twig_first(Twig_Environment $env, $item) | ||
649 | { | ||
650 | $elements = twig_slice($env, $item, 0, 1, false); | ||
651 | |||
652 | return is_string($elements) ? $elements[0] : current($elements); | ||
653 | } | ||
654 | |||
655 | /** | ||
656 | * Returns the last element of the item. | ||
657 | * | ||
658 | * @param Twig_Environment $env A Twig_Environment instance | ||
659 | * @param mixed $item A variable | ||
660 | * | ||
661 | * @return mixed The last element of the item | ||
662 | */ | ||
663 | function twig_last(Twig_Environment $env, $item) | ||
664 | { | ||
665 | $elements = twig_slice($env, $item, -1, 1, false); | ||
666 | |||
667 | return is_string($elements) ? $elements[0] : current($elements); | ||
668 | } | ||
669 | |||
670 | /** | ||
671 | * Joins the values to a string. | ||
672 | * | ||
673 | * The separator between elements is an empty string per default, you can define it with the optional parameter. | ||
674 | * | ||
675 | * <pre> | ||
676 | * {{ [1, 2, 3]|join('|') }} | ||
677 | * {# returns 1|2|3 #} | ||
678 | * | ||
679 | * {{ [1, 2, 3]|join }} | ||
680 | * {# returns 123 #} | ||
681 | * </pre> | ||
682 | * | ||
683 | * @param array $value An array | ||
684 | * @param string $glue The separator | ||
685 | * | ||
686 | * @return string The concatenated string | ||
687 | */ | ||
688 | function twig_join_filter($value, $glue = '') | ||
689 | { | ||
690 | if ($value instanceof Traversable) { | ||
691 | $value = iterator_to_array($value, false); | ||
692 | } | ||
693 | |||
694 | return implode($glue, (array) $value); | ||
695 | } | ||
696 | |||
697 | /** | ||
698 | * Splits the string into an array. | ||
699 | * | ||
700 | * <pre> | ||
701 | * {{ "one,two,three"|split(',') }} | ||
702 | * {# returns [one, two, three] #} | ||
703 | * | ||
704 | * {{ "one,two,three,four,five"|split(',', 3) }} | ||
705 | * {# returns [one, two, "three,four,five"] #} | ||
706 | * | ||
707 | * {{ "123"|split('') }} | ||
708 | * {# returns [1, 2, 3] #} | ||
709 | * | ||
710 | * {{ "aabbcc"|split('', 2) }} | ||
711 | * {# returns [aa, bb, cc] #} | ||
712 | * </pre> | ||
713 | * | ||
714 | * @param string $value A string | ||
715 | * @param string $delimiter The delimiter | ||
716 | * @param integer $limit The limit | ||
717 | * | ||
718 | * @return array The split string as an array | ||
719 | */ | ||
720 | function twig_split_filter($value, $delimiter, $limit = null) | ||
721 | { | ||
722 | if (empty($delimiter)) { | ||
723 | return str_split($value, null === $limit ? 1 : $limit); | ||
724 | } | ||
725 | |||
726 | return null === $limit ? explode($delimiter, $value) : explode($delimiter, $value, $limit); | ||
727 | } | ||
728 | |||
729 | // The '_default' filter is used internally to avoid using the ternary operator | ||
730 | // which costs a lot for big contexts (before PHP 5.4). So, on average, | ||
731 | // a function call is cheaper. | ||
732 | function _twig_default_filter($value, $default = '') | ||
733 | { | ||
734 | if (twig_test_empty($value)) { | ||
735 | return $default; | ||
736 | } | ||
737 | |||
738 | return $value; | ||
739 | } | ||
740 | |||
741 | /** | ||
742 | * Returns the keys for the given array. | ||
743 | * | ||
744 | * It is useful when you want to iterate over the keys of an array: | ||
745 | * | ||
746 | * <pre> | ||
747 | * {% for key in array|keys %} | ||
748 | * {# ... #} | ||
749 | * {% endfor %} | ||
750 | * </pre> | ||
751 | * | ||
752 | * @param array $array An array | ||
753 | * | ||
754 | * @return array The keys | ||
755 | */ | ||
756 | function twig_get_array_keys_filter($array) | ||
757 | { | ||
758 | if (is_object($array) && $array instanceof Traversable) { | ||
759 | return array_keys(iterator_to_array($array)); | ||
760 | } | ||
761 | |||
762 | if (!is_array($array)) { | ||
763 | return array(); | ||
764 | } | ||
765 | |||
766 | return array_keys($array); | ||
767 | } | ||
768 | |||
769 | /** | ||
770 | * Reverses a variable. | ||
771 | * | ||
772 | * @param Twig_Environment $env A Twig_Environment instance | ||
773 | * @param array|Traversable|string $item An array, a Traversable instance, or a string | ||
774 | * @param Boolean $preserveKeys Whether to preserve key or not | ||
775 | * | ||
776 | * @return mixed The reversed input | ||
777 | */ | ||
778 | function twig_reverse_filter(Twig_Environment $env, $item, $preserveKeys = false) | ||
779 | { | ||
780 | if (is_object($item) && $item instanceof Traversable) { | ||
781 | return array_reverse(iterator_to_array($item), $preserveKeys); | ||
782 | } | ||
783 | |||
784 | if (is_array($item)) { | ||
785 | return array_reverse($item, $preserveKeys); | ||
786 | } | ||
787 | |||
788 | if (null !== $charset = $env->getCharset()) { | ||
789 | $string = (string) $item; | ||
790 | |||
791 | if ('UTF-8' != $charset) { | ||
792 | $item = twig_convert_encoding($string, 'UTF-8', $charset); | ||
793 | } | ||
794 | |||
795 | preg_match_all('/./us', $item, $matches); | ||
796 | |||
797 | $string = implode('', array_reverse($matches[0])); | ||
798 | |||
799 | if ('UTF-8' != $charset) { | ||
800 | $string = twig_convert_encoding($string, $charset, 'UTF-8'); | ||
801 | } | ||
802 | |||
803 | return $string; | ||
804 | } | ||
805 | |||
806 | return strrev((string) $item); | ||
807 | } | ||
808 | |||
809 | /** | ||
810 | * Sorts an array. | ||
811 | * | ||
812 | * @param array $array An array | ||
813 | */ | ||
814 | function twig_sort_filter($array) | ||
815 | { | ||
816 | asort($array); | ||
817 | |||
818 | return $array; | ||
819 | } | ||
820 | |||
821 | /* used internally */ | ||
822 | function twig_in_filter($value, $compare) | ||
823 | { | ||
824 | if (is_array($compare)) { | ||
825 | return in_array($value, $compare, is_object($value)); | ||
826 | } elseif (is_string($compare)) { | ||
827 | if (!strlen($value)) { | ||
828 | return empty($compare); | ||
829 | } | ||
830 | |||
831 | return false !== strpos($compare, (string) $value); | ||
832 | } elseif ($compare instanceof Traversable) { | ||
833 | return in_array($value, iterator_to_array($compare, false), is_object($value)); | ||
834 | } | ||
835 | |||
836 | return false; | ||
837 | } | ||
838 | |||
839 | /** | ||
840 | * Escapes a string. | ||
841 | * | ||
842 | * @param Twig_Environment $env A Twig_Environment instance | ||
843 | * @param string $string The value to be escaped | ||
844 | * @param string $strategy The escaping strategy | ||
845 | * @param string $charset The charset | ||
846 | * @param Boolean $autoescape Whether the function is called by the auto-escaping feature (true) or by the developer (false) | ||
847 | */ | ||
848 | function twig_escape_filter(Twig_Environment $env, $string, $strategy = 'html', $charset = null, $autoescape = false) | ||
849 | { | ||
850 | if ($autoescape && $string instanceof Twig_Markup) { | ||
851 | return $string; | ||
852 | } | ||
853 | |||
854 | if (!is_string($string)) { | ||
855 | if (is_object($string) && method_exists($string, '__toString')) { | ||
856 | $string = (string) $string; | ||
857 | } else { | ||
858 | return $string; | ||
859 | } | ||
860 | } | ||
861 | |||
862 | if (null === $charset) { | ||
863 | $charset = $env->getCharset(); | ||
864 | } | ||
865 | |||
866 | switch ($strategy) { | ||
867 | case 'html': | ||
868 | // see http://php.net/htmlspecialchars | ||
869 | |||
870 | // Using a static variable to avoid initializing the array | ||
871 | // each time the function is called. Moving the declaration on the | ||
872 | // top of the function slow downs other escaping strategies. | ||
873 | static $htmlspecialcharsCharsets = array( | ||
874 | 'ISO-8859-1' => true, 'ISO8859-1' => true, | ||
875 | 'ISO-8859-15' => true, 'ISO8859-15' => true, | ||
876 | 'utf-8' => true, 'UTF-8' => true, | ||
877 | 'CP866' => true, 'IBM866' => true, '866' => true, | ||
878 | 'CP1251' => true, 'WINDOWS-1251' => true, 'WIN-1251' => true, | ||
879 | '1251' => true, | ||
880 | 'CP1252' => true, 'WINDOWS-1252' => true, '1252' => true, | ||
881 | 'KOI8-R' => true, 'KOI8-RU' => true, 'KOI8R' => true, | ||
882 | 'BIG5' => true, '950' => true, | ||
883 | 'GB2312' => true, '936' => true, | ||
884 | 'BIG5-HKSCS' => true, | ||
885 | 'SHIFT_JIS' => true, 'SJIS' => true, '932' => true, | ||
886 | 'EUC-JP' => true, 'EUCJP' => true, | ||
887 | 'ISO8859-5' => true, 'ISO-8859-5' => true, 'MACROMAN' => true, | ||
888 | ); | ||
889 | |||
890 | if (isset($htmlspecialcharsCharsets[$charset])) { | ||
891 | return htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, $charset); | ||
892 | } | ||
893 | |||
894 | if (isset($htmlspecialcharsCharsets[strtoupper($charset)])) { | ||
895 | // cache the lowercase variant for future iterations | ||
896 | $htmlspecialcharsCharsets[$charset] = true; | ||
897 | |||
898 | return htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, $charset); | ||
899 | } | ||
900 | |||
901 | $string = twig_convert_encoding($string, 'UTF-8', $charset); | ||
902 | $string = htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); | ||
903 | |||
904 | return twig_convert_encoding($string, $charset, 'UTF-8'); | ||
905 | |||
906 | case 'js': | ||
907 | // escape all non-alphanumeric characters | ||
908 | // into their \xHH or \uHHHH representations | ||
909 | if ('UTF-8' != $charset) { | ||
910 | $string = twig_convert_encoding($string, 'UTF-8', $charset); | ||
911 | } | ||
912 | |||
913 | if (0 == strlen($string) ? false : (1 == preg_match('/^./su', $string) ? false : true)) { | ||
914 | throw new Twig_Error_Runtime('The string to escape is not a valid UTF-8 string.'); | ||
915 | } | ||
916 | |||
917 | $string = preg_replace_callback('#[^a-zA-Z0-9,\._]#Su', '_twig_escape_js_callback', $string); | ||
918 | |||
919 | if ('UTF-8' != $charset) { | ||
920 | $string = twig_convert_encoding($string, $charset, 'UTF-8'); | ||
921 | } | ||
922 | |||
923 | return $string; | ||
924 | |||
925 | case 'css': | ||
926 | if ('UTF-8' != $charset) { | ||
927 | $string = twig_convert_encoding($string, 'UTF-8', $charset); | ||
928 | } | ||
929 | |||
930 | if (0 == strlen($string) ? false : (1 == preg_match('/^./su', $string) ? false : true)) { | ||
931 | throw new Twig_Error_Runtime('The string to escape is not a valid UTF-8 string.'); | ||
932 | } | ||
933 | |||
934 | $string = preg_replace_callback('#[^a-zA-Z0-9]#Su', '_twig_escape_css_callback', $string); | ||
935 | |||
936 | if ('UTF-8' != $charset) { | ||
937 | $string = twig_convert_encoding($string, $charset, 'UTF-8'); | ||
938 | } | ||
939 | |||
940 | return $string; | ||
941 | |||
942 | case 'html_attr': | ||
943 | if ('UTF-8' != $charset) { | ||
944 | $string = twig_convert_encoding($string, 'UTF-8', $charset); | ||
945 | } | ||
946 | |||
947 | if (0 == strlen($string) ? false : (1 == preg_match('/^./su', $string) ? false : true)) { | ||
948 | throw new Twig_Error_Runtime('The string to escape is not a valid UTF-8 string.'); | ||
949 | } | ||
950 | |||
951 | $string = preg_replace_callback('#[^a-zA-Z0-9,\.\-_]#Su', '_twig_escape_html_attr_callback', $string); | ||
952 | |||
953 | if ('UTF-8' != $charset) { | ||
954 | $string = twig_convert_encoding($string, $charset, 'UTF-8'); | ||
955 | } | ||
956 | |||
957 | return $string; | ||
958 | |||
959 | case 'url': | ||
960 | // hackish test to avoid version_compare that is much slower, this works unless PHP releases a 5.10.* | ||
961 | // at that point however PHP 5.2.* support can be removed | ||
962 | if (PHP_VERSION < '5.3.0') { | ||
963 | return str_replace('%7E', '~', rawurlencode($string)); | ||
964 | } | ||
965 | |||
966 | return rawurlencode($string); | ||
967 | |||
968 | default: | ||
969 | throw new Twig_Error_Runtime(sprintf('Invalid escaping strategy "%s" (valid ones: html, js, url, css, and html_attr).', $strategy)); | ||
970 | } | ||
971 | } | ||
972 | |||
973 | /* used internally */ | ||
974 | function twig_escape_filter_is_safe(Twig_Node $filterArgs) | ||
975 | { | ||
976 | foreach ($filterArgs as $arg) { | ||
977 | if ($arg instanceof Twig_Node_Expression_Constant) { | ||
978 | return array($arg->getAttribute('value')); | ||
979 | } | ||
980 | |||
981 | return array(); | ||
982 | } | ||
983 | |||
984 | return array('html'); | ||
985 | } | ||
986 | |||
987 | if (function_exists('mb_convert_encoding')) { | ||
988 | function twig_convert_encoding($string, $to, $from) | ||
989 | { | ||
990 | return mb_convert_encoding($string, $to, $from); | ||
991 | } | ||
992 | } elseif (function_exists('iconv')) { | ||
993 | function twig_convert_encoding($string, $to, $from) | ||
994 | { | ||
995 | return iconv($from, $to, $string); | ||
996 | } | ||
997 | } else { | ||
998 | function twig_convert_encoding($string, $to, $from) | ||
999 | { | ||
1000 | throw new Twig_Error_Runtime('No suitable convert encoding function (use UTF-8 as your encoding or install the iconv or mbstring extension).'); | ||
1001 | } | ||
1002 | } | ||
1003 | |||
1004 | function _twig_escape_js_callback($matches) | ||
1005 | { | ||
1006 | $char = $matches[0]; | ||
1007 | |||
1008 | // \xHH | ||
1009 | if (!isset($char[1])) { | ||
1010 | return '\\x'.strtoupper(substr('00'.bin2hex($char), -2)); | ||
1011 | } | ||
1012 | |||
1013 | // \uHHHH | ||
1014 | $char = twig_convert_encoding($char, 'UTF-16BE', 'UTF-8'); | ||
1015 | |||
1016 | return '\\u'.strtoupper(substr('0000'.bin2hex($char), -4)); | ||
1017 | } | ||
1018 | |||
1019 | function _twig_escape_css_callback($matches) | ||
1020 | { | ||
1021 | $char = $matches[0]; | ||
1022 | |||
1023 | // \xHH | ||
1024 | if (!isset($char[1])) { | ||
1025 | $hex = ltrim(strtoupper(bin2hex($char)), '0'); | ||
1026 | if (0 === strlen($hex)) { | ||
1027 | $hex = '0'; | ||
1028 | } | ||
1029 | |||
1030 | return '\\'.$hex.' '; | ||
1031 | } | ||
1032 | |||
1033 | // \uHHHH | ||
1034 | $char = twig_convert_encoding($char, 'UTF-16BE', 'UTF-8'); | ||
1035 | |||
1036 | return '\\'.ltrim(strtoupper(bin2hex($char)), '0').' '; | ||
1037 | } | ||
1038 | |||
1039 | /** | ||
1040 | * This function is adapted from code coming from Zend Framework. | ||
1041 | * | ||
1042 | * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) | ||
1043 | * @license http://framework.zend.com/license/new-bsd New BSD License | ||
1044 | */ | ||
1045 | function _twig_escape_html_attr_callback($matches) | ||
1046 | { | ||
1047 | /* | ||
1048 | * While HTML supports far more named entities, the lowest common denominator | ||
1049 | * has become HTML5's XML Serialisation which is restricted to the those named | ||
1050 | * entities that XML supports. Using HTML entities would result in this error: | ||
1051 | * XML Parsing Error: undefined entity | ||
1052 | */ | ||
1053 | static $entityMap = array( | ||
1054 | 34 => 'quot', /* quotation mark */ | ||
1055 | 38 => 'amp', /* ampersand */ | ||
1056 | 60 => 'lt', /* less-than sign */ | ||
1057 | 62 => 'gt', /* greater-than sign */ | ||
1058 | ); | ||
1059 | |||
1060 | $chr = $matches[0]; | ||
1061 | $ord = ord($chr); | ||
1062 | |||
1063 | /** | ||
1064 | * The following replaces characters undefined in HTML with the | ||
1065 | * hex entity for the Unicode replacement character. | ||
1066 | */ | ||
1067 | if (($ord <= 0x1f && $chr != "\t" && $chr != "\n" && $chr != "\r") || ($ord >= 0x7f && $ord <= 0x9f)) { | ||
1068 | return '�'; | ||
1069 | } | ||
1070 | |||
1071 | /** | ||
1072 | * Check if the current character to escape has a name entity we should | ||
1073 | * replace it with while grabbing the hex value of the character. | ||
1074 | */ | ||
1075 | if (strlen($chr) == 1) { | ||
1076 | $hex = strtoupper(substr('00'.bin2hex($chr), -2)); | ||
1077 | } else { | ||
1078 | $chr = twig_convert_encoding($chr, 'UTF-16BE', 'UTF-8'); | ||
1079 | $hex = strtoupper(substr('0000'.bin2hex($chr), -4)); | ||
1080 | } | ||
1081 | |||
1082 | $int = hexdec($hex); | ||
1083 | if (array_key_exists($int, $entityMap)) { | ||
1084 | return sprintf('&%s;', $entityMap[$int]); | ||
1085 | } | ||
1086 | |||
1087 | /** | ||
1088 | * Per OWASP recommendations, we'll use hex entities for any other | ||
1089 | * characters where a named entity does not exist. | ||
1090 | */ | ||
1091 | |||
1092 | return sprintf('&#x%s;', $hex); | ||
1093 | } | ||
1094 | |||
1095 | // add multibyte extensions if possible | ||
1096 | if (function_exists('mb_get_info')) { | ||
1097 | /** | ||
1098 | * Returns the length of a variable. | ||
1099 | * | ||
1100 | * @param Twig_Environment $env A Twig_Environment instance | ||
1101 | * @param mixed $thing A variable | ||
1102 | * | ||
1103 | * @return integer The length of the value | ||
1104 | */ | ||
1105 | function twig_length_filter(Twig_Environment $env, $thing) | ||
1106 | { | ||
1107 | return is_scalar($thing) ? mb_strlen($thing, $env->getCharset()) : count($thing); | ||
1108 | } | ||
1109 | |||
1110 | /** | ||
1111 | * Converts a string to uppercase. | ||
1112 | * | ||
1113 | * @param Twig_Environment $env A Twig_Environment instance | ||
1114 | * @param string $string A string | ||
1115 | * | ||
1116 | * @return string The uppercased string | ||
1117 | */ | ||
1118 | function twig_upper_filter(Twig_Environment $env, $string) | ||
1119 | { | ||
1120 | if (null !== ($charset = $env->getCharset())) { | ||
1121 | return mb_strtoupper($string, $charset); | ||
1122 | } | ||
1123 | |||
1124 | return strtoupper($string); | ||
1125 | } | ||
1126 | |||
1127 | /** | ||
1128 | * Converts a string to lowercase. | ||
1129 | * | ||
1130 | * @param Twig_Environment $env A Twig_Environment instance | ||
1131 | * @param string $string A string | ||
1132 | * | ||
1133 | * @return string The lowercased string | ||
1134 | */ | ||
1135 | function twig_lower_filter(Twig_Environment $env, $string) | ||
1136 | { | ||
1137 | if (null !== ($charset = $env->getCharset())) { | ||
1138 | return mb_strtolower($string, $charset); | ||
1139 | } | ||
1140 | |||
1141 | return strtolower($string); | ||
1142 | } | ||
1143 | |||
1144 | /** | ||
1145 | * Returns a titlecased string. | ||
1146 | * | ||
1147 | * @param Twig_Environment $env A Twig_Environment instance | ||
1148 | * @param string $string A string | ||
1149 | * | ||
1150 | * @return string The titlecased string | ||
1151 | */ | ||
1152 | function twig_title_string_filter(Twig_Environment $env, $string) | ||
1153 | { | ||
1154 | if (null !== ($charset = $env->getCharset())) { | ||
1155 | return mb_convert_case($string, MB_CASE_TITLE, $charset); | ||
1156 | } | ||
1157 | |||
1158 | return ucwords(strtolower($string)); | ||
1159 | } | ||
1160 | |||
1161 | /** | ||
1162 | * Returns a capitalized string. | ||
1163 | * | ||
1164 | * @param Twig_Environment $env A Twig_Environment instance | ||
1165 | * @param string $string A string | ||
1166 | * | ||
1167 | * @return string The capitalized string | ||
1168 | */ | ||
1169 | function twig_capitalize_string_filter(Twig_Environment $env, $string) | ||
1170 | { | ||
1171 | if (null !== ($charset = $env->getCharset())) { | ||
1172 | return mb_strtoupper(mb_substr($string, 0, 1, $charset), $charset). | ||
1173 | mb_strtolower(mb_substr($string, 1, mb_strlen($string, $charset), $charset), $charset); | ||
1174 | } | ||
1175 | |||
1176 | return ucfirst(strtolower($string)); | ||
1177 | } | ||
1178 | } | ||
1179 | // and byte fallback | ||
1180 | else { | ||
1181 | /** | ||
1182 | * Returns the length of a variable. | ||
1183 | * | ||
1184 | * @param Twig_Environment $env A Twig_Environment instance | ||
1185 | * @param mixed $thing A variable | ||
1186 | * | ||
1187 | * @return integer The length of the value | ||
1188 | */ | ||
1189 | function twig_length_filter(Twig_Environment $env, $thing) | ||
1190 | { | ||
1191 | return is_scalar($thing) ? strlen($thing) : count($thing); | ||
1192 | } | ||
1193 | |||
1194 | /** | ||
1195 | * Returns a titlecased string. | ||
1196 | * | ||
1197 | * @param Twig_Environment $env A Twig_Environment instance | ||
1198 | * @param string $string A string | ||
1199 | * | ||
1200 | * @return string The titlecased string | ||
1201 | */ | ||
1202 | function twig_title_string_filter(Twig_Environment $env, $string) | ||
1203 | { | ||
1204 | return ucwords(strtolower($string)); | ||
1205 | } | ||
1206 | |||
1207 | /** | ||
1208 | * Returns a capitalized string. | ||
1209 | * | ||
1210 | * @param Twig_Environment $env A Twig_Environment instance | ||
1211 | * @param string $string A string | ||
1212 | * | ||
1213 | * @return string The capitalized string | ||
1214 | */ | ||
1215 | function twig_capitalize_string_filter(Twig_Environment $env, $string) | ||
1216 | { | ||
1217 | return ucfirst(strtolower($string)); | ||
1218 | } | ||
1219 | } | ||
1220 | |||
1221 | /* used internally */ | ||
1222 | function twig_ensure_traversable($seq) | ||
1223 | { | ||
1224 | if ($seq instanceof Traversable || is_array($seq)) { | ||
1225 | return $seq; | ||
1226 | } | ||
1227 | |||
1228 | return array(); | ||
1229 | } | ||
1230 | |||
1231 | /** | ||
1232 | * Checks if a variable is empty. | ||
1233 | * | ||
1234 | * <pre> | ||
1235 | * {# evaluates to true if the foo variable is null, false, or the empty string #} | ||
1236 | * {% if foo is empty %} | ||
1237 | * {# ... #} | ||
1238 | * {% endif %} | ||
1239 | * </pre> | ||
1240 | * | ||
1241 | * @param mixed $value A variable | ||
1242 | * | ||
1243 | * @return Boolean true if the value is empty, false otherwise | ||
1244 | */ | ||
1245 | function twig_test_empty($value) | ||
1246 | { | ||
1247 | if ($value instanceof Countable) { | ||
1248 | return 0 == count($value); | ||
1249 | } | ||
1250 | |||
1251 | return '' === $value || false === $value || null === $value || array() === $value; | ||
1252 | } | ||
1253 | |||
1254 | /** | ||
1255 | * Checks if a variable is traversable. | ||
1256 | * | ||
1257 | * <pre> | ||
1258 | * {# evaluates to true if the foo variable is an array or a traversable object #} | ||
1259 | * {% if foo is traversable %} | ||
1260 | * {# ... #} | ||
1261 | * {% endif %} | ||
1262 | * </pre> | ||
1263 | * | ||
1264 | * @param mixed $value A variable | ||
1265 | * | ||
1266 | * @return Boolean true if the value is traversable | ||
1267 | */ | ||
1268 | function twig_test_iterable($value) | ||
1269 | { | ||
1270 | return $value instanceof Traversable || is_array($value); | ||
1271 | } | ||
1272 | |||
1273 | /** | ||
1274 | * Renders a template. | ||
1275 | * | ||
1276 | * @param string $template The template to render | ||
1277 | * @param array $variables The variables to pass to the template | ||
1278 | * @param Boolean $with_context Whether to pass the current context variables or not | ||
1279 | * @param Boolean $ignore_missing Whether to ignore missing templates or not | ||
1280 | * @param Boolean $sandboxed Whether to sandbox the template or not | ||
1281 | * | ||
1282 | * @return string The rendered template | ||
1283 | */ | ||
1284 | function twig_include(Twig_Environment $env, $context, $template, $variables = array(), $withContext = true, $ignoreMissing = false, $sandboxed = false) | ||
1285 | { | ||
1286 | if ($withContext) { | ||
1287 | $variables = array_merge($context, $variables); | ||
1288 | } | ||
1289 | |||
1290 | if ($isSandboxed = $sandboxed && $env->hasExtension('sandbox')) { | ||
1291 | $sandbox = $env->getExtension('sandbox'); | ||
1292 | if (!$alreadySandboxed = $sandbox->isSandboxed()) { | ||
1293 | $sandbox->enableSandbox(); | ||
1294 | } | ||
1295 | } | ||
1296 | |||
1297 | try { | ||
1298 | return $env->resolveTemplate($template)->render($variables); | ||
1299 | } catch (Twig_Error_Loader $e) { | ||
1300 | if (!$ignoreMissing) { | ||
1301 | throw $e; | ||
1302 | } | ||
1303 | } | ||
1304 | |||
1305 | if ($isSandboxed && !$alreadySandboxed) { | ||
1306 | $sandbox->disableSandbox(); | ||
1307 | } | ||
1308 | } | ||
1309 | |||
1310 | /** | ||
1311 | * Provides the ability to get constants from instances as well as class/global constants. | ||
1312 | * | ||
1313 | * @param string $constant The name of the constant | ||
1314 | * @param null|object $object The object to get the constant from | ||
1315 | * | ||
1316 | * @return string | ||
1317 | */ | ||
1318 | function twig_constant($constant, $object = null) | ||
1319 | { | ||
1320 | if (null !== $object) { | ||
1321 | $constant = get_class($object).'::'.$constant; | ||
1322 | } | ||
1323 | |||
1324 | return constant($constant); | ||
1325 | } | ||
1326 | |||
1327 | /** | ||
1328 | * Batches item. | ||
1329 | * | ||
1330 | * @param array $items An array of items | ||
1331 | * @param integer $size The size of the batch | ||
1332 | * @param string $fill A string to fill missing items | ||
1333 | * | ||
1334 | * @return array | ||
1335 | */ | ||
1336 | function twig_array_batch($items, $size, $fill = null) | ||
1337 | { | ||
1338 | if ($items instanceof Traversable) { | ||
1339 | $items = iterator_to_array($items, false); | ||
1340 | } | ||
1341 | |||
1342 | $size = ceil($size); | ||
1343 | |||
1344 | $result = array_chunk($items, $size, true); | ||
1345 | |||
1346 | if (null !== $fill) { | ||
1347 | $last = count($result) - 1; | ||
1348 | $result[$last] = array_merge( | ||
1349 | $result[$last], | ||
1350 | array_fill(0, $size - count($result[$last]), $fill) | ||
1351 | ); | ||
1352 | } | ||
1353 | |||
1354 | return $result; | ||
1355 | } | ||
diff --git a/inc/Twig/Extension/Debug.php b/inc/Twig/Extension/Debug.php new file mode 100644 index 00000000..e3a85bfe --- /dev/null +++ b/inc/Twig/Extension/Debug.php | |||
@@ -0,0 +1,71 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2011 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 | class Twig_Extension_Debug extends Twig_Extension | ||
12 | { | ||
13 | /** | ||
14 | * Returns a list of global functions to add to the existing list. | ||
15 | * | ||
16 | * @return array An array of global functions | ||
17 | */ | ||
18 | public function getFunctions() | ||
19 | { | ||
20 | // dump is safe if var_dump is overridden by xdebug | ||
21 | $isDumpOutputHtmlSafe = extension_loaded('xdebug') | ||
22 | // false means that it was not set (and the default is on) or it explicitly enabled | ||
23 | && (false === ini_get('xdebug.overload_var_dump') || ini_get('xdebug.overload_var_dump')) | ||
24 | // false means that it was not set (and the default is on) or it explicitly enabled | ||
25 | // xdebug.overload_var_dump produces HTML only when html_errors is also enabled | ||
26 | && (false === ini_get('html_errors') || ini_get('html_errors')) | ||
27 | || 'cli' === php_sapi_name() | ||
28 | ; | ||
29 | |||
30 | return array( | ||
31 | new Twig_SimpleFunction('dump', 'twig_var_dump', array('is_safe' => $isDumpOutputHtmlSafe ? array('html') : array(), 'needs_context' => true, 'needs_environment' => true)), | ||
32 | ); | ||
33 | } | ||
34 | |||
35 | /** | ||
36 | * Returns the name of the extension. | ||
37 | * | ||
38 | * @return string The extension name | ||
39 | */ | ||
40 | public function getName() | ||
41 | { | ||
42 | return 'debug'; | ||
43 | } | ||
44 | } | ||
45 | |||
46 | function twig_var_dump(Twig_Environment $env, $context) | ||
47 | { | ||
48 | if (!$env->isDebug()) { | ||
49 | return; | ||
50 | } | ||
51 | |||
52 | ob_start(); | ||
53 | |||
54 | $count = func_num_args(); | ||
55 | if (2 === $count) { | ||
56 | $vars = array(); | ||
57 | foreach ($context as $key => $value) { | ||
58 | if (!$value instanceof Twig_Template) { | ||
59 | $vars[$key] = $value; | ||
60 | } | ||
61 | } | ||
62 | |||
63 | var_dump($vars); | ||
64 | } else { | ||
65 | for ($i = 2; $i < $count; $i++) { | ||
66 | var_dump(func_get_arg($i)); | ||
67 | } | ||
68 | } | ||
69 | |||
70 | return ob_get_clean(); | ||
71 | } | ||
diff --git a/inc/Twig/Extension/Escaper.php b/inc/Twig/Extension/Escaper.php new file mode 100644 index 00000000..c9a7f68e --- /dev/null +++ b/inc/Twig/Extension/Escaper.php | |||
@@ -0,0 +1,107 @@ | |||
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 | class Twig_Extension_Escaper extends Twig_Extension | ||
12 | { | ||
13 | protected $defaultStrategy; | ||
14 | |||
15 | public function __construct($defaultStrategy = 'html') | ||
16 | { | ||
17 | $this->setDefaultStrategy($defaultStrategy); | ||
18 | } | ||
19 | |||
20 | /** | ||
21 | * Returns the token parser instances to add to the existing list. | ||
22 | * | ||
23 | * @return array An array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances | ||
24 | */ | ||
25 | public function getTokenParsers() | ||
26 | { | ||
27 | return array(new Twig_TokenParser_AutoEscape()); | ||
28 | } | ||
29 | |||
30 | /** | ||
31 | * Returns the node visitor instances to add to the existing list. | ||
32 | * | ||
33 | * @return array An array of Twig_NodeVisitorInterface instances | ||
34 | */ | ||
35 | public function getNodeVisitors() | ||
36 | { | ||
37 | return array(new Twig_NodeVisitor_Escaper()); | ||
38 | } | ||
39 | |||
40 | /** | ||
41 | * Returns a list of filters to add to the existing list. | ||
42 | * | ||
43 | * @return array An array of filters | ||
44 | */ | ||
45 | public function getFilters() | ||
46 | { | ||
47 | return array( | ||
48 | new Twig_SimpleFilter('raw', 'twig_raw_filter', array('is_safe' => array('all'))), | ||
49 | ); | ||
50 | } | ||
51 | |||
52 | /** | ||
53 | * Sets the default strategy to use when not defined by the user. | ||
54 | * | ||
55 | * The strategy can be a valid PHP callback that takes the template | ||
56 | * "filename" as an argument and returns the strategy to use. | ||
57 | * | ||
58 | * @param mixed $defaultStrategy An escaping strategy | ||
59 | */ | ||
60 | public function setDefaultStrategy($defaultStrategy) | ||
61 | { | ||
62 | // for BC | ||
63 | if (true === $defaultStrategy) { | ||
64 | $defaultStrategy = 'html'; | ||
65 | } | ||
66 | |||
67 | $this->defaultStrategy = $defaultStrategy; | ||
68 | } | ||
69 | |||
70 | /** | ||
71 | * Gets the default strategy to use when not defined by the user. | ||
72 | * | ||
73 | * @param string $filename The template "filename" | ||
74 | * | ||
75 | * @return string The default strategy to use for the template | ||
76 | */ | ||
77 | public function getDefaultStrategy($filename) | ||
78 | { | ||
79 | // disable string callables to avoid calling a function named html or js, | ||
80 | // or any other upcoming escaping strategy | ||
81 | if (!is_string($this->defaultStrategy) && is_callable($this->defaultStrategy)) { | ||
82 | return call_user_func($this->defaultStrategy, $filename); | ||
83 | } | ||
84 | |||
85 | return $this->defaultStrategy; | ||
86 | } | ||
87 | |||
88 | /** | ||
89 | * Returns the name of the extension. | ||
90 | * | ||
91 | * @return string The extension name | ||
92 | */ | ||
93 | public function getName() | ||
94 | { | ||
95 | return 'escaper'; | ||
96 | } | ||
97 | } | ||
98 | |||
99 | /** | ||
100 | * Marks a variable as being safe. | ||
101 | * | ||
102 | * @param string $string A PHP variable | ||
103 | */ | ||
104 | function twig_raw_filter($string) | ||
105 | { | ||
106 | return $string; | ||
107 | } | ||
diff --git a/inc/Twig/Extension/Optimizer.php b/inc/Twig/Extension/Optimizer.php new file mode 100644 index 00000000..013fcb62 --- /dev/null +++ b/inc/Twig/Extension/Optimizer.php | |||
@@ -0,0 +1,35 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 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 | class Twig_Extension_Optimizer extends Twig_Extension | ||
12 | { | ||
13 | protected $optimizers; | ||
14 | |||
15 | public function __construct($optimizers = -1) | ||
16 | { | ||
17 | $this->optimizers = $optimizers; | ||
18 | } | ||
19 | |||
20 | /** | ||
21 | * {@inheritdoc} | ||
22 | */ | ||
23 | public function getNodeVisitors() | ||
24 | { | ||
25 | return array(new Twig_NodeVisitor_Optimizer($this->optimizers)); | ||
26 | } | ||
27 | |||
28 | /** | ||
29 | * {@inheritdoc} | ||
30 | */ | ||
31 | public function getName() | ||
32 | { | ||
33 | return 'optimizer'; | ||
34 | } | ||
35 | } | ||
diff --git a/inc/Twig/Extension/Sandbox.php b/inc/Twig/Extension/Sandbox.php new file mode 100644 index 00000000..bf76c11a --- /dev/null +++ b/inc/Twig/Extension/Sandbox.php | |||
@@ -0,0 +1,112 @@ | |||
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 | class Twig_Extension_Sandbox extends Twig_Extension | ||
12 | { | ||
13 | protected $sandboxedGlobally; | ||
14 | protected $sandboxed; | ||
15 | protected $policy; | ||
16 | |||
17 | public function __construct(Twig_Sandbox_SecurityPolicyInterface $policy, $sandboxed = false) | ||
18 | { | ||
19 | $this->policy = $policy; | ||
20 | $this->sandboxedGlobally = $sandboxed; | ||
21 | } | ||
22 | |||
23 | /** | ||
24 | * Returns the token parser instances to add to the existing list. | ||
25 | * | ||
26 | * @return array An array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances | ||
27 | */ | ||
28 | public function getTokenParsers() | ||
29 | { | ||
30 | return array(new Twig_TokenParser_Sandbox()); | ||
31 | } | ||
32 | |||
33 | /** | ||
34 | * Returns the node visitor instances to add to the existing list. | ||
35 | * | ||
36 | * @return array An array of Twig_NodeVisitorInterface instances | ||
37 | */ | ||
38 | public function getNodeVisitors() | ||
39 | { | ||
40 | return array(new Twig_NodeVisitor_Sandbox()); | ||
41 | } | ||
42 | |||
43 | public function enableSandbox() | ||
44 | { | ||
45 | $this->sandboxed = true; | ||
46 | } | ||
47 | |||
48 | public function disableSandbox() | ||
49 | { | ||
50 | $this->sandboxed = false; | ||
51 | } | ||
52 | |||
53 | public function isSandboxed() | ||
54 | { | ||
55 | return $this->sandboxedGlobally || $this->sandboxed; | ||
56 | } | ||
57 | |||
58 | public function isSandboxedGlobally() | ||
59 | { | ||
60 | return $this->sandboxedGlobally; | ||
61 | } | ||
62 | |||
63 | public function setSecurityPolicy(Twig_Sandbox_SecurityPolicyInterface $policy) | ||
64 | { | ||
65 | $this->policy = $policy; | ||
66 | } | ||
67 | |||
68 | public function getSecurityPolicy() | ||
69 | { | ||
70 | return $this->policy; | ||
71 | } | ||
72 | |||
73 | public function checkSecurity($tags, $filters, $functions) | ||
74 | { | ||
75 | if ($this->isSandboxed()) { | ||
76 | $this->policy->checkSecurity($tags, $filters, $functions); | ||
77 | } | ||
78 | } | ||
79 | |||
80 | public function checkMethodAllowed($obj, $method) | ||
81 | { | ||
82 | if ($this->isSandboxed()) { | ||
83 | $this->policy->checkMethodAllowed($obj, $method); | ||
84 | } | ||
85 | } | ||
86 | |||
87 | public function checkPropertyAllowed($obj, $method) | ||
88 | { | ||
89 | if ($this->isSandboxed()) { | ||
90 | $this->policy->checkPropertyAllowed($obj, $method); | ||
91 | } | ||
92 | } | ||
93 | |||
94 | public function ensureToStringAllowed($obj) | ||
95 | { | ||
96 | if (is_object($obj)) { | ||
97 | $this->policy->checkMethodAllowed($obj, '__toString'); | ||
98 | } | ||
99 | |||
100 | return $obj; | ||
101 | } | ||
102 | |||
103 | /** | ||
104 | * Returns the name of the extension. | ||
105 | * | ||
106 | * @return string The extension name | ||
107 | */ | ||
108 | public function getName() | ||
109 | { | ||
110 | return 'sandbox'; | ||
111 | } | ||
112 | } | ||
diff --git a/inc/Twig/Extension/Staging.php b/inc/Twig/Extension/Staging.php new file mode 100644 index 00000000..8ab0f459 --- /dev/null +++ b/inc/Twig/Extension/Staging.php | |||
@@ -0,0 +1,113 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2012 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 | * Internal class. | ||
14 | * | ||
15 | * This class is used by Twig_Environment as a staging area and must not be used directly. | ||
16 | * | ||
17 | * @author Fabien Potencier <fabien@symfony.com> | ||
18 | */ | ||
19 | class Twig_Extension_Staging extends Twig_Extension | ||
20 | { | ||
21 | protected $functions = array(); | ||
22 | protected $filters = array(); | ||
23 | protected $visitors = array(); | ||
24 | protected $tokenParsers = array(); | ||
25 | protected $globals = array(); | ||
26 | protected $tests = array(); | ||
27 | |||
28 | public function addFunction($name, $function) | ||
29 | { | ||
30 | $this->functions[$name] = $function; | ||
31 | } | ||
32 | |||
33 | /** | ||
34 | * {@inheritdoc} | ||
35 | */ | ||
36 | public function getFunctions() | ||
37 | { | ||
38 | return $this->functions; | ||
39 | } | ||
40 | |||
41 | public function addFilter($name, $filter) | ||
42 | { | ||
43 | $this->filters[$name] = $filter; | ||
44 | } | ||
45 | |||
46 | /** | ||
47 | * {@inheritdoc} | ||
48 | */ | ||
49 | public function getFilters() | ||
50 | { | ||
51 | return $this->filters; | ||
52 | } | ||
53 | |||
54 | public function addNodeVisitor(Twig_NodeVisitorInterface $visitor) | ||
55 | { | ||
56 | $this->visitors[] = $visitor; | ||
57 | } | ||
58 | |||
59 | /** | ||
60 | * {@inheritdoc} | ||
61 | */ | ||
62 | public function getNodeVisitors() | ||
63 | { | ||
64 | return $this->visitors; | ||
65 | } | ||
66 | |||
67 | public function addTokenParser(Twig_TokenParserInterface $parser) | ||
68 | { | ||
69 | $this->tokenParsers[] = $parser; | ||
70 | } | ||
71 | |||
72 | /** | ||
73 | * {@inheritdoc} | ||
74 | */ | ||
75 | public function getTokenParsers() | ||
76 | { | ||
77 | return $this->tokenParsers; | ||
78 | } | ||
79 | |||
80 | public function addGlobal($name, $value) | ||
81 | { | ||
82 | $this->globals[$name] = $value; | ||
83 | } | ||
84 | |||
85 | /** | ||
86 | * {@inheritdoc} | ||
87 | */ | ||
88 | public function getGlobals() | ||
89 | { | ||
90 | return $this->globals; | ||
91 | } | ||
92 | |||
93 | public function addTest($name, $test) | ||
94 | { | ||
95 | $this->tests[$name] = $test; | ||
96 | } | ||
97 | |||
98 | /** | ||
99 | * {@inheritdoc} | ||
100 | */ | ||
101 | public function getTests() | ||
102 | { | ||
103 | return $this->tests; | ||
104 | } | ||
105 | |||
106 | /** | ||
107 | * {@inheritdoc} | ||
108 | */ | ||
109 | public function getName() | ||
110 | { | ||
111 | return 'staging'; | ||
112 | } | ||
113 | } | ||
diff --git a/inc/Twig/Extension/StringLoader.php b/inc/Twig/Extension/StringLoader.php new file mode 100644 index 00000000..20f3f994 --- /dev/null +++ b/inc/Twig/Extension/StringLoader.php | |||
@@ -0,0 +1,64 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2012 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 | class Twig_Extension_StringLoader extends Twig_Extension | ||
12 | { | ||
13 | /** | ||
14 | * {@inheritdoc} | ||
15 | */ | ||
16 | public function getFunctions() | ||
17 | { | ||
18 | return array( | ||
19 | new Twig_SimpleFunction('template_from_string', 'twig_template_from_string', array('needs_environment' => true)), | ||
20 | ); | ||
21 | } | ||
22 | |||
23 | /** | ||
24 | * {@inheritdoc} | ||
25 | */ | ||
26 | public function getName() | ||
27 | { | ||
28 | return 'string_loader'; | ||
29 | } | ||
30 | } | ||
31 | |||
32 | /** | ||
33 | * Loads a template from a string. | ||
34 | * | ||
35 | * <pre> | ||
36 | * {{ include(template_from_string("Hello {{ name }}")) }} | ||
37 | * </pre> | ||
38 | * | ||
39 | * @param Twig_Environment $env A Twig_Environment instance | ||
40 | * @param string $template A template as a string | ||
41 | * | ||
42 | * @return Twig_Template A Twig_Template instance | ||
43 | */ | ||
44 | function twig_template_from_string(Twig_Environment $env, $template) | ||
45 | { | ||
46 | static $loader; | ||
47 | |||
48 | if (null === $loader) { | ||
49 | $loader = new Twig_Loader_String(); | ||
50 | } | ||
51 | |||
52 | $current = $env->getLoader(); | ||
53 | $env->setLoader($loader); | ||
54 | try { | ||
55 | $template = $env->loadTemplate($template); | ||
56 | } catch (Exception $e) { | ||
57 | $env->setLoader($current); | ||
58 | |||
59 | throw $e; | ||
60 | } | ||
61 | $env->setLoader($current); | ||
62 | |||
63 | return $template; | ||
64 | } | ||
diff --git a/inc/Twig/ExtensionInterface.php b/inc/Twig/ExtensionInterface.php new file mode 100644 index 00000000..f189e9d9 --- /dev/null +++ b/inc/Twig/ExtensionInterface.php | |||
@@ -0,0 +1,83 @@ | |||
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 | * Interface implemented by extension classes. | ||
14 | * | ||
15 | * @author Fabien Potencier <fabien@symfony.com> | ||
16 | */ | ||
17 | interface Twig_ExtensionInterface | ||
18 | { | ||
19 | /** | ||
20 | * Initializes the runtime environment. | ||
21 | * | ||
22 | * This is where you can load some file that contains filter functions for instance. | ||
23 | * | ||
24 | * @param Twig_Environment $environment The current Twig_Environment instance | ||
25 | */ | ||
26 | public function initRuntime(Twig_Environment $environment); | ||
27 | |||
28 | /** | ||
29 | * Returns the token parser instances to add to the existing list. | ||
30 | * | ||
31 | * @return array An array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances | ||
32 | */ | ||
33 | public function getTokenParsers(); | ||
34 | |||
35 | /** | ||
36 | * Returns the node visitor instances to add to the existing list. | ||
37 | * | ||
38 | * @return array An array of Twig_NodeVisitorInterface instances | ||
39 | */ | ||
40 | public function getNodeVisitors(); | ||
41 | |||
42 | /** | ||
43 | * Returns a list of filters to add to the existing list. | ||
44 | * | ||
45 | * @return array An array of filters | ||
46 | */ | ||
47 | public function getFilters(); | ||
48 | |||
49 | /** | ||
50 | * Returns a list of tests to add to the existing list. | ||
51 | * | ||
52 | * @return array An array of tests | ||
53 | */ | ||
54 | public function getTests(); | ||
55 | |||
56 | /** | ||
57 | * Returns a list of functions to add to the existing list. | ||
58 | * | ||
59 | * @return array An array of functions | ||
60 | */ | ||
61 | public function getFunctions(); | ||
62 | |||
63 | /** | ||
64 | * Returns a list of operators to add to the existing list. | ||
65 | * | ||
66 | * @return array An array of operators | ||
67 | */ | ||
68 | public function getOperators(); | ||
69 | |||
70 | /** | ||
71 | * Returns a list of global variables to add to the existing list. | ||
72 | * | ||
73 | * @return array An array of global variables | ||
74 | */ | ||
75 | public function getGlobals(); | ||
76 | |||
77 | /** | ||
78 | * Returns the name of the extension. | ||
79 | * | ||
80 | * @return string The extension name | ||
81 | */ | ||
82 | public function getName(); | ||
83 | } | ||
diff --git a/inc/Twig/Extensions/Autoloader.php b/inc/Twig/Extensions/Autoloader.php new file mode 100644 index 00000000..f23cced6 --- /dev/null +++ b/inc/Twig/Extensions/Autoloader.php | |||
@@ -0,0 +1,45 @@ | |||
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 | * Autoloads Twig Extensions classes. | ||
14 | * | ||
15 | * @package twig | ||
16 | * @author Fabien Potencier <fabien.potencier@symfony-project.com> | ||
17 | */ | ||
18 | class Twig_Extensions_Autoloader | ||
19 | { | ||
20 | /** | ||
21 | * Registers Twig_Extensions_Autoloader as an SPL autoloader. | ||
22 | */ | ||
23 | static public function register() | ||
24 | { | ||
25 | spl_autoload_register(array(new self, 'autoload')); | ||
26 | } | ||
27 | |||
28 | /** | ||
29 | * Handles autoloading of classes. | ||
30 | * | ||
31 | * @param string $class A class name. | ||
32 | * | ||
33 | * @return boolean Returns true if the class has been loaded | ||
34 | */ | ||
35 | static public function autoload($class) | ||
36 | { | ||
37 | if (0 !== strpos($class, 'Twig_Extensions')) { | ||
38 | return; | ||
39 | } | ||
40 | |||
41 | if (file_exists($file = dirname(__FILE__).'/../../'.str_replace('_', '/', $class).'.php')) { | ||
42 | require $file; | ||
43 | } | ||
44 | } | ||
45 | } | ||
diff --git a/inc/Twig/Extensions/Extension/Debug.php b/inc/Twig/Extensions/Extension/Debug.php new file mode 100644 index 00000000..8974ce20 --- /dev/null +++ b/inc/Twig/Extensions/Extension/Debug.php | |||
@@ -0,0 +1,34 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 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 | class Twig_Extensions_Extension_Debug extends Twig_Extension | ||
12 | { | ||
13 | /** | ||
14 | * Returns the token parser instance to add to the existing list. | ||
15 | * | ||
16 | * @return array An array of Twig_TokenParser instances | ||
17 | */ | ||
18 | public function getTokenParsers() | ||
19 | { | ||
20 | return array( | ||
21 | new Twig_Extensions_TokenParser_Debug(), | ||
22 | ); | ||
23 | } | ||
24 | |||
25 | /** | ||
26 | * Returns the name of the extension. | ||
27 | * | ||
28 | * @return string The extension name | ||
29 | */ | ||
30 | public function getName() | ||
31 | { | ||
32 | return 'debug'; | ||
33 | } | ||
34 | } | ||
diff --git a/inc/Twig/Extensions/Extension/I18n.php b/inc/Twig/Extensions/Extension/I18n.php new file mode 100644 index 00000000..3702aa22 --- /dev/null +++ b/inc/Twig/Extensions/Extension/I18n.php | |||
@@ -0,0 +1,44 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 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 | class Twig_Extensions_Extension_I18n extends Twig_Extension | ||
12 | { | ||
13 | /** | ||
14 | * Returns the token parser instances to add to the existing list. | ||
15 | * | ||
16 | * @return array An array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances | ||
17 | */ | ||
18 | public function getTokenParsers() | ||
19 | { | ||
20 | return array(new Twig_Extensions_TokenParser_Trans()); | ||
21 | } | ||
22 | |||
23 | /** | ||
24 | * Returns a list of filters to add to the existing list. | ||
25 | * | ||
26 | * @return array An array of filters | ||
27 | */ | ||
28 | public function getFilters() | ||
29 | { | ||
30 | return array( | ||
31 | 'trans' => new Twig_Filter_Function('gettext'), | ||
32 | ); | ||
33 | } | ||
34 | |||
35 | /** | ||
36 | * Returns the name of the extension. | ||
37 | * | ||
38 | * @return string The extension name | ||
39 | */ | ||
40 | public function getName() | ||
41 | { | ||
42 | return 'i18n'; | ||
43 | } | ||
44 | } | ||
diff --git a/inc/Twig/Extensions/Extension/Intl.php b/inc/Twig/Extensions/Extension/Intl.php new file mode 100644 index 00000000..40f7fc20 --- /dev/null +++ b/inc/Twig/Extensions/Extension/Intl.php | |||
@@ -0,0 +1,66 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 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 | class Twig_Extensions_Extension_Intl extends Twig_Extension | ||
13 | { | ||
14 | public function __construct() | ||
15 | { | ||
16 | if (!class_exists('IntlDateFormatter')) { | ||
17 | throw new RuntimeException('The intl extension is needed to use intl-based filters.'); | ||
18 | } | ||
19 | } | ||
20 | |||
21 | /** | ||
22 | * Returns a list of filters to add to the existing list. | ||
23 | * | ||
24 | * @return array An array of filters | ||
25 | */ | ||
26 | public function getFilters() | ||
27 | { | ||
28 | return array( | ||
29 | 'localizeddate' => new Twig_Filter_Function('twig_localized_date_filter', array('needs_environment' => true)), | ||
30 | ); | ||
31 | } | ||
32 | |||
33 | /** | ||
34 | * Returns the name of the extension. | ||
35 | * | ||
36 | * @return string The extension name | ||
37 | */ | ||
38 | public function getName() | ||
39 | { | ||
40 | return 'intl'; | ||
41 | } | ||
42 | } | ||
43 | |||
44 | function twig_localized_date_filter(Twig_Environment $env, $date, $dateFormat = 'medium', $timeFormat = 'medium', $locale = null, $timezone = null, $format = null) | ||
45 | { | ||
46 | $date = twig_date_converter($env, $date, $timezone); | ||
47 | |||
48 | $formatValues = array( | ||
49 | 'none' => IntlDateFormatter::NONE, | ||
50 | 'short' => IntlDateFormatter::SHORT, | ||
51 | 'medium' => IntlDateFormatter::MEDIUM, | ||
52 | 'long' => IntlDateFormatter::LONG, | ||
53 | 'full' => IntlDateFormatter::FULL, | ||
54 | ); | ||
55 | |||
56 | $formatter = IntlDateFormatter::create( | ||
57 | $locale !== null ? $locale : Locale::getDefault(), | ||
58 | $formatValues[$dateFormat], | ||
59 | $formatValues[$timeFormat], | ||
60 | $date->getTimezone()->getName(), | ||
61 | IntlDateFormatter::GREGORIAN, | ||
62 | $format | ||
63 | ); | ||
64 | |||
65 | return $formatter->format($date->getTimestamp()); | ||
66 | } | ||
diff --git a/inc/Twig/Extensions/Extension/Text.php b/inc/Twig/Extensions/Extension/Text.php new file mode 100644 index 00000000..0a3dc35e --- /dev/null +++ b/inc/Twig/Extensions/Extension/Text.php | |||
@@ -0,0 +1,109 @@ | |||
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 | * @author Henrik Bjornskov <hb@peytz.dk> | ||
12 | * @package Twig | ||
13 | * @subpackage Twig-extensions | ||
14 | */ | ||
15 | class Twig_Extensions_Extension_Text extends Twig_Extension | ||
16 | { | ||
17 | /** | ||
18 | * Returns a list of filters. | ||
19 | * | ||
20 | * @return array | ||
21 | */ | ||
22 | public function getFilters() | ||
23 | { | ||
24 | $filters = array( | ||
25 | 'truncate' => new Twig_Filter_Function('twig_truncate_filter', array('needs_environment' => true)), | ||
26 | 'wordwrap' => new Twig_Filter_Function('twig_wordwrap_filter', array('needs_environment' => true)), | ||
27 | ); | ||
28 | |||
29 | if (version_compare(Twig_Environment::VERSION, '1.5.0-DEV', '<')) { | ||
30 | $filters['nl2br'] = new Twig_Filter_Function('twig_nl2br_filter', array('pre_escape' => 'html', 'is_safe' => array('html'))); | ||
31 | } | ||
32 | |||
33 | return $filters; | ||
34 | } | ||
35 | |||
36 | /** | ||
37 | * Name of this extension | ||
38 | * | ||
39 | * @return string | ||
40 | */ | ||
41 | public function getName() | ||
42 | { | ||
43 | return 'Text'; | ||
44 | } | ||
45 | } | ||
46 | |||
47 | function twig_nl2br_filter($value, $sep = '<br />') | ||
48 | { | ||
49 | return str_replace("\n", $sep."\n", $value); | ||
50 | } | ||
51 | |||
52 | if (function_exists('mb_get_info')) { | ||
53 | function twig_truncate_filter(Twig_Environment $env, $value, $length = 30, $preserve = false, $separator = '...') | ||
54 | { | ||
55 | if (mb_strlen($value, $env->getCharset()) > $length) { | ||
56 | if ($preserve) { | ||
57 | if (false !== ($breakpoint = mb_strpos($value, ' ', $length, $env->getCharset()))) { | ||
58 | $length = $breakpoint; | ||
59 | } | ||
60 | } | ||
61 | |||
62 | return rtrim(mb_substr($value, 0, $length, $env->getCharset())) . $separator; | ||
63 | } | ||
64 | |||
65 | return $value; | ||
66 | } | ||
67 | |||
68 | function twig_wordwrap_filter(Twig_Environment $env, $value, $length = 80, $separator = "\n", $preserve = false) | ||
69 | { | ||
70 | $sentences = array(); | ||
71 | |||
72 | $previous = mb_regex_encoding(); | ||
73 | mb_regex_encoding($env->getCharset()); | ||
74 | |||
75 | $pieces = mb_split($separator, $value); | ||
76 | mb_regex_encoding($previous); | ||
77 | |||
78 | foreach ($pieces as $piece) { | ||
79 | while(!$preserve && mb_strlen($piece, $env->getCharset()) > $length) { | ||
80 | $sentences[] = mb_substr($piece, 0, $length, $env->getCharset()); | ||
81 | $piece = mb_substr($piece, $length, 2048, $env->getCharset()); | ||
82 | } | ||
83 | |||
84 | $sentences[] = $piece; | ||
85 | } | ||
86 | |||
87 | return implode($separator, $sentences); | ||
88 | } | ||
89 | } else { | ||
90 | function twig_truncate_filter(Twig_Environment $env, $value, $length = 30, $preserve = false, $separator = '...') | ||
91 | { | ||
92 | if (strlen($value) > $length) { | ||
93 | if ($preserve) { | ||
94 | if (false !== ($breakpoint = strpos($value, ' ', $length))) { | ||
95 | $length = $breakpoint; | ||
96 | } | ||
97 | } | ||
98 | |||
99 | return rtrim(substr($value, 0, $length)) . $separator; | ||
100 | } | ||
101 | |||
102 | return $value; | ||
103 | } | ||
104 | |||
105 | function twig_wordwrap_filter(Twig_Environment $env, $value, $length = 80, $separator = "\n", $preserve = false) | ||
106 | { | ||
107 | return wordwrap($value, $length, $separator, !$preserve); | ||
108 | } | ||
109 | } \ No newline at end of file | ||
diff --git a/inc/Twig/Extensions/Gettext/Extractor.php b/inc/Twig/Extensions/Gettext/Extractor.php new file mode 100644 index 00000000..e7fa1af2 --- /dev/null +++ b/inc/Twig/Extensions/Gettext/Extractor.php | |||
@@ -0,0 +1,95 @@ | |||
1 | <?php | ||
2 | |||
3 | /** | ||
4 | * This file is part of the Twig Gettext utility. | ||
5 | * | ||
6 | * (c) Саша Стаменковић <umpirsky@gmail.com> | ||
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 | namespace Twig\Gettext; | ||
13 | |||
14 | use Symfony\Component\Filesystem\Filesystem; | ||
15 | |||
16 | /** | ||
17 | * Extracts translations from twig templates. | ||
18 | * | ||
19 | * @author Саша Стаменковић <umpirsky@gmail.com> | ||
20 | */ | ||
21 | class Extractor | ||
22 | { | ||
23 | /** | ||
24 | * @var \Twig_Environment | ||
25 | */ | ||
26 | protected $environment; | ||
27 | |||
28 | /** | ||
29 | * Template cached file names. | ||
30 | * | ||
31 | * @var string[] | ||
32 | */ | ||
33 | protected $templates; | ||
34 | |||
35 | /** | ||
36 | * Gettext parameters. | ||
37 | * | ||
38 | * @var string[] | ||
39 | */ | ||
40 | protected $parameters; | ||
41 | |||
42 | public function __construct(\Twig_Environment $environment) | ||
43 | { | ||
44 | $this->environment = $environment; | ||
45 | $this->reset(); | ||
46 | } | ||
47 | |||
48 | protected function reset() | ||
49 | { | ||
50 | $this->templates = array(); | ||
51 | $this->parameters = array(); | ||
52 | } | ||
53 | |||
54 | public function addTemplate($path) | ||
55 | { | ||
56 | $this->environment->loadTemplate($path); | ||
57 | $this->templates[] = $this->environment->getCacheFilename($path); | ||
58 | } | ||
59 | |||
60 | public function addGettextParameter($parameter) | ||
61 | { | ||
62 | $this->parameters[] = $parameter; | ||
63 | } | ||
64 | |||
65 | public function setGettextParameters(array $parameters) | ||
66 | { | ||
67 | $this->parameters = $parameters; | ||
68 | } | ||
69 | |||
70 | public function extract() | ||
71 | { | ||
72 | $command = 'xgettext'; | ||
73 | $command .= ' '.join(' ', $this->parameters); | ||
74 | $command .= ' '.join(' ', $this->templates); | ||
75 | |||
76 | $error = 0; | ||
77 | $output = system($command, $error); | ||
78 | if (0 !== $error) { | ||
79 | throw new \RuntimeException(sprintf( | ||
80 | 'Gettext command "%s" failed with error code %s and output: %s', | ||
81 | $command, | ||
82 | $error, | ||
83 | $output | ||
84 | )); | ||
85 | } | ||
86 | |||
87 | $this->reset(); | ||
88 | } | ||
89 | |||
90 | public function __destruct() | ||
91 | { | ||
92 | $filesystem = new Filesystem(); | ||
93 | $filesystem->remove($this->environment->getCache()); | ||
94 | } | ||
95 | } | ||
diff --git a/inc/Twig/Extensions/Gettext/Loader/Filesystem.php b/inc/Twig/Extensions/Gettext/Loader/Filesystem.php new file mode 100644 index 00000000..b011b032 --- /dev/null +++ b/inc/Twig/Extensions/Gettext/Loader/Filesystem.php | |||
@@ -0,0 +1,58 @@ | |||
1 | <?php | ||
2 | |||
3 | /** | ||
4 | * This file is part of the Twig Gettext utility. | ||
5 | * | ||
6 | * (c) Саша Стаменковић <umpirsky@gmail.com> | ||
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 | namespace Twig\Gettext\Loader; | ||
13 | |||
14 | /** | ||
15 | * Loads template from the filesystem. | ||
16 | * | ||
17 | * @author Саша Стаменковић <umpirsky@gmail.com> | ||
18 | */ | ||
19 | class Filesystem extends \Twig_Loader_Filesystem | ||
20 | { | ||
21 | /** | ||
22 | * Hacked find template to allow loading templates by absolute path. | ||
23 | * | ||
24 | * @param string $name template name or absolute path | ||
25 | */ | ||
26 | protected function findTemplate($name) | ||
27 | { | ||
28 | // normalize name | ||
29 | $name = preg_replace('#/{2,}#', '/', strtr($name, '\\', '/')); | ||
30 | |||
31 | if (isset($this->cache[$name])) { | ||
32 | return $this->cache[$name]; | ||
33 | } | ||
34 | |||
35 | $this->validateName($name); | ||
36 | |||
37 | $namespace = '__main__'; | ||
38 | if (isset($name[0]) && '@' == $name[0]) { | ||
39 | if (false === $pos = strpos($name, '/')) { | ||
40 | throw new \InvalidArgumentException(sprintf('Malformed namespaced template name "%s" (expecting "@namespace/template_name").', $name)); | ||
41 | } | ||
42 | |||
43 | $namespace = substr($name, 1, $pos - 1); | ||
44 | |||
45 | $name = substr($name, $pos + 1); | ||
46 | } | ||
47 | |||
48 | if (!isset($this->paths[$namespace])) { | ||
49 | throw new \Twig_Error_Loader(sprintf('There are no registered paths for namespace "%s".', $namespace)); | ||
50 | } | ||
51 | |||
52 | if (is_file($name)) { | ||
53 | return $this->cache[$name] = $name; | ||
54 | } | ||
55 | |||
56 | return __DIR__.'/../Test/Fixtures/twig/empty.twig'; | ||
57 | } | ||
58 | } | ||
diff --git a/inc/Twig/Extensions/Gettext/Routing/Generator/UrlGenerator.php b/inc/Twig/Extensions/Gettext/Routing/Generator/UrlGenerator.php new file mode 100644 index 00000000..9e3431bd --- /dev/null +++ b/inc/Twig/Extensions/Gettext/Routing/Generator/UrlGenerator.php | |||
@@ -0,0 +1,39 @@ | |||
1 | <?php | ||
2 | |||
3 | /** | ||
4 | * This file is part of the Twig Gettext utility. | ||
5 | * | ||
6 | * (c) Саша Стаменковић <umpirsky@gmail.com> | ||
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 | namespace Twig\Gettext\Routing\Generator; | ||
13 | |||
14 | use Symfony\Component\Routing\Generator\UrlGeneratorInterface; | ||
15 | use Symfony\Component\Routing\RequestContext; | ||
16 | |||
17 | /** | ||
18 | * Dummy url generator. | ||
19 | * | ||
20 | * @author Саша Стаменковић <umpirsky@gmail.com> | ||
21 | */ | ||
22 | class UrlGenerator implements UrlGeneratorInterface | ||
23 | { | ||
24 | protected $context; | ||
25 | |||
26 | public function generate($name, $parameters = array(), $absolute = false) | ||
27 | { | ||
28 | } | ||
29 | |||
30 | public function getContext() | ||
31 | { | ||
32 | return $this->context; | ||
33 | } | ||
34 | |||
35 | public function setContext(RequestContext $context) | ||
36 | { | ||
37 | $this->context = $context; | ||
38 | } | ||
39 | } | ||
diff --git a/inc/Twig/Extensions/Gettext/Test/ExtractorTest.php b/inc/Twig/Extensions/Gettext/Test/ExtractorTest.php new file mode 100644 index 00000000..d467835f --- /dev/null +++ b/inc/Twig/Extensions/Gettext/Test/ExtractorTest.php | |||
@@ -0,0 +1,123 @@ | |||
1 | <?php | ||
2 | |||
3 | /** | ||
4 | * This file is part of the Twig Gettext utility. | ||
5 | * | ||
6 | * (c) Саша Стаменковић <umpirsky@gmail.com> | ||
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 | namespace Twig\Gettext\Test; | ||
13 | |||
14 | use Twig\Gettext\Extractor; | ||
15 | use Twig\Gettext\Loader\Filesystem; | ||
16 | use Symfony\Component\Translation\Loader\PoFileLoader; | ||
17 | |||
18 | /** | ||
19 | * @author Саша Стаменковић <umpirsky@gmail.com> | ||
20 | */ | ||
21 | class ExtractorTest extends \PHPUnit_Framework_TestCase | ||
22 | { | ||
23 | /** | ||
24 | * @var \Twig_Environment | ||
25 | */ | ||
26 | protected $twig; | ||
27 | |||
28 | /** | ||
29 | * @var PoFileLoader | ||
30 | */ | ||
31 | protected $loader; | ||
32 | |||
33 | protected function setUp() | ||
34 | { | ||
35 | $this->twig = new \Twig_Environment(new Filesystem('/'), array( | ||
36 | 'cache' => '/tmp/cache/'.uniqid(), | ||
37 | 'auto_reload' => true | ||
38 | )); | ||
39 | $this->twig->addExtension(new \Twig_Extensions_Extension_I18n()); | ||
40 | |||
41 | $this->loader = new PoFileLoader(); | ||
42 | } | ||
43 | |||
44 | /** | ||
45 | * @dataProvider testExtractDataProvider | ||
46 | */ | ||
47 | public function testExtract(array $templates, array $parameters, array $messages) | ||
48 | { | ||
49 | $extractor = new Extractor($this->twig); | ||
50 | |||
51 | foreach ($templates as $template) { | ||
52 | $extractor->addTemplate($template); | ||
53 | } | ||
54 | foreach ($parameters as $parameter) { | ||
55 | $extractor->addGettextParameter($parameter); | ||
56 | } | ||
57 | |||
58 | $extractor->extract(); | ||
59 | |||
60 | $catalog = $this->loader->load($this->getPotFile(), null); | ||
61 | |||
62 | foreach ($messages as $message) { | ||
63 | $this->assertTrue( | ||
64 | $catalog->has($message), | ||
65 | sprintf('Message "%s" not found in catalog.', $message) | ||
66 | ); | ||
67 | } | ||
68 | } | ||
69 | |||
70 | public function testExtractDataProvider() | ||
71 | { | ||
72 | return array( | ||
73 | array( | ||
74 | array( | ||
75 | __DIR__.'/Fixtures/twig/singular.twig', | ||
76 | __DIR__.'/Fixtures/twig/plural.twig', | ||
77 | ), | ||
78 | $this->getGettextParameters(), | ||
79 | array( | ||
80 | 'Hello %name%!', | ||
81 | 'Hello World!', | ||
82 | 'Hey %name%, I have one apple.', | ||
83 | 'Hey %name%, I have %count% apples.', | ||
84 | ), | ||
85 | ), | ||
86 | ); | ||
87 | } | ||
88 | |||
89 | public function testExtractNoTranslations() | ||
90 | { | ||
91 | $extractor = new Extractor($this->twig); | ||
92 | |||
93 | $extractor->addTemplate(__DIR__.'/Fixtures/twig/empty.twig'); | ||
94 | $extractor->setGettextParameters($this->getGettextParameters()); | ||
95 | |||
96 | $extractor->extract(); | ||
97 | |||
98 | $catalog = $this->loader->load($this->getPotFile(), null); | ||
99 | |||
100 | $this->assertEmpty($catalog->all('messages')); | ||
101 | } | ||
102 | |||
103 | private function getPotFile() | ||
104 | { | ||
105 | return __DIR__.'/Fixtures/messages.pot'; | ||
106 | } | ||
107 | |||
108 | private function getGettextParameters() | ||
109 | { | ||
110 | return array( | ||
111 | '--force-po', | ||
112 | '-o', | ||
113 | $this->getPotFile(), | ||
114 | ); | ||
115 | } | ||
116 | |||
117 | protected function tearDown() | ||
118 | { | ||
119 | if (file_exists($this->getPotFile())) { | ||
120 | unlink($this->getPotFile()); | ||
121 | } | ||
122 | } | ||
123 | } | ||
diff --git a/inc/Twig/Extensions/Gettext/Test/Fixtures/twig/empty.twig b/inc/Twig/Extensions/Gettext/Test/Fixtures/twig/empty.twig new file mode 100644 index 00000000..05f0d26a --- /dev/null +++ b/inc/Twig/Extensions/Gettext/Test/Fixtures/twig/empty.twig | |||
@@ -0,0 +1 @@ | |||
Nothing to translate here. | |||
diff --git a/inc/Twig/Extensions/Gettext/Test/Fixtures/twig/plural.twig b/inc/Twig/Extensions/Gettext/Test/Fixtures/twig/plural.twig new file mode 100644 index 00000000..f9754ff4 --- /dev/null +++ b/inc/Twig/Extensions/Gettext/Test/Fixtures/twig/plural.twig | |||
@@ -0,0 +1,5 @@ | |||
1 | {% trans %} | ||
2 | Hey {{ name }}, I have one apple. | ||
3 | {% plural apple_count %} | ||
4 | Hey {{ name }}, I have {{ count }} apples. | ||
5 | {% endtrans %} | ||
diff --git a/inc/Twig/Extensions/Gettext/Test/Fixtures/twig/singular.twig b/inc/Twig/Extensions/Gettext/Test/Fixtures/twig/singular.twig new file mode 100644 index 00000000..d757cf90 --- /dev/null +++ b/inc/Twig/Extensions/Gettext/Test/Fixtures/twig/singular.twig | |||
@@ -0,0 +1,9 @@ | |||
1 | {% trans "Hello World!" %} | ||
2 | |||
3 | {% trans %} | ||
4 | Hello World! | ||
5 | {% endtrans %} | ||
6 | |||
7 | {% trans %} | ||
8 | Hello {{ name }}! | ||
9 | {% endtrans %} | ||
diff --git a/inc/Twig/Extensions/Grammar.php b/inc/Twig/Extensions/Grammar.php new file mode 100644 index 00000000..4d031b19 --- /dev/null +++ b/inc/Twig/Extensions/Grammar.php | |||
@@ -0,0 +1,30 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 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 | abstract class Twig_Extensions_Grammar implements Twig_Extensions_GrammarInterface | ||
12 | { | ||
13 | protected $name; | ||
14 | protected $parser; | ||
15 | |||
16 | public function __construct($name) | ||
17 | { | ||
18 | $this->name = $name; | ||
19 | } | ||
20 | |||
21 | public function setParser(Twig_ParserInterface $parser) | ||
22 | { | ||
23 | $this->parser = $parser; | ||
24 | } | ||
25 | |||
26 | public function getName() | ||
27 | { | ||
28 | return $this->name; | ||
29 | } | ||
30 | } | ||
diff --git a/inc/Twig/Extensions/Grammar/Arguments.php b/inc/Twig/Extensions/Grammar/Arguments.php new file mode 100644 index 00000000..158c05ac --- /dev/null +++ b/inc/Twig/Extensions/Grammar/Arguments.php | |||
@@ -0,0 +1,22 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 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 | class Twig_Extensions_Grammar_Arguments extends Twig_Extensions_Grammar | ||
12 | { | ||
13 | public function __toString() | ||
14 | { | ||
15 | return sprintf('<%s:arguments>', $this->name); | ||
16 | } | ||
17 | |||
18 | public function parse(Twig_Token $token) | ||
19 | { | ||
20 | return $this->parser->getExpressionParser()->parseArguments(); | ||
21 | } | ||
22 | } | ||
diff --git a/inc/Twig/Extensions/Grammar/Array.php b/inc/Twig/Extensions/Grammar/Array.php new file mode 100644 index 00000000..34aece0f --- /dev/null +++ b/inc/Twig/Extensions/Grammar/Array.php | |||
@@ -0,0 +1,22 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 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 | class Twig_Extensions_Grammar_Array extends Twig_Extensions_Grammar | ||
12 | { | ||
13 | public function __toString() | ||
14 | { | ||
15 | return sprintf('<%s:array>', $this->name); | ||
16 | } | ||
17 | |||
18 | public function parse(Twig_Token $token) | ||
19 | { | ||
20 | return $this->parser->getExpressionParser()->parseArrayExpression(); | ||
21 | } | ||
22 | } | ||
diff --git a/inc/Twig/Extensions/Grammar/Body.php b/inc/Twig/Extensions/Grammar/Body.php new file mode 100644 index 00000000..540cfc75 --- /dev/null +++ b/inc/Twig/Extensions/Grammar/Body.php | |||
@@ -0,0 +1,39 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 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 | class Twig_Extensions_Grammar_Body extends Twig_Extensions_Grammar | ||
12 | { | ||
13 | protected $end; | ||
14 | |||
15 | public function __construct($name, $end = null) | ||
16 | { | ||
17 | parent::__construct($name); | ||
18 | |||
19 | $this->end = null === $end ? 'end'.$name : $end; | ||
20 | } | ||
21 | |||
22 | public function __toString() | ||
23 | { | ||
24 | return sprintf('<%s:body>', $this->name); | ||
25 | } | ||
26 | |||
27 | public function parse(Twig_Token $token) | ||
28 | { | ||
29 | $stream = $this->parser->getStream(); | ||
30 | $stream->expect(Twig_Token::BLOCK_END_TYPE); | ||
31 | |||
32 | return $this->parser->subparse(array($this, 'decideBlockEnd'), true); | ||
33 | } | ||
34 | |||
35 | public function decideBlockEnd(Twig_Token $token) | ||
36 | { | ||
37 | return $token->test($this->end); | ||
38 | } | ||
39 | } | ||
diff --git a/inc/Twig/Extensions/Grammar/Boolean.php b/inc/Twig/Extensions/Grammar/Boolean.php new file mode 100644 index 00000000..c0048090 --- /dev/null +++ b/inc/Twig/Extensions/Grammar/Boolean.php | |||
@@ -0,0 +1,24 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 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 | class Twig_Extensions_Grammar_Boolean extends Twig_Extensions_Grammar | ||
12 | { | ||
13 | public function __toString() | ||
14 | { | ||
15 | return sprintf('<%s:boolean>', $this->name); | ||
16 | } | ||
17 | |||
18 | public function parse(Twig_Token $token) | ||
19 | { | ||
20 | $this->parser->getStream()->expect(Twig_Token::NAME_TYPE, array('true', 'false')); | ||
21 | |||
22 | return new Twig_Node_Expression_Constant('true' === $token->getValue() ? true : false, $token->getLine()); | ||
23 | } | ||
24 | } | ||
diff --git a/inc/Twig/Extensions/Grammar/Constant.php b/inc/Twig/Extensions/Grammar/Constant.php new file mode 100644 index 00000000..9df60458 --- /dev/null +++ b/inc/Twig/Extensions/Grammar/Constant.php | |||
@@ -0,0 +1,37 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 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 | class Twig_Extensions_Grammar_Constant extends Twig_Extensions_Grammar | ||
12 | { | ||
13 | protected $type; | ||
14 | |||
15 | public function __construct($name, $type = null) | ||
16 | { | ||
17 | $this->name = $name; | ||
18 | $this->type = null === $type ? Twig_Token::NAME_TYPE : $type; | ||
19 | } | ||
20 | |||
21 | public function __toString() | ||
22 | { | ||
23 | return $this->name; | ||
24 | } | ||
25 | |||
26 | public function parse(Twig_Token $token) | ||
27 | { | ||
28 | $this->parser->getStream()->expect($this->type, $this->name); | ||
29 | |||
30 | return $this->name; | ||
31 | } | ||
32 | |||
33 | public function getType() | ||
34 | { | ||
35 | return $this->type; | ||
36 | } | ||
37 | } | ||
diff --git a/inc/Twig/Extensions/Grammar/Expression.php b/inc/Twig/Extensions/Grammar/Expression.php new file mode 100644 index 00000000..4c33df0e --- /dev/null +++ b/inc/Twig/Extensions/Grammar/Expression.php | |||
@@ -0,0 +1,22 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 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 | class Twig_Extensions_Grammar_Expression extends Twig_Extensions_Grammar | ||
12 | { | ||
13 | public function __toString() | ||
14 | { | ||
15 | return sprintf('<%s>', $this->name); | ||
16 | } | ||
17 | |||
18 | public function parse(Twig_Token $token) | ||
19 | { | ||
20 | return $this->parser->getExpressionParser()->parseExpression(); | ||
21 | } | ||
22 | } | ||
diff --git a/inc/Twig/Extensions/Grammar/Hash.php b/inc/Twig/Extensions/Grammar/Hash.php new file mode 100644 index 00000000..98b07d20 --- /dev/null +++ b/inc/Twig/Extensions/Grammar/Hash.php | |||
@@ -0,0 +1,22 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 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 | class Twig_Extensions_Grammar_Hash extends Twig_Extensions_Grammar | ||
12 | { | ||
13 | public function __toString() | ||
14 | { | ||
15 | return sprintf('<%s:hash>', $this->name); | ||
16 | } | ||
17 | |||
18 | public function parse(Twig_Token $token) | ||
19 | { | ||
20 | return $this->parser->getExpressionParser()->parseHashExpression(); | ||
21 | } | ||
22 | } | ||
diff --git a/inc/Twig/Extensions/Grammar/Number.php b/inc/Twig/Extensions/Grammar/Number.php new file mode 100644 index 00000000..f0857d20 --- /dev/null +++ b/inc/Twig/Extensions/Grammar/Number.php | |||
@@ -0,0 +1,24 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 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 | class Twig_Extensions_Grammar_Number extends Twig_Extensions_Grammar | ||
12 | { | ||
13 | public function __toString() | ||
14 | { | ||
15 | return sprintf('<%s:number>', $this->name); | ||
16 | } | ||
17 | |||
18 | public function parse(Twig_Token $token) | ||
19 | { | ||
20 | $this->parser->getStream()->expect(Twig_Token::NUMBER_TYPE); | ||
21 | |||
22 | return new Twig_Node_Expression_Constant($token->getValue(), $token->getLine()); | ||
23 | } | ||
24 | } | ||
diff --git a/inc/Twig/Extensions/Grammar/Optional.php b/inc/Twig/Extensions/Grammar/Optional.php new file mode 100644 index 00000000..da427485 --- /dev/null +++ b/inc/Twig/Extensions/Grammar/Optional.php | |||
@@ -0,0 +1,69 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 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 | class Twig_Extensions_Grammar_Optional extends Twig_Extensions_Grammar | ||
12 | { | ||
13 | protected $grammar; | ||
14 | |||
15 | public function __construct() | ||
16 | { | ||
17 | $this->grammar = array(); | ||
18 | foreach (func_get_args() as $grammar) { | ||
19 | $this->addGrammar($grammar); | ||
20 | } | ||
21 | } | ||
22 | |||
23 | public function __toString() | ||
24 | { | ||
25 | $repr = array(); | ||
26 | foreach ($this->grammar as $grammar) { | ||
27 | $repr[] = (string) $grammar; | ||
28 | } | ||
29 | |||
30 | return sprintf('[%s]', implode(' ', $repr)); | ||
31 | } | ||
32 | |||
33 | public function addGrammar(Twig_Extensions_GrammarInterface $grammar) | ||
34 | { | ||
35 | $this->grammar[] = $grammar; | ||
36 | } | ||
37 | |||
38 | public function parse(Twig_Token $token) | ||
39 | { | ||
40 | // test if we have the optional element before consuming it | ||
41 | if ($this->grammar[0] instanceof Twig_Extensions_Grammar_Constant) { | ||
42 | if (!$this->parser->getStream()->test($this->grammar[0]->getType(), $this->grammar[0]->getName())) { | ||
43 | return array(); | ||
44 | } | ||
45 | } elseif ($this->grammar[0] instanceof Twig_Extensions_Grammar_Name) { | ||
46 | if (!$this->parser->getStream()->test(Twig_Token::NAME_TYPE)) { | ||
47 | return array(); | ||
48 | } | ||
49 | } elseif ($this->parser->getStream()->test(Twig_Token::BLOCK_END_TYPE)) { | ||
50 | // if this is not a Constant or a Name, it must be the last element of the tag | ||
51 | |||
52 | return array(); | ||
53 | } | ||
54 | |||
55 | $elements = array(); | ||
56 | foreach ($this->grammar as $grammar) { | ||
57 | $grammar->setParser($this->parser); | ||
58 | |||
59 | $element = $grammar->parse($token); | ||
60 | if (is_array($element)) { | ||
61 | $elements = array_merge($elements, $element); | ||
62 | } else { | ||
63 | $elements[$grammar->getName()] = $element; | ||
64 | } | ||
65 | } | ||
66 | |||
67 | return $elements; | ||
68 | } | ||
69 | } | ||
diff --git a/inc/Twig/Extensions/Grammar/Switch.php b/inc/Twig/Extensions/Grammar/Switch.php new file mode 100644 index 00000000..4245f2c8 --- /dev/null +++ b/inc/Twig/Extensions/Grammar/Switch.php | |||
@@ -0,0 +1,24 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 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 | class Twig_Extensions_Grammar_Switch extends Twig_Extensions_Grammar | ||
12 | { | ||
13 | public function __toString() | ||
14 | { | ||
15 | return sprintf('<%s:switch>', $this->name); | ||
16 | } | ||
17 | |||
18 | public function parse(Twig_Token $token) | ||
19 | { | ||
20 | $this->parser->getStream()->expect(Twig_Token::NAME_TYPE, $this->name); | ||
21 | |||
22 | return new Twig_Node_Expression_Constant(true, $token->getLine()); | ||
23 | } | ||
24 | } | ||
diff --git a/inc/Twig/Extensions/Grammar/Tag.php b/inc/Twig/Extensions/Grammar/Tag.php new file mode 100644 index 00000000..727f2610 --- /dev/null +++ b/inc/Twig/Extensions/Grammar/Tag.php | |||
@@ -0,0 +1,56 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 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 | class Twig_Extensions_Grammar_Tag extends Twig_Extensions_Grammar | ||
12 | { | ||
13 | protected $grammar; | ||
14 | |||
15 | public function __construct() | ||
16 | { | ||
17 | $this->grammar = array(); | ||
18 | foreach (func_get_args() as $grammar) { | ||
19 | $this->addGrammar($grammar); | ||
20 | } | ||
21 | } | ||
22 | |||
23 | public function __toString() | ||
24 | { | ||
25 | $repr = array(); | ||
26 | foreach ($this->grammar as $grammar) { | ||
27 | $repr[] = (string) $grammar; | ||
28 | } | ||
29 | |||
30 | return implode(' ', $repr); | ||
31 | } | ||
32 | |||
33 | public function addGrammar(Twig_Extensions_GrammarInterface $grammar) | ||
34 | { | ||
35 | $this->grammar[] = $grammar; | ||
36 | } | ||
37 | |||
38 | public function parse(Twig_Token $token) | ||
39 | { | ||
40 | $elements = array(); | ||
41 | foreach ($this->grammar as $grammar) { | ||
42 | $grammar->setParser($this->parser); | ||
43 | |||
44 | $element = $grammar->parse($token); | ||
45 | if (is_array($element)) { | ||
46 | $elements = array_merge($elements, $element); | ||
47 | } else { | ||
48 | $elements[$grammar->getName()] = $element; | ||
49 | } | ||
50 | } | ||
51 | |||
52 | $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE); | ||
53 | |||
54 | return $elements; | ||
55 | } | ||
56 | } | ||
diff --git a/inc/Twig/Extensions/GrammarInterface.php b/inc/Twig/Extensions/GrammarInterface.php new file mode 100644 index 00000000..22713bf2 --- /dev/null +++ b/inc/Twig/Extensions/GrammarInterface.php | |||
@@ -0,0 +1,18 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 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 | interface Twig_Extensions_GrammarInterface | ||
12 | { | ||
13 | function setParser(Twig_ParserInterface $parser); | ||
14 | |||
15 | function parse(Twig_Token $token); | ||
16 | |||
17 | function getName(); | ||
18 | } | ||
diff --git a/inc/Twig/Extensions/Node/Debug.php b/inc/Twig/Extensions/Node/Debug.php new file mode 100644 index 00000000..7d01bbe5 --- /dev/null +++ b/inc/Twig/Extensions/Node/Debug.php | |||
@@ -0,0 +1,69 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 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 | * Represents a debug node. | ||
14 | * | ||
15 | * @package twig | ||
16 | * @subpackage Twig-extensions | ||
17 | * @author Fabien Potencier <fabien.potencier@symfony-project.com> | ||
18 | * @version SVN: $Id$ | ||
19 | */ | ||
20 | class Twig_Extensions_Node_Debug extends Twig_Node | ||
21 | { | ||
22 | public function __construct(Twig_Node_Expression $expr = null, $lineno, $tag = null) | ||
23 | { | ||
24 | parent::__construct(array('expr' => $expr), array(), $lineno, $tag); | ||
25 | } | ||
26 | |||
27 | /** | ||
28 | * Compiles the node to PHP. | ||
29 | * | ||
30 | * @param Twig_Compiler A Twig_Compiler instance | ||
31 | */ | ||
32 | public function compile(Twig_Compiler $compiler) | ||
33 | { | ||
34 | $compiler->addDebugInfo($this); | ||
35 | |||
36 | $compiler | ||
37 | ->write("if (\$this->env->isDebug()) {\n") | ||
38 | ->indent() | ||
39 | ; | ||
40 | |||
41 | if (null === $this->getNode('expr')) { | ||
42 | // remove embedded templates (macros) from the context | ||
43 | $compiler | ||
44 | ->write("\$vars = array();\n") | ||
45 | ->write("foreach (\$context as \$key => \$value) {\n") | ||
46 | ->indent() | ||
47 | ->write("if (!\$value instanceof Twig_Template) {\n") | ||
48 | ->indent() | ||
49 | ->write("\$vars[\$key] = \$value;\n") | ||
50 | ->outdent() | ||
51 | ->write("}\n") | ||
52 | ->outdent() | ||
53 | ->write("}\n") | ||
54 | ->write("var_dump(\$vars);\n") | ||
55 | ; | ||
56 | } else { | ||
57 | $compiler | ||
58 | ->write("var_dump(") | ||
59 | ->subcompile($this->getNode('expr')) | ||
60 | ->raw(");\n") | ||
61 | ; | ||
62 | } | ||
63 | |||
64 | $compiler | ||
65 | ->outdent() | ||
66 | ->write("}\n") | ||
67 | ; | ||
68 | } | ||
69 | } | ||
diff --git a/inc/Twig/Extensions/Node/Trans.php b/inc/Twig/Extensions/Node/Trans.php new file mode 100644 index 00000000..d12564a7 --- /dev/null +++ b/inc/Twig/Extensions/Node/Trans.php | |||
@@ -0,0 +1,133 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 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 | * Represents a trans node. | ||
14 | * | ||
15 | * @package twig | ||
16 | * @author Fabien Potencier <fabien.potencier@symfony-project.com> | ||
17 | */ | ||
18 | class Twig_Extensions_Node_Trans extends Twig_Node | ||
19 | { | ||
20 | public function __construct(Twig_NodeInterface $body, Twig_NodeInterface $plural = null, Twig_Node_Expression $count = null, $lineno, $tag = null) | ||
21 | { | ||
22 | parent::__construct(array('count' => $count, 'body' => $body, 'plural' => $plural), array(), $lineno, $tag); | ||
23 | } | ||
24 | |||
25 | /** | ||
26 | * Compiles the node to PHP. | ||
27 | * | ||
28 | * @param Twig_Compiler A Twig_Compiler instance | ||
29 | */ | ||
30 | public function compile(Twig_Compiler $compiler) | ||
31 | { | ||
32 | $compiler->addDebugInfo($this); | ||
33 | |||
34 | list($msg, $vars) = $this->compileString($this->getNode('body')); | ||
35 | |||
36 | if (null !== $this->getNode('plural')) { | ||
37 | list($msg1, $vars1) = $this->compileString($this->getNode('plural')); | ||
38 | |||
39 | $vars = array_merge($vars, $vars1); | ||
40 | } | ||
41 | |||
42 | $function = null === $this->getNode('plural') ? 'gettext' : 'ngettext'; | ||
43 | |||
44 | if ($vars) { | ||
45 | $compiler | ||
46 | ->write('echo strtr('.$function.'(') | ||
47 | ->subcompile($msg) | ||
48 | ; | ||
49 | |||
50 | if (null !== $this->getNode('plural')) { | ||
51 | $compiler | ||
52 | ->raw(', ') | ||
53 | ->subcompile($msg1) | ||
54 | ->raw(', abs(') | ||
55 | ->subcompile($this->getNode('count')) | ||
56 | ->raw(')') | ||
57 | ; | ||
58 | } | ||
59 | |||
60 | $compiler->raw('), array('); | ||
61 | |||
62 | foreach ($vars as $var) { | ||
63 | if ('count' === $var->getAttribute('name')) { | ||
64 | $compiler | ||
65 | ->string('%count%') | ||
66 | ->raw(' => abs(') | ||
67 | ->subcompile($this->getNode('count')) | ||
68 | ->raw('), ') | ||
69 | ; | ||
70 | } else { | ||
71 | $compiler | ||
72 | ->string('%'.$var->getAttribute('name').'%') | ||
73 | ->raw(' => ') | ||
74 | ->subcompile($var) | ||
75 | ->raw(', ') | ||
76 | ; | ||
77 | } | ||
78 | } | ||
79 | |||
80 | $compiler->raw("));\n"); | ||
81 | } else { | ||
82 | $compiler | ||
83 | ->write('echo '.$function.'(') | ||
84 | ->subcompile($msg) | ||
85 | ; | ||
86 | |||
87 | if (null !== $this->getNode('plural')) { | ||
88 | $compiler | ||
89 | ->raw(', ') | ||
90 | ->subcompile($msg1) | ||
91 | ->raw(', abs(') | ||
92 | ->subcompile($this->getNode('count')) | ||
93 | ->raw(')') | ||
94 | ; | ||
95 | } | ||
96 | |||
97 | $compiler->raw(");\n"); | ||
98 | } | ||
99 | } | ||
100 | |||
101 | protected function compileString(Twig_NodeInterface $body) | ||
102 | { | ||
103 | if ($body instanceof Twig_Node_Expression_Name || $body instanceof Twig_Node_Expression_Constant || $body instanceof Twig_Node_Expression_TempName) { | ||
104 | return array($body, array()); | ||
105 | } | ||
106 | |||
107 | $vars = array(); | ||
108 | if (count($body)) { | ||
109 | $msg = ''; | ||
110 | |||
111 | foreach ($body as $node) { | ||
112 | if (get_class($node) === 'Twig_Node' && $node->getNode(0) instanceof Twig_Node_SetTemp) { | ||
113 | $node = $node->getNode(1); | ||
114 | } | ||
115 | |||
116 | if ($node instanceof Twig_Node_Print) { | ||
117 | $n = $node->getNode('expr'); | ||
118 | while ($n instanceof Twig_Node_Expression_Filter) { | ||
119 | $n = $n->getNode('node'); | ||
120 | } | ||
121 | $msg .= sprintf('%%%s%%', $n->getAttribute('name')); | ||
122 | $vars[] = new Twig_Node_Expression_Name($n->getAttribute('name'), $n->getLine()); | ||
123 | } else { | ||
124 | $msg .= $node->getAttribute('data'); | ||
125 | } | ||
126 | } | ||
127 | } else { | ||
128 | $msg = $body->getAttribute('data'); | ||
129 | } | ||
130 | |||
131 | return array(new Twig_Node(array(new Twig_Node_Expression_Constant(trim($msg), $body->getLine()))), $vars); | ||
132 | } | ||
133 | } | ||
diff --git a/inc/Twig/Extensions/SimpleTokenParser.php b/inc/Twig/Extensions/SimpleTokenParser.php new file mode 100644 index 00000000..49546487 --- /dev/null +++ b/inc/Twig/Extensions/SimpleTokenParser.php | |||
@@ -0,0 +1,132 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 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 | abstract class Twig_Extensions_SimpleTokenParser extends Twig_TokenParser | ||
12 | { | ||
13 | /** | ||
14 | * Parses a token and returns a node. | ||
15 | * | ||
16 | * @param Twig_Token $token A Twig_Token instance | ||
17 | * | ||
18 | * @return Twig_NodeInterface A Twig_NodeInterface instance | ||
19 | */ | ||
20 | public function parse(Twig_Token $token) | ||
21 | { | ||
22 | $grammar = $this->getGrammar(); | ||
23 | if (!is_object($grammar)) { | ||
24 | $grammar = self::parseGrammar($grammar); | ||
25 | } | ||
26 | |||
27 | $grammar->setParser($this->parser); | ||
28 | $values = $grammar->parse($token); | ||
29 | |||
30 | return $this->getNode($values, $token->getLine()); | ||
31 | } | ||
32 | |||
33 | /** | ||
34 | * Gets the grammar as an object or as a string. | ||
35 | * | ||
36 | * @return string|Twig_Extensions_Grammar A Twig_Extensions_Grammar instance or a string | ||
37 | */ | ||
38 | abstract protected function getGrammar(); | ||
39 | |||
40 | /** | ||
41 | * Gets the nodes based on the parsed values. | ||
42 | * | ||
43 | * @param array $values An array of values | ||
44 | * @param integer $line The parser line | ||
45 | */ | ||
46 | abstract protected function getNode(array $values, $line); | ||
47 | |||
48 | protected function getAttribute($node, $attribute, $arguments = array(), $type = Twig_Node_Expression_GetAttr::TYPE_ANY, $line = -1) | ||
49 | { | ||
50 | return new Twig_Node_Expression_GetAttr( | ||
51 | $node instanceof Twig_NodeInterface ? $node : new Twig_Node_Expression_Name($node, $line), | ||
52 | $attribute instanceof Twig_NodeInterface ? $attribute : new Twig_Node_Expression_Constant($attribute, $line), | ||
53 | $arguments instanceof Twig_NodeInterface ? $arguments : new Twig_Node($arguments), | ||
54 | $type, | ||
55 | $line | ||
56 | ); | ||
57 | } | ||
58 | |||
59 | protected function call($node, $attribute, $arguments = array(), $line = -1) | ||
60 | { | ||
61 | return $this->getAttribute($node, $attribute, $arguments, Twig_Node_Expression_GetAttr::TYPE_METHOD, $line); | ||
62 | } | ||
63 | |||
64 | protected function markAsSafe(Twig_NodeInterface $node, $line = -1) | ||
65 | { | ||
66 | return new Twig_Node_Expression_Filter( | ||
67 | $node, | ||
68 | new Twig_Node_Expression_Constant('raw', $line), | ||
69 | new Twig_Node(), | ||
70 | $line | ||
71 | ); | ||
72 | } | ||
73 | |||
74 | protected function output(Twig_NodeInterface $node, $line = -1) | ||
75 | { | ||
76 | return new Twig_Node_Print($node, $line); | ||
77 | } | ||
78 | |||
79 | protected function getNodeValues(array $values) | ||
80 | { | ||
81 | $nodes = array(); | ||
82 | foreach ($values as $value) { | ||
83 | if ($value instanceof Twig_NodeInterface) { | ||
84 | $nodes[] = $value; | ||
85 | } | ||
86 | } | ||
87 | |||
88 | return $nodes; | ||
89 | } | ||
90 | |||
91 | static public function parseGrammar($str, $main = true) | ||
92 | { | ||
93 | static $cursor; | ||
94 | |||
95 | if (true === $main) { | ||
96 | $cursor = 0; | ||
97 | $grammar = new Twig_Extensions_Grammar_Tag(); | ||
98 | } else { | ||
99 | $grammar = new Twig_Extensions_Grammar_Optional(); | ||
100 | } | ||
101 | |||
102 | while ($cursor < strlen($str)) { | ||
103 | if (preg_match('/\s+/A', $str, $match, null, $cursor)) { | ||
104 | $cursor += strlen($match[0]); | ||
105 | } elseif (preg_match('/<(\w+)(?:\:(\w+))?>/A', $str, $match, null, $cursor)) { | ||
106 | $class = sprintf('Twig_Extensions_Grammar_%s', ucfirst(isset($match[2]) ? $match[2] : 'Expression')); | ||
107 | if (!class_exists($class)) { | ||
108 | throw new Twig_Error_Runtime(sprintf('Unable to understand "%s" in grammar (%s class does not exist)', $match[0], $class)); | ||
109 | } | ||
110 | $grammar->addGrammar(new $class($match[1])); | ||
111 | $cursor += strlen($match[0]); | ||
112 | } elseif (preg_match('/\w+/A', $str, $match, null, $cursor)) { | ||
113 | $grammar->addGrammar(new Twig_Extensions_Grammar_Constant($match[0])); | ||
114 | $cursor += strlen($match[0]); | ||
115 | } elseif (preg_match('/,/A', $str, $match, null, $cursor)) { | ||
116 | $grammar->addGrammar(new Twig_Extensions_Grammar_Constant($match[0], Twig_Token::PUNCTUATION_TYPE)); | ||
117 | $cursor += strlen($match[0]); | ||
118 | } elseif (preg_match('/\[/A', $str, $match, null, $cursor)) { | ||
119 | $cursor += strlen($match[0]); | ||
120 | $grammar->addGrammar(self::parseGrammar($str, false)); | ||
121 | } elseif (true !== $main && preg_match('/\]/A', $str, $match, null, $cursor)) { | ||
122 | $cursor += strlen($match[0]); | ||
123 | |||
124 | return $grammar; | ||
125 | } else { | ||
126 | throw new Twig_Error_Runtime(sprintf('Unable to parse grammar "%s" near "...%s..."', $str, substr($str, $cursor, 10))); | ||
127 | } | ||
128 | } | ||
129 | |||
130 | return $grammar; | ||
131 | } | ||
132 | } | ||
diff --git a/inc/Twig/Extensions/TokenParser/Debug.php b/inc/Twig/Extensions/TokenParser/Debug.php new file mode 100644 index 00000000..4a7dfcc0 --- /dev/null +++ b/inc/Twig/Extensions/TokenParser/Debug.php | |||
@@ -0,0 +1,42 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2009-2010 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 | class Twig_Extensions_TokenParser_Debug extends Twig_TokenParser | ||
12 | { | ||
13 | /** | ||
14 | * Parses a token and returns a node. | ||
15 | * | ||
16 | * @param Twig_Token $token A Twig_Token instance | ||
17 | * | ||
18 | * @return Twig_NodeInterface A Twig_NodeInterface instance | ||
19 | */ | ||
20 | public function parse(Twig_Token $token) | ||
21 | { | ||
22 | $lineno = $token->getLine(); | ||
23 | |||
24 | $expr = null; | ||
25 | if (!$this->parser->getStream()->test(Twig_Token::BLOCK_END_TYPE)) { | ||
26 | $expr = $this->parser->getExpressionParser()->parseExpression(); | ||
27 | } | ||
28 | $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE); | ||
29 | |||
30 | return new Twig_Extensions_Node_Debug($expr, $lineno, $this->getTag()); | ||
31 | } | ||
32 | |||
33 | /** | ||
34 | * Gets the tag name associated with this token parser. | ||
35 | * | ||
36 | * @param string The tag name | ||
37 | */ | ||
38 | public function getTag() | ||
39 | { | ||
40 | return 'debug'; | ||
41 | } | ||
42 | } | ||
diff --git a/inc/Twig/Extensions/TokenParser/Trans.php b/inc/Twig/Extensions/TokenParser/Trans.php new file mode 100644 index 00000000..5e2dc464 --- /dev/null +++ b/inc/Twig/Extensions/TokenParser/Trans.php | |||
@@ -0,0 +1,80 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 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 | class Twig_Extensions_TokenParser_Trans extends Twig_TokenParser | ||
12 | { | ||
13 | /** | ||
14 | * Parses a token and returns a node. | ||
15 | * | ||
16 | * @param Twig_Token $token A Twig_Token instance | ||
17 | * | ||
18 | * @return Twig_NodeInterface A Twig_NodeInterface instance | ||
19 | */ | ||
20 | public function parse(Twig_Token $token) | ||
21 | { | ||
22 | $lineno = $token->getLine(); | ||
23 | $stream = $this->parser->getStream(); | ||
24 | $count = null; | ||
25 | $plural = null; | ||
26 | |||
27 | if (!$stream->test(Twig_Token::BLOCK_END_TYPE)) { | ||
28 | $body = $this->parser->getExpressionParser()->parseExpression(); | ||
29 | } else { | ||
30 | $stream->expect(Twig_Token::BLOCK_END_TYPE); | ||
31 | $body = $this->parser->subparse(array($this, 'decideForFork')); | ||
32 | if ('plural' === $stream->next()->getValue()) { | ||
33 | $count = $this->parser->getExpressionParser()->parseExpression(); | ||
34 | $stream->expect(Twig_Token::BLOCK_END_TYPE); | ||
35 | $plural = $this->parser->subparse(array($this, 'decideForEnd'), true); | ||
36 | } | ||
37 | } | ||
38 | |||
39 | $stream->expect(Twig_Token::BLOCK_END_TYPE); | ||
40 | |||
41 | $this->checkTransString($body, $lineno); | ||
42 | |||
43 | return new Twig_Extensions_Node_Trans($body, $plural, $count, $lineno, $this->getTag()); | ||
44 | } | ||
45 | |||
46 | public function decideForFork(Twig_Token $token) | ||
47 | { | ||
48 | return $token->test(array('plural', 'endtrans')); | ||
49 | } | ||
50 | |||
51 | public function decideForEnd(Twig_Token $token) | ||
52 | { | ||
53 | return $token->test('endtrans'); | ||
54 | } | ||
55 | |||
56 | /** | ||
57 | * Gets the tag name associated with this token parser. | ||
58 | * | ||
59 | * @param string The tag name | ||
60 | */ | ||
61 | public function getTag() | ||
62 | { | ||
63 | return 'trans'; | ||
64 | } | ||
65 | |||
66 | protected function checkTransString(Twig_NodeInterface $body, $lineno) | ||
67 | { | ||
68 | foreach ($body as $i => $node) { | ||
69 | if ( | ||
70 | $node instanceof Twig_Node_Text | ||
71 | || | ||
72 | ($node instanceof Twig_Node_Print && $node->getNode('expr') instanceof Twig_Node_Expression_Name) | ||
73 | ) { | ||
74 | continue; | ||
75 | } | ||
76 | |||
77 | throw new Twig_Error_Syntax(sprintf('The text to be translated with "trans" can only contain references to simple variables'), $lineno); | ||
78 | } | ||
79 | } | ||
80 | } | ||
diff --git a/inc/Twig/Filter.php b/inc/Twig/Filter.php new file mode 100644 index 00000000..5cfbb662 --- /dev/null +++ b/inc/Twig/Filter.php | |||
@@ -0,0 +1,81 @@ | |||
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 | * Represents a template filter. | ||
14 | * | ||
15 | * Use Twig_SimpleFilter instead. | ||
16 | * | ||
17 | * @author Fabien Potencier <fabien@symfony.com> | ||
18 | * @deprecated since 1.12 (to be removed in 2.0) | ||
19 | */ | ||
20 | abstract class Twig_Filter implements Twig_FilterInterface, Twig_FilterCallableInterface | ||
21 | { | ||
22 | protected $options; | ||
23 | protected $arguments = array(); | ||
24 | |||
25 | public function __construct(array $options = array()) | ||
26 | { | ||
27 | $this->options = array_merge(array( | ||
28 | 'needs_environment' => false, | ||
29 | 'needs_context' => false, | ||
30 | 'pre_escape' => null, | ||
31 | 'preserves_safety' => null, | ||
32 | 'callable' => null, | ||
33 | ), $options); | ||
34 | } | ||
35 | |||
36 | public function setArguments($arguments) | ||
37 | { | ||
38 | $this->arguments = $arguments; | ||
39 | } | ||
40 | |||
41 | public function getArguments() | ||
42 | { | ||
43 | return $this->arguments; | ||
44 | } | ||
45 | |||
46 | public function needsEnvironment() | ||
47 | { | ||
48 | return $this->options['needs_environment']; | ||
49 | } | ||
50 | |||
51 | public function needsContext() | ||
52 | { | ||
53 | return $this->options['needs_context']; | ||
54 | } | ||
55 | |||
56 | public function getSafe(Twig_Node $filterArgs) | ||
57 | { | ||
58 | if (isset($this->options['is_safe'])) { | ||
59 | return $this->options['is_safe']; | ||
60 | } | ||
61 | |||
62 | if (isset($this->options['is_safe_callback'])) { | ||
63 | return call_user_func($this->options['is_safe_callback'], $filterArgs); | ||
64 | } | ||
65 | } | ||
66 | |||
67 | public function getPreservesSafety() | ||
68 | { | ||
69 | return $this->options['preserves_safety']; | ||
70 | } | ||
71 | |||
72 | public function getPreEscape() | ||
73 | { | ||
74 | return $this->options['pre_escape']; | ||
75 | } | ||
76 | |||
77 | public function getCallable() | ||
78 | { | ||
79 | return $this->options['callable']; | ||
80 | } | ||
81 | } | ||
diff --git a/inc/Twig/Filter/Function.php b/inc/Twig/Filter/Function.php new file mode 100644 index 00000000..ad374a55 --- /dev/null +++ b/inc/Twig/Filter/Function.php | |||
@@ -0,0 +1,37 @@ | |||
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 | * Represents a function template filter. | ||
14 | * | ||
15 | * Use Twig_SimpleFilter instead. | ||
16 | * | ||
17 | * @author Fabien Potencier <fabien@symfony.com> | ||
18 | * @deprecated since 1.12 (to be removed in 2.0) | ||
19 | */ | ||
20 | class Twig_Filter_Function extends Twig_Filter | ||
21 | { | ||
22 | protected $function; | ||
23 | |||
24 | public function __construct($function, array $options = array()) | ||
25 | { | ||
26 | $options['callable'] = $function; | ||
27 | |||
28 | parent::__construct($options); | ||
29 | |||
30 | $this->function = $function; | ||
31 | } | ||
32 | |||
33 | public function compile() | ||
34 | { | ||
35 | return $this->function; | ||
36 | } | ||
37 | } | ||
diff --git a/inc/Twig/Filter/Method.php b/inc/Twig/Filter/Method.php new file mode 100644 index 00000000..63c8c3be --- /dev/null +++ b/inc/Twig/Filter/Method.php | |||
@@ -0,0 +1,39 @@ | |||
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 | * Represents a method template filter. | ||
14 | * | ||
15 | * Use Twig_SimpleFilter instead. | ||
16 | * | ||
17 | * @author Fabien Potencier <fabien@symfony.com> | ||
18 | * @deprecated since 1.12 (to be removed in 2.0) | ||
19 | */ | ||
20 | class Twig_Filter_Method extends Twig_Filter | ||
21 | { | ||
22 | protected $extension; | ||
23 | protected $method; | ||
24 | |||
25 | public function __construct(Twig_ExtensionInterface $extension, $method, array $options = array()) | ||
26 | { | ||
27 | $options['callable'] = array($extension, $method); | ||
28 | |||
29 | parent::__construct($options); | ||
30 | |||
31 | $this->extension = $extension; | ||
32 | $this->method = $method; | ||
33 | } | ||
34 | |||
35 | public function compile() | ||
36 | { | ||
37 | return sprintf('$this->env->getExtension(\'%s\')->%s', $this->extension->getName(), $this->method); | ||
38 | } | ||
39 | } | ||
diff --git a/inc/Twig/Filter/Node.php b/inc/Twig/Filter/Node.php new file mode 100644 index 00000000..8744c5e0 --- /dev/null +++ b/inc/Twig/Filter/Node.php | |||
@@ -0,0 +1,39 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2011 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 | * Represents a template filter as a node. | ||
14 | * | ||
15 | * Use Twig_SimpleFilter instead. | ||
16 | * | ||
17 | * @author Fabien Potencier <fabien@symfony.com> | ||
18 | * @deprecated since 1.12 (to be removed in 2.0) | ||
19 | */ | ||
20 | class Twig_Filter_Node extends Twig_Filter | ||
21 | { | ||
22 | protected $class; | ||
23 | |||
24 | public function __construct($class, array $options = array()) | ||
25 | { | ||
26 | parent::__construct($options); | ||
27 | |||
28 | $this->class = $class; | ||
29 | } | ||
30 | |||
31 | public function getClass() | ||
32 | { | ||
33 | return $this->class; | ||
34 | } | ||
35 | |||
36 | public function compile() | ||
37 | { | ||
38 | } | ||
39 | } | ||
diff --git a/inc/Twig/FilterCallableInterface.php b/inc/Twig/FilterCallableInterface.php new file mode 100644 index 00000000..145534df --- /dev/null +++ b/inc/Twig/FilterCallableInterface.php | |||
@@ -0,0 +1,23 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2012 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 | * Represents a callable template filter. | ||
14 | * | ||
15 | * Use Twig_SimpleFilter instead. | ||
16 | * | ||
17 | * @author Fabien Potencier <fabien@symfony.com> | ||
18 | * @deprecated since 1.12 (to be removed in 2.0) | ||
19 | */ | ||
20 | interface Twig_FilterCallableInterface | ||
21 | { | ||
22 | public function getCallable(); | ||
23 | } | ||
diff --git a/inc/Twig/FilterInterface.php b/inc/Twig/FilterInterface.php new file mode 100644 index 00000000..5319ecc9 --- /dev/null +++ b/inc/Twig/FilterInterface.php | |||
@@ -0,0 +1,42 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 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 | * Represents a template filter. | ||
14 | * | ||
15 | * Use Twig_SimpleFilter instead. | ||
16 | * | ||
17 | * @author Fabien Potencier <fabien@symfony.com> | ||
18 | * @deprecated since 1.12 (to be removed in 2.0) | ||
19 | */ | ||
20 | interface Twig_FilterInterface | ||
21 | { | ||
22 | /** | ||
23 | * Compiles a filter. | ||
24 | * | ||
25 | * @return string The PHP code for the filter | ||
26 | */ | ||
27 | public function compile(); | ||
28 | |||
29 | public function needsEnvironment(); | ||
30 | |||
31 | public function needsContext(); | ||
32 | |||
33 | public function getSafe(Twig_Node $filterArgs); | ||
34 | |||
35 | public function getPreservesSafety(); | ||
36 | |||
37 | public function getPreEscape(); | ||
38 | |||
39 | public function setArguments($arguments); | ||
40 | |||
41 | public function getArguments(); | ||
42 | } | ||
diff --git a/inc/Twig/Function.php b/inc/Twig/Function.php new file mode 100644 index 00000000..b5ffb2b0 --- /dev/null +++ b/inc/Twig/Function.php | |||
@@ -0,0 +1,71 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 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 | * Represents a template function. | ||
14 | * | ||
15 | * Use Twig_SimpleFunction instead. | ||
16 | * | ||
17 | * @author Fabien Potencier <fabien@symfony.com> | ||
18 | * @deprecated since 1.12 (to be removed in 2.0) | ||
19 | */ | ||
20 | abstract class Twig_Function implements Twig_FunctionInterface, Twig_FunctionCallableInterface | ||
21 | { | ||
22 | protected $options; | ||
23 | protected $arguments = array(); | ||
24 | |||
25 | public function __construct(array $options = array()) | ||
26 | { | ||
27 | $this->options = array_merge(array( | ||
28 | 'needs_environment' => false, | ||
29 | 'needs_context' => false, | ||
30 | 'callable' => null, | ||
31 | ), $options); | ||
32 | } | ||
33 | |||
34 | public function setArguments($arguments) | ||
35 | { | ||
36 | $this->arguments = $arguments; | ||
37 | } | ||
38 | |||
39 | public function getArguments() | ||
40 | { | ||
41 | return $this->arguments; | ||
42 | } | ||
43 | |||
44 | public function needsEnvironment() | ||
45 | { | ||
46 | return $this->options['needs_environment']; | ||
47 | } | ||
48 | |||
49 | public function needsContext() | ||
50 | { | ||
51 | return $this->options['needs_context']; | ||
52 | } | ||
53 | |||
54 | public function getSafe(Twig_Node $functionArgs) | ||
55 | { | ||
56 | if (isset($this->options['is_safe'])) { | ||
57 | return $this->options['is_safe']; | ||
58 | } | ||
59 | |||
60 | if (isset($this->options['is_safe_callback'])) { | ||
61 | return call_user_func($this->options['is_safe_callback'], $functionArgs); | ||
62 | } | ||
63 | |||
64 | return array(); | ||
65 | } | ||
66 | |||
67 | public function getCallable() | ||
68 | { | ||
69 | return $this->options['callable']; | ||
70 | } | ||
71 | } | ||
diff --git a/inc/Twig/Function/Function.php b/inc/Twig/Function/Function.php new file mode 100644 index 00000000..d1e1b96a --- /dev/null +++ b/inc/Twig/Function/Function.php | |||
@@ -0,0 +1,38 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2009 Fabien Potencier | ||
7 | * (c) 2010 Arnaud Le Blanc | ||
8 | * | ||
9 | * For the full copyright and license information, please view the LICENSE | ||
10 | * file that was distributed with this source code. | ||
11 | */ | ||
12 | |||
13 | /** | ||
14 | * Represents a function template function. | ||
15 | * | ||
16 | * Use Twig_SimpleFunction instead. | ||
17 | * | ||
18 | * @author Arnaud Le Blanc <arnaud.lb@gmail.com> | ||
19 | * @deprecated since 1.12 (to be removed in 2.0) | ||
20 | */ | ||
21 | class Twig_Function_Function extends Twig_Function | ||
22 | { | ||
23 | protected $function; | ||
24 | |||
25 | public function __construct($function, array $options = array()) | ||
26 | { | ||
27 | $options['callable'] = $function; | ||
28 | |||
29 | parent::__construct($options); | ||
30 | |||
31 | $this->function = $function; | ||
32 | } | ||
33 | |||
34 | public function compile() | ||
35 | { | ||
36 | return $this->function; | ||
37 | } | ||
38 | } | ||
diff --git a/inc/Twig/Function/Method.php b/inc/Twig/Function/Method.php new file mode 100644 index 00000000..67039a95 --- /dev/null +++ b/inc/Twig/Function/Method.php | |||
@@ -0,0 +1,40 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2009 Fabien Potencier | ||
7 | * (c) 2010 Arnaud Le Blanc | ||
8 | * | ||
9 | * For the full copyright and license information, please view the LICENSE | ||
10 | * file that was distributed with this source code. | ||
11 | */ | ||
12 | |||
13 | /** | ||
14 | * Represents a method template function. | ||
15 | * | ||
16 | * Use Twig_SimpleFunction instead. | ||
17 | * | ||
18 | * @author Arnaud Le Blanc <arnaud.lb@gmail.com> | ||
19 | * @deprecated since 1.12 (to be removed in 2.0) | ||
20 | */ | ||
21 | class Twig_Function_Method extends Twig_Function | ||
22 | { | ||
23 | protected $extension; | ||
24 | protected $method; | ||
25 | |||
26 | public function __construct(Twig_ExtensionInterface $extension, $method, array $options = array()) | ||
27 | { | ||
28 | $options['callable'] = array($extension, $method); | ||
29 | |||
30 | parent::__construct($options); | ||
31 | |||
32 | $this->extension = $extension; | ||
33 | $this->method = $method; | ||
34 | } | ||
35 | |||
36 | public function compile() | ||
37 | { | ||
38 | return sprintf('$this->env->getExtension(\'%s\')->%s', $this->extension->getName(), $this->method); | ||
39 | } | ||
40 | } | ||
diff --git a/inc/Twig/Function/Node.php b/inc/Twig/Function/Node.php new file mode 100644 index 00000000..06a0d0db --- /dev/null +++ b/inc/Twig/Function/Node.php | |||
@@ -0,0 +1,39 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2011 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 | * Represents a template function as a node. | ||
14 | * | ||
15 | * Use Twig_SimpleFunction instead. | ||
16 | * | ||
17 | * @author Fabien Potencier <fabien@symfony.com> | ||
18 | * @deprecated since 1.12 (to be removed in 2.0) | ||
19 | */ | ||
20 | class Twig_Function_Node extends Twig_Function | ||
21 | { | ||
22 | protected $class; | ||
23 | |||
24 | public function __construct($class, array $options = array()) | ||
25 | { | ||
26 | parent::__construct($options); | ||
27 | |||
28 | $this->class = $class; | ||
29 | } | ||
30 | |||
31 | public function getClass() | ||
32 | { | ||
33 | return $this->class; | ||
34 | } | ||
35 | |||
36 | public function compile() | ||
37 | { | ||
38 | } | ||
39 | } | ||
diff --git a/inc/Twig/FunctionCallableInterface.php b/inc/Twig/FunctionCallableInterface.php new file mode 100644 index 00000000..0aab4f5e --- /dev/null +++ b/inc/Twig/FunctionCallableInterface.php | |||
@@ -0,0 +1,23 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2012 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 | * Represents a callable template function. | ||
14 | * | ||
15 | * Use Twig_SimpleFunction instead. | ||
16 | * | ||
17 | * @author Fabien Potencier <fabien@symfony.com> | ||
18 | * @deprecated since 1.12 (to be removed in 2.0) | ||
19 | */ | ||
20 | interface Twig_FunctionCallableInterface | ||
21 | { | ||
22 | public function getCallable(); | ||
23 | } | ||
diff --git a/inc/Twig/FunctionInterface.php b/inc/Twig/FunctionInterface.php new file mode 100644 index 00000000..67f4f89c --- /dev/null +++ b/inc/Twig/FunctionInterface.php | |||
@@ -0,0 +1,39 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 Fabien Potencier | ||
7 | * (c) 2010 Arnaud Le Blanc | ||
8 | * | ||
9 | * For the full copyright and license information, please view the LICENSE | ||
10 | * file that was distributed with this source code. | ||
11 | */ | ||
12 | |||
13 | /** | ||
14 | * Represents a template function. | ||
15 | * | ||
16 | * Use Twig_SimpleFunction instead. | ||
17 | * | ||
18 | * @author Arnaud Le Blanc <arnaud.lb@gmail.com> | ||
19 | * @deprecated since 1.12 (to be removed in 2.0) | ||
20 | */ | ||
21 | interface Twig_FunctionInterface | ||
22 | { | ||
23 | /** | ||
24 | * Compiles a function. | ||
25 | * | ||
26 | * @return string The PHP code for the function | ||
27 | */ | ||
28 | public function compile(); | ||
29 | |||
30 | public function needsEnvironment(); | ||
31 | |||
32 | public function needsContext(); | ||
33 | |||
34 | public function getSafe(Twig_Node $filterArgs); | ||
35 | |||
36 | public function setArguments($arguments); | ||
37 | |||
38 | public function getArguments(); | ||
39 | } | ||
diff --git a/inc/Twig/Gettext/Extractor.php b/inc/Twig/Gettext/Extractor.php new file mode 100644 index 00000000..e7fa1af2 --- /dev/null +++ b/inc/Twig/Gettext/Extractor.php | |||
@@ -0,0 +1,95 @@ | |||
1 | <?php | ||
2 | |||
3 | /** | ||
4 | * This file is part of the Twig Gettext utility. | ||
5 | * | ||
6 | * (c) Саша Стаменковић <umpirsky@gmail.com> | ||
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 | namespace Twig\Gettext; | ||
13 | |||
14 | use Symfony\Component\Filesystem\Filesystem; | ||
15 | |||
16 | /** | ||
17 | * Extracts translations from twig templates. | ||
18 | * | ||
19 | * @author Саша Стаменковић <umpirsky@gmail.com> | ||
20 | */ | ||
21 | class Extractor | ||
22 | { | ||
23 | /** | ||
24 | * @var \Twig_Environment | ||
25 | */ | ||
26 | protected $environment; | ||
27 | |||
28 | /** | ||
29 | * Template cached file names. | ||
30 | * | ||
31 | * @var string[] | ||
32 | */ | ||
33 | protected $templates; | ||
34 | |||
35 | /** | ||
36 | * Gettext parameters. | ||
37 | * | ||
38 | * @var string[] | ||
39 | */ | ||
40 | protected $parameters; | ||
41 | |||
42 | public function __construct(\Twig_Environment $environment) | ||
43 | { | ||
44 | $this->environment = $environment; | ||
45 | $this->reset(); | ||
46 | } | ||
47 | |||
48 | protected function reset() | ||
49 | { | ||
50 | $this->templates = array(); | ||
51 | $this->parameters = array(); | ||
52 | } | ||
53 | |||
54 | public function addTemplate($path) | ||
55 | { | ||
56 | $this->environment->loadTemplate($path); | ||
57 | $this->templates[] = $this->environment->getCacheFilename($path); | ||
58 | } | ||
59 | |||
60 | public function addGettextParameter($parameter) | ||
61 | { | ||
62 | $this->parameters[] = $parameter; | ||
63 | } | ||
64 | |||
65 | public function setGettextParameters(array $parameters) | ||
66 | { | ||
67 | $this->parameters = $parameters; | ||
68 | } | ||
69 | |||
70 | public function extract() | ||
71 | { | ||
72 | $command = 'xgettext'; | ||
73 | $command .= ' '.join(' ', $this->parameters); | ||
74 | $command .= ' '.join(' ', $this->templates); | ||
75 | |||
76 | $error = 0; | ||
77 | $output = system($command, $error); | ||
78 | if (0 !== $error) { | ||
79 | throw new \RuntimeException(sprintf( | ||
80 | 'Gettext command "%s" failed with error code %s and output: %s', | ||
81 | $command, | ||
82 | $error, | ||
83 | $output | ||
84 | )); | ||
85 | } | ||
86 | |||
87 | $this->reset(); | ||
88 | } | ||
89 | |||
90 | public function __destruct() | ||
91 | { | ||
92 | $filesystem = new Filesystem(); | ||
93 | $filesystem->remove($this->environment->getCache()); | ||
94 | } | ||
95 | } | ||
diff --git a/inc/Twig/Gettext/Loader/Filesystem.php b/inc/Twig/Gettext/Loader/Filesystem.php new file mode 100644 index 00000000..b011b032 --- /dev/null +++ b/inc/Twig/Gettext/Loader/Filesystem.php | |||
@@ -0,0 +1,58 @@ | |||
1 | <?php | ||
2 | |||
3 | /** | ||
4 | * This file is part of the Twig Gettext utility. | ||
5 | * | ||
6 | * (c) Саша Стаменковић <umpirsky@gmail.com> | ||
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 | namespace Twig\Gettext\Loader; | ||
13 | |||
14 | /** | ||
15 | * Loads template from the filesystem. | ||
16 | * | ||
17 | * @author Саша Стаменковић <umpirsky@gmail.com> | ||
18 | */ | ||
19 | class Filesystem extends \Twig_Loader_Filesystem | ||
20 | { | ||
21 | /** | ||
22 | * Hacked find template to allow loading templates by absolute path. | ||
23 | * | ||
24 | * @param string $name template name or absolute path | ||
25 | */ | ||
26 | protected function findTemplate($name) | ||
27 | { | ||
28 | // normalize name | ||
29 | $name = preg_replace('#/{2,}#', '/', strtr($name, '\\', '/')); | ||
30 | |||
31 | if (isset($this->cache[$name])) { | ||
32 | return $this->cache[$name]; | ||
33 | } | ||
34 | |||
35 | $this->validateName($name); | ||
36 | |||
37 | $namespace = '__main__'; | ||
38 | if (isset($name[0]) && '@' == $name[0]) { | ||
39 | if (false === $pos = strpos($name, '/')) { | ||
40 | throw new \InvalidArgumentException(sprintf('Malformed namespaced template name "%s" (expecting "@namespace/template_name").', $name)); | ||
41 | } | ||
42 | |||
43 | $namespace = substr($name, 1, $pos - 1); | ||
44 | |||
45 | $name = substr($name, $pos + 1); | ||
46 | } | ||
47 | |||
48 | if (!isset($this->paths[$namespace])) { | ||
49 | throw new \Twig_Error_Loader(sprintf('There are no registered paths for namespace "%s".', $namespace)); | ||
50 | } | ||
51 | |||
52 | if (is_file($name)) { | ||
53 | return $this->cache[$name] = $name; | ||
54 | } | ||
55 | |||
56 | return __DIR__.'/../Test/Fixtures/twig/empty.twig'; | ||
57 | } | ||
58 | } | ||
diff --git a/inc/Twig/Gettext/Routing/Generator/UrlGenerator.php b/inc/Twig/Gettext/Routing/Generator/UrlGenerator.php new file mode 100644 index 00000000..9e3431bd --- /dev/null +++ b/inc/Twig/Gettext/Routing/Generator/UrlGenerator.php | |||
@@ -0,0 +1,39 @@ | |||
1 | <?php | ||
2 | |||
3 | /** | ||
4 | * This file is part of the Twig Gettext utility. | ||
5 | * | ||
6 | * (c) Саша Стаменковић <umpirsky@gmail.com> | ||
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 | namespace Twig\Gettext\Routing\Generator; | ||
13 | |||
14 | use Symfony\Component\Routing\Generator\UrlGeneratorInterface; | ||
15 | use Symfony\Component\Routing\RequestContext; | ||
16 | |||
17 | /** | ||
18 | * Dummy url generator. | ||
19 | * | ||
20 | * @author Саша Стаменковић <umpirsky@gmail.com> | ||
21 | */ | ||
22 | class UrlGenerator implements UrlGeneratorInterface | ||
23 | { | ||
24 | protected $context; | ||
25 | |||
26 | public function generate($name, $parameters = array(), $absolute = false) | ||
27 | { | ||
28 | } | ||
29 | |||
30 | public function getContext() | ||
31 | { | ||
32 | return $this->context; | ||
33 | } | ||
34 | |||
35 | public function setContext(RequestContext $context) | ||
36 | { | ||
37 | $this->context = $context; | ||
38 | } | ||
39 | } | ||
diff --git a/inc/Twig/Gettext/Test/ExtractorTest.php b/inc/Twig/Gettext/Test/ExtractorTest.php new file mode 100644 index 00000000..d467835f --- /dev/null +++ b/inc/Twig/Gettext/Test/ExtractorTest.php | |||
@@ -0,0 +1,123 @@ | |||
1 | <?php | ||
2 | |||
3 | /** | ||
4 | * This file is part of the Twig Gettext utility. | ||
5 | * | ||
6 | * (c) Саша Стаменковић <umpirsky@gmail.com> | ||
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 | namespace Twig\Gettext\Test; | ||
13 | |||
14 | use Twig\Gettext\Extractor; | ||
15 | use Twig\Gettext\Loader\Filesystem; | ||
16 | use Symfony\Component\Translation\Loader\PoFileLoader; | ||
17 | |||
18 | /** | ||
19 | * @author Саша Стаменковић <umpirsky@gmail.com> | ||
20 | */ | ||
21 | class ExtractorTest extends \PHPUnit_Framework_TestCase | ||
22 | { | ||
23 | /** | ||
24 | * @var \Twig_Environment | ||
25 | */ | ||
26 | protected $twig; | ||
27 | |||
28 | /** | ||
29 | * @var PoFileLoader | ||
30 | */ | ||
31 | protected $loader; | ||
32 | |||
33 | protected function setUp() | ||
34 | { | ||
35 | $this->twig = new \Twig_Environment(new Filesystem('/'), array( | ||
36 | 'cache' => '/tmp/cache/'.uniqid(), | ||
37 | 'auto_reload' => true | ||
38 | )); | ||
39 | $this->twig->addExtension(new \Twig_Extensions_Extension_I18n()); | ||
40 | |||
41 | $this->loader = new PoFileLoader(); | ||
42 | } | ||
43 | |||
44 | /** | ||
45 | * @dataProvider testExtractDataProvider | ||
46 | */ | ||
47 | public function testExtract(array $templates, array $parameters, array $messages) | ||
48 | { | ||
49 | $extractor = new Extractor($this->twig); | ||
50 | |||
51 | foreach ($templates as $template) { | ||
52 | $extractor->addTemplate($template); | ||
53 | } | ||
54 | foreach ($parameters as $parameter) { | ||
55 | $extractor->addGettextParameter($parameter); | ||
56 | } | ||
57 | |||
58 | $extractor->extract(); | ||
59 | |||
60 | $catalog = $this->loader->load($this->getPotFile(), null); | ||
61 | |||
62 | foreach ($messages as $message) { | ||
63 | $this->assertTrue( | ||
64 | $catalog->has($message), | ||
65 | sprintf('Message "%s" not found in catalog.', $message) | ||
66 | ); | ||
67 | } | ||
68 | } | ||
69 | |||
70 | public function testExtractDataProvider() | ||
71 | { | ||
72 | return array( | ||
73 | array( | ||
74 | array( | ||
75 | __DIR__.'/Fixtures/twig/singular.twig', | ||
76 | __DIR__.'/Fixtures/twig/plural.twig', | ||
77 | ), | ||
78 | $this->getGettextParameters(), | ||
79 | array( | ||
80 | 'Hello %name%!', | ||
81 | 'Hello World!', | ||
82 | 'Hey %name%, I have one apple.', | ||
83 | 'Hey %name%, I have %count% apples.', | ||
84 | ), | ||
85 | ), | ||
86 | ); | ||
87 | } | ||
88 | |||
89 | public function testExtractNoTranslations() | ||
90 | { | ||
91 | $extractor = new Extractor($this->twig); | ||
92 | |||
93 | $extractor->addTemplate(__DIR__.'/Fixtures/twig/empty.twig'); | ||
94 | $extractor->setGettextParameters($this->getGettextParameters()); | ||
95 | |||
96 | $extractor->extract(); | ||
97 | |||
98 | $catalog = $this->loader->load($this->getPotFile(), null); | ||
99 | |||
100 | $this->assertEmpty($catalog->all('messages')); | ||
101 | } | ||
102 | |||
103 | private function getPotFile() | ||
104 | { | ||
105 | return __DIR__.'/Fixtures/messages.pot'; | ||
106 | } | ||
107 | |||
108 | private function getGettextParameters() | ||
109 | { | ||
110 | return array( | ||
111 | '--force-po', | ||
112 | '-o', | ||
113 | $this->getPotFile(), | ||
114 | ); | ||
115 | } | ||
116 | |||
117 | protected function tearDown() | ||
118 | { | ||
119 | if (file_exists($this->getPotFile())) { | ||
120 | unlink($this->getPotFile()); | ||
121 | } | ||
122 | } | ||
123 | } | ||
diff --git a/inc/Twig/Gettext/Test/Fixtures/twig/empty.twig b/inc/Twig/Gettext/Test/Fixtures/twig/empty.twig new file mode 100644 index 00000000..05f0d26a --- /dev/null +++ b/inc/Twig/Gettext/Test/Fixtures/twig/empty.twig | |||
@@ -0,0 +1 @@ | |||
Nothing to translate here. | |||
diff --git a/inc/Twig/Gettext/Test/Fixtures/twig/plural.twig b/inc/Twig/Gettext/Test/Fixtures/twig/plural.twig new file mode 100644 index 00000000..f9754ff4 --- /dev/null +++ b/inc/Twig/Gettext/Test/Fixtures/twig/plural.twig | |||
@@ -0,0 +1,5 @@ | |||
1 | {% trans %} | ||
2 | Hey {{ name }}, I have one apple. | ||
3 | {% plural apple_count %} | ||
4 | Hey {{ name }}, I have {{ count }} apples. | ||
5 | {% endtrans %} | ||
diff --git a/inc/Twig/Gettext/Test/Fixtures/twig/singular.twig b/inc/Twig/Gettext/Test/Fixtures/twig/singular.twig new file mode 100644 index 00000000..d757cf90 --- /dev/null +++ b/inc/Twig/Gettext/Test/Fixtures/twig/singular.twig | |||
@@ -0,0 +1,9 @@ | |||
1 | {% trans "Hello World!" %} | ||
2 | |||
3 | {% trans %} | ||
4 | Hello World! | ||
5 | {% endtrans %} | ||
6 | |||
7 | {% trans %} | ||
8 | Hello {{ name }}! | ||
9 | {% endtrans %} | ||
diff --git a/inc/Twig/Lexer.php b/inc/Twig/Lexer.php new file mode 100644 index 00000000..000b038e --- /dev/null +++ b/inc/Twig/Lexer.php | |||
@@ -0,0 +1,408 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2009 Fabien Potencier | ||
7 | * (c) 2009 Armin Ronacher | ||
8 | * | ||
9 | * For the full copyright and license information, please view the LICENSE | ||
10 | * file that was distributed with this source code. | ||
11 | */ | ||
12 | |||
13 | /** | ||
14 | * Lexes a template string. | ||
15 | * | ||
16 | * @author Fabien Potencier <fabien@symfony.com> | ||
17 | */ | ||
18 | class Twig_Lexer implements Twig_LexerInterface | ||
19 | { | ||
20 | protected $tokens; | ||
21 | protected $code; | ||
22 | protected $cursor; | ||
23 | protected $lineno; | ||
24 | protected $end; | ||
25 | protected $state; | ||
26 | protected $states; | ||
27 | protected $brackets; | ||
28 | protected $env; | ||
29 | protected $filename; | ||
30 | protected $options; | ||
31 | protected $regexes; | ||
32 | protected $position; | ||
33 | protected $positions; | ||
34 | protected $currentVarBlockLine; | ||
35 | |||
36 | const STATE_DATA = 0; | ||
37 | const STATE_BLOCK = 1; | ||
38 | const STATE_VAR = 2; | ||
39 | const STATE_STRING = 3; | ||
40 | const STATE_INTERPOLATION = 4; | ||
41 | |||
42 | const REGEX_NAME = '/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/A'; | ||
43 | const REGEX_NUMBER = '/[0-9]+(?:\.[0-9]+)?/A'; | ||
44 | const REGEX_STRING = '/"([^#"\\\\]*(?:\\\\.[^#"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'/As'; | ||
45 | const REGEX_DQ_STRING_DELIM = '/"/A'; | ||
46 | const REGEX_DQ_STRING_PART = '/[^#"\\\\]*(?:(?:\\\\.|#(?!\{))[^#"\\\\]*)*/As'; | ||
47 | const PUNCTUATION = '()[]{}?:.,|'; | ||
48 | |||
49 | public function __construct(Twig_Environment $env, array $options = array()) | ||
50 | { | ||
51 | $this->env = $env; | ||
52 | |||
53 | $this->options = array_merge(array( | ||
54 | 'tag_comment' => array('{#', '#}'), | ||
55 | 'tag_block' => array('{%', '%}'), | ||
56 | 'tag_variable' => array('{{', '}}'), | ||
57 | 'whitespace_trim' => '-', | ||
58 | 'interpolation' => array('#{', '}'), | ||
59 | ), $options); | ||
60 | |||
61 | $this->regexes = array( | ||
62 | 'lex_var' => '/\s*'.preg_quote($this->options['whitespace_trim'].$this->options['tag_variable'][1], '/').'\s*|\s*'.preg_quote($this->options['tag_variable'][1], '/').'/A', | ||
63 | 'lex_block' => '/\s*(?:'.preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '/').'\s*|\s*'.preg_quote($this->options['tag_block'][1], '/').')\n?/A', | ||
64 | 'lex_raw_data' => '/('.preg_quote($this->options['tag_block'][0].$this->options['whitespace_trim'], '/').'|'.preg_quote($this->options['tag_block'][0], '/').')\s*(?:end%s)\s*(?:'.preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '/').'\s*|\s*'.preg_quote($this->options['tag_block'][1], '/').')/s', | ||
65 | 'operator' => $this->getOperatorRegex(), | ||
66 | 'lex_comment' => '/(?:'.preg_quote($this->options['whitespace_trim'], '/').preg_quote($this->options['tag_comment'][1], '/').'\s*|'.preg_quote($this->options['tag_comment'][1], '/').')\n?/s', | ||
67 | 'lex_block_raw' => '/\s*(raw|verbatim)\s*(?:'.preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '/').'\s*|\s*'.preg_quote($this->options['tag_block'][1], '/').')/As', | ||
68 | 'lex_block_line' => '/\s*line\s+(\d+)\s*'.preg_quote($this->options['tag_block'][1], '/').'/As', | ||
69 | 'lex_tokens_start' => '/('.preg_quote($this->options['tag_variable'][0], '/').'|'.preg_quote($this->options['tag_block'][0], '/').'|'.preg_quote($this->options['tag_comment'][0], '/').')('.preg_quote($this->options['whitespace_trim'], '/').')?/s', | ||
70 | 'interpolation_start' => '/'.preg_quote($this->options['interpolation'][0], '/').'\s*/A', | ||
71 | 'interpolation_end' => '/\s*'.preg_quote($this->options['interpolation'][1], '/').'/A', | ||
72 | ); | ||
73 | } | ||
74 | |||
75 | /** | ||
76 | * Tokenizes a source code. | ||
77 | * | ||
78 | * @param string $code The source code | ||
79 | * @param string $filename A unique identifier for the source code | ||
80 | * | ||
81 | * @return Twig_TokenStream A token stream instance | ||
82 | */ | ||
83 | public function tokenize($code, $filename = null) | ||
84 | { | ||
85 | if (function_exists('mb_internal_encoding') && ((int) ini_get('mbstring.func_overload')) & 2) { | ||
86 | $mbEncoding = mb_internal_encoding(); | ||
87 | mb_internal_encoding('ASCII'); | ||
88 | } | ||
89 | |||
90 | $this->code = str_replace(array("\r\n", "\r"), "\n", $code); | ||
91 | $this->filename = $filename; | ||
92 | $this->cursor = 0; | ||
93 | $this->lineno = 1; | ||
94 | $this->end = strlen($this->code); | ||
95 | $this->tokens = array(); | ||
96 | $this->state = self::STATE_DATA; | ||
97 | $this->states = array(); | ||
98 | $this->brackets = array(); | ||
99 | $this->position = -1; | ||
100 | |||
101 | // find all token starts in one go | ||
102 | preg_match_all($this->regexes['lex_tokens_start'], $this->code, $matches, PREG_OFFSET_CAPTURE); | ||
103 | $this->positions = $matches; | ||
104 | |||
105 | while ($this->cursor < $this->end) { | ||
106 | // dispatch to the lexing functions depending | ||
107 | // on the current state | ||
108 | switch ($this->state) { | ||
109 | case self::STATE_DATA: | ||
110 | $this->lexData(); | ||
111 | break; | ||
112 | |||
113 | case self::STATE_BLOCK: | ||
114 | $this->lexBlock(); | ||
115 | break; | ||
116 | |||
117 | case self::STATE_VAR: | ||
118 | $this->lexVar(); | ||
119 | break; | ||
120 | |||
121 | case self::STATE_STRING: | ||
122 | $this->lexString(); | ||
123 | break; | ||
124 | |||
125 | case self::STATE_INTERPOLATION: | ||
126 | $this->lexInterpolation(); | ||
127 | break; | ||
128 | } | ||
129 | } | ||
130 | |||
131 | $this->pushToken(Twig_Token::EOF_TYPE); | ||
132 | |||
133 | if (!empty($this->brackets)) { | ||
134 | list($expect, $lineno) = array_pop($this->brackets); | ||
135 | throw new Twig_Error_Syntax(sprintf('Unclosed "%s"', $expect), $lineno, $this->filename); | ||
136 | } | ||
137 | |||
138 | if (isset($mbEncoding)) { | ||
139 | mb_internal_encoding($mbEncoding); | ||
140 | } | ||
141 | |||
142 | return new Twig_TokenStream($this->tokens, $this->filename); | ||
143 | } | ||
144 | |||
145 | protected function lexData() | ||
146 | { | ||
147 | // if no matches are left we return the rest of the template as simple text token | ||
148 | if ($this->position == count($this->positions[0]) - 1) { | ||
149 | $this->pushToken(Twig_Token::TEXT_TYPE, substr($this->code, $this->cursor)); | ||
150 | $this->cursor = $this->end; | ||
151 | |||
152 | return; | ||
153 | } | ||
154 | |||
155 | // Find the first token after the current cursor | ||
156 | $position = $this->positions[0][++$this->position]; | ||
157 | while ($position[1] < $this->cursor) { | ||
158 | if ($this->position == count($this->positions[0]) - 1) { | ||
159 | return; | ||
160 | } | ||
161 | $position = $this->positions[0][++$this->position]; | ||
162 | } | ||
163 | |||
164 | // push the template text first | ||
165 | $text = $textContent = substr($this->code, $this->cursor, $position[1] - $this->cursor); | ||
166 | if (isset($this->positions[2][$this->position][0])) { | ||
167 | $text = rtrim($text); | ||
168 | } | ||
169 | $this->pushToken(Twig_Token::TEXT_TYPE, $text); | ||
170 | $this->moveCursor($textContent.$position[0]); | ||
171 | |||
172 | switch ($this->positions[1][$this->position][0]) { | ||
173 | case $this->options['tag_comment'][0]: | ||
174 | $this->lexComment(); | ||
175 | break; | ||
176 | |||
177 | case $this->options['tag_block'][0]: | ||
178 | // raw data? | ||
179 | if (preg_match($this->regexes['lex_block_raw'], $this->code, $match, null, $this->cursor)) { | ||
180 | $this->moveCursor($match[0]); | ||
181 | $this->lexRawData($match[1]); | ||
182 | // {% line \d+ %} | ||
183 | } elseif (preg_match($this->regexes['lex_block_line'], $this->code, $match, null, $this->cursor)) { | ||
184 | $this->moveCursor($match[0]); | ||
185 | $this->lineno = (int) $match[1]; | ||
186 | } else { | ||
187 | $this->pushToken(Twig_Token::BLOCK_START_TYPE); | ||
188 | $this->pushState(self::STATE_BLOCK); | ||
189 | $this->currentVarBlockLine = $this->lineno; | ||
190 | } | ||
191 | break; | ||
192 | |||
193 | case $this->options['tag_variable'][0]: | ||
194 | $this->pushToken(Twig_Token::VAR_START_TYPE); | ||
195 | $this->pushState(self::STATE_VAR); | ||
196 | $this->currentVarBlockLine = $this->lineno; | ||
197 | break; | ||
198 | } | ||
199 | } | ||
200 | |||
201 | protected function lexBlock() | ||
202 | { | ||
203 | if (empty($this->brackets) && preg_match($this->regexes['lex_block'], $this->code, $match, null, $this->cursor)) { | ||
204 | $this->pushToken(Twig_Token::BLOCK_END_TYPE); | ||
205 | $this->moveCursor($match[0]); | ||
206 | $this->popState(); | ||
207 | } else { | ||
208 | $this->lexExpression(); | ||
209 | } | ||
210 | } | ||
211 | |||
212 | protected function lexVar() | ||
213 | { | ||
214 | if (empty($this->brackets) && preg_match($this->regexes['lex_var'], $this->code, $match, null, $this->cursor)) { | ||
215 | $this->pushToken(Twig_Token::VAR_END_TYPE); | ||
216 | $this->moveCursor($match[0]); | ||
217 | $this->popState(); | ||
218 | } else { | ||
219 | $this->lexExpression(); | ||
220 | } | ||
221 | } | ||
222 | |||
223 | protected function lexExpression() | ||
224 | { | ||
225 | // whitespace | ||
226 | if (preg_match('/\s+/A', $this->code, $match, null, $this->cursor)) { | ||
227 | $this->moveCursor($match[0]); | ||
228 | |||
229 | if ($this->cursor >= $this->end) { | ||
230 | throw new Twig_Error_Syntax(sprintf('Unclosed "%s"', $this->state === self::STATE_BLOCK ? 'block' : 'variable'), $this->currentVarBlockLine, $this->filename); | ||
231 | } | ||
232 | } | ||
233 | |||
234 | // operators | ||
235 | if (preg_match($this->regexes['operator'], $this->code, $match, null, $this->cursor)) { | ||
236 | $this->pushToken(Twig_Token::OPERATOR_TYPE, $match[0]); | ||
237 | $this->moveCursor($match[0]); | ||
238 | } | ||
239 | // names | ||
240 | elseif (preg_match(self::REGEX_NAME, $this->code, $match, null, $this->cursor)) { | ||
241 | $this->pushToken(Twig_Token::NAME_TYPE, $match[0]); | ||
242 | $this->moveCursor($match[0]); | ||
243 | } | ||
244 | // numbers | ||
245 | elseif (preg_match(self::REGEX_NUMBER, $this->code, $match, null, $this->cursor)) { | ||
246 | $number = (float) $match[0]; // floats | ||
247 | if (ctype_digit($match[0]) && $number <= PHP_INT_MAX) { | ||
248 | $number = (int) $match[0]; // integers lower than the maximum | ||
249 | } | ||
250 | $this->pushToken(Twig_Token::NUMBER_TYPE, $number); | ||
251 | $this->moveCursor($match[0]); | ||
252 | } | ||
253 | // punctuation | ||
254 | elseif (false !== strpos(self::PUNCTUATION, $this->code[$this->cursor])) { | ||
255 | // opening bracket | ||
256 | if (false !== strpos('([{', $this->code[$this->cursor])) { | ||
257 | $this->brackets[] = array($this->code[$this->cursor], $this->lineno); | ||
258 | } | ||
259 | // closing bracket | ||
260 | elseif (false !== strpos(')]}', $this->code[$this->cursor])) { | ||
261 | if (empty($this->brackets)) { | ||
262 | throw new Twig_Error_Syntax(sprintf('Unexpected "%s"', $this->code[$this->cursor]), $this->lineno, $this->filename); | ||
263 | } | ||
264 | |||
265 | list($expect, $lineno) = array_pop($this->brackets); | ||
266 | if ($this->code[$this->cursor] != strtr($expect, '([{', ')]}')) { | ||
267 | throw new Twig_Error_Syntax(sprintf('Unclosed "%s"', $expect), $lineno, $this->filename); | ||
268 | } | ||
269 | } | ||
270 | |||
271 | $this->pushToken(Twig_Token::PUNCTUATION_TYPE, $this->code[$this->cursor]); | ||
272 | ++$this->cursor; | ||
273 | } | ||
274 | // strings | ||
275 | elseif (preg_match(self::REGEX_STRING, $this->code, $match, null, $this->cursor)) { | ||
276 | $this->pushToken(Twig_Token::STRING_TYPE, stripcslashes(substr($match[0], 1, -1))); | ||
277 | $this->moveCursor($match[0]); | ||
278 | } | ||
279 | // opening double quoted string | ||
280 | elseif (preg_match(self::REGEX_DQ_STRING_DELIM, $this->code, $match, null, $this->cursor)) { | ||
281 | $this->brackets[] = array('"', $this->lineno); | ||
282 | $this->pushState(self::STATE_STRING); | ||
283 | $this->moveCursor($match[0]); | ||
284 | } | ||
285 | // unlexable | ||
286 | else { | ||
287 | throw new Twig_Error_Syntax(sprintf('Unexpected character "%s"', $this->code[$this->cursor]), $this->lineno, $this->filename); | ||
288 | } | ||
289 | } | ||
290 | |||
291 | protected function lexRawData($tag) | ||
292 | { | ||
293 | if (!preg_match(str_replace('%s', $tag, $this->regexes['lex_raw_data']), $this->code, $match, PREG_OFFSET_CAPTURE, $this->cursor)) { | ||
294 | throw new Twig_Error_Syntax(sprintf('Unexpected end of file: Unclosed "%s" block', $tag), $this->lineno, $this->filename); | ||
295 | } | ||
296 | |||
297 | $text = substr($this->code, $this->cursor, $match[0][1] - $this->cursor); | ||
298 | $this->moveCursor($text.$match[0][0]); | ||
299 | |||
300 | if (false !== strpos($match[1][0], $this->options['whitespace_trim'])) { | ||
301 | $text = rtrim($text); | ||
302 | } | ||
303 | |||
304 | $this->pushToken(Twig_Token::TEXT_TYPE, $text); | ||
305 | } | ||
306 | |||
307 | protected function lexComment() | ||
308 | { | ||
309 | if (!preg_match($this->regexes['lex_comment'], $this->code, $match, PREG_OFFSET_CAPTURE, $this->cursor)) { | ||
310 | throw new Twig_Error_Syntax('Unclosed comment', $this->lineno, $this->filename); | ||
311 | } | ||
312 | |||
313 | $this->moveCursor(substr($this->code, $this->cursor, $match[0][1] - $this->cursor).$match[0][0]); | ||
314 | } | ||
315 | |||
316 | protected function lexString() | ||
317 | { | ||
318 | if (preg_match($this->regexes['interpolation_start'], $this->code, $match, null, $this->cursor)) { | ||
319 | $this->brackets[] = array($this->options['interpolation'][0], $this->lineno); | ||
320 | $this->pushToken(Twig_Token::INTERPOLATION_START_TYPE); | ||
321 | $this->moveCursor($match[0]); | ||
322 | $this->pushState(self::STATE_INTERPOLATION); | ||
323 | |||
324 | } elseif (preg_match(self::REGEX_DQ_STRING_PART, $this->code, $match, null, $this->cursor) && strlen($match[0]) > 0) { | ||
325 | $this->pushToken(Twig_Token::STRING_TYPE, stripcslashes($match[0])); | ||
326 | $this->moveCursor($match[0]); | ||
327 | |||
328 | } elseif (preg_match(self::REGEX_DQ_STRING_DELIM, $this->code, $match, null, $this->cursor)) { | ||
329 | |||
330 | list($expect, $lineno) = array_pop($this->brackets); | ||
331 | if ($this->code[$this->cursor] != '"') { | ||
332 | throw new Twig_Error_Syntax(sprintf('Unclosed "%s"', $expect), $lineno, $this->filename); | ||
333 | } | ||
334 | |||
335 | $this->popState(); | ||
336 | ++$this->cursor; | ||
337 | } | ||
338 | } | ||
339 | |||
340 | protected function lexInterpolation() | ||
341 | { | ||
342 | $bracket = end($this->brackets); | ||
343 | if ($this->options['interpolation'][0] === $bracket[0] && preg_match($this->regexes['interpolation_end'], $this->code, $match, null, $this->cursor)) { | ||
344 | array_pop($this->brackets); | ||
345 | $this->pushToken(Twig_Token::INTERPOLATION_END_TYPE); | ||
346 | $this->moveCursor($match[0]); | ||
347 | $this->popState(); | ||
348 | } else { | ||
349 | $this->lexExpression(); | ||
350 | } | ||
351 | } | ||
352 | |||
353 | protected function pushToken($type, $value = '') | ||
354 | { | ||
355 | // do not push empty text tokens | ||
356 | if (Twig_Token::TEXT_TYPE === $type && '' === $value) { | ||
357 | return; | ||
358 | } | ||
359 | |||
360 | $this->tokens[] = new Twig_Token($type, $value, $this->lineno); | ||
361 | } | ||
362 | |||
363 | protected function moveCursor($text) | ||
364 | { | ||
365 | $this->cursor += strlen($text); | ||
366 | $this->lineno += substr_count($text, "\n"); | ||
367 | } | ||
368 | |||
369 | protected function getOperatorRegex() | ||
370 | { | ||
371 | $operators = array_merge( | ||
372 | array('='), | ||
373 | array_keys($this->env->getUnaryOperators()), | ||
374 | array_keys($this->env->getBinaryOperators()) | ||
375 | ); | ||
376 | |||
377 | $operators = array_combine($operators, array_map('strlen', $operators)); | ||
378 | arsort($operators); | ||
379 | |||
380 | $regex = array(); | ||
381 | foreach ($operators as $operator => $length) { | ||
382 | // an operator that ends with a character must be followed by | ||
383 | // a whitespace or a parenthesis | ||
384 | if (ctype_alpha($operator[$length - 1])) { | ||
385 | $regex[] = preg_quote($operator, '/').'(?=[\s()])'; | ||
386 | } else { | ||
387 | $regex[] = preg_quote($operator, '/'); | ||
388 | } | ||
389 | } | ||
390 | |||
391 | return '/'.implode('|', $regex).'/A'; | ||
392 | } | ||
393 | |||
394 | protected function pushState($state) | ||
395 | { | ||
396 | $this->states[] = $this->state; | ||
397 | $this->state = $state; | ||
398 | } | ||
399 | |||
400 | protected function popState() | ||
401 | { | ||
402 | if (0 === count($this->states)) { | ||
403 | throw new Exception('Cannot pop state without a previous state'); | ||
404 | } | ||
405 | |||
406 | $this->state = array_pop($this->states); | ||
407 | } | ||
408 | } | ||
diff --git a/inc/Twig/LexerInterface.php b/inc/Twig/LexerInterface.php new file mode 100644 index 00000000..4b83f81b --- /dev/null +++ b/inc/Twig/LexerInterface.php | |||
@@ -0,0 +1,29 @@ | |||
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 | * Interface implemented by lexer classes. | ||
14 | * | ||
15 | * @author Fabien Potencier <fabien@symfony.com> | ||
16 | * @deprecated since 1.12 (to be removed in 2.0) | ||
17 | */ | ||
18 | interface Twig_LexerInterface | ||
19 | { | ||
20 | /** | ||
21 | * Tokenizes a source code. | ||
22 | * | ||
23 | * @param string $code The source code | ||
24 | * @param string $filename A unique identifier for the source code | ||
25 | * | ||
26 | * @return Twig_TokenStream A token stream instance | ||
27 | */ | ||
28 | public function tokenize($code, $filename = null); | ||
29 | } | ||
diff --git a/inc/Twig/Loader/Array.php b/inc/Twig/Loader/Array.php new file mode 100644 index 00000000..89087aea --- /dev/null +++ b/inc/Twig/Loader/Array.php | |||
@@ -0,0 +1,98 @@ | |||
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 | * Loads a template from an array. | ||
14 | * | ||
15 | * When using this loader with a cache mechanism, you should know that a new cache | ||
16 | * key is generated each time a template content "changes" (the cache key being the | ||
17 | * source code of the template). If you don't want to see your cache grows out of | ||
18 | * control, you need to take care of clearing the old cache file by yourself. | ||
19 | * | ||
20 | * @author Fabien Potencier <fabien@symfony.com> | ||
21 | */ | ||
22 | class Twig_Loader_Array implements Twig_LoaderInterface, Twig_ExistsLoaderInterface | ||
23 | { | ||
24 | protected $templates; | ||
25 | |||
26 | /** | ||
27 | * Constructor. | ||
28 | * | ||
29 | * @param array $templates An array of templates (keys are the names, and values are the source code) | ||
30 | * | ||
31 | * @see Twig_Loader | ||
32 | */ | ||
33 | public function __construct(array $templates) | ||
34 | { | ||
35 | $this->templates = array(); | ||
36 | foreach ($templates as $name => $template) { | ||
37 | $this->templates[$name] = $template; | ||
38 | } | ||
39 | } | ||
40 | |||
41 | /** | ||
42 | * Adds or overrides a template. | ||
43 | * | ||
44 | * @param string $name The template name | ||
45 | * @param string $template The template source | ||
46 | */ | ||
47 | public function setTemplate($name, $template) | ||
48 | { | ||
49 | $this->templates[(string) $name] = $template; | ||
50 | } | ||
51 | |||
52 | /** | ||
53 | * {@inheritdoc} | ||
54 | */ | ||
55 | public function getSource($name) | ||
56 | { | ||
57 | $name = (string) $name; | ||
58 | if (!isset($this->templates[$name])) { | ||
59 | throw new Twig_Error_Loader(sprintf('Template "%s" is not defined.', $name)); | ||
60 | } | ||
61 | |||
62 | return $this->templates[$name]; | ||
63 | } | ||
64 | |||
65 | /** | ||
66 | * {@inheritdoc} | ||
67 | */ | ||
68 | public function exists($name) | ||
69 | { | ||
70 | return isset($this->templates[(string) $name]); | ||
71 | } | ||
72 | |||
73 | /** | ||
74 | * {@inheritdoc} | ||
75 | */ | ||
76 | public function getCacheKey($name) | ||
77 | { | ||
78 | $name = (string) $name; | ||
79 | if (!isset($this->templates[$name])) { | ||
80 | throw new Twig_Error_Loader(sprintf('Template "%s" is not defined.', $name)); | ||
81 | } | ||
82 | |||
83 | return $this->templates[$name]; | ||
84 | } | ||
85 | |||
86 | /** | ||
87 | * {@inheritdoc} | ||
88 | */ | ||
89 | public function isFresh($name, $time) | ||
90 | { | ||
91 | $name = (string) $name; | ||
92 | if (!isset($this->templates[$name])) { | ||
93 | throw new Twig_Error_Loader(sprintf('Template "%s" is not defined.', $name)); | ||
94 | } | ||
95 | |||
96 | return true; | ||
97 | } | ||
98 | } | ||
diff --git a/inc/Twig/Loader/Chain.php b/inc/Twig/Loader/Chain.php new file mode 100644 index 00000000..1f1cf065 --- /dev/null +++ b/inc/Twig/Loader/Chain.php | |||
@@ -0,0 +1,139 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2011 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 | * Loads templates from other loaders. | ||
14 | * | ||
15 | * @author Fabien Potencier <fabien@symfony.com> | ||
16 | */ | ||
17 | class Twig_Loader_Chain implements Twig_LoaderInterface, Twig_ExistsLoaderInterface | ||
18 | { | ||
19 | private $hasSourceCache = array(); | ||
20 | protected $loaders; | ||
21 | |||
22 | /** | ||
23 | * Constructor. | ||
24 | * | ||
25 | * @param Twig_LoaderInterface[] $loaders An array of loader instances | ||
26 | */ | ||
27 | public function __construct(array $loaders = array()) | ||
28 | { | ||
29 | $this->loaders = array(); | ||
30 | foreach ($loaders as $loader) { | ||
31 | $this->addLoader($loader); | ||
32 | } | ||
33 | } | ||
34 | |||
35 | /** | ||
36 | * Adds a loader instance. | ||
37 | * | ||
38 | * @param Twig_LoaderInterface $loader A Loader instance | ||
39 | */ | ||
40 | public function addLoader(Twig_LoaderInterface $loader) | ||
41 | { | ||
42 | $this->loaders[] = $loader; | ||
43 | $this->hasSourceCache = array(); | ||
44 | } | ||
45 | |||
46 | /** | ||
47 | * {@inheritdoc} | ||
48 | */ | ||
49 | public function getSource($name) | ||
50 | { | ||
51 | $exceptions = array(); | ||
52 | foreach ($this->loaders as $loader) { | ||
53 | if ($loader instanceof Twig_ExistsLoaderInterface && !$loader->exists($name)) { | ||
54 | continue; | ||
55 | } | ||
56 | |||
57 | try { | ||
58 | return $loader->getSource($name); | ||
59 | } catch (Twig_Error_Loader $e) { | ||
60 | $exceptions[] = $e->getMessage(); | ||
61 | } | ||
62 | } | ||
63 | |||
64 | throw new Twig_Error_Loader(sprintf('Template "%s" is not defined (%s).', $name, implode(', ', $exceptions))); | ||
65 | } | ||
66 | |||
67 | /** | ||
68 | * {@inheritdoc} | ||
69 | */ | ||
70 | public function exists($name) | ||
71 | { | ||
72 | $name = (string) $name; | ||
73 | |||
74 | if (isset($this->hasSourceCache[$name])) { | ||
75 | return $this->hasSourceCache[$name]; | ||
76 | } | ||
77 | |||
78 | foreach ($this->loaders as $loader) { | ||
79 | if ($loader instanceof Twig_ExistsLoaderInterface) { | ||
80 | if ($loader->exists($name)) { | ||
81 | return $this->hasSourceCache[$name] = true; | ||
82 | } | ||
83 | |||
84 | continue; | ||
85 | } | ||
86 | |||
87 | try { | ||
88 | $loader->getSource($name); | ||
89 | |||
90 | return $this->hasSourceCache[$name] = true; | ||
91 | } catch (Twig_Error_Loader $e) { | ||
92 | } | ||
93 | } | ||
94 | |||
95 | return $this->hasSourceCache[$name] = false; | ||
96 | } | ||
97 | |||
98 | /** | ||
99 | * {@inheritdoc} | ||
100 | */ | ||
101 | public function getCacheKey($name) | ||
102 | { | ||
103 | $exceptions = array(); | ||
104 | foreach ($this->loaders as $loader) { | ||
105 | if ($loader instanceof Twig_ExistsLoaderInterface && !$loader->exists($name)) { | ||
106 | continue; | ||
107 | } | ||
108 | |||
109 | try { | ||
110 | return $loader->getCacheKey($name); | ||
111 | } catch (Twig_Error_Loader $e) { | ||
112 | $exceptions[] = get_class($loader).': '.$e->getMessage(); | ||
113 | } | ||
114 | } | ||
115 | |||
116 | throw new Twig_Error_Loader(sprintf('Template "%s" is not defined (%s).', $name, implode(' ', $exceptions))); | ||
117 | } | ||
118 | |||
119 | /** | ||
120 | * {@inheritdoc} | ||
121 | */ | ||
122 | public function isFresh($name, $time) | ||
123 | { | ||
124 | $exceptions = array(); | ||
125 | foreach ($this->loaders as $loader) { | ||
126 | if ($loader instanceof Twig_ExistsLoaderInterface && !$loader->exists($name)) { | ||
127 | continue; | ||
128 | } | ||
129 | |||
130 | try { | ||
131 | return $loader->isFresh($name, $time); | ||
132 | } catch (Twig_Error_Loader $e) { | ||
133 | $exceptions[] = get_class($loader).': '.$e->getMessage(); | ||
134 | } | ||
135 | } | ||
136 | |||
137 | throw new Twig_Error_Loader(sprintf('Template "%s" is not defined (%s).', $name, implode(' ', $exceptions))); | ||
138 | } | ||
139 | } | ||
diff --git a/inc/Twig/Loader/Filesystem.php b/inc/Twig/Loader/Filesystem.php new file mode 100644 index 00000000..f9211cbd --- /dev/null +++ b/inc/Twig/Loader/Filesystem.php | |||
@@ -0,0 +1,223 @@ | |||
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 | * Loads template from the filesystem. | ||
14 | * | ||
15 | * @author Fabien Potencier <fabien@symfony.com> | ||
16 | */ | ||
17 | class Twig_Loader_Filesystem implements Twig_LoaderInterface, Twig_ExistsLoaderInterface | ||
18 | { | ||
19 | protected $paths; | ||
20 | protected $cache; | ||
21 | |||
22 | /** | ||
23 | * Constructor. | ||
24 | * | ||
25 | * @param string|array $paths A path or an array of paths where to look for templates | ||
26 | */ | ||
27 | public function __construct($paths = array()) | ||
28 | { | ||
29 | if ($paths) { | ||
30 | $this->setPaths($paths); | ||
31 | } | ||
32 | } | ||
33 | |||
34 | /** | ||
35 | * Returns the paths to the templates. | ||
36 | * | ||
37 | * @param string $namespace A path namespace | ||
38 | * | ||
39 | * @return array The array of paths where to look for templates | ||
40 | */ | ||
41 | public function getPaths($namespace = '__main__') | ||
42 | { | ||
43 | return isset($this->paths[$namespace]) ? $this->paths[$namespace] : array(); | ||
44 | } | ||
45 | |||
46 | /** | ||
47 | * Returns the path namespaces. | ||
48 | * | ||
49 | * The "__main__" namespace is always defined. | ||
50 | * | ||
51 | * @return array The array of defined namespaces | ||
52 | */ | ||
53 | public function getNamespaces() | ||
54 | { | ||
55 | return array_keys($this->paths); | ||
56 | } | ||
57 | |||
58 | /** | ||
59 | * Sets the paths where templates are stored. | ||
60 | * | ||
61 | * @param string|array $paths A path or an array of paths where to look for templates | ||
62 | * @param string $namespace A path namespace | ||
63 | */ | ||
64 | public function setPaths($paths, $namespace = '__main__') | ||
65 | { | ||
66 | if (!is_array($paths)) { | ||
67 | $paths = array($paths); | ||
68 | } | ||
69 | |||
70 | $this->paths[$namespace] = array(); | ||
71 | foreach ($paths as $path) { | ||
72 | $this->addPath($path, $namespace); | ||
73 | } | ||
74 | } | ||
75 | |||
76 | /** | ||
77 | * Adds a path where templates are stored. | ||
78 | * | ||
79 | * @param string $path A path where to look for templates | ||
80 | * @param string $namespace A path name | ||
81 | * | ||
82 | * @throws Twig_Error_Loader | ||
83 | */ | ||
84 | public function addPath($path, $namespace = '__main__') | ||
85 | { | ||
86 | // invalidate the cache | ||
87 | $this->cache = array(); | ||
88 | |||
89 | if (!is_dir($path)) { | ||
90 | throw new Twig_Error_Loader(sprintf('The "%s" directory does not exist.', $path)); | ||
91 | } | ||
92 | |||
93 | $this->paths[$namespace][] = rtrim($path, '/\\'); | ||
94 | } | ||
95 | |||
96 | /** | ||
97 | * Prepends a path where templates are stored. | ||
98 | * | ||
99 | * @param string $path A path where to look for templates | ||
100 | * @param string $namespace A path name | ||
101 | * | ||
102 | * @throws Twig_Error_Loader | ||
103 | */ | ||
104 | public function prependPath($path, $namespace = '__main__') | ||
105 | { | ||
106 | // invalidate the cache | ||
107 | $this->cache = array(); | ||
108 | |||
109 | if (!is_dir($path)) { | ||
110 | throw new Twig_Error_Loader(sprintf('The "%s" directory does not exist.', $path)); | ||
111 | } | ||
112 | |||
113 | $path = rtrim($path, '/\\'); | ||
114 | |||
115 | if (!isset($this->paths[$namespace])) { | ||
116 | $this->paths[$namespace][] = $path; | ||
117 | } else { | ||
118 | array_unshift($this->paths[$namespace], $path); | ||
119 | } | ||
120 | } | ||
121 | |||
122 | /** | ||
123 | * {@inheritdoc} | ||
124 | */ | ||
125 | public function getSource($name) | ||
126 | { | ||
127 | return file_get_contents($this->findTemplate($name)); | ||
128 | } | ||
129 | |||
130 | /** | ||
131 | * {@inheritdoc} | ||
132 | */ | ||
133 | public function getCacheKey($name) | ||
134 | { | ||
135 | return $this->findTemplate($name); | ||
136 | } | ||
137 | |||
138 | /** | ||
139 | * {@inheritdoc} | ||
140 | */ | ||
141 | public function exists($name) | ||
142 | { | ||
143 | $name = (string) $name; | ||
144 | if (isset($this->cache[$name])) { | ||
145 | return true; | ||
146 | } | ||
147 | |||
148 | try { | ||
149 | $this->findTemplate($name); | ||
150 | |||
151 | return true; | ||
152 | } catch (Twig_Error_Loader $exception) { | ||
153 | return false; | ||
154 | } | ||
155 | } | ||
156 | |||
157 | /** | ||
158 | * {@inheritdoc} | ||
159 | */ | ||
160 | public function isFresh($name, $time) | ||
161 | { | ||
162 | return filemtime($this->findTemplate($name)) <= $time; | ||
163 | } | ||
164 | |||
165 | protected function findTemplate($name) | ||
166 | { | ||
167 | $name = (string) $name; | ||
168 | |||
169 | // normalize name | ||
170 | $name = preg_replace('#/{2,}#', '/', strtr($name, '\\', '/')); | ||
171 | |||
172 | if (isset($this->cache[$name])) { | ||
173 | return $this->cache[$name]; | ||
174 | } | ||
175 | |||
176 | $this->validateName($name); | ||
177 | |||
178 | $namespace = '__main__'; | ||
179 | if (isset($name[0]) && '@' == $name[0]) { | ||
180 | if (false === $pos = strpos($name, '/')) { | ||
181 | throw new Twig_Error_Loader(sprintf('Malformed namespaced template name "%s" (expecting "@namespace/template_name").', $name)); | ||
182 | } | ||
183 | |||
184 | $namespace = substr($name, 1, $pos - 1); | ||
185 | |||
186 | $name = substr($name, $pos + 1); | ||
187 | } | ||
188 | |||
189 | if (!isset($this->paths[$namespace])) { | ||
190 | throw new Twig_Error_Loader(sprintf('There are no registered paths for namespace "%s".', $namespace)); | ||
191 | } | ||
192 | |||
193 | foreach ($this->paths[$namespace] as $path) { | ||
194 | if (is_file($path.'/'.$name)) { | ||
195 | return $this->cache[$name] = $path.'/'.$name; | ||
196 | } | ||
197 | } | ||
198 | |||
199 | throw new Twig_Error_Loader(sprintf('Unable to find template "%s" (looked into: %s).', $name, implode(', ', $this->paths[$namespace]))); | ||
200 | } | ||
201 | |||
202 | protected function validateName($name) | ||
203 | { | ||
204 | if (false !== strpos($name, "\0")) { | ||
205 | throw new Twig_Error_Loader('A template name cannot contain NUL bytes.'); | ||
206 | } | ||
207 | |||
208 | $name = ltrim($name, '/'); | ||
209 | $parts = explode('/', $name); | ||
210 | $level = 0; | ||
211 | foreach ($parts as $part) { | ||
212 | if ('..' === $part) { | ||
213 | --$level; | ||
214 | } elseif ('.' !== $part) { | ||
215 | ++$level; | ||
216 | } | ||
217 | |||
218 | if ($level < 0) { | ||
219 | throw new Twig_Error_Loader(sprintf('Looks like you try to load a template outside configured directories (%s).', $name)); | ||
220 | } | ||
221 | } | ||
222 | } | ||
223 | } | ||
diff --git a/inc/Twig/Loader/String.php b/inc/Twig/Loader/String.php new file mode 100644 index 00000000..8ad9856c --- /dev/null +++ b/inc/Twig/Loader/String.php | |||
@@ -0,0 +1,59 @@ | |||
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 | * Loads a template from a string. | ||
14 | * | ||
15 | * This loader should only be used for unit testing as it has many limitations | ||
16 | * (for instance, the include or extends tag does not make any sense for a string | ||
17 | * loader). | ||
18 | * | ||
19 | * When using this loader with a cache mechanism, you should know that a new cache | ||
20 | * key is generated each time a template content "changes" (the cache key being the | ||
21 | * source code of the template). If you don't want to see your cache grows out of | ||
22 | * control, you need to take care of clearing the old cache file by yourself. | ||
23 | * | ||
24 | * @author Fabien Potencier <fabien@symfony.com> | ||
25 | */ | ||
26 | class Twig_Loader_String implements Twig_LoaderInterface, Twig_ExistsLoaderInterface | ||
27 | { | ||
28 | /** | ||
29 | * {@inheritdoc} | ||
30 | */ | ||
31 | public function getSource($name) | ||
32 | { | ||
33 | return $name; | ||
34 | } | ||
35 | |||
36 | /** | ||
37 | * {@inheritdoc} | ||
38 | */ | ||
39 | public function exists($name) | ||
40 | { | ||
41 | return true; | ||
42 | } | ||
43 | |||
44 | /** | ||
45 | * {@inheritdoc} | ||
46 | */ | ||
47 | public function getCacheKey($name) | ||
48 | { | ||
49 | return $name; | ||
50 | } | ||
51 | |||
52 | /** | ||
53 | * {@inheritdoc} | ||
54 | */ | ||
55 | public function isFresh($name, $time) | ||
56 | { | ||
57 | return true; | ||
58 | } | ||
59 | } | ||
diff --git a/inc/Twig/LoaderInterface.php b/inc/Twig/LoaderInterface.php new file mode 100644 index 00000000..927786d1 --- /dev/null +++ b/inc/Twig/LoaderInterface.php | |||
@@ -0,0 +1,52 @@ | |||
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 | * Interface all loaders must implement. | ||
14 | * | ||
15 | * @author Fabien Potencier <fabien@symfony.com> | ||
16 | */ | ||
17 | interface Twig_LoaderInterface | ||
18 | { | ||
19 | /** | ||
20 | * Gets the source code of a template, given its name. | ||
21 | * | ||
22 | * @param string $name The name of the template to load | ||
23 | * | ||
24 | * @return string The template source code | ||
25 | * | ||
26 | * @throws Twig_Error_Loader When $name is not found | ||
27 | */ | ||
28 | public function getSource($name); | ||
29 | |||
30 | /** | ||
31 | * Gets the cache key to use for the cache for a given template name. | ||
32 | * | ||
33 | * @param string $name The name of the template to load | ||
34 | * | ||
35 | * @return string The cache key | ||
36 | * | ||
37 | * @throws Twig_Error_Loader When $name is not found | ||
38 | */ | ||
39 | public function getCacheKey($name); | ||
40 | |||
41 | /** | ||
42 | * Returns true if the template is still fresh. | ||
43 | * | ||
44 | * @param string $name The template name | ||
45 | * @param timestamp $time The last modification time of the cached template | ||
46 | * | ||
47 | * @return Boolean true if the template is fresh, false otherwise | ||
48 | * | ||
49 | * @throws Twig_Error_Loader When $name is not found | ||
50 | */ | ||
51 | public function isFresh($name, $time); | ||
52 | } | ||
diff --git a/inc/Twig/Markup.php b/inc/Twig/Markup.php new file mode 100644 index 00000000..69871fcb --- /dev/null +++ b/inc/Twig/Markup.php | |||
@@ -0,0 +1,37 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 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 | * Marks a content as safe. | ||
14 | * | ||
15 | * @author Fabien Potencier <fabien@symfony.com> | ||
16 | */ | ||
17 | class Twig_Markup implements Countable | ||
18 | { | ||
19 | protected $content; | ||
20 | protected $charset; | ||
21 | |||
22 | public function __construct($content, $charset) | ||
23 | { | ||
24 | $this->content = (string) $content; | ||
25 | $this->charset = $charset; | ||
26 | } | ||
27 | |||
28 | public function __toString() | ||
29 | { | ||
30 | return $this->content; | ||
31 | } | ||
32 | |||
33 | public function count() | ||
34 | { | ||
35 | return function_exists('mb_get_info') ? mb_strlen($this->content, $this->charset) : strlen($this->content); | ||
36 | } | ||
37 | } | ||
diff --git a/inc/Twig/Node.php b/inc/Twig/Node.php new file mode 100644 index 00000000..931b4635 --- /dev/null +++ b/inc/Twig/Node.php | |||
@@ -0,0 +1,226 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2009 Fabien Potencier | ||
7 | * (c) 2009 Armin Ronacher | ||
8 | * | ||
9 | * For the full copyright and license information, please view the LICENSE | ||
10 | * file that was distributed with this source code. | ||
11 | */ | ||
12 | |||
13 | /** | ||
14 | * Represents a node in the AST. | ||
15 | * | ||
16 | * @author Fabien Potencier <fabien@symfony.com> | ||
17 | */ | ||
18 | class Twig_Node implements Twig_NodeInterface | ||
19 | { | ||
20 | protected $nodes; | ||
21 | protected $attributes; | ||
22 | protected $lineno; | ||
23 | protected $tag; | ||
24 | |||
25 | /** | ||
26 | * Constructor. | ||
27 | * | ||
28 | * The nodes are automatically made available as properties ($this->node). | ||
29 | * The attributes are automatically made available as array items ($this['name']). | ||
30 | * | ||
31 | * @param array $nodes An array of named nodes | ||
32 | * @param array $attributes An array of attributes (should not be nodes) | ||
33 | * @param integer $lineno The line number | ||
34 | * @param string $tag The tag name associated with the Node | ||
35 | */ | ||
36 | public function __construct(array $nodes = array(), array $attributes = array(), $lineno = 0, $tag = null) | ||
37 | { | ||
38 | $this->nodes = $nodes; | ||
39 | $this->attributes = $attributes; | ||
40 | $this->lineno = $lineno; | ||
41 | $this->tag = $tag; | ||
42 | } | ||
43 | |||
44 | public function __toString() | ||
45 | { | ||
46 | $attributes = array(); | ||
47 | foreach ($this->attributes as $name => $value) { | ||
48 | $attributes[] = sprintf('%s: %s', $name, str_replace("\n", '', var_export($value, true))); | ||
49 | } | ||
50 | |||
51 | $repr = array(get_class($this).'('.implode(', ', $attributes)); | ||
52 | |||
53 | if (count($this->nodes)) { | ||
54 | foreach ($this->nodes as $name => $node) { | ||
55 | $len = strlen($name) + 4; | ||
56 | $noderepr = array(); | ||
57 | foreach (explode("\n", (string) $node) as $line) { | ||
58 | $noderepr[] = str_repeat(' ', $len).$line; | ||
59 | } | ||
60 | |||
61 | $repr[] = sprintf(' %s: %s', $name, ltrim(implode("\n", $noderepr))); | ||
62 | } | ||
63 | |||
64 | $repr[] = ')'; | ||
65 | } else { | ||
66 | $repr[0] .= ')'; | ||
67 | } | ||
68 | |||
69 | return implode("\n", $repr); | ||
70 | } | ||
71 | |||
72 | public function toXml($asDom = false) | ||
73 | { | ||
74 | $dom = new DOMDocument('1.0', 'UTF-8'); | ||
75 | $dom->formatOutput = true; | ||
76 | $dom->appendChild($xml = $dom->createElement('twig')); | ||
77 | |||
78 | $xml->appendChild($node = $dom->createElement('node')); | ||
79 | $node->setAttribute('class', get_class($this)); | ||
80 | |||
81 | foreach ($this->attributes as $name => $value) { | ||
82 | $node->appendChild($attribute = $dom->createElement('attribute')); | ||
83 | $attribute->setAttribute('name', $name); | ||
84 | $attribute->appendChild($dom->createTextNode($value)); | ||
85 | } | ||
86 | |||
87 | foreach ($this->nodes as $name => $n) { | ||
88 | if (null === $n) { | ||
89 | continue; | ||
90 | } | ||
91 | |||
92 | $child = $n->toXml(true)->getElementsByTagName('node')->item(0); | ||
93 | $child = $dom->importNode($child, true); | ||
94 | $child->setAttribute('name', $name); | ||
95 | |||
96 | $node->appendChild($child); | ||
97 | } | ||
98 | |||
99 | return $asDom ? $dom : $dom->saveXml(); | ||
100 | } | ||
101 | |||
102 | public function compile(Twig_Compiler $compiler) | ||
103 | { | ||
104 | foreach ($this->nodes as $node) { | ||
105 | $node->compile($compiler); | ||
106 | } | ||
107 | } | ||
108 | |||
109 | public function getLine() | ||
110 | { | ||
111 | return $this->lineno; | ||
112 | } | ||
113 | |||
114 | public function getNodeTag() | ||
115 | { | ||
116 | return $this->tag; | ||
117 | } | ||
118 | |||
119 | /** | ||
120 | * Returns true if the attribute is defined. | ||
121 | * | ||
122 | * @param string The attribute name | ||
123 | * | ||
124 | * @return Boolean true if the attribute is defined, false otherwise | ||
125 | */ | ||
126 | public function hasAttribute($name) | ||
127 | { | ||
128 | return array_key_exists($name, $this->attributes); | ||
129 | } | ||
130 | |||
131 | /** | ||
132 | * Gets an attribute. | ||
133 | * | ||
134 | * @param string The attribute name | ||
135 | * | ||
136 | * @return mixed The attribute value | ||
137 | */ | ||
138 | public function getAttribute($name) | ||
139 | { | ||
140 | if (!array_key_exists($name, $this->attributes)) { | ||
141 | throw new LogicException(sprintf('Attribute "%s" does not exist for Node "%s".', $name, get_class($this))); | ||
142 | } | ||
143 | |||
144 | return $this->attributes[$name]; | ||
145 | } | ||
146 | |||
147 | /** | ||
148 | * Sets an attribute. | ||
149 | * | ||
150 | * @param string The attribute name | ||
151 | * @param mixed The attribute value | ||
152 | */ | ||
153 | public function setAttribute($name, $value) | ||
154 | { | ||
155 | $this->attributes[$name] = $value; | ||
156 | } | ||
157 | |||
158 | /** | ||
159 | * Removes an attribute. | ||
160 | * | ||
161 | * @param string The attribute name | ||
162 | */ | ||
163 | public function removeAttribute($name) | ||
164 | { | ||
165 | unset($this->attributes[$name]); | ||
166 | } | ||
167 | |||
168 | /** | ||
169 | * Returns true if the node with the given identifier exists. | ||
170 | * | ||
171 | * @param string The node name | ||
172 | * | ||
173 | * @return Boolean true if the node with the given name exists, false otherwise | ||
174 | */ | ||
175 | public function hasNode($name) | ||
176 | { | ||
177 | return array_key_exists($name, $this->nodes); | ||
178 | } | ||
179 | |||
180 | /** | ||
181 | * Gets a node by name. | ||
182 | * | ||
183 | * @param string The node name | ||
184 | * | ||
185 | * @return Twig_Node A Twig_Node instance | ||
186 | */ | ||
187 | public function getNode($name) | ||
188 | { | ||
189 | if (!array_key_exists($name, $this->nodes)) { | ||
190 | throw new LogicException(sprintf('Node "%s" does not exist for Node "%s".', $name, get_class($this))); | ||
191 | } | ||
192 | |||
193 | return $this->nodes[$name]; | ||
194 | } | ||
195 | |||
196 | /** | ||
197 | * Sets a node. | ||
198 | * | ||
199 | * @param string The node name | ||
200 | * @param Twig_Node A Twig_Node instance | ||
201 | */ | ||
202 | public function setNode($name, $node = null) | ||
203 | { | ||
204 | $this->nodes[$name] = $node; | ||
205 | } | ||
206 | |||
207 | /** | ||
208 | * Removes a node by name. | ||
209 | * | ||
210 | * @param string The node name | ||
211 | */ | ||
212 | public function removeNode($name) | ||
213 | { | ||
214 | unset($this->nodes[$name]); | ||
215 | } | ||
216 | |||
217 | public function count() | ||
218 | { | ||
219 | return count($this->nodes); | ||
220 | } | ||
221 | |||
222 | public function getIterator() | ||
223 | { | ||
224 | return new ArrayIterator($this->nodes); | ||
225 | } | ||
226 | } | ||
diff --git a/inc/Twig/Node/AutoEscape.php b/inc/Twig/Node/AutoEscape.php new file mode 100644 index 00000000..8f190e0b --- /dev/null +++ b/inc/Twig/Node/AutoEscape.php | |||
@@ -0,0 +1,39 @@ | |||
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 | * Represents an autoescape node. | ||
14 | * | ||
15 | * The value is the escaping strategy (can be html, js, ...) | ||
16 | * | ||
17 | * The true value is equivalent to html. | ||
18 | * | ||
19 | * If autoescaping is disabled, then the value is false. | ||
20 | * | ||
21 | * @author Fabien Potencier <fabien@symfony.com> | ||
22 | */ | ||
23 | class Twig_Node_AutoEscape extends Twig_Node | ||
24 | { | ||
25 | public function __construct($value, Twig_NodeInterface $body, $lineno, $tag = 'autoescape') | ||
26 | { | ||
27 | parent::__construct(array('body' => $body), array('value' => $value), $lineno, $tag); | ||
28 | } | ||
29 | |||
30 | /** | ||
31 | * Compiles the node to PHP. | ||
32 | * | ||
33 | * @param Twig_Compiler A Twig_Compiler instance | ||
34 | */ | ||
35 | public function compile(Twig_Compiler $compiler) | ||
36 | { | ||
37 | $compiler->subcompile($this->getNode('body')); | ||
38 | } | ||
39 | } | ||
diff --git a/inc/Twig/Node/Block.php b/inc/Twig/Node/Block.php new file mode 100644 index 00000000..50eb67ed --- /dev/null +++ b/inc/Twig/Node/Block.php | |||
@@ -0,0 +1,44 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2009 Fabien Potencier | ||
7 | * (c) 2009 Armin Ronacher | ||
8 | * | ||
9 | * For the full copyright and license information, please view the LICENSE | ||
10 | * file that was distributed with this source code. | ||
11 | */ | ||
12 | |||
13 | /** | ||
14 | * Represents a block node. | ||
15 | * | ||
16 | * @author Fabien Potencier <fabien@symfony.com> | ||
17 | */ | ||
18 | class Twig_Node_Block extends Twig_Node | ||
19 | { | ||
20 | public function __construct($name, Twig_NodeInterface $body, $lineno, $tag = null) | ||
21 | { | ||
22 | parent::__construct(array('body' => $body), array('name' => $name), $lineno, $tag); | ||
23 | } | ||
24 | |||
25 | /** | ||
26 | * Compiles the node to PHP. | ||
27 | * | ||
28 | * @param Twig_Compiler A Twig_Compiler instance | ||
29 | */ | ||
30 | public function compile(Twig_Compiler $compiler) | ||
31 | { | ||
32 | $compiler | ||
33 | ->addDebugInfo($this) | ||
34 | ->write(sprintf("public function block_%s(\$context, array \$blocks = array())\n", $this->getAttribute('name')), "{\n") | ||
35 | ->indent() | ||
36 | ; | ||
37 | |||
38 | $compiler | ||
39 | ->subcompile($this->getNode('body')) | ||
40 | ->outdent() | ||
41 | ->write("}\n\n") | ||
42 | ; | ||
43 | } | ||
44 | } | ||
diff --git a/inc/Twig/Node/BlockReference.php b/inc/Twig/Node/BlockReference.php new file mode 100644 index 00000000..013e369e --- /dev/null +++ b/inc/Twig/Node/BlockReference.php | |||
@@ -0,0 +1,37 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2009 Fabien Potencier | ||
7 | * (c) 2009 Armin Ronacher | ||
8 | * | ||
9 | * For the full copyright and license information, please view the LICENSE | ||
10 | * file that was distributed with this source code. | ||
11 | */ | ||
12 | |||
13 | /** | ||
14 | * Represents a block call node. | ||
15 | * | ||
16 | * @author Fabien Potencier <fabien@symfony.com> | ||
17 | */ | ||
18 | class Twig_Node_BlockReference extends Twig_Node implements Twig_NodeOutputInterface | ||
19 | { | ||
20 | public function __construct($name, $lineno, $tag = null) | ||
21 | { | ||
22 | parent::__construct(array(), array('name' => $name), $lineno, $tag); | ||
23 | } | ||
24 | |||
25 | /** | ||
26 | * Compiles the node to PHP. | ||
27 | * | ||
28 | * @param Twig_Compiler A Twig_Compiler instance | ||
29 | */ | ||
30 | public function compile(Twig_Compiler $compiler) | ||
31 | { | ||
32 | $compiler | ||
33 | ->addDebugInfo($this) | ||
34 | ->write(sprintf("\$this->displayBlock('%s', \$context, \$blocks);\n", $this->getAttribute('name'))) | ||
35 | ; | ||
36 | } | ||
37 | } | ||
diff --git a/inc/Twig/Node/Body.php b/inc/Twig/Node/Body.php new file mode 100644 index 00000000..3ffb1342 --- /dev/null +++ b/inc/Twig/Node/Body.php | |||
@@ -0,0 +1,19 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2011 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 | * Represents a body node. | ||
14 | * | ||
15 | * @author Fabien Potencier <fabien@symfony.com> | ||
16 | */ | ||
17 | class Twig_Node_Body extends Twig_Node | ||
18 | { | ||
19 | } | ||
diff --git a/inc/Twig/Node/Do.php b/inc/Twig/Node/Do.php new file mode 100644 index 00000000..c528066b --- /dev/null +++ b/inc/Twig/Node/Do.php | |||
@@ -0,0 +1,38 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2011 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 | * Represents a do node. | ||
14 | * | ||
15 | * @author Fabien Potencier <fabien@symfony.com> | ||
16 | */ | ||
17 | class Twig_Node_Do extends Twig_Node | ||
18 | { | ||
19 | public function __construct(Twig_Node_Expression $expr, $lineno, $tag = null) | ||
20 | { | ||
21 | parent::__construct(array('expr' => $expr), array(), $lineno, $tag); | ||
22 | } | ||
23 | |||
24 | /** | ||
25 | * Compiles the node to PHP. | ||
26 | * | ||
27 | * @param Twig_Compiler A Twig_Compiler instance | ||
28 | */ | ||
29 | public function compile(Twig_Compiler $compiler) | ||
30 | { | ||
31 | $compiler | ||
32 | ->addDebugInfo($this) | ||
33 | ->write('') | ||
34 | ->subcompile($this->getNode('expr')) | ||
35 | ->raw(";\n") | ||
36 | ; | ||
37 | } | ||
38 | } | ||
diff --git a/inc/Twig/Node/Embed.php b/inc/Twig/Node/Embed.php new file mode 100644 index 00000000..4c9456dc --- /dev/null +++ b/inc/Twig/Node/Embed.php | |||
@@ -0,0 +1,38 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2012 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 | * Represents an embed node. | ||
14 | * | ||
15 | * @author Fabien Potencier <fabien@symfony.com> | ||
16 | */ | ||
17 | class Twig_Node_Embed extends Twig_Node_Include | ||
18 | { | ||
19 | // we don't inject the module to avoid node visitors to traverse it twice (as it will be already visited in the main module) | ||
20 | public function __construct($filename, $index, Twig_Node_Expression $variables = null, $only = false, $ignoreMissing = false, $lineno, $tag = null) | ||
21 | { | ||
22 | parent::__construct(new Twig_Node_Expression_Constant('not_used', $lineno), $variables, $only, $ignoreMissing, $lineno, $tag); | ||
23 | |||
24 | $this->setAttribute('filename', $filename); | ||
25 | $this->setAttribute('index', $index); | ||
26 | } | ||
27 | |||
28 | protected function addGetTemplate(Twig_Compiler $compiler) | ||
29 | { | ||
30 | $compiler | ||
31 | ->write("\$this->env->loadTemplate(") | ||
32 | ->string($this->getAttribute('filename')) | ||
33 | ->raw(', ') | ||
34 | ->string($this->getAttribute('index')) | ||
35 | ->raw(")") | ||
36 | ; | ||
37 | } | ||
38 | } | ||
diff --git a/inc/Twig/Node/Expression.php b/inc/Twig/Node/Expression.php new file mode 100644 index 00000000..a7382e7d --- /dev/null +++ b/inc/Twig/Node/Expression.php | |||
@@ -0,0 +1,20 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2009 Fabien Potencier | ||
7 | * (c) 2009 Armin Ronacher | ||
8 | * | ||
9 | * For the full copyright and license information, please view the LICENSE | ||
10 | * file that was distributed with this source code. | ||
11 | */ | ||
12 | |||
13 | /** | ||
14 | * Abstract class for all nodes that represents an expression. | ||
15 | * | ||
16 | * @author Fabien Potencier <fabien@symfony.com> | ||
17 | */ | ||
18 | abstract class Twig_Node_Expression extends Twig_Node | ||
19 | { | ||
20 | } | ||
diff --git a/inc/Twig/Node/Expression/Array.php b/inc/Twig/Node/Expression/Array.php new file mode 100644 index 00000000..1da785fe --- /dev/null +++ b/inc/Twig/Node/Expression/Array.php | |||
@@ -0,0 +1,86 @@ | |||
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 | class Twig_Node_Expression_Array extends Twig_Node_Expression | ||
12 | { | ||
13 | protected $index; | ||
14 | |||
15 | public function __construct(array $elements, $lineno) | ||
16 | { | ||
17 | parent::__construct($elements, array(), $lineno); | ||
18 | |||
19 | $this->index = -1; | ||
20 | foreach ($this->getKeyValuePairs() as $pair) { | ||
21 | if ($pair['key'] instanceof Twig_Node_Expression_Constant && ctype_digit((string) $pair['key']->getAttribute('value')) && $pair['key']->getAttribute('value') > $this->index) { | ||
22 | $this->index = $pair['key']->getAttribute('value'); | ||
23 | } | ||
24 | } | ||
25 | } | ||
26 | |||
27 | public function getKeyValuePairs() | ||
28 | { | ||
29 | $pairs = array(); | ||
30 | |||
31 | foreach (array_chunk($this->nodes, 2) as $pair) { | ||
32 | $pairs[] = array( | ||
33 | 'key' => $pair[0], | ||
34 | 'value' => $pair[1], | ||
35 | ); | ||
36 | } | ||
37 | |||
38 | return $pairs; | ||
39 | } | ||
40 | |||
41 | public function hasElement(Twig_Node_Expression $key) | ||
42 | { | ||
43 | foreach ($this->getKeyValuePairs() as $pair) { | ||
44 | // we compare the string representation of the keys | ||
45 | // to avoid comparing the line numbers which are not relevant here. | ||
46 | if ((string) $key == (string) $pair['key']) { | ||
47 | return true; | ||
48 | } | ||
49 | } | ||
50 | |||
51 | return false; | ||
52 | } | ||
53 | |||
54 | public function addElement(Twig_Node_Expression $value, Twig_Node_Expression $key = null) | ||
55 | { | ||
56 | if (null === $key) { | ||
57 | $key = new Twig_Node_Expression_Constant(++$this->index, $value->getLine()); | ||
58 | } | ||
59 | |||
60 | array_push($this->nodes, $key, $value); | ||
61 | } | ||
62 | |||
63 | /** | ||
64 | * Compiles the node to PHP. | ||
65 | * | ||
66 | * @param Twig_Compiler A Twig_Compiler instance | ||
67 | */ | ||
68 | public function compile(Twig_Compiler $compiler) | ||
69 | { | ||
70 | $compiler->raw('array('); | ||
71 | $first = true; | ||
72 | foreach ($this->getKeyValuePairs() as $pair) { | ||
73 | if (!$first) { | ||
74 | $compiler->raw(', '); | ||
75 | } | ||
76 | $first = false; | ||
77 | |||
78 | $compiler | ||
79 | ->subcompile($pair['key']) | ||
80 | ->raw(' => ') | ||
81 | ->subcompile($pair['value']) | ||
82 | ; | ||
83 | } | ||
84 | $compiler->raw(')'); | ||
85 | } | ||
86 | } | ||
diff --git a/inc/Twig/Node/Expression/AssignName.php b/inc/Twig/Node/Expression/AssignName.php new file mode 100644 index 00000000..2ddea78c --- /dev/null +++ b/inc/Twig/Node/Expression/AssignName.php | |||
@@ -0,0 +1,28 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2009 Fabien Potencier | ||
7 | * (c) 2009 Armin Ronacher | ||
8 | * | ||
9 | * For the full copyright and license information, please view the LICENSE | ||
10 | * file that was distributed with this source code. | ||
11 | */ | ||
12 | |||
13 | class Twig_Node_Expression_AssignName extends Twig_Node_Expression_Name | ||
14 | { | ||
15 | /** | ||
16 | * Compiles the node to PHP. | ||
17 | * | ||
18 | * @param Twig_Compiler A Twig_Compiler instance | ||
19 | */ | ||
20 | public function compile(Twig_Compiler $compiler) | ||
21 | { | ||
22 | $compiler | ||
23 | ->raw('$context[') | ||
24 | ->string($this->getAttribute('name')) | ||
25 | ->raw(']') | ||
26 | ; | ||
27 | } | ||
28 | } | ||
diff --git a/inc/Twig/Node/Expression/Binary.php b/inc/Twig/Node/Expression/Binary.php new file mode 100644 index 00000000..9dd5de2c --- /dev/null +++ b/inc/Twig/Node/Expression/Binary.php | |||
@@ -0,0 +1,40 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2009 Fabien Potencier | ||
7 | * (c) 2009 Armin Ronacher | ||
8 | * | ||
9 | * For the full copyright and license information, please view the LICENSE | ||
10 | * file that was distributed with this source code. | ||
11 | */ | ||
12 | abstract class Twig_Node_Expression_Binary extends Twig_Node_Expression | ||
13 | { | ||
14 | public function __construct(Twig_NodeInterface $left, Twig_NodeInterface $right, $lineno) | ||
15 | { | ||
16 | parent::__construct(array('left' => $left, 'right' => $right), array(), $lineno); | ||
17 | } | ||
18 | |||
19 | /** | ||
20 | * Compiles the node to PHP. | ||
21 | * | ||
22 | * @param Twig_Compiler A Twig_Compiler instance | ||
23 | */ | ||
24 | public function compile(Twig_Compiler $compiler) | ||
25 | { | ||
26 | $compiler | ||
27 | ->raw('(') | ||
28 | ->subcompile($this->getNode('left')) | ||
29 | ->raw(' ') | ||
30 | ; | ||
31 | $this->operator($compiler); | ||
32 | $compiler | ||
33 | ->raw(' ') | ||
34 | ->subcompile($this->getNode('right')) | ||
35 | ->raw(')') | ||
36 | ; | ||
37 | } | ||
38 | |||
39 | abstract public function operator(Twig_Compiler $compiler); | ||
40 | } | ||
diff --git a/inc/Twig/Node/Expression/Binary/Add.php b/inc/Twig/Node/Expression/Binary/Add.php new file mode 100644 index 00000000..0ef8e117 --- /dev/null +++ b/inc/Twig/Node/Expression/Binary/Add.php | |||
@@ -0,0 +1,18 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2009 Fabien Potencier | ||
7 | * (c) 2009 Armin Ronacher | ||
8 | * | ||
9 | * For the full copyright and license information, please view the LICENSE | ||
10 | * file that was distributed with this source code. | ||
11 | */ | ||
12 | class Twig_Node_Expression_Binary_Add extends Twig_Node_Expression_Binary | ||
13 | { | ||
14 | public function operator(Twig_Compiler $compiler) | ||
15 | { | ||
16 | return $compiler->raw('+'); | ||
17 | } | ||
18 | } | ||
diff --git a/inc/Twig/Node/Expression/Binary/And.php b/inc/Twig/Node/Expression/Binary/And.php new file mode 100644 index 00000000..d5752ebb --- /dev/null +++ b/inc/Twig/Node/Expression/Binary/And.php | |||
@@ -0,0 +1,18 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2009 Fabien Potencier | ||
7 | * (c) 2009 Armin Ronacher | ||
8 | * | ||
9 | * For the full copyright and license information, please view the LICENSE | ||
10 | * file that was distributed with this source code. | ||
11 | */ | ||
12 | class Twig_Node_Expression_Binary_And extends Twig_Node_Expression_Binary | ||
13 | { | ||
14 | public function operator(Twig_Compiler $compiler) | ||
15 | { | ||
16 | return $compiler->raw('&&'); | ||
17 | } | ||
18 | } | ||
diff --git a/inc/Twig/Node/Expression/Binary/BitwiseAnd.php b/inc/Twig/Node/Expression/Binary/BitwiseAnd.php new file mode 100644 index 00000000..9a46d845 --- /dev/null +++ b/inc/Twig/Node/Expression/Binary/BitwiseAnd.php | |||
@@ -0,0 +1,18 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2009 Fabien Potencier | ||
7 | * (c) 2009 Armin Ronacher | ||
8 | * | ||
9 | * For the full copyright and license information, please view the LICENSE | ||
10 | * file that was distributed with this source code. | ||
11 | */ | ||
12 | class Twig_Node_Expression_Binary_BitwiseAnd extends Twig_Node_Expression_Binary | ||
13 | { | ||
14 | public function operator(Twig_Compiler $compiler) | ||
15 | { | ||
16 | return $compiler->raw('&'); | ||
17 | } | ||
18 | } | ||
diff --git a/inc/Twig/Node/Expression/Binary/BitwiseOr.php b/inc/Twig/Node/Expression/Binary/BitwiseOr.php new file mode 100644 index 00000000..058a20bf --- /dev/null +++ b/inc/Twig/Node/Expression/Binary/BitwiseOr.php | |||
@@ -0,0 +1,18 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2009 Fabien Potencier | ||
7 | * (c) 2009 Armin Ronacher | ||
8 | * | ||
9 | * For the full copyright and license information, please view the LICENSE | ||
10 | * file that was distributed with this source code. | ||
11 | */ | ||
12 | class Twig_Node_Expression_Binary_BitwiseOr extends Twig_Node_Expression_Binary | ||
13 | { | ||
14 | public function operator(Twig_Compiler $compiler) | ||
15 | { | ||
16 | return $compiler->raw('|'); | ||
17 | } | ||
18 | } | ||
diff --git a/inc/Twig/Node/Expression/Binary/BitwiseXor.php b/inc/Twig/Node/Expression/Binary/BitwiseXor.php new file mode 100644 index 00000000..f4da73d4 --- /dev/null +++ b/inc/Twig/Node/Expression/Binary/BitwiseXor.php | |||
@@ -0,0 +1,18 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2009 Fabien Potencier | ||
7 | * (c) 2009 Armin Ronacher | ||
8 | * | ||
9 | * For the full copyright and license information, please view the LICENSE | ||
10 | * file that was distributed with this source code. | ||
11 | */ | ||
12 | class Twig_Node_Expression_Binary_BitwiseXor extends Twig_Node_Expression_Binary | ||
13 | { | ||
14 | public function operator(Twig_Compiler $compiler) | ||
15 | { | ||
16 | return $compiler->raw('^'); | ||
17 | } | ||
18 | } | ||
diff --git a/inc/Twig/Node/Expression/Binary/Concat.php b/inc/Twig/Node/Expression/Binary/Concat.php new file mode 100644 index 00000000..f9a64627 --- /dev/null +++ b/inc/Twig/Node/Expression/Binary/Concat.php | |||
@@ -0,0 +1,18 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2009 Fabien Potencier | ||
7 | * (c) 2009 Armin Ronacher | ||
8 | * | ||
9 | * For the full copyright and license information, please view the LICENSE | ||
10 | * file that was distributed with this source code. | ||
11 | */ | ||
12 | class Twig_Node_Expression_Binary_Concat extends Twig_Node_Expression_Binary | ||
13 | { | ||
14 | public function operator(Twig_Compiler $compiler) | ||
15 | { | ||
16 | return $compiler->raw('.'); | ||
17 | } | ||
18 | } | ||
diff --git a/inc/Twig/Node/Expression/Binary/Div.php b/inc/Twig/Node/Expression/Binary/Div.php new file mode 100644 index 00000000..e0797a61 --- /dev/null +++ b/inc/Twig/Node/Expression/Binary/Div.php | |||
@@ -0,0 +1,18 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2009 Fabien Potencier | ||
7 | * (c) 2009 Armin Ronacher | ||
8 | * | ||
9 | * For the full copyright and license information, please view the LICENSE | ||
10 | * file that was distributed with this source code. | ||
11 | */ | ||
12 | class Twig_Node_Expression_Binary_Div extends Twig_Node_Expression_Binary | ||
13 | { | ||
14 | public function operator(Twig_Compiler $compiler) | ||
15 | { | ||
16 | return $compiler->raw('/'); | ||
17 | } | ||
18 | } | ||
diff --git a/inc/Twig/Node/Expression/Binary/Equal.php b/inc/Twig/Node/Expression/Binary/Equal.php new file mode 100644 index 00000000..7b1236d0 --- /dev/null +++ b/inc/Twig/Node/Expression/Binary/Equal.php | |||
@@ -0,0 +1,17 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 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 | class Twig_Node_Expression_Binary_Equal extends Twig_Node_Expression_Binary | ||
12 | { | ||
13 | public function operator(Twig_Compiler $compiler) | ||
14 | { | ||
15 | return $compiler->raw('=='); | ||
16 | } | ||
17 | } | ||
diff --git a/inc/Twig/Node/Expression/Binary/FloorDiv.php b/inc/Twig/Node/Expression/Binary/FloorDiv.php new file mode 100644 index 00000000..7fbd0556 --- /dev/null +++ b/inc/Twig/Node/Expression/Binary/FloorDiv.php | |||
@@ -0,0 +1,29 @@ | |||
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 | class Twig_Node_Expression_Binary_FloorDiv extends Twig_Node_Expression_Binary | ||
12 | { | ||
13 | /** | ||
14 | * Compiles the node to PHP. | ||
15 | * | ||
16 | * @param Twig_Compiler A Twig_Compiler instance | ||
17 | */ | ||
18 | public function compile(Twig_Compiler $compiler) | ||
19 | { | ||
20 | $compiler->raw('intval(floor('); | ||
21 | parent::compile($compiler); | ||
22 | $compiler->raw('))'); | ||
23 | } | ||
24 | |||
25 | public function operator(Twig_Compiler $compiler) | ||
26 | { | ||
27 | return $compiler->raw('/'); | ||
28 | } | ||
29 | } | ||
diff --git a/inc/Twig/Node/Expression/Binary/Greater.php b/inc/Twig/Node/Expression/Binary/Greater.php new file mode 100644 index 00000000..a110bd92 --- /dev/null +++ b/inc/Twig/Node/Expression/Binary/Greater.php | |||
@@ -0,0 +1,17 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 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 | class Twig_Node_Expression_Binary_Greater extends Twig_Node_Expression_Binary | ||
12 | { | ||
13 | public function operator(Twig_Compiler $compiler) | ||
14 | { | ||
15 | return $compiler->raw('>'); | ||
16 | } | ||
17 | } | ||
diff --git a/inc/Twig/Node/Expression/Binary/GreaterEqual.php b/inc/Twig/Node/Expression/Binary/GreaterEqual.php new file mode 100644 index 00000000..3754fed2 --- /dev/null +++ b/inc/Twig/Node/Expression/Binary/GreaterEqual.php | |||
@@ -0,0 +1,17 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 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 | class Twig_Node_Expression_Binary_GreaterEqual extends Twig_Node_Expression_Binary | ||
12 | { | ||
13 | public function operator(Twig_Compiler $compiler) | ||
14 | { | ||
15 | return $compiler->raw('>='); | ||
16 | } | ||
17 | } | ||
diff --git a/inc/Twig/Node/Expression/Binary/In.php b/inc/Twig/Node/Expression/Binary/In.php new file mode 100644 index 00000000..788f9377 --- /dev/null +++ b/inc/Twig/Node/Expression/Binary/In.php | |||
@@ -0,0 +1,33 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 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 | class Twig_Node_Expression_Binary_In extends Twig_Node_Expression_Binary | ||
12 | { | ||
13 | /** | ||
14 | * Compiles the node to PHP. | ||
15 | * | ||
16 | * @param Twig_Compiler A Twig_Compiler instance | ||
17 | */ | ||
18 | public function compile(Twig_Compiler $compiler) | ||
19 | { | ||
20 | $compiler | ||
21 | ->raw('twig_in_filter(') | ||
22 | ->subcompile($this->getNode('left')) | ||
23 | ->raw(', ') | ||
24 | ->subcompile($this->getNode('right')) | ||
25 | ->raw(')') | ||
26 | ; | ||
27 | } | ||
28 | |||
29 | public function operator(Twig_Compiler $compiler) | ||
30 | { | ||
31 | return $compiler->raw('in'); | ||
32 | } | ||
33 | } | ||
diff --git a/inc/Twig/Node/Expression/Binary/Less.php b/inc/Twig/Node/Expression/Binary/Less.php new file mode 100644 index 00000000..45fd3004 --- /dev/null +++ b/inc/Twig/Node/Expression/Binary/Less.php | |||
@@ -0,0 +1,17 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 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 | class Twig_Node_Expression_Binary_Less extends Twig_Node_Expression_Binary | ||
12 | { | ||
13 | public function operator(Twig_Compiler $compiler) | ||
14 | { | ||
15 | return $compiler->raw('<'); | ||
16 | } | ||
17 | } | ||
diff --git a/inc/Twig/Node/Expression/Binary/LessEqual.php b/inc/Twig/Node/Expression/Binary/LessEqual.php new file mode 100644 index 00000000..e38e257c --- /dev/null +++ b/inc/Twig/Node/Expression/Binary/LessEqual.php | |||
@@ -0,0 +1,17 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 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 | class Twig_Node_Expression_Binary_LessEqual extends Twig_Node_Expression_Binary | ||
12 | { | ||
13 | public function operator(Twig_Compiler $compiler) | ||
14 | { | ||
15 | return $compiler->raw('<='); | ||
16 | } | ||
17 | } | ||
diff --git a/inc/Twig/Node/Expression/Binary/Mod.php b/inc/Twig/Node/Expression/Binary/Mod.php new file mode 100644 index 00000000..9924114f --- /dev/null +++ b/inc/Twig/Node/Expression/Binary/Mod.php | |||
@@ -0,0 +1,18 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2009 Fabien Potencier | ||
7 | * (c) 2009 Armin Ronacher | ||
8 | * | ||
9 | * For the full copyright and license information, please view the LICENSE | ||
10 | * file that was distributed with this source code. | ||
11 | */ | ||
12 | class Twig_Node_Expression_Binary_Mod extends Twig_Node_Expression_Binary | ||
13 | { | ||
14 | public function operator(Twig_Compiler $compiler) | ||
15 | { | ||
16 | return $compiler->raw('%'); | ||
17 | } | ||
18 | } | ||
diff --git a/inc/Twig/Node/Expression/Binary/Mul.php b/inc/Twig/Node/Expression/Binary/Mul.php new file mode 100644 index 00000000..c91529ca --- /dev/null +++ b/inc/Twig/Node/Expression/Binary/Mul.php | |||
@@ -0,0 +1,18 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2009 Fabien Potencier | ||
7 | * (c) 2009 Armin Ronacher | ||
8 | * | ||
9 | * For the full copyright and license information, please view the LICENSE | ||
10 | * file that was distributed with this source code. | ||
11 | */ | ||
12 | class Twig_Node_Expression_Binary_Mul extends Twig_Node_Expression_Binary | ||
13 | { | ||
14 | public function operator(Twig_Compiler $compiler) | ||
15 | { | ||
16 | return $compiler->raw('*'); | ||
17 | } | ||
18 | } | ||
diff --git a/inc/Twig/Node/Expression/Binary/NotEqual.php b/inc/Twig/Node/Expression/Binary/NotEqual.php new file mode 100644 index 00000000..26867ba2 --- /dev/null +++ b/inc/Twig/Node/Expression/Binary/NotEqual.php | |||
@@ -0,0 +1,17 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 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 | class Twig_Node_Expression_Binary_NotEqual extends Twig_Node_Expression_Binary | ||
12 | { | ||
13 | public function operator(Twig_Compiler $compiler) | ||
14 | { | ||
15 | return $compiler->raw('!='); | ||
16 | } | ||
17 | } | ||
diff --git a/inc/Twig/Node/Expression/Binary/NotIn.php b/inc/Twig/Node/Expression/Binary/NotIn.php new file mode 100644 index 00000000..f347b7b6 --- /dev/null +++ b/inc/Twig/Node/Expression/Binary/NotIn.php | |||
@@ -0,0 +1,33 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 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 | class Twig_Node_Expression_Binary_NotIn extends Twig_Node_Expression_Binary | ||
12 | { | ||
13 | /** | ||
14 | * Compiles the node to PHP. | ||
15 | * | ||
16 | * @param Twig_Compiler A Twig_Compiler instance | ||
17 | */ | ||
18 | public function compile(Twig_Compiler $compiler) | ||
19 | { | ||
20 | $compiler | ||
21 | ->raw('!twig_in_filter(') | ||
22 | ->subcompile($this->getNode('left')) | ||
23 | ->raw(', ') | ||
24 | ->subcompile($this->getNode('right')) | ||
25 | ->raw(')') | ||
26 | ; | ||
27 | } | ||
28 | |||
29 | public function operator(Twig_Compiler $compiler) | ||
30 | { | ||
31 | return $compiler->raw('not in'); | ||
32 | } | ||
33 | } | ||
diff --git a/inc/Twig/Node/Expression/Binary/Or.php b/inc/Twig/Node/Expression/Binary/Or.php new file mode 100644 index 00000000..adba49c6 --- /dev/null +++ b/inc/Twig/Node/Expression/Binary/Or.php | |||
@@ -0,0 +1,18 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2009 Fabien Potencier | ||
7 | * (c) 2009 Armin Ronacher | ||
8 | * | ||
9 | * For the full copyright and license information, please view the LICENSE | ||
10 | * file that was distributed with this source code. | ||
11 | */ | ||
12 | class Twig_Node_Expression_Binary_Or extends Twig_Node_Expression_Binary | ||
13 | { | ||
14 | public function operator(Twig_Compiler $compiler) | ||
15 | { | ||
16 | return $compiler->raw('||'); | ||
17 | } | ||
18 | } | ||
diff --git a/inc/Twig/Node/Expression/Binary/Power.php b/inc/Twig/Node/Expression/Binary/Power.php new file mode 100644 index 00000000..b2c59040 --- /dev/null +++ b/inc/Twig/Node/Expression/Binary/Power.php | |||
@@ -0,0 +1,33 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 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 | class Twig_Node_Expression_Binary_Power extends Twig_Node_Expression_Binary | ||
12 | { | ||
13 | /** | ||
14 | * Compiles the node to PHP. | ||
15 | * | ||
16 | * @param Twig_Compiler A Twig_Compiler instance | ||
17 | */ | ||
18 | public function compile(Twig_Compiler $compiler) | ||
19 | { | ||
20 | $compiler | ||
21 | ->raw('pow(') | ||
22 | ->subcompile($this->getNode('left')) | ||
23 | ->raw(', ') | ||
24 | ->subcompile($this->getNode('right')) | ||
25 | ->raw(')') | ||
26 | ; | ||
27 | } | ||
28 | |||
29 | public function operator(Twig_Compiler $compiler) | ||
30 | { | ||
31 | return $compiler->raw('**'); | ||
32 | } | ||
33 | } | ||
diff --git a/inc/Twig/Node/Expression/Binary/Range.php b/inc/Twig/Node/Expression/Binary/Range.php new file mode 100644 index 00000000..bea4f2a6 --- /dev/null +++ b/inc/Twig/Node/Expression/Binary/Range.php | |||
@@ -0,0 +1,33 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 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 | class Twig_Node_Expression_Binary_Range extends Twig_Node_Expression_Binary | ||
12 | { | ||
13 | /** | ||
14 | * Compiles the node to PHP. | ||
15 | * | ||
16 | * @param Twig_Compiler A Twig_Compiler instance | ||
17 | */ | ||
18 | public function compile(Twig_Compiler $compiler) | ||
19 | { | ||
20 | $compiler | ||
21 | ->raw('range(') | ||
22 | ->subcompile($this->getNode('left')) | ||
23 | ->raw(', ') | ||
24 | ->subcompile($this->getNode('right')) | ||
25 | ->raw(')') | ||
26 | ; | ||
27 | } | ||
28 | |||
29 | public function operator(Twig_Compiler $compiler) | ||
30 | { | ||
31 | return $compiler->raw('..'); | ||
32 | } | ||
33 | } | ||
diff --git a/inc/Twig/Node/Expression/Binary/Sub.php b/inc/Twig/Node/Expression/Binary/Sub.php new file mode 100644 index 00000000..d4463991 --- /dev/null +++ b/inc/Twig/Node/Expression/Binary/Sub.php | |||
@@ -0,0 +1,18 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2009 Fabien Potencier | ||
7 | * (c) 2009 Armin Ronacher | ||
8 | * | ||
9 | * For the full copyright and license information, please view the LICENSE | ||
10 | * file that was distributed with this source code. | ||
11 | */ | ||
12 | class Twig_Node_Expression_Binary_Sub extends Twig_Node_Expression_Binary | ||
13 | { | ||
14 | public function operator(Twig_Compiler $compiler) | ||
15 | { | ||
16 | return $compiler->raw('-'); | ||
17 | } | ||
18 | } | ||
diff --git a/inc/Twig/Node/Expression/BlockReference.php b/inc/Twig/Node/Expression/BlockReference.php new file mode 100644 index 00000000..647196eb --- /dev/null +++ b/inc/Twig/Node/Expression/BlockReference.php | |||
@@ -0,0 +1,51 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2009 Fabien Potencier | ||
7 | * (c) 2009 Armin Ronacher | ||
8 | * | ||
9 | * For the full copyright and license information, please view the LICENSE | ||
10 | * file that was distributed with this source code. | ||
11 | */ | ||
12 | |||
13 | /** | ||
14 | * Represents a block call node. | ||
15 | * | ||
16 | * @author Fabien Potencier <fabien@symfony.com> | ||
17 | */ | ||
18 | class Twig_Node_Expression_BlockReference extends Twig_Node_Expression | ||
19 | { | ||
20 | public function __construct(Twig_NodeInterface $name, $asString = false, $lineno, $tag = null) | ||
21 | { | ||
22 | parent::__construct(array('name' => $name), array('as_string' => $asString, 'output' => false), $lineno, $tag); | ||
23 | } | ||
24 | |||
25 | /** | ||
26 | * Compiles the node to PHP. | ||
27 | * | ||
28 | * @param Twig_Compiler A Twig_Compiler instance | ||
29 | */ | ||
30 | public function compile(Twig_Compiler $compiler) | ||
31 | { | ||
32 | if ($this->getAttribute('as_string')) { | ||
33 | $compiler->raw('(string) '); | ||
34 | } | ||
35 | |||
36 | if ($this->getAttribute('output')) { | ||
37 | $compiler | ||
38 | ->addDebugInfo($this) | ||
39 | ->write("\$this->displayBlock(") | ||
40 | ->subcompile($this->getNode('name')) | ||
41 | ->raw(", \$context, \$blocks);\n") | ||
42 | ; | ||
43 | } else { | ||
44 | $compiler | ||
45 | ->raw("\$this->renderBlock(") | ||
46 | ->subcompile($this->getNode('name')) | ||
47 | ->raw(", \$context, \$blocks)") | ||
48 | ; | ||
49 | } | ||
50 | } | ||
51 | } | ||
diff --git a/inc/Twig/Node/Expression/Call.php b/inc/Twig/Node/Expression/Call.php new file mode 100644 index 00000000..87b62deb --- /dev/null +++ b/inc/Twig/Node/Expression/Call.php | |||
@@ -0,0 +1,178 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2012 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 | abstract class Twig_Node_Expression_Call extends Twig_Node_Expression | ||
12 | { | ||
13 | protected function compileCallable(Twig_Compiler $compiler) | ||
14 | { | ||
15 | $callable = $this->getAttribute('callable'); | ||
16 | |||
17 | $closingParenthesis = false; | ||
18 | if ($callable) { | ||
19 | if (is_string($callable)) { | ||
20 | $compiler->raw($callable); | ||
21 | } elseif (is_array($callable) && $callable[0] instanceof Twig_ExtensionInterface) { | ||
22 | $compiler->raw(sprintf('$this->env->getExtension(\'%s\')->%s', $callable[0]->getName(), $callable[1])); | ||
23 | } else { | ||
24 | $type = ucfirst($this->getAttribute('type')); | ||
25 | $compiler->raw(sprintf('call_user_func_array($this->env->get%s(\'%s\')->getCallable(), array', $type, $this->getAttribute('name'))); | ||
26 | $closingParenthesis = true; | ||
27 | } | ||
28 | } else { | ||
29 | $compiler->raw($this->getAttribute('thing')->compile()); | ||
30 | } | ||
31 | |||
32 | $this->compileArguments($compiler); | ||
33 | |||
34 | if ($closingParenthesis) { | ||
35 | $compiler->raw(')'); | ||
36 | } | ||
37 | } | ||
38 | |||
39 | protected function compileArguments(Twig_Compiler $compiler) | ||
40 | { | ||
41 | $compiler->raw('('); | ||
42 | |||
43 | $first = true; | ||
44 | |||
45 | if ($this->hasAttribute('needs_environment') && $this->getAttribute('needs_environment')) { | ||
46 | $compiler->raw('$this->env'); | ||
47 | $first = false; | ||
48 | } | ||
49 | |||
50 | if ($this->hasAttribute('needs_context') && $this->getAttribute('needs_context')) { | ||
51 | if (!$first) { | ||
52 | $compiler->raw(', '); | ||
53 | } | ||
54 | $compiler->raw('$context'); | ||
55 | $first = false; | ||
56 | } | ||
57 | |||
58 | if ($this->hasAttribute('arguments')) { | ||
59 | foreach ($this->getAttribute('arguments') as $argument) { | ||
60 | if (!$first) { | ||
61 | $compiler->raw(', '); | ||
62 | } | ||
63 | $compiler->string($argument); | ||
64 | $first = false; | ||
65 | } | ||
66 | } | ||
67 | |||
68 | if ($this->hasNode('node')) { | ||
69 | if (!$first) { | ||
70 | $compiler->raw(', '); | ||
71 | } | ||
72 | $compiler->subcompile($this->getNode('node')); | ||
73 | $first = false; | ||
74 | } | ||
75 | |||
76 | if ($this->hasNode('arguments') && null !== $this->getNode('arguments')) { | ||
77 | $callable = $this->hasAttribute('callable') ? $this->getAttribute('callable') : null; | ||
78 | |||
79 | $arguments = $this->getArguments($callable, $this->getNode('arguments')); | ||
80 | |||
81 | foreach ($arguments as $node) { | ||
82 | if (!$first) { | ||
83 | $compiler->raw(', '); | ||
84 | } | ||
85 | $compiler->subcompile($node); | ||
86 | $first = false; | ||
87 | } | ||
88 | } | ||
89 | |||
90 | $compiler->raw(')'); | ||
91 | } | ||
92 | |||
93 | protected function getArguments($callable, $arguments) | ||
94 | { | ||
95 | $parameters = array(); | ||
96 | $named = false; | ||
97 | foreach ($arguments as $name => $node) { | ||
98 | if (!is_int($name)) { | ||
99 | $named = true; | ||
100 | $name = $this->normalizeName($name); | ||
101 | } elseif ($named) { | ||
102 | throw new Twig_Error_Syntax(sprintf('Positional arguments cannot be used after named arguments for %s "%s".', $this->getAttribute('type'), $this->getAttribute('name'))); | ||
103 | } | ||
104 | |||
105 | $parameters[$name] = $node; | ||
106 | } | ||
107 | |||
108 | if (!$named) { | ||
109 | return $parameters; | ||
110 | } | ||
111 | |||
112 | if (!$callable) { | ||
113 | throw new LogicException(sprintf('Named arguments are not supported for %s "%s".', $this->getAttribute('type'), $this->getAttribute('name'))); | ||
114 | } | ||
115 | |||
116 | // manage named arguments | ||
117 | if (is_array($callable)) { | ||
118 | $r = new ReflectionMethod($callable[0], $callable[1]); | ||
119 | } elseif (is_object($callable) && !$callable instanceof Closure) { | ||
120 | $r = new ReflectionObject($callable); | ||
121 | $r = $r->getMethod('__invoke'); | ||
122 | } else { | ||
123 | $r = new ReflectionFunction($callable); | ||
124 | } | ||
125 | |||
126 | $definition = $r->getParameters(); | ||
127 | if ($this->hasNode('node')) { | ||
128 | array_shift($definition); | ||
129 | } | ||
130 | if ($this->hasAttribute('needs_environment') && $this->getAttribute('needs_environment')) { | ||
131 | array_shift($definition); | ||
132 | } | ||
133 | if ($this->hasAttribute('needs_context') && $this->getAttribute('needs_context')) { | ||
134 | array_shift($definition); | ||
135 | } | ||
136 | if ($this->hasAttribute('arguments') && null !== $this->getAttribute('arguments')) { | ||
137 | foreach ($this->getAttribute('arguments') as $argument) { | ||
138 | array_shift($definition); | ||
139 | } | ||
140 | } | ||
141 | |||
142 | $arguments = array(); | ||
143 | $pos = 0; | ||
144 | foreach ($definition as $param) { | ||
145 | $name = $this->normalizeName($param->name); | ||
146 | |||
147 | if (array_key_exists($name, $parameters)) { | ||
148 | if (array_key_exists($pos, $parameters)) { | ||
149 | throw new Twig_Error_Syntax(sprintf('Arguments "%s" is defined twice for %s "%s".', $name, $this->getAttribute('type'), $this->getAttribute('name'))); | ||
150 | } | ||
151 | |||
152 | $arguments[] = $parameters[$name]; | ||
153 | unset($parameters[$name]); | ||
154 | } elseif (array_key_exists($pos, $parameters)) { | ||
155 | $arguments[] = $parameters[$pos]; | ||
156 | unset($parameters[$pos]); | ||
157 | ++$pos; | ||
158 | } elseif ($param->isDefaultValueAvailable()) { | ||
159 | $arguments[] = new Twig_Node_Expression_Constant($param->getDefaultValue(), -1); | ||
160 | } elseif ($param->isOptional()) { | ||
161 | break; | ||
162 | } else { | ||
163 | throw new Twig_Error_Syntax(sprintf('Value for argument "%s" is required for %s "%s".', $name, $this->getAttribute('type'), $this->getAttribute('name'))); | ||
164 | } | ||
165 | } | ||
166 | |||
167 | foreach (array_keys($parameters) as $name) { | ||
168 | throw new Twig_Error_Syntax(sprintf('Unknown argument "%s" for %s "%s".', $name, $this->getAttribute('type'), $this->getAttribute('name'))); | ||
169 | } | ||
170 | |||
171 | return $arguments; | ||
172 | } | ||
173 | |||
174 | protected function normalizeName($name) | ||
175 | { | ||
176 | return strtolower(preg_replace(array('/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'), array('\\1_\\2', '\\1_\\2'), $name)); | ||
177 | } | ||
178 | } | ||
diff --git a/inc/Twig/Node/Expression/Conditional.php b/inc/Twig/Node/Expression/Conditional.php new file mode 100644 index 00000000..edcb1e2d --- /dev/null +++ b/inc/Twig/Node/Expression/Conditional.php | |||
@@ -0,0 +1,31 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2009 Fabien Potencier | ||
7 | * (c) 2009 Armin Ronacher | ||
8 | * | ||
9 | * For the full copyright and license information, please view the LICENSE | ||
10 | * file that was distributed with this source code. | ||
11 | */ | ||
12 | class Twig_Node_Expression_Conditional extends Twig_Node_Expression | ||
13 | { | ||
14 | public function __construct(Twig_Node_Expression $expr1, Twig_Node_Expression $expr2, Twig_Node_Expression $expr3, $lineno) | ||
15 | { | ||
16 | parent::__construct(array('expr1' => $expr1, 'expr2' => $expr2, 'expr3' => $expr3), array(), $lineno); | ||
17 | } | ||
18 | |||
19 | public function compile(Twig_Compiler $compiler) | ||
20 | { | ||
21 | $compiler | ||
22 | ->raw('((') | ||
23 | ->subcompile($this->getNode('expr1')) | ||
24 | ->raw(') ? (') | ||
25 | ->subcompile($this->getNode('expr2')) | ||
26 | ->raw(') : (') | ||
27 | ->subcompile($this->getNode('expr3')) | ||
28 | ->raw('))') | ||
29 | ; | ||
30 | } | ||
31 | } | ||
diff --git a/inc/Twig/Node/Expression/Constant.php b/inc/Twig/Node/Expression/Constant.php new file mode 100644 index 00000000..a91dc698 --- /dev/null +++ b/inc/Twig/Node/Expression/Constant.php | |||
@@ -0,0 +1,23 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2009 Fabien Potencier | ||
7 | * (c) 2009 Armin Ronacher | ||
8 | * | ||
9 | * For the full copyright and license information, please view the LICENSE | ||
10 | * file that was distributed with this source code. | ||
11 | */ | ||
12 | class Twig_Node_Expression_Constant extends Twig_Node_Expression | ||
13 | { | ||
14 | public function __construct($value, $lineno) | ||
15 | { | ||
16 | parent::__construct(array(), array('value' => $value), $lineno); | ||
17 | } | ||
18 | |||
19 | public function compile(Twig_Compiler $compiler) | ||
20 | { | ||
21 | $compiler->repr($this->getAttribute('value')); | ||
22 | } | ||
23 | } | ||
diff --git a/inc/Twig/Node/Expression/ExtensionReference.php b/inc/Twig/Node/Expression/ExtensionReference.php new file mode 100644 index 00000000..00ac6701 --- /dev/null +++ b/inc/Twig/Node/Expression/ExtensionReference.php | |||
@@ -0,0 +1,33 @@ | |||
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 | * Represents an extension call node. | ||
14 | * | ||
15 | * @author Fabien Potencier <fabien@symfony.com> | ||
16 | */ | ||
17 | class Twig_Node_Expression_ExtensionReference extends Twig_Node_Expression | ||
18 | { | ||
19 | public function __construct($name, $lineno, $tag = null) | ||
20 | { | ||
21 | parent::__construct(array(), array('name' => $name), $lineno, $tag); | ||
22 | } | ||
23 | |||
24 | /** | ||
25 | * Compiles the node to PHP. | ||
26 | * | ||
27 | * @param Twig_Compiler A Twig_Compiler instance | ||
28 | */ | ||
29 | public function compile(Twig_Compiler $compiler) | ||
30 | { | ||
31 | $compiler->raw(sprintf("\$this->env->getExtension('%s')", $this->getAttribute('name'))); | ||
32 | } | ||
33 | } | ||
diff --git a/inc/Twig/Node/Expression/Filter.php b/inc/Twig/Node/Expression/Filter.php new file mode 100644 index 00000000..207b062a --- /dev/null +++ b/inc/Twig/Node/Expression/Filter.php | |||
@@ -0,0 +1,36 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2009 Fabien Potencier | ||
7 | * (c) 2009 Armin Ronacher | ||
8 | * | ||
9 | * For the full copyright and license information, please view the LICENSE | ||
10 | * file that was distributed with this source code. | ||
11 | */ | ||
12 | class Twig_Node_Expression_Filter extends Twig_Node_Expression_Call | ||
13 | { | ||
14 | public function __construct(Twig_NodeInterface $node, Twig_Node_Expression_Constant $filterName, Twig_NodeInterface $arguments, $lineno, $tag = null) | ||
15 | { | ||
16 | parent::__construct(array('node' => $node, 'filter' => $filterName, 'arguments' => $arguments), array(), $lineno, $tag); | ||
17 | } | ||
18 | |||
19 | public function compile(Twig_Compiler $compiler) | ||
20 | { | ||
21 | $name = $this->getNode('filter')->getAttribute('value'); | ||
22 | $filter = $compiler->getEnvironment()->getFilter($name); | ||
23 | |||
24 | $this->setAttribute('name', $name); | ||
25 | $this->setAttribute('type', 'filter'); | ||
26 | $this->setAttribute('thing', $filter); | ||
27 | $this->setAttribute('needs_environment', $filter->needsEnvironment()); | ||
28 | $this->setAttribute('needs_context', $filter->needsContext()); | ||
29 | $this->setAttribute('arguments', $filter->getArguments()); | ||
30 | if ($filter instanceof Twig_FilterCallableInterface || $filter instanceof Twig_SimpleFilter) { | ||
31 | $this->setAttribute('callable', $filter->getCallable()); | ||
32 | } | ||
33 | |||
34 | $this->compileCallable($compiler); | ||
35 | } | ||
36 | } | ||
diff --git a/inc/Twig/Node/Expression/Filter/Default.php b/inc/Twig/Node/Expression/Filter/Default.php new file mode 100644 index 00000000..1827c888 --- /dev/null +++ b/inc/Twig/Node/Expression/Filter/Default.php | |||
@@ -0,0 +1,43 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2011 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 | * Returns the value or the default value when it is undefined or empty. | ||
14 | * | ||
15 | * <pre> | ||
16 | * {{ var.foo|default('foo item on var is not defined') }} | ||
17 | * </pre> | ||
18 | * | ||
19 | * @author Fabien Potencier <fabien@symfony.com> | ||
20 | */ | ||
21 | class Twig_Node_Expression_Filter_Default extends Twig_Node_Expression_Filter | ||
22 | { | ||
23 | public function __construct(Twig_NodeInterface $node, Twig_Node_Expression_Constant $filterName, Twig_NodeInterface $arguments, $lineno, $tag = null) | ||
24 | { | ||
25 | $default = new Twig_Node_Expression_Filter($node, new Twig_Node_Expression_Constant('default', $node->getLine()), $arguments, $node->getLine()); | ||
26 | |||
27 | if ('default' === $filterName->getAttribute('value') && ($node instanceof Twig_Node_Expression_Name || $node instanceof Twig_Node_Expression_GetAttr)) { | ||
28 | $test = new Twig_Node_Expression_Test_Defined(clone $node, 'defined', new Twig_Node(), $node->getLine()); | ||
29 | $false = count($arguments) ? $arguments->getNode(0) : new Twig_Node_Expression_Constant('', $node->getLine()); | ||
30 | |||
31 | $node = new Twig_Node_Expression_Conditional($test, $default, $false, $node->getLine()); | ||
32 | } else { | ||
33 | $node = $default; | ||
34 | } | ||
35 | |||
36 | parent::__construct($node, $filterName, $arguments, $lineno, $tag); | ||
37 | } | ||
38 | |||
39 | public function compile(Twig_Compiler $compiler) | ||
40 | { | ||
41 | $compiler->subcompile($this->getNode('node')); | ||
42 | } | ||
43 | } | ||
diff --git a/inc/Twig/Node/Expression/Function.php b/inc/Twig/Node/Expression/Function.php new file mode 100644 index 00000000..3e1f6b55 --- /dev/null +++ b/inc/Twig/Node/Expression/Function.php | |||
@@ -0,0 +1,35 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 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 | class Twig_Node_Expression_Function extends Twig_Node_Expression_Call | ||
12 | { | ||
13 | public function __construct($name, Twig_NodeInterface $arguments, $lineno) | ||
14 | { | ||
15 | parent::__construct(array('arguments' => $arguments), array('name' => $name), $lineno); | ||
16 | } | ||
17 | |||
18 | public function compile(Twig_Compiler $compiler) | ||
19 | { | ||
20 | $name = $this->getAttribute('name'); | ||
21 | $function = $compiler->getEnvironment()->getFunction($name); | ||
22 | |||
23 | $this->setAttribute('name', $name); | ||
24 | $this->setAttribute('type', 'function'); | ||
25 | $this->setAttribute('thing', $function); | ||
26 | $this->setAttribute('needs_environment', $function->needsEnvironment()); | ||
27 | $this->setAttribute('needs_context', $function->needsContext()); | ||
28 | $this->setAttribute('arguments', $function->getArguments()); | ||
29 | if ($function instanceof Twig_FunctionCallableInterface || $function instanceof Twig_SimpleFunction) { | ||
30 | $this->setAttribute('callable', $function->getCallable()); | ||
31 | } | ||
32 | |||
33 | $this->compileCallable($compiler); | ||
34 | } | ||
35 | } | ||
diff --git a/inc/Twig/Node/Expression/GetAttr.php b/inc/Twig/Node/Expression/GetAttr.php new file mode 100644 index 00000000..81a9b137 --- /dev/null +++ b/inc/Twig/Node/Expression/GetAttr.php | |||
@@ -0,0 +1,53 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2009 Fabien Potencier | ||
7 | * (c) 2009 Armin Ronacher | ||
8 | * | ||
9 | * For the full copyright and license information, please view the LICENSE | ||
10 | * file that was distributed with this source code. | ||
11 | */ | ||
12 | class Twig_Node_Expression_GetAttr extends Twig_Node_Expression | ||
13 | { | ||
14 | public function __construct(Twig_Node_Expression $node, Twig_Node_Expression $attribute, Twig_Node_Expression_Array $arguments, $type, $lineno) | ||
15 | { | ||
16 | parent::__construct(array('node' => $node, 'attribute' => $attribute, 'arguments' => $arguments), array('type' => $type, 'is_defined_test' => false, 'ignore_strict_check' => false, 'disable_c_ext' => false), $lineno); | ||
17 | } | ||
18 | |||
19 | public function compile(Twig_Compiler $compiler) | ||
20 | { | ||
21 | if (function_exists('twig_template_get_attributes') && !$this->getAttribute('disable_c_ext')) { | ||
22 | $compiler->raw('twig_template_get_attributes($this, '); | ||
23 | } else { | ||
24 | $compiler->raw('$this->getAttribute('); | ||
25 | } | ||
26 | |||
27 | if ($this->getAttribute('ignore_strict_check')) { | ||
28 | $this->getNode('node')->setAttribute('ignore_strict_check', true); | ||
29 | } | ||
30 | |||
31 | $compiler->subcompile($this->getNode('node')); | ||
32 | |||
33 | $compiler->raw(', ')->subcompile($this->getNode('attribute')); | ||
34 | |||
35 | if (count($this->getNode('arguments')) || Twig_TemplateInterface::ANY_CALL !== $this->getAttribute('type') || $this->getAttribute('is_defined_test') || $this->getAttribute('ignore_strict_check')) { | ||
36 | $compiler->raw(', ')->subcompile($this->getNode('arguments')); | ||
37 | |||
38 | if (Twig_TemplateInterface::ANY_CALL !== $this->getAttribute('type') || $this->getAttribute('is_defined_test') || $this->getAttribute('ignore_strict_check')) { | ||
39 | $compiler->raw(', ')->repr($this->getAttribute('type')); | ||
40 | } | ||
41 | |||
42 | if ($this->getAttribute('is_defined_test') || $this->getAttribute('ignore_strict_check')) { | ||
43 | $compiler->raw(', '.($this->getAttribute('is_defined_test') ? 'true' : 'false')); | ||
44 | } | ||
45 | |||
46 | if ($this->getAttribute('ignore_strict_check')) { | ||
47 | $compiler->raw(', '.($this->getAttribute('ignore_strict_check') ? 'true' : 'false')); | ||
48 | } | ||
49 | } | ||
50 | |||
51 | $compiler->raw(')'); | ||
52 | } | ||
53 | } | ||
diff --git a/inc/Twig/Node/Expression/MethodCall.php b/inc/Twig/Node/Expression/MethodCall.php new file mode 100644 index 00000000..620b02bf --- /dev/null +++ b/inc/Twig/Node/Expression/MethodCall.php | |||
@@ -0,0 +1,41 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2012 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 | class Twig_Node_Expression_MethodCall extends Twig_Node_Expression | ||
12 | { | ||
13 | public function __construct(Twig_Node_Expression $node, $method, Twig_Node_Expression_Array $arguments, $lineno) | ||
14 | { | ||
15 | parent::__construct(array('node' => $node, 'arguments' => $arguments), array('method' => $method, 'safe' => false), $lineno); | ||
16 | |||
17 | if ($node instanceof Twig_Node_Expression_Name) { | ||
18 | $node->setAttribute('always_defined', true); | ||
19 | } | ||
20 | } | ||
21 | |||
22 | public function compile(Twig_Compiler $compiler) | ||
23 | { | ||
24 | $compiler | ||
25 | ->subcompile($this->getNode('node')) | ||
26 | ->raw('->') | ||
27 | ->raw($this->getAttribute('method')) | ||
28 | ->raw('(') | ||
29 | ; | ||
30 | $first = true; | ||
31 | foreach ($this->getNode('arguments')->getKeyValuePairs() as $pair) { | ||
32 | if (!$first) { | ||
33 | $compiler->raw(', '); | ||
34 | } | ||
35 | $first = false; | ||
36 | |||
37 | $compiler->subcompile($pair['value']); | ||
38 | } | ||
39 | $compiler->raw(')'); | ||
40 | } | ||
41 | } | ||
diff --git a/inc/Twig/Node/Expression/Name.php b/inc/Twig/Node/Expression/Name.php new file mode 100644 index 00000000..3b8fae01 --- /dev/null +++ b/inc/Twig/Node/Expression/Name.php | |||
@@ -0,0 +1,88 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2009 Fabien Potencier | ||
7 | * (c) 2009 Armin Ronacher | ||
8 | * | ||
9 | * For the full copyright and license information, please view the LICENSE | ||
10 | * file that was distributed with this source code. | ||
11 | */ | ||
12 | class Twig_Node_Expression_Name extends Twig_Node_Expression | ||
13 | { | ||
14 | protected $specialVars = array( | ||
15 | '_self' => '$this', | ||
16 | '_context' => '$context', | ||
17 | '_charset' => '$this->env->getCharset()', | ||
18 | ); | ||
19 | |||
20 | public function __construct($name, $lineno) | ||
21 | { | ||
22 | parent::__construct(array(), array('name' => $name, 'is_defined_test' => false, 'ignore_strict_check' => false, 'always_defined' => false), $lineno); | ||
23 | } | ||
24 | |||
25 | public function compile(Twig_Compiler $compiler) | ||
26 | { | ||
27 | $name = $this->getAttribute('name'); | ||
28 | |||
29 | if ($this->getAttribute('is_defined_test')) { | ||
30 | if ($this->isSpecial()) { | ||
31 | $compiler->repr(true); | ||
32 | } else { | ||
33 | $compiler->raw('array_key_exists(')->repr($name)->raw(', $context)'); | ||
34 | } | ||
35 | } elseif ($this->isSpecial()) { | ||
36 | $compiler->raw($this->specialVars[$name]); | ||
37 | } elseif ($this->getAttribute('always_defined')) { | ||
38 | $compiler | ||
39 | ->raw('$context[') | ||
40 | ->string($name) | ||
41 | ->raw(']') | ||
42 | ; | ||
43 | } else { | ||
44 | // remove the non-PHP 5.4 version when PHP 5.3 support is dropped | ||
45 | // as the non-optimized version is just a workaround for slow ternary operator | ||
46 | // when the context has a lot of variables | ||
47 | if (version_compare(phpversion(), '5.4.0RC1', '>=')) { | ||
48 | // PHP 5.4 ternary operator performance was optimized | ||
49 | $compiler | ||
50 | ->raw('(isset($context[') | ||
51 | ->string($name) | ||
52 | ->raw(']) ? $context[') | ||
53 | ->string($name) | ||
54 | ->raw('] : ') | ||
55 | ; | ||
56 | |||
57 | if ($this->getAttribute('ignore_strict_check') || !$compiler->getEnvironment()->isStrictVariables()) { | ||
58 | $compiler->raw('null)'); | ||
59 | } else { | ||
60 | $compiler->raw('$this->getContext($context, ')->string($name)->raw('))'); | ||
61 | } | ||
62 | } else { | ||
63 | $compiler | ||
64 | ->raw('$this->getContext($context, ') | ||
65 | ->string($name) | ||
66 | ; | ||
67 | |||
68 | if ($this->getAttribute('ignore_strict_check')) { | ||
69 | $compiler->raw(', true'); | ||
70 | } | ||
71 | |||
72 | $compiler | ||
73 | ->raw(')') | ||
74 | ; | ||
75 | } | ||
76 | } | ||
77 | } | ||
78 | |||
79 | public function isSpecial() | ||
80 | { | ||
81 | return isset($this->specialVars[$this->getAttribute('name')]); | ||
82 | } | ||
83 | |||
84 | public function isSimple() | ||
85 | { | ||
86 | return !$this->isSpecial() && !$this->getAttribute('is_defined_test'); | ||
87 | } | ||
88 | } | ||
diff --git a/inc/Twig/Node/Expression/Parent.php b/inc/Twig/Node/Expression/Parent.php new file mode 100644 index 00000000..dcf618c0 --- /dev/null +++ b/inc/Twig/Node/Expression/Parent.php | |||
@@ -0,0 +1,47 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2009 Fabien Potencier | ||
7 | * (c) 2009 Armin Ronacher | ||
8 | * | ||
9 | * For the full copyright and license information, please view the LICENSE | ||
10 | * file that was distributed with this source code. | ||
11 | */ | ||
12 | |||
13 | /** | ||
14 | * Represents a parent node. | ||
15 | * | ||
16 | * @author Fabien Potencier <fabien@symfony.com> | ||
17 | */ | ||
18 | class Twig_Node_Expression_Parent extends Twig_Node_Expression | ||
19 | { | ||
20 | public function __construct($name, $lineno, $tag = null) | ||
21 | { | ||
22 | parent::__construct(array(), array('output' => false, 'name' => $name), $lineno, $tag); | ||
23 | } | ||
24 | |||
25 | /** | ||
26 | * Compiles the node to PHP. | ||
27 | * | ||
28 | * @param Twig_Compiler A Twig_Compiler instance | ||
29 | */ | ||
30 | public function compile(Twig_Compiler $compiler) | ||
31 | { | ||
32 | if ($this->getAttribute('output')) { | ||
33 | $compiler | ||
34 | ->addDebugInfo($this) | ||
35 | ->write("\$this->displayParentBlock(") | ||
36 | ->string($this->getAttribute('name')) | ||
37 | ->raw(", \$context, \$blocks);\n") | ||
38 | ; | ||
39 | } else { | ||
40 | $compiler | ||
41 | ->raw("\$this->renderParentBlock(") | ||
42 | ->string($this->getAttribute('name')) | ||
43 | ->raw(", \$context, \$blocks)") | ||
44 | ; | ||
45 | } | ||
46 | } | ||
47 | } | ||
diff --git a/inc/Twig/Node/Expression/TempName.php b/inc/Twig/Node/Expression/TempName.php new file mode 100644 index 00000000..e6b058e8 --- /dev/null +++ b/inc/Twig/Node/Expression/TempName.php | |||
@@ -0,0 +1,26 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2011 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 | class Twig_Node_Expression_TempName extends Twig_Node_Expression | ||
12 | { | ||
13 | public function __construct($name, $lineno) | ||
14 | { | ||
15 | parent::__construct(array(), array('name' => $name), $lineno); | ||
16 | } | ||
17 | |||
18 | public function compile(Twig_Compiler $compiler) | ||
19 | { | ||
20 | $compiler | ||
21 | ->raw('$_') | ||
22 | ->raw($this->getAttribute('name')) | ||
23 | ->raw('_') | ||
24 | ; | ||
25 | } | ||
26 | } | ||
diff --git a/inc/Twig/Node/Expression/Test.php b/inc/Twig/Node/Expression/Test.php new file mode 100644 index 00000000..639f501a --- /dev/null +++ b/inc/Twig/Node/Expression/Test.php | |||
@@ -0,0 +1,32 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 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 | class Twig_Node_Expression_Test extends Twig_Node_Expression_Call | ||
12 | { | ||
13 | public function __construct(Twig_NodeInterface $node, $name, Twig_NodeInterface $arguments = null, $lineno) | ||
14 | { | ||
15 | parent::__construct(array('node' => $node, 'arguments' => $arguments), array('name' => $name), $lineno); | ||
16 | } | ||
17 | |||
18 | public function compile(Twig_Compiler $compiler) | ||
19 | { | ||
20 | $name = $this->getAttribute('name'); | ||
21 | $test = $compiler->getEnvironment()->getTest($name); | ||
22 | |||
23 | $this->setAttribute('name', $name); | ||
24 | $this->setAttribute('type', 'test'); | ||
25 | $this->setAttribute('thing', $test); | ||
26 | if ($test instanceof Twig_TestCallableInterface || $test instanceof Twig_SimpleTest) { | ||
27 | $this->setAttribute('callable', $test->getCallable()); | ||
28 | } | ||
29 | |||
30 | $this->compileCallable($compiler); | ||
31 | } | ||
32 | } | ||
diff --git a/inc/Twig/Node/Expression/Test/Constant.php b/inc/Twig/Node/Expression/Test/Constant.php new file mode 100644 index 00000000..de55f5f5 --- /dev/null +++ b/inc/Twig/Node/Expression/Test/Constant.php | |||
@@ -0,0 +1,46 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2011 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 | * Checks if a variable is the exact same value as a constant. | ||
14 | * | ||
15 | * <pre> | ||
16 | * {% if post.status is constant('Post::PUBLISHED') %} | ||
17 | * the status attribute is exactly the same as Post::PUBLISHED | ||
18 | * {% endif %} | ||
19 | * </pre> | ||
20 | * | ||
21 | * @author Fabien Potencier <fabien@symfony.com> | ||
22 | */ | ||
23 | class Twig_Node_Expression_Test_Constant extends Twig_Node_Expression_Test | ||
24 | { | ||
25 | public function compile(Twig_Compiler $compiler) | ||
26 | { | ||
27 | $compiler | ||
28 | ->raw('(') | ||
29 | ->subcompile($this->getNode('node')) | ||
30 | ->raw(' === constant(') | ||
31 | ; | ||
32 | |||
33 | if ($this->getNode('arguments')->hasNode(1)) { | ||
34 | $compiler | ||
35 | ->raw('get_class(') | ||
36 | ->subcompile($this->getNode('arguments')->getNode(1)) | ||
37 | ->raw(')."::".') | ||
38 | ; | ||
39 | } | ||
40 | |||
41 | $compiler | ||
42 | ->subcompile($this->getNode('arguments')->getNode(0)) | ||
43 | ->raw('))') | ||
44 | ; | ||
45 | } | ||
46 | } | ||
diff --git a/inc/Twig/Node/Expression/Test/Defined.php b/inc/Twig/Node/Expression/Test/Defined.php new file mode 100644 index 00000000..247b2e23 --- /dev/null +++ b/inc/Twig/Node/Expression/Test/Defined.php | |||
@@ -0,0 +1,54 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2011 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 | * Checks if a variable is defined in the current context. | ||
14 | * | ||
15 | * <pre> | ||
16 | * {# defined works with variable names and variable attributes #} | ||
17 | * {% if foo is defined %} | ||
18 | * {# ... #} | ||
19 | * {% endif %} | ||
20 | * </pre> | ||
21 | * | ||
22 | * @author Fabien Potencier <fabien@symfony.com> | ||
23 | */ | ||
24 | class Twig_Node_Expression_Test_Defined extends Twig_Node_Expression_Test | ||
25 | { | ||
26 | public function __construct(Twig_NodeInterface $node, $name, Twig_NodeInterface $arguments = null, $lineno) | ||
27 | { | ||
28 | parent::__construct($node, $name, $arguments, $lineno); | ||
29 | |||
30 | if ($node instanceof Twig_Node_Expression_Name) { | ||
31 | $node->setAttribute('is_defined_test', true); | ||
32 | } elseif ($node instanceof Twig_Node_Expression_GetAttr) { | ||
33 | $node->setAttribute('is_defined_test', true); | ||
34 | |||
35 | $this->changeIgnoreStrictCheck($node); | ||
36 | } else { | ||
37 | throw new Twig_Error_Syntax('The "defined" test only works with simple variables', $this->getLine()); | ||
38 | } | ||
39 | } | ||
40 | |||
41 | protected function changeIgnoreStrictCheck(Twig_Node_Expression_GetAttr $node) | ||
42 | { | ||
43 | $node->setAttribute('ignore_strict_check', true); | ||
44 | |||
45 | if ($node->getNode('node') instanceof Twig_Node_Expression_GetAttr) { | ||
46 | $this->changeIgnoreStrictCheck($node->getNode('node')); | ||
47 | } | ||
48 | } | ||
49 | |||
50 | public function compile(Twig_Compiler $compiler) | ||
51 | { | ||
52 | $compiler->subcompile($this->getNode('node')); | ||
53 | } | ||
54 | } | ||
diff --git a/inc/Twig/Node/Expression/Test/Divisibleby.php b/inc/Twig/Node/Expression/Test/Divisibleby.php new file mode 100644 index 00000000..0aceb530 --- /dev/null +++ b/inc/Twig/Node/Expression/Test/Divisibleby.php | |||
@@ -0,0 +1,33 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2011 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 | * Checks if a variable is divisible by a number. | ||
14 | * | ||
15 | * <pre> | ||
16 | * {% if loop.index is divisibleby(3) %} | ||
17 | * </pre> | ||
18 | * | ||
19 | * @author Fabien Potencier <fabien@symfony.com> | ||
20 | */ | ||
21 | class Twig_Node_Expression_Test_Divisibleby extends Twig_Node_Expression_Test | ||
22 | { | ||
23 | public function compile(Twig_Compiler $compiler) | ||
24 | { | ||
25 | $compiler | ||
26 | ->raw('(0 == ') | ||
27 | ->subcompile($this->getNode('node')) | ||
28 | ->raw(' % ') | ||
29 | ->subcompile($this->getNode('arguments')->getNode(0)) | ||
30 | ->raw(')') | ||
31 | ; | ||
32 | } | ||
33 | } | ||
diff --git a/inc/Twig/Node/Expression/Test/Even.php b/inc/Twig/Node/Expression/Test/Even.php new file mode 100644 index 00000000..d7853e89 --- /dev/null +++ b/inc/Twig/Node/Expression/Test/Even.php | |||
@@ -0,0 +1,32 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2011 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 | * Checks if a number is even. | ||
14 | * | ||
15 | * <pre> | ||
16 | * {{ var is even }} | ||
17 | * </pre> | ||
18 | * | ||
19 | * @author Fabien Potencier <fabien@symfony.com> | ||
20 | */ | ||
21 | class Twig_Node_Expression_Test_Even extends Twig_Node_Expression_Test | ||
22 | { | ||
23 | public function compile(Twig_Compiler $compiler) | ||
24 | { | ||
25 | $compiler | ||
26 | ->raw('(') | ||
27 | ->subcompile($this->getNode('node')) | ||
28 | ->raw(' % 2 == 0') | ||
29 | ->raw(')') | ||
30 | ; | ||
31 | } | ||
32 | } | ||
diff --git a/inc/Twig/Node/Expression/Test/Null.php b/inc/Twig/Node/Expression/Test/Null.php new file mode 100644 index 00000000..1c83825a --- /dev/null +++ b/inc/Twig/Node/Expression/Test/Null.php | |||
@@ -0,0 +1,31 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2011 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 | * Checks that a variable is null. | ||
14 | * | ||
15 | * <pre> | ||
16 | * {{ var is none }} | ||
17 | * </pre> | ||
18 | * | ||
19 | * @author Fabien Potencier <fabien@symfony.com> | ||
20 | */ | ||
21 | class Twig_Node_Expression_Test_Null extends Twig_Node_Expression_Test | ||
22 | { | ||
23 | public function compile(Twig_Compiler $compiler) | ||
24 | { | ||
25 | $compiler | ||
26 | ->raw('(null === ') | ||
27 | ->subcompile($this->getNode('node')) | ||
28 | ->raw(')') | ||
29 | ; | ||
30 | } | ||
31 | } | ||
diff --git a/inc/Twig/Node/Expression/Test/Odd.php b/inc/Twig/Node/Expression/Test/Odd.php new file mode 100644 index 00000000..421c19e8 --- /dev/null +++ b/inc/Twig/Node/Expression/Test/Odd.php | |||
@@ -0,0 +1,32 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2011 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 | * Checks if a number is odd. | ||
14 | * | ||
15 | * <pre> | ||
16 | * {{ var is odd }} | ||
17 | * </pre> | ||
18 | * | ||
19 | * @author Fabien Potencier <fabien@symfony.com> | ||
20 | */ | ||
21 | class Twig_Node_Expression_Test_Odd extends Twig_Node_Expression_Test | ||
22 | { | ||
23 | public function compile(Twig_Compiler $compiler) | ||
24 | { | ||
25 | $compiler | ||
26 | ->raw('(') | ||
27 | ->subcompile($this->getNode('node')) | ||
28 | ->raw(' % 2 == 1') | ||
29 | ->raw(')') | ||
30 | ; | ||
31 | } | ||
32 | } | ||
diff --git a/inc/Twig/Node/Expression/Test/Sameas.php b/inc/Twig/Node/Expression/Test/Sameas.php new file mode 100644 index 00000000..b48905ee --- /dev/null +++ b/inc/Twig/Node/Expression/Test/Sameas.php | |||
@@ -0,0 +1,29 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2011 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 | * Checks if a variable is the same as another one (=== in PHP). | ||
14 | * | ||
15 | * @author Fabien Potencier <fabien@symfony.com> | ||
16 | */ | ||
17 | class Twig_Node_Expression_Test_Sameas extends Twig_Node_Expression_Test | ||
18 | { | ||
19 | public function compile(Twig_Compiler $compiler) | ||
20 | { | ||
21 | $compiler | ||
22 | ->raw('(') | ||
23 | ->subcompile($this->getNode('node')) | ||
24 | ->raw(' === ') | ||
25 | ->subcompile($this->getNode('arguments')->getNode(0)) | ||
26 | ->raw(')') | ||
27 | ; | ||
28 | } | ||
29 | } | ||
diff --git a/inc/Twig/Node/Expression/Unary.php b/inc/Twig/Node/Expression/Unary.php new file mode 100644 index 00000000..c514388e --- /dev/null +++ b/inc/Twig/Node/Expression/Unary.php | |||
@@ -0,0 +1,30 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2009 Fabien Potencier | ||
7 | * (c) 2009 Armin Ronacher | ||
8 | * | ||
9 | * For the full copyright and license information, please view the LICENSE | ||
10 | * file that was distributed with this source code. | ||
11 | */ | ||
12 | abstract class Twig_Node_Expression_Unary extends Twig_Node_Expression | ||
13 | { | ||
14 | public function __construct(Twig_NodeInterface $node, $lineno) | ||
15 | { | ||
16 | parent::__construct(array('node' => $node), array(), $lineno); | ||
17 | } | ||
18 | |||
19 | public function compile(Twig_Compiler $compiler) | ||
20 | { | ||
21 | $compiler->raw('('); | ||
22 | $this->operator($compiler); | ||
23 | $compiler | ||
24 | ->subcompile($this->getNode('node')) | ||
25 | ->raw(')') | ||
26 | ; | ||
27 | } | ||
28 | |||
29 | abstract public function operator(Twig_Compiler $compiler); | ||
30 | } | ||
diff --git a/inc/Twig/Node/Expression/Unary/Neg.php b/inc/Twig/Node/Expression/Unary/Neg.php new file mode 100644 index 00000000..2a3937ec --- /dev/null +++ b/inc/Twig/Node/Expression/Unary/Neg.php | |||
@@ -0,0 +1,18 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2009 Fabien Potencier | ||
7 | * (c) 2009 Armin Ronacher | ||
8 | * | ||
9 | * For the full copyright and license information, please view the LICENSE | ||
10 | * file that was distributed with this source code. | ||
11 | */ | ||
12 | class Twig_Node_Expression_Unary_Neg extends Twig_Node_Expression_Unary | ||
13 | { | ||
14 | public function operator(Twig_Compiler $compiler) | ||
15 | { | ||
16 | $compiler->raw('-'); | ||
17 | } | ||
18 | } | ||
diff --git a/inc/Twig/Node/Expression/Unary/Not.php b/inc/Twig/Node/Expression/Unary/Not.php new file mode 100644 index 00000000..f94073cf --- /dev/null +++ b/inc/Twig/Node/Expression/Unary/Not.php | |||
@@ -0,0 +1,18 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2009 Fabien Potencier | ||
7 | * (c) 2009 Armin Ronacher | ||
8 | * | ||
9 | * For the full copyright and license information, please view the LICENSE | ||
10 | * file that was distributed with this source code. | ||
11 | */ | ||
12 | class Twig_Node_Expression_Unary_Not extends Twig_Node_Expression_Unary | ||
13 | { | ||
14 | public function operator(Twig_Compiler $compiler) | ||
15 | { | ||
16 | $compiler->raw('!'); | ||
17 | } | ||
18 | } | ||
diff --git a/inc/Twig/Node/Expression/Unary/Pos.php b/inc/Twig/Node/Expression/Unary/Pos.php new file mode 100644 index 00000000..04edb52a --- /dev/null +++ b/inc/Twig/Node/Expression/Unary/Pos.php | |||
@@ -0,0 +1,18 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2009 Fabien Potencier | ||
7 | * (c) 2009 Armin Ronacher | ||
8 | * | ||
9 | * For the full copyright and license information, please view the LICENSE | ||
10 | * file that was distributed with this source code. | ||
11 | */ | ||
12 | class Twig_Node_Expression_Unary_Pos extends Twig_Node_Expression_Unary | ||
13 | { | ||
14 | public function operator(Twig_Compiler $compiler) | ||
15 | { | ||
16 | $compiler->raw('+'); | ||
17 | } | ||
18 | } | ||
diff --git a/inc/Twig/Node/Flush.php b/inc/Twig/Node/Flush.php new file mode 100644 index 00000000..0467ddce --- /dev/null +++ b/inc/Twig/Node/Flush.php | |||
@@ -0,0 +1,36 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2011 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 | * Represents a flush node. | ||
14 | * | ||
15 | * @author Fabien Potencier <fabien@symfony.com> | ||
16 | */ | ||
17 | class Twig_Node_Flush extends Twig_Node | ||
18 | { | ||
19 | public function __construct($lineno, $tag) | ||
20 | { | ||
21 | parent::__construct(array(), array(), $lineno, $tag); | ||
22 | } | ||
23 | |||
24 | /** | ||
25 | * Compiles the node to PHP. | ||
26 | * | ||
27 | * @param Twig_Compiler A Twig_Compiler instance | ||
28 | */ | ||
29 | public function compile(Twig_Compiler $compiler) | ||
30 | { | ||
31 | $compiler | ||
32 | ->addDebugInfo($this) | ||
33 | ->write("flush();\n") | ||
34 | ; | ||
35 | } | ||
36 | } | ||
diff --git a/inc/Twig/Node/For.php b/inc/Twig/Node/For.php new file mode 100644 index 00000000..d1ff371d --- /dev/null +++ b/inc/Twig/Node/For.php | |||
@@ -0,0 +1,112 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2009 Fabien Potencier | ||
7 | * (c) 2009 Armin Ronacher | ||
8 | * | ||
9 | * For the full copyright and license information, please view the LICENSE | ||
10 | * file that was distributed with this source code. | ||
11 | */ | ||
12 | |||
13 | /** | ||
14 | * Represents a for node. | ||
15 | * | ||
16 | * @author Fabien Potencier <fabien@symfony.com> | ||
17 | */ | ||
18 | class Twig_Node_For extends Twig_Node | ||
19 | { | ||
20 | protected $loop; | ||
21 | |||
22 | public function __construct(Twig_Node_Expression_AssignName $keyTarget, Twig_Node_Expression_AssignName $valueTarget, Twig_Node_Expression $seq, Twig_Node_Expression $ifexpr = null, Twig_NodeInterface $body, Twig_NodeInterface $else = null, $lineno, $tag = null) | ||
23 | { | ||
24 | $body = new Twig_Node(array($body, $this->loop = new Twig_Node_ForLoop($lineno, $tag))); | ||
25 | |||
26 | if (null !== $ifexpr) { | ||
27 | $body = new Twig_Node_If(new Twig_Node(array($ifexpr, $body)), null, $lineno, $tag); | ||
28 | } | ||
29 | |||
30 | parent::__construct(array('key_target' => $keyTarget, 'value_target' => $valueTarget, 'seq' => $seq, 'body' => $body, 'else' => $else), array('with_loop' => true, 'ifexpr' => null !== $ifexpr), $lineno, $tag); | ||
31 | } | ||
32 | |||
33 | /** | ||
34 | * Compiles the node to PHP. | ||
35 | * | ||
36 | * @param Twig_Compiler A Twig_Compiler instance | ||
37 | */ | ||
38 | public function compile(Twig_Compiler $compiler) | ||
39 | { | ||
40 | $compiler | ||
41 | ->addDebugInfo($this) | ||
42 | // the (array) cast bypasses a PHP 5.2.6 bug | ||
43 | ->write("\$context['_parent'] = (array) \$context;\n") | ||
44 | ->write("\$context['_seq'] = twig_ensure_traversable(") | ||
45 | ->subcompile($this->getNode('seq')) | ||
46 | ->raw(");\n") | ||
47 | ; | ||
48 | |||
49 | if (null !== $this->getNode('else')) { | ||
50 | $compiler->write("\$context['_iterated'] = false;\n"); | ||
51 | } | ||
52 | |||
53 | if ($this->getAttribute('with_loop')) { | ||
54 | $compiler | ||
55 | ->write("\$context['loop'] = array(\n") | ||
56 | ->write(" 'parent' => \$context['_parent'],\n") | ||
57 | ->write(" 'index0' => 0,\n") | ||
58 | ->write(" 'index' => 1,\n") | ||
59 | ->write(" 'first' => true,\n") | ||
60 | ->write(");\n") | ||
61 | ; | ||
62 | |||
63 | if (!$this->getAttribute('ifexpr')) { | ||
64 | $compiler | ||
65 | ->write("if (is_array(\$context['_seq']) || (is_object(\$context['_seq']) && \$context['_seq'] instanceof Countable)) {\n") | ||
66 | ->indent() | ||
67 | ->write("\$length = count(\$context['_seq']);\n") | ||
68 | ->write("\$context['loop']['revindex0'] = \$length - 1;\n") | ||
69 | ->write("\$context['loop']['revindex'] = \$length;\n") | ||
70 | ->write("\$context['loop']['length'] = \$length;\n") | ||
71 | ->write("\$context['loop']['last'] = 1 === \$length;\n") | ||
72 | ->outdent() | ||
73 | ->write("}\n") | ||
74 | ; | ||
75 | } | ||
76 | } | ||
77 | |||
78 | $this->loop->setAttribute('else', null !== $this->getNode('else')); | ||
79 | $this->loop->setAttribute('with_loop', $this->getAttribute('with_loop')); | ||
80 | $this->loop->setAttribute('ifexpr', $this->getAttribute('ifexpr')); | ||
81 | |||
82 | $compiler | ||
83 | ->write("foreach (\$context['_seq'] as ") | ||
84 | ->subcompile($this->getNode('key_target')) | ||
85 | ->raw(" => ") | ||
86 | ->subcompile($this->getNode('value_target')) | ||
87 | ->raw(") {\n") | ||
88 | ->indent() | ||
89 | ->subcompile($this->getNode('body')) | ||
90 | ->outdent() | ||
91 | ->write("}\n") | ||
92 | ; | ||
93 | |||
94 | if (null !== $this->getNode('else')) { | ||
95 | $compiler | ||
96 | ->write("if (!\$context['_iterated']) {\n") | ||
97 | ->indent() | ||
98 | ->subcompile($this->getNode('else')) | ||
99 | ->outdent() | ||
100 | ->write("}\n") | ||
101 | ; | ||
102 | } | ||
103 | |||
104 | $compiler->write("\$_parent = \$context['_parent'];\n"); | ||
105 | |||
106 | // remove some "private" loop variables (needed for nested loops) | ||
107 | $compiler->write('unset($context[\'_seq\'], $context[\'_iterated\'], $context[\''.$this->getNode('key_target')->getAttribute('name').'\'], $context[\''.$this->getNode('value_target')->getAttribute('name').'\'], $context[\'_parent\'], $context[\'loop\']);'."\n"); | ||
108 | |||
109 | // keep the values set in the inner context for variables defined in the outer context | ||
110 | $compiler->write("\$context = array_intersect_key(\$context, \$_parent) + \$_parent;\n"); | ||
111 | } | ||
112 | } | ||
diff --git a/inc/Twig/Node/ForLoop.php b/inc/Twig/Node/ForLoop.php new file mode 100644 index 00000000..b8841583 --- /dev/null +++ b/inc/Twig/Node/ForLoop.php | |||
@@ -0,0 +1,55 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2011 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 | * Internal node used by the for node. | ||
14 | * | ||
15 | * @author Fabien Potencier <fabien@symfony.com> | ||
16 | */ | ||
17 | class Twig_Node_ForLoop extends Twig_Node | ||
18 | { | ||
19 | public function __construct($lineno, $tag = null) | ||
20 | { | ||
21 | parent::__construct(array(), array('with_loop' => false, 'ifexpr' => false, 'else' => false), $lineno, $tag); | ||
22 | } | ||
23 | |||
24 | /** | ||
25 | * Compiles the node to PHP. | ||
26 | * | ||
27 | * @param Twig_Compiler A Twig_Compiler instance | ||
28 | */ | ||
29 | public function compile(Twig_Compiler $compiler) | ||
30 | { | ||
31 | if ($this->getAttribute('else')) { | ||
32 | $compiler->write("\$context['_iterated'] = true;\n"); | ||
33 | } | ||
34 | |||
35 | if ($this->getAttribute('with_loop')) { | ||
36 | $compiler | ||
37 | ->write("++\$context['loop']['index0'];\n") | ||
38 | ->write("++\$context['loop']['index'];\n") | ||
39 | ->write("\$context['loop']['first'] = false;\n") | ||
40 | ; | ||
41 | |||
42 | if (!$this->getAttribute('ifexpr')) { | ||
43 | $compiler | ||
44 | ->write("if (isset(\$context['loop']['length'])) {\n") | ||
45 | ->indent() | ||
46 | ->write("--\$context['loop']['revindex0'];\n") | ||
47 | ->write("--\$context['loop']['revindex'];\n") | ||
48 | ->write("\$context['loop']['last'] = 0 === \$context['loop']['revindex0'];\n") | ||
49 | ->outdent() | ||
50 | ->write("}\n") | ||
51 | ; | ||
52 | } | ||
53 | } | ||
54 | } | ||
55 | } | ||
diff --git a/inc/Twig/Node/If.php b/inc/Twig/Node/If.php new file mode 100644 index 00000000..4296a8d6 --- /dev/null +++ b/inc/Twig/Node/If.php | |||
@@ -0,0 +1,66 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2009 Fabien Potencier | ||
7 | * (c) 2009 Armin Ronacher | ||
8 | * | ||
9 | * For the full copyright and license information, please view the LICENSE | ||
10 | * file that was distributed with this source code. | ||
11 | */ | ||
12 | |||
13 | /** | ||
14 | * Represents an if node. | ||
15 | * | ||
16 | * @author Fabien Potencier <fabien@symfony.com> | ||
17 | */ | ||
18 | class Twig_Node_If extends Twig_Node | ||
19 | { | ||
20 | public function __construct(Twig_NodeInterface $tests, Twig_NodeInterface $else = null, $lineno, $tag = null) | ||
21 | { | ||
22 | parent::__construct(array('tests' => $tests, 'else' => $else), array(), $lineno, $tag); | ||
23 | } | ||
24 | |||
25 | /** | ||
26 | * Compiles the node to PHP. | ||
27 | * | ||
28 | * @param Twig_Compiler A Twig_Compiler instance | ||
29 | */ | ||
30 | public function compile(Twig_Compiler $compiler) | ||
31 | { | ||
32 | $compiler->addDebugInfo($this); | ||
33 | for ($i = 0; $i < count($this->getNode('tests')); $i += 2) { | ||
34 | if ($i > 0) { | ||
35 | $compiler | ||
36 | ->outdent() | ||
37 | ->write("} elseif (") | ||
38 | ; | ||
39 | } else { | ||
40 | $compiler | ||
41 | ->write('if (') | ||
42 | ; | ||
43 | } | ||
44 | |||
45 | $compiler | ||
46 | ->subcompile($this->getNode('tests')->getNode($i)) | ||
47 | ->raw(") {\n") | ||
48 | ->indent() | ||
49 | ->subcompile($this->getNode('tests')->getNode($i + 1)) | ||
50 | ; | ||
51 | } | ||
52 | |||
53 | if ($this->hasNode('else') && null !== $this->getNode('else')) { | ||
54 | $compiler | ||
55 | ->outdent() | ||
56 | ->write("} else {\n") | ||
57 | ->indent() | ||
58 | ->subcompile($this->getNode('else')) | ||
59 | ; | ||
60 | } | ||
61 | |||
62 | $compiler | ||
63 | ->outdent() | ||
64 | ->write("}\n"); | ||
65 | } | ||
66 | } | ||
diff --git a/inc/Twig/Node/Import.php b/inc/Twig/Node/Import.php new file mode 100644 index 00000000..99efc091 --- /dev/null +++ b/inc/Twig/Node/Import.php | |||
@@ -0,0 +1,50 @@ | |||
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 | * Represents an import node. | ||
14 | * | ||
15 | * @author Fabien Potencier <fabien@symfony.com> | ||
16 | */ | ||
17 | class Twig_Node_Import extends Twig_Node | ||
18 | { | ||
19 | public function __construct(Twig_Node_Expression $expr, Twig_Node_Expression $var, $lineno, $tag = null) | ||
20 | { | ||
21 | parent::__construct(array('expr' => $expr, 'var' => $var), array(), $lineno, $tag); | ||
22 | } | ||
23 | |||
24 | /** | ||
25 | * Compiles the node to PHP. | ||
26 | * | ||
27 | * @param Twig_Compiler A Twig_Compiler instance | ||
28 | */ | ||
29 | public function compile(Twig_Compiler $compiler) | ||
30 | { | ||
31 | $compiler | ||
32 | ->addDebugInfo($this) | ||
33 | ->write('') | ||
34 | ->subcompile($this->getNode('var')) | ||
35 | ->raw(' = ') | ||
36 | ; | ||
37 | |||
38 | if ($this->getNode('expr') instanceof Twig_Node_Expression_Name && '_self' === $this->getNode('expr')->getAttribute('name')) { | ||
39 | $compiler->raw("\$this"); | ||
40 | } else { | ||
41 | $compiler | ||
42 | ->raw('$this->env->loadTemplate(') | ||
43 | ->subcompile($this->getNode('expr')) | ||
44 | ->raw(")") | ||
45 | ; | ||
46 | } | ||
47 | |||
48 | $compiler->raw(";\n"); | ||
49 | } | ||
50 | } | ||
diff --git a/inc/Twig/Node/Include.php b/inc/Twig/Node/Include.php new file mode 100644 index 00000000..ed4a3751 --- /dev/null +++ b/inc/Twig/Node/Include.php | |||
@@ -0,0 +1,99 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2009 Fabien Potencier | ||
7 | * (c) 2009 Armin Ronacher | ||
8 | * | ||
9 | * For the full copyright and license information, please view the LICENSE | ||
10 | * file that was distributed with this source code. | ||
11 | */ | ||
12 | |||
13 | /** | ||
14 | * Represents an include node. | ||
15 | * | ||
16 | * @author Fabien Potencier <fabien@symfony.com> | ||
17 | */ | ||
18 | class Twig_Node_Include extends Twig_Node implements Twig_NodeOutputInterface | ||
19 | { | ||
20 | public function __construct(Twig_Node_Expression $expr, Twig_Node_Expression $variables = null, $only = false, $ignoreMissing = false, $lineno, $tag = null) | ||
21 | { | ||
22 | parent::__construct(array('expr' => $expr, 'variables' => $variables), array('only' => (Boolean) $only, 'ignore_missing' => (Boolean) $ignoreMissing), $lineno, $tag); | ||
23 | } | ||
24 | |||
25 | /** | ||
26 | * Compiles the node to PHP. | ||
27 | * | ||
28 | * @param Twig_Compiler A Twig_Compiler instance | ||
29 | */ | ||
30 | public function compile(Twig_Compiler $compiler) | ||
31 | { | ||
32 | $compiler->addDebugInfo($this); | ||
33 | |||
34 | if ($this->getAttribute('ignore_missing')) { | ||
35 | $compiler | ||
36 | ->write("try {\n") | ||
37 | ->indent() | ||
38 | ; | ||
39 | } | ||
40 | |||
41 | $this->addGetTemplate($compiler); | ||
42 | |||
43 | $compiler->raw('->display('); | ||
44 | |||
45 | $this->addTemplateArguments($compiler); | ||
46 | |||
47 | $compiler->raw(");\n"); | ||
48 | |||
49 | if ($this->getAttribute('ignore_missing')) { | ||
50 | $compiler | ||
51 | ->outdent() | ||
52 | ->write("} catch (Twig_Error_Loader \$e) {\n") | ||
53 | ->indent() | ||
54 | ->write("// ignore missing template\n") | ||
55 | ->outdent() | ||
56 | ->write("}\n\n") | ||
57 | ; | ||
58 | } | ||
59 | } | ||
60 | |||
61 | protected function addGetTemplate(Twig_Compiler $compiler) | ||
62 | { | ||
63 | if ($this->getNode('expr') instanceof Twig_Node_Expression_Constant) { | ||
64 | $compiler | ||
65 | ->write("\$this->env->loadTemplate(") | ||
66 | ->subcompile($this->getNode('expr')) | ||
67 | ->raw(")") | ||
68 | ; | ||
69 | } else { | ||
70 | $compiler | ||
71 | ->write("\$template = \$this->env->resolveTemplate(") | ||
72 | ->subcompile($this->getNode('expr')) | ||
73 | ->raw(");\n") | ||
74 | ->write('$template') | ||
75 | ; | ||
76 | } | ||
77 | } | ||
78 | |||
79 | protected function addTemplateArguments(Twig_Compiler $compiler) | ||
80 | { | ||
81 | if (false === $this->getAttribute('only')) { | ||
82 | if (null === $this->getNode('variables')) { | ||
83 | $compiler->raw('$context'); | ||
84 | } else { | ||
85 | $compiler | ||
86 | ->raw('array_merge($context, ') | ||
87 | ->subcompile($this->getNode('variables')) | ||
88 | ->raw(')') | ||
89 | ; | ||
90 | } | ||
91 | } else { | ||
92 | if (null === $this->getNode('variables')) { | ||
93 | $compiler->raw('array()'); | ||
94 | } else { | ||
95 | $compiler->subcompile($this->getNode('variables')); | ||
96 | } | ||
97 | } | ||
98 | } | ||
99 | } | ||
diff --git a/inc/Twig/Node/Macro.php b/inc/Twig/Node/Macro.php new file mode 100644 index 00000000..89910618 --- /dev/null +++ b/inc/Twig/Node/Macro.php | |||
@@ -0,0 +1,96 @@ | |||
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 | * Represents a macro node. | ||
14 | * | ||
15 | * @author Fabien Potencier <fabien@symfony.com> | ||
16 | */ | ||
17 | class Twig_Node_Macro extends Twig_Node | ||
18 | { | ||
19 | public function __construct($name, Twig_NodeInterface $body, Twig_NodeInterface $arguments, $lineno, $tag = null) | ||
20 | { | ||
21 | parent::__construct(array('body' => $body, 'arguments' => $arguments), array('name' => $name), $lineno, $tag); | ||
22 | } | ||
23 | |||
24 | /** | ||
25 | * Compiles the node to PHP. | ||
26 | * | ||
27 | * @param Twig_Compiler A Twig_Compiler instance | ||
28 | */ | ||
29 | public function compile(Twig_Compiler $compiler) | ||
30 | { | ||
31 | $compiler | ||
32 | ->addDebugInfo($this) | ||
33 | ->write(sprintf("public function get%s(", $this->getAttribute('name'))) | ||
34 | ; | ||
35 | |||
36 | $count = count($this->getNode('arguments')); | ||
37 | $pos = 0; | ||
38 | foreach ($this->getNode('arguments') as $name => $default) { | ||
39 | $compiler | ||
40 | ->raw('$_'.$name.' = ') | ||
41 | ->subcompile($default) | ||
42 | ; | ||
43 | |||
44 | if (++$pos < $count) { | ||
45 | $compiler->raw(', '); | ||
46 | } | ||
47 | } | ||
48 | |||
49 | $compiler | ||
50 | ->raw(")\n") | ||
51 | ->write("{\n") | ||
52 | ->indent() | ||
53 | ; | ||
54 | |||
55 | if (!count($this->getNode('arguments'))) { | ||
56 | $compiler->write("\$context = \$this->env->getGlobals();\n\n"); | ||
57 | } else { | ||
58 | $compiler | ||
59 | ->write("\$context = \$this->env->mergeGlobals(array(\n") | ||
60 | ->indent() | ||
61 | ; | ||
62 | |||
63 | foreach ($this->getNode('arguments') as $name => $default) { | ||
64 | $compiler | ||
65 | ->write('') | ||
66 | ->string($name) | ||
67 | ->raw(' => $_'.$name) | ||
68 | ->raw(",\n") | ||
69 | ; | ||
70 | } | ||
71 | |||
72 | $compiler | ||
73 | ->outdent() | ||
74 | ->write("));\n\n") | ||
75 | ; | ||
76 | } | ||
77 | |||
78 | $compiler | ||
79 | ->write("\$blocks = array();\n\n") | ||
80 | ->write("ob_start();\n") | ||
81 | ->write("try {\n") | ||
82 | ->indent() | ||
83 | ->subcompile($this->getNode('body')) | ||
84 | ->outdent() | ||
85 | ->write("} catch (Exception \$e) {\n") | ||
86 | ->indent() | ||
87 | ->write("ob_end_clean();\n\n") | ||
88 | ->write("throw \$e;\n") | ||
89 | ->outdent() | ||
90 | ->write("}\n\n") | ||
91 | ->write("return ('' === \$tmp = ob_get_clean()) ? '' : new Twig_Markup(\$tmp, \$this->env->getCharset());\n") | ||
92 | ->outdent() | ||
93 | ->write("}\n\n") | ||
94 | ; | ||
95 | } | ||
96 | } | ||
diff --git a/inc/Twig/Node/Module.php b/inc/Twig/Node/Module.php new file mode 100644 index 00000000..585048b8 --- /dev/null +++ b/inc/Twig/Node/Module.php | |||
@@ -0,0 +1,371 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2009 Fabien Potencier | ||
7 | * (c) 2009 Armin Ronacher | ||
8 | * | ||
9 | * For the full copyright and license information, please view the LICENSE | ||
10 | * file that was distributed with this source code. | ||
11 | */ | ||
12 | |||
13 | /** | ||
14 | * Represents a module node. | ||
15 | * | ||
16 | * @author Fabien Potencier <fabien@symfony.com> | ||
17 | */ | ||
18 | class Twig_Node_Module extends Twig_Node | ||
19 | { | ||
20 | public function __construct(Twig_NodeInterface $body, Twig_Node_Expression $parent = null, Twig_NodeInterface $blocks, Twig_NodeInterface $macros, Twig_NodeInterface $traits, $embeddedTemplates, $filename) | ||
21 | { | ||
22 | // embedded templates are set as attributes so that they are only visited once by the visitors | ||
23 | parent::__construct(array('parent' => $parent, 'body' => $body, 'blocks' => $blocks, 'macros' => $macros, 'traits' => $traits), array('filename' => $filename, 'index' => null, 'embedded_templates' => $embeddedTemplates), 1); | ||
24 | } | ||
25 | |||
26 | public function setIndex($index) | ||
27 | { | ||
28 | $this->setAttribute('index', $index); | ||
29 | } | ||
30 | |||
31 | /** | ||
32 | * Compiles the node to PHP. | ||
33 | * | ||
34 | * @param Twig_Compiler A Twig_Compiler instance | ||
35 | */ | ||
36 | public function compile(Twig_Compiler $compiler) | ||
37 | { | ||
38 | $this->compileTemplate($compiler); | ||
39 | |||
40 | foreach ($this->getAttribute('embedded_templates') as $template) { | ||
41 | $compiler->subcompile($template); | ||
42 | } | ||
43 | } | ||
44 | |||
45 | protected function compileTemplate(Twig_Compiler $compiler) | ||
46 | { | ||
47 | if (!$this->getAttribute('index')) { | ||
48 | $compiler->write('<?php'); | ||
49 | } | ||
50 | |||
51 | $this->compileClassHeader($compiler); | ||
52 | |||
53 | if (count($this->getNode('blocks')) || count($this->getNode('traits')) || null === $this->getNode('parent') || $this->getNode('parent') instanceof Twig_Node_Expression_Constant) { | ||
54 | $this->compileConstructor($compiler); | ||
55 | } | ||
56 | |||
57 | $this->compileGetParent($compiler); | ||
58 | |||
59 | $this->compileDisplayHeader($compiler); | ||
60 | |||
61 | $this->compileDisplayBody($compiler); | ||
62 | |||
63 | $this->compileDisplayFooter($compiler); | ||
64 | |||
65 | $compiler->subcompile($this->getNode('blocks')); | ||
66 | |||
67 | $this->compileMacros($compiler); | ||
68 | |||
69 | $this->compileGetTemplateName($compiler); | ||
70 | |||
71 | $this->compileIsTraitable($compiler); | ||
72 | |||
73 | $this->compileDebugInfo($compiler); | ||
74 | |||
75 | $this->compileClassFooter($compiler); | ||
76 | } | ||
77 | |||
78 | protected function compileGetParent(Twig_Compiler $compiler) | ||
79 | { | ||
80 | if (null === $this->getNode('parent')) { | ||
81 | return; | ||
82 | } | ||
83 | |||
84 | $compiler | ||
85 | ->write("protected function doGetParent(array \$context)\n", "{\n") | ||
86 | ->indent() | ||
87 | ->write("return ") | ||
88 | ; | ||
89 | |||
90 | if ($this->getNode('parent') instanceof Twig_Node_Expression_Constant) { | ||
91 | $compiler->subcompile($this->getNode('parent')); | ||
92 | } else { | ||
93 | $compiler | ||
94 | ->raw("\$this->env->resolveTemplate(") | ||
95 | ->subcompile($this->getNode('parent')) | ||
96 | ->raw(")") | ||
97 | ; | ||
98 | } | ||
99 | |||
100 | $compiler | ||
101 | ->raw(";\n") | ||
102 | ->outdent() | ||
103 | ->write("}\n\n") | ||
104 | ; | ||
105 | } | ||
106 | |||
107 | protected function compileDisplayBody(Twig_Compiler $compiler) | ||
108 | { | ||
109 | $compiler->subcompile($this->getNode('body')); | ||
110 | |||
111 | if (null !== $this->getNode('parent')) { | ||
112 | if ($this->getNode('parent') instanceof Twig_Node_Expression_Constant) { | ||
113 | $compiler->write("\$this->parent"); | ||
114 | } else { | ||
115 | $compiler->write("\$this->getParent(\$context)"); | ||
116 | } | ||
117 | $compiler->raw("->display(\$context, array_merge(\$this->blocks, \$blocks));\n"); | ||
118 | } | ||
119 | } | ||
120 | |||
121 | protected function compileClassHeader(Twig_Compiler $compiler) | ||
122 | { | ||
123 | $compiler | ||
124 | ->write("\n\n") | ||
125 | // if the filename contains */, add a blank to avoid a PHP parse error | ||
126 | ->write("/* ".str_replace('*/', '* /', $this->getAttribute('filename'))." */\n") | ||
127 | ->write('class '.$compiler->getEnvironment()->getTemplateClass($this->getAttribute('filename'), $this->getAttribute('index'))) | ||
128 | ->raw(sprintf(" extends %s\n", $compiler->getEnvironment()->getBaseTemplateClass())) | ||
129 | ->write("{\n") | ||
130 | ->indent() | ||
131 | ; | ||
132 | } | ||
133 | |||
134 | protected function compileConstructor(Twig_Compiler $compiler) | ||
135 | { | ||
136 | $compiler | ||
137 | ->write("public function __construct(Twig_Environment \$env)\n", "{\n") | ||
138 | ->indent() | ||
139 | ->write("parent::__construct(\$env);\n\n") | ||
140 | ; | ||
141 | |||
142 | // parent | ||
143 | if (null === $this->getNode('parent')) { | ||
144 | $compiler->write("\$this->parent = false;\n\n"); | ||
145 | } elseif ($this->getNode('parent') instanceof Twig_Node_Expression_Constant) { | ||
146 | $compiler | ||
147 | ->write("\$this->parent = \$this->env->loadTemplate(") | ||
148 | ->subcompile($this->getNode('parent')) | ||
149 | ->raw(");\n\n") | ||
150 | ; | ||
151 | } | ||
152 | |||
153 | $countTraits = count($this->getNode('traits')); | ||
154 | if ($countTraits) { | ||
155 | // traits | ||
156 | foreach ($this->getNode('traits') as $i => $trait) { | ||
157 | $this->compileLoadTemplate($compiler, $trait->getNode('template'), sprintf('$_trait_%s', $i)); | ||
158 | |||
159 | $compiler | ||
160 | ->addDebugInfo($trait->getNode('template')) | ||
161 | ->write(sprintf("if (!\$_trait_%s->isTraitable()) {\n", $i)) | ||
162 | ->indent() | ||
163 | ->write("throw new Twig_Error_Runtime('Template \"'.") | ||
164 | ->subcompile($trait->getNode('template')) | ||
165 | ->raw(".'\" cannot be used as a trait.');\n") | ||
166 | ->outdent() | ||
167 | ->write("}\n") | ||
168 | ->write(sprintf("\$_trait_%s_blocks = \$_trait_%s->getBlocks();\n\n", $i, $i)) | ||
169 | ; | ||
170 | |||
171 | foreach ($trait->getNode('targets') as $key => $value) { | ||
172 | $compiler | ||
173 | ->write(sprintf("\$_trait_%s_blocks[", $i)) | ||
174 | ->subcompile($value) | ||
175 | ->raw(sprintf("] = \$_trait_%s_blocks[", $i)) | ||
176 | ->string($key) | ||
177 | ->raw(sprintf("]; unset(\$_trait_%s_blocks[", $i)) | ||
178 | ->string($key) | ||
179 | ->raw("]);\n\n") | ||
180 | ; | ||
181 | } | ||
182 | } | ||
183 | |||
184 | if ($countTraits > 1) { | ||
185 | $compiler | ||
186 | ->write("\$this->traits = array_merge(\n") | ||
187 | ->indent() | ||
188 | ; | ||
189 | |||
190 | for ($i = 0; $i < $countTraits; $i++) { | ||
191 | $compiler | ||
192 | ->write(sprintf("\$_trait_%s_blocks".($i == $countTraits - 1 ? '' : ',')."\n", $i)) | ||
193 | ; | ||
194 | } | ||
195 | |||
196 | $compiler | ||
197 | ->outdent() | ||
198 | ->write(");\n\n") | ||
199 | ; | ||
200 | } else { | ||
201 | $compiler | ||
202 | ->write("\$this->traits = \$_trait_0_blocks;\n\n") | ||
203 | ; | ||
204 | } | ||
205 | |||
206 | $compiler | ||
207 | ->write("\$this->blocks = array_merge(\n") | ||
208 | ->indent() | ||
209 | ->write("\$this->traits,\n") | ||
210 | ->write("array(\n") | ||
211 | ; | ||
212 | } else { | ||
213 | $compiler | ||
214 | ->write("\$this->blocks = array(\n") | ||
215 | ; | ||
216 | } | ||
217 | |||
218 | // blocks | ||
219 | $compiler | ||
220 | ->indent() | ||
221 | ; | ||
222 | |||
223 | foreach ($this->getNode('blocks') as $name => $node) { | ||
224 | $compiler | ||
225 | ->write(sprintf("'%s' => array(\$this, 'block_%s'),\n", $name, $name)) | ||
226 | ; | ||
227 | } | ||
228 | |||
229 | if ($countTraits) { | ||
230 | $compiler | ||
231 | ->outdent() | ||
232 | ->write(")\n") | ||
233 | ; | ||
234 | } | ||
235 | |||
236 | $compiler | ||
237 | ->outdent() | ||
238 | ->write(");\n") | ||
239 | ->outdent() | ||
240 | ->write("}\n\n"); | ||
241 | ; | ||
242 | } | ||
243 | |||
244 | protected function compileDisplayHeader(Twig_Compiler $compiler) | ||
245 | { | ||
246 | $compiler | ||
247 | ->write("protected function doDisplay(array \$context, array \$blocks = array())\n", "{\n") | ||
248 | ->indent() | ||
249 | ; | ||
250 | } | ||
251 | |||
252 | protected function compileDisplayFooter(Twig_Compiler $compiler) | ||
253 | { | ||
254 | $compiler | ||
255 | ->outdent() | ||
256 | ->write("}\n\n") | ||
257 | ; | ||
258 | } | ||
259 | |||
260 | protected function compileClassFooter(Twig_Compiler $compiler) | ||
261 | { | ||
262 | $compiler | ||
263 | ->outdent() | ||
264 | ->write("}\n") | ||
265 | ; | ||
266 | } | ||
267 | |||
268 | protected function compileMacros(Twig_Compiler $compiler) | ||
269 | { | ||
270 | $compiler->subcompile($this->getNode('macros')); | ||
271 | } | ||
272 | |||
273 | protected function compileGetTemplateName(Twig_Compiler $compiler) | ||
274 | { | ||
275 | $compiler | ||
276 | ->write("public function getTemplateName()\n", "{\n") | ||
277 | ->indent() | ||
278 | ->write('return ') | ||
279 | ->repr($this->getAttribute('filename')) | ||
280 | ->raw(";\n") | ||
281 | ->outdent() | ||
282 | ->write("}\n\n") | ||
283 | ; | ||
284 | } | ||
285 | |||
286 | protected function compileIsTraitable(Twig_Compiler $compiler) | ||
287 | { | ||
288 | // A template can be used as a trait if: | ||
289 | // * it has no parent | ||
290 | // * it has no macros | ||
291 | // * it has no body | ||
292 | // | ||
293 | // Put another way, a template can be used as a trait if it | ||
294 | // only contains blocks and use statements. | ||
295 | $traitable = null === $this->getNode('parent') && 0 === count($this->getNode('macros')); | ||
296 | if ($traitable) { | ||
297 | if ($this->getNode('body') instanceof Twig_Node_Body) { | ||
298 | $nodes = $this->getNode('body')->getNode(0); | ||
299 | } else { | ||
300 | $nodes = $this->getNode('body'); | ||
301 | } | ||
302 | |||
303 | if (!count($nodes)) { | ||
304 | $nodes = new Twig_Node(array($nodes)); | ||
305 | } | ||
306 | |||
307 | foreach ($nodes as $node) { | ||
308 | if (!count($node)) { | ||
309 | continue; | ||
310 | } | ||
311 | |||
312 | if ($node instanceof Twig_Node_Text && ctype_space($node->getAttribute('data'))) { | ||
313 | continue; | ||
314 | } | ||
315 | |||
316 | if ($node instanceof Twig_Node_BlockReference) { | ||
317 | continue; | ||
318 | } | ||
319 | |||
320 | $traitable = false; | ||
321 | break; | ||
322 | } | ||
323 | } | ||
324 | |||
325 | if ($traitable) { | ||
326 | return; | ||
327 | } | ||
328 | |||
329 | $compiler | ||
330 | ->write("public function isTraitable()\n", "{\n") | ||
331 | ->indent() | ||
332 | ->write(sprintf("return %s;\n", $traitable ? 'true' : 'false')) | ||
333 | ->outdent() | ||
334 | ->write("}\n\n") | ||
335 | ; | ||
336 | } | ||
337 | |||
338 | protected function compileDebugInfo(Twig_Compiler $compiler) | ||
339 | { | ||
340 | $compiler | ||
341 | ->write("public function getDebugInfo()\n", "{\n") | ||
342 | ->indent() | ||
343 | ->write(sprintf("return %s;\n", str_replace("\n", '', var_export(array_reverse($compiler->getDebugInfo(), true), true)))) | ||
344 | ->outdent() | ||
345 | ->write("}\n") | ||
346 | ; | ||
347 | } | ||
348 | |||
349 | protected function compileLoadTemplate(Twig_Compiler $compiler, $node, $var) | ||
350 | { | ||
351 | if ($node instanceof Twig_Node_Expression_Constant) { | ||
352 | $compiler | ||
353 | ->write(sprintf("%s = \$this->env->loadTemplate(", $var)) | ||
354 | ->subcompile($node) | ||
355 | ->raw(");\n") | ||
356 | ; | ||
357 | } else { | ||
358 | $compiler | ||
359 | ->write(sprintf("%s = ", $var)) | ||
360 | ->subcompile($node) | ||
361 | ->raw(";\n") | ||
362 | ->write(sprintf("if (!%s", $var)) | ||
363 | ->raw(" instanceof Twig_Template) {\n") | ||
364 | ->indent() | ||
365 | ->write(sprintf("%s = \$this->env->loadTemplate(%s);\n", $var, $var)) | ||
366 | ->outdent() | ||
367 | ->write("}\n") | ||
368 | ; | ||
369 | } | ||
370 | } | ||
371 | } | ||
diff --git a/inc/Twig/Node/Print.php b/inc/Twig/Node/Print.php new file mode 100644 index 00000000..b0c41d1d --- /dev/null +++ b/inc/Twig/Node/Print.php | |||
@@ -0,0 +1,39 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2009 Fabien Potencier | ||
7 | * (c) 2009 Armin Ronacher | ||
8 | * | ||
9 | * For the full copyright and license information, please view the LICENSE | ||
10 | * file that was distributed with this source code. | ||
11 | */ | ||
12 | |||
13 | /** | ||
14 | * Represents a node that outputs an expression. | ||
15 | * | ||
16 | * @author Fabien Potencier <fabien@symfony.com> | ||
17 | */ | ||
18 | class Twig_Node_Print extends Twig_Node implements Twig_NodeOutputInterface | ||
19 | { | ||
20 | public function __construct(Twig_Node_Expression $expr, $lineno, $tag = null) | ||
21 | { | ||
22 | parent::__construct(array('expr' => $expr), array(), $lineno, $tag); | ||
23 | } | ||
24 | |||
25 | /** | ||
26 | * Compiles the node to PHP. | ||
27 | * | ||
28 | * @param Twig_Compiler A Twig_Compiler instance | ||
29 | */ | ||
30 | public function compile(Twig_Compiler $compiler) | ||
31 | { | ||
32 | $compiler | ||
33 | ->addDebugInfo($this) | ||
34 | ->write('echo ') | ||
35 | ->subcompile($this->getNode('expr')) | ||
36 | ->raw(";\n") | ||
37 | ; | ||
38 | } | ||
39 | } | ||
diff --git a/inc/Twig/Node/Sandbox.php b/inc/Twig/Node/Sandbox.php new file mode 100644 index 00000000..8cf3ed44 --- /dev/null +++ b/inc/Twig/Node/Sandbox.php | |||
@@ -0,0 +1,47 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 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 | * Represents a sandbox node. | ||
14 | * | ||
15 | * @author Fabien Potencier <fabien@symfony.com> | ||
16 | */ | ||
17 | class Twig_Node_Sandbox extends Twig_Node | ||
18 | { | ||
19 | public function __construct(Twig_NodeInterface $body, $lineno, $tag = null) | ||
20 | { | ||
21 | parent::__construct(array('body' => $body), array(), $lineno, $tag); | ||
22 | } | ||
23 | |||
24 | /** | ||
25 | * Compiles the node to PHP. | ||
26 | * | ||
27 | * @param Twig_Compiler A Twig_Compiler instance | ||
28 | */ | ||
29 | public function compile(Twig_Compiler $compiler) | ||
30 | { | ||
31 | $compiler | ||
32 | ->addDebugInfo($this) | ||
33 | ->write("\$sandbox = \$this->env->getExtension('sandbox');\n") | ||
34 | ->write("if (!\$alreadySandboxed = \$sandbox->isSandboxed()) {\n") | ||
35 | ->indent() | ||
36 | ->write("\$sandbox->enableSandbox();\n") | ||
37 | ->outdent() | ||
38 | ->write("}\n") | ||
39 | ->subcompile($this->getNode('body')) | ||
40 | ->write("if (!\$alreadySandboxed) {\n") | ||
41 | ->indent() | ||
42 | ->write("\$sandbox->disableSandbox();\n") | ||
43 | ->outdent() | ||
44 | ->write("}\n") | ||
45 | ; | ||
46 | } | ||
47 | } | ||
diff --git a/inc/Twig/Node/SandboxedModule.php b/inc/Twig/Node/SandboxedModule.php new file mode 100644 index 00000000..be1f5daa --- /dev/null +++ b/inc/Twig/Node/SandboxedModule.php | |||
@@ -0,0 +1,60 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2009 Fabien Potencier | ||
7 | * (c) 2009 Armin Ronacher | ||
8 | * | ||
9 | * For the full copyright and license information, please view the LICENSE | ||
10 | * file that was distributed with this source code. | ||
11 | */ | ||
12 | |||
13 | /** | ||
14 | * Represents a module node. | ||
15 | * | ||
16 | * @author Fabien Potencier <fabien@symfony.com> | ||
17 | */ | ||
18 | class Twig_Node_SandboxedModule extends Twig_Node_Module | ||
19 | { | ||
20 | protected $usedFilters; | ||
21 | protected $usedTags; | ||
22 | protected $usedFunctions; | ||
23 | |||
24 | public function __construct(Twig_Node_Module $node, array $usedFilters, array $usedTags, array $usedFunctions) | ||
25 | { | ||
26 | parent::__construct($node->getNode('body'), $node->getNode('parent'), $node->getNode('blocks'), $node->getNode('macros'), $node->getNode('traits'), $node->getAttribute('embedded_templates'), $node->getAttribute('filename'), $node->getLine(), $node->getNodeTag()); | ||
27 | |||
28 | $this->setAttribute('index', $node->getAttribute('index')); | ||
29 | |||
30 | $this->usedFilters = $usedFilters; | ||
31 | $this->usedTags = $usedTags; | ||
32 | $this->usedFunctions = $usedFunctions; | ||
33 | } | ||
34 | |||
35 | protected function compileDisplayBody(Twig_Compiler $compiler) | ||
36 | { | ||
37 | $compiler->write("\$this->checkSecurity();\n"); | ||
38 | |||
39 | parent::compileDisplayBody($compiler); | ||
40 | } | ||
41 | |||
42 | protected function compileDisplayFooter(Twig_Compiler $compiler) | ||
43 | { | ||
44 | parent::compileDisplayFooter($compiler); | ||
45 | |||
46 | $compiler | ||
47 | ->write("protected function checkSecurity()\n", "{\n") | ||
48 | ->indent() | ||
49 | ->write("\$this->env->getExtension('sandbox')->checkSecurity(\n") | ||
50 | ->indent() | ||
51 | ->write(!$this->usedTags ? "array(),\n" : "array('".implode('\', \'', $this->usedTags)."'),\n") | ||
52 | ->write(!$this->usedFilters ? "array(),\n" : "array('".implode('\', \'', $this->usedFilters)."'),\n") | ||
53 | ->write(!$this->usedFunctions ? "array()\n" : "array('".implode('\', \'', $this->usedFunctions)."')\n") | ||
54 | ->outdent() | ||
55 | ->write(");\n") | ||
56 | ->outdent() | ||
57 | ->write("}\n\n") | ||
58 | ; | ||
59 | } | ||
60 | } | ||
diff --git a/inc/Twig/Node/SandboxedPrint.php b/inc/Twig/Node/SandboxedPrint.php new file mode 100644 index 00000000..73dfaa96 --- /dev/null +++ b/inc/Twig/Node/SandboxedPrint.php | |||
@@ -0,0 +1,59 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 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_Node_SandboxedPrint adds a check for the __toString() method | ||
14 | * when the variable is an object and the sandbox is activated. | ||
15 | * | ||
16 | * When there is a simple Print statement, like {{ article }}, | ||
17 | * and if the sandbox is enabled, we need to check that the __toString() | ||
18 | * method is allowed if 'article' is an object. | ||
19 | * | ||
20 | * @author Fabien Potencier <fabien@symfony.com> | ||
21 | */ | ||
22 | class Twig_Node_SandboxedPrint extends Twig_Node_Print | ||
23 | { | ||
24 | public function __construct(Twig_Node_Expression $expr, $lineno, $tag = null) | ||
25 | { | ||
26 | parent::__construct($expr, $lineno, $tag); | ||
27 | } | ||
28 | |||
29 | /** | ||
30 | * Compiles the node to PHP. | ||
31 | * | ||
32 | * @param Twig_Compiler A Twig_Compiler instance | ||
33 | */ | ||
34 | public function compile(Twig_Compiler $compiler) | ||
35 | { | ||
36 | $compiler | ||
37 | ->addDebugInfo($this) | ||
38 | ->write('echo $this->env->getExtension(\'sandbox\')->ensureToStringAllowed(') | ||
39 | ->subcompile($this->getNode('expr')) | ||
40 | ->raw(");\n") | ||
41 | ; | ||
42 | } | ||
43 | |||
44 | /** | ||
45 | * Removes node filters. | ||
46 | * | ||
47 | * This is mostly needed when another visitor adds filters (like the escaper one). | ||
48 | * | ||
49 | * @param Twig_Node $node A Node | ||
50 | */ | ||
51 | protected function removeNodeFilter($node) | ||
52 | { | ||
53 | if ($node instanceof Twig_Node_Expression_Filter) { | ||
54 | return $this->removeNodeFilter($node->getNode('node')); | ||
55 | } | ||
56 | |||
57 | return $node; | ||
58 | } | ||
59 | } | ||
diff --git a/inc/Twig/Node/Set.php b/inc/Twig/Node/Set.php new file mode 100644 index 00000000..4c9c16ce --- /dev/null +++ b/inc/Twig/Node/Set.php | |||
@@ -0,0 +1,101 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 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 | * Represents a set node. | ||
14 | * | ||
15 | * @author Fabien Potencier <fabien@symfony.com> | ||
16 | */ | ||
17 | class Twig_Node_Set extends Twig_Node | ||
18 | { | ||
19 | public function __construct($capture, Twig_NodeInterface $names, Twig_NodeInterface $values, $lineno, $tag = null) | ||
20 | { | ||
21 | parent::__construct(array('names' => $names, 'values' => $values), array('capture' => $capture, 'safe' => false), $lineno, $tag); | ||
22 | |||
23 | /* | ||
24 | * Optimizes the node when capture is used for a large block of text. | ||
25 | * | ||
26 | * {% set foo %}foo{% endset %} is compiled to $context['foo'] = new Twig_Markup("foo"); | ||
27 | */ | ||
28 | if ($this->getAttribute('capture')) { | ||
29 | $this->setAttribute('safe', true); | ||
30 | |||
31 | $values = $this->getNode('values'); | ||
32 | if ($values instanceof Twig_Node_Text) { | ||
33 | $this->setNode('values', new Twig_Node_Expression_Constant($values->getAttribute('data'), $values->getLine())); | ||
34 | $this->setAttribute('capture', false); | ||
35 | } | ||
36 | } | ||
37 | } | ||
38 | |||
39 | /** | ||
40 | * Compiles the node to PHP. | ||
41 | * | ||
42 | * @param Twig_Compiler A Twig_Compiler instance | ||
43 | */ | ||
44 | public function compile(Twig_Compiler $compiler) | ||
45 | { | ||
46 | $compiler->addDebugInfo($this); | ||
47 | |||
48 | if (count($this->getNode('names')) > 1) { | ||
49 | $compiler->write('list('); | ||
50 | foreach ($this->getNode('names') as $idx => $node) { | ||
51 | if ($idx) { | ||
52 | $compiler->raw(', '); | ||
53 | } | ||
54 | |||
55 | $compiler->subcompile($node); | ||
56 | } | ||
57 | $compiler->raw(')'); | ||
58 | } else { | ||
59 | if ($this->getAttribute('capture')) { | ||
60 | $compiler | ||
61 | ->write("ob_start();\n") | ||
62 | ->subcompile($this->getNode('values')) | ||
63 | ; | ||
64 | } | ||
65 | |||
66 | $compiler->subcompile($this->getNode('names'), false); | ||
67 | |||
68 | if ($this->getAttribute('capture')) { | ||
69 | $compiler->raw(" = ('' === \$tmp = ob_get_clean()) ? '' : new Twig_Markup(\$tmp, \$this->env->getCharset())"); | ||
70 | } | ||
71 | } | ||
72 | |||
73 | if (!$this->getAttribute('capture')) { | ||
74 | $compiler->raw(' = '); | ||
75 | |||
76 | if (count($this->getNode('names')) > 1) { | ||
77 | $compiler->write('array('); | ||
78 | foreach ($this->getNode('values') as $idx => $value) { | ||
79 | if ($idx) { | ||
80 | $compiler->raw(', '); | ||
81 | } | ||
82 | |||
83 | $compiler->subcompile($value); | ||
84 | } | ||
85 | $compiler->raw(')'); | ||
86 | } else { | ||
87 | if ($this->getAttribute('safe')) { | ||
88 | $compiler | ||
89 | ->raw("('' === \$tmp = ") | ||
90 | ->subcompile($this->getNode('values')) | ||
91 | ->raw(") ? '' : new Twig_Markup(\$tmp, \$this->env->getCharset())") | ||
92 | ; | ||
93 | } else { | ||
94 | $compiler->subcompile($this->getNode('values')); | ||
95 | } | ||
96 | } | ||
97 | } | ||
98 | |||
99 | $compiler->raw(";\n"); | ||
100 | } | ||
101 | } | ||
diff --git a/inc/Twig/Node/SetTemp.php b/inc/Twig/Node/SetTemp.php new file mode 100644 index 00000000..3bdd1cb7 --- /dev/null +++ b/inc/Twig/Node/SetTemp.php | |||
@@ -0,0 +1,35 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2011 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 | class Twig_Node_SetTemp extends Twig_Node | ||
13 | { | ||
14 | public function __construct($name, $lineno) | ||
15 | { | ||
16 | parent::__construct(array(), array('name' => $name), $lineno); | ||
17 | } | ||
18 | |||
19 | public function compile(Twig_Compiler $compiler) | ||
20 | { | ||
21 | $name = $this->getAttribute('name'); | ||
22 | $compiler | ||
23 | ->addDebugInfo($this) | ||
24 | ->write('if (isset($context[') | ||
25 | ->string($name) | ||
26 | ->raw('])) { $_') | ||
27 | ->raw($name) | ||
28 | ->raw('_ = $context[') | ||
29 | ->repr($name) | ||
30 | ->raw(']; } else { $_') | ||
31 | ->raw($name) | ||
32 | ->raw("_ = null; }\n") | ||
33 | ; | ||
34 | } | ||
35 | } | ||
diff --git a/inc/Twig/Node/Spaceless.php b/inc/Twig/Node/Spaceless.php new file mode 100644 index 00000000..7555fa0f --- /dev/null +++ b/inc/Twig/Node/Spaceless.php | |||
@@ -0,0 +1,40 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 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 | * Represents a spaceless node. | ||
14 | * | ||
15 | * It removes spaces between HTML tags. | ||
16 | * | ||
17 | * @author Fabien Potencier <fabien@symfony.com> | ||
18 | */ | ||
19 | class Twig_Node_Spaceless extends Twig_Node | ||
20 | { | ||
21 | public function __construct(Twig_NodeInterface $body, $lineno, $tag = 'spaceless') | ||
22 | { | ||
23 | parent::__construct(array('body' => $body), array(), $lineno, $tag); | ||
24 | } | ||
25 | |||
26 | /** | ||
27 | * Compiles the node to PHP. | ||
28 | * | ||
29 | * @param Twig_Compiler A Twig_Compiler instance | ||
30 | */ | ||
31 | public function compile(Twig_Compiler $compiler) | ||
32 | { | ||
33 | $compiler | ||
34 | ->addDebugInfo($this) | ||
35 | ->write("ob_start();\n") | ||
36 | ->subcompile($this->getNode('body')) | ||
37 | ->write("echo trim(preg_replace('/>\s+</', '><', ob_get_clean()));\n") | ||
38 | ; | ||
39 | } | ||
40 | } | ||
diff --git a/inc/Twig/Node/Text.php b/inc/Twig/Node/Text.php new file mode 100644 index 00000000..21bdcea1 --- /dev/null +++ b/inc/Twig/Node/Text.php | |||
@@ -0,0 +1,39 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2009 Fabien Potencier | ||
7 | * (c) 2009 Armin Ronacher | ||
8 | * | ||
9 | * For the full copyright and license information, please view the LICENSE | ||
10 | * file that was distributed with this source code. | ||
11 | */ | ||
12 | |||
13 | /** | ||
14 | * Represents a text node. | ||
15 | * | ||
16 | * @author Fabien Potencier <fabien@symfony.com> | ||
17 | */ | ||
18 | class Twig_Node_Text extends Twig_Node implements Twig_NodeOutputInterface | ||
19 | { | ||
20 | public function __construct($data, $lineno) | ||
21 | { | ||
22 | parent::__construct(array(), array('data' => $data), $lineno); | ||
23 | } | ||
24 | |||
25 | /** | ||
26 | * Compiles the node to PHP. | ||
27 | * | ||
28 | * @param Twig_Compiler A Twig_Compiler instance | ||
29 | */ | ||
30 | public function compile(Twig_Compiler $compiler) | ||
31 | { | ||
32 | $compiler | ||
33 | ->addDebugInfo($this) | ||
34 | ->write('echo ') | ||
35 | ->string($this->getAttribute('data')) | ||
36 | ->raw(";\n") | ||
37 | ; | ||
38 | } | ||
39 | } | ||
diff --git a/inc/Twig/NodeInterface.php b/inc/Twig/NodeInterface.php new file mode 100644 index 00000000..f0ef7258 --- /dev/null +++ b/inc/Twig/NodeInterface.php | |||
@@ -0,0 +1,30 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 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 | * Represents a node in the AST. | ||
14 | * | ||
15 | * @author Fabien Potencier <fabien@symfony.com> | ||
16 | * @deprecated since 1.12 (to be removed in 2.0) | ||
17 | */ | ||
18 | interface Twig_NodeInterface extends Countable, IteratorAggregate | ||
19 | { | ||
20 | /** | ||
21 | * Compiles the node to PHP. | ||
22 | * | ||
23 | * @param Twig_Compiler A Twig_Compiler instance | ||
24 | */ | ||
25 | public function compile(Twig_Compiler $compiler); | ||
26 | |||
27 | public function getLine(); | ||
28 | |||
29 | public function getNodeTag(); | ||
30 | } | ||
diff --git a/inc/Twig/NodeOutputInterface.php b/inc/Twig/NodeOutputInterface.php new file mode 100644 index 00000000..22172c09 --- /dev/null +++ b/inc/Twig/NodeOutputInterface.php | |||
@@ -0,0 +1,19 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 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 | * Represents a displayable node in the AST. | ||
14 | * | ||
15 | * @author Fabien Potencier <fabien@symfony.com> | ||
16 | */ | ||
17 | interface Twig_NodeOutputInterface | ||
18 | { | ||
19 | } | ||
diff --git a/inc/Twig/NodeTraverser.php b/inc/Twig/NodeTraverser.php new file mode 100644 index 00000000..28cba1ad --- /dev/null +++ b/inc/Twig/NodeTraverser.php | |||
@@ -0,0 +1,88 @@ | |||
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_NodeTraverser is a node traverser. | ||
14 | * | ||
15 | * It visits all nodes and their children and call the given visitor for each. | ||
16 | * | ||
17 | * @author Fabien Potencier <fabien@symfony.com> | ||
18 | */ | ||
19 | class Twig_NodeTraverser | ||
20 | { | ||
21 | protected $env; | ||
22 | protected $visitors; | ||
23 | |||
24 | /** | ||
25 | * Constructor. | ||
26 | * | ||
27 | * @param Twig_Environment $env A Twig_Environment instance | ||
28 | * @param array $visitors An array of Twig_NodeVisitorInterface instances | ||
29 | */ | ||
30 | public function __construct(Twig_Environment $env, array $visitors = array()) | ||
31 | { | ||
32 | $this->env = $env; | ||
33 | $this->visitors = array(); | ||
34 | foreach ($visitors as $visitor) { | ||
35 | $this->addVisitor($visitor); | ||
36 | } | ||
37 | } | ||
38 | |||
39 | /** | ||
40 | * Adds a visitor. | ||
41 | * | ||
42 | * @param Twig_NodeVisitorInterface $visitor A Twig_NodeVisitorInterface instance | ||
43 | */ | ||
44 | public function addVisitor(Twig_NodeVisitorInterface $visitor) | ||
45 | { | ||
46 | if (!isset($this->visitors[$visitor->getPriority()])) { | ||
47 | $this->visitors[$visitor->getPriority()] = array(); | ||
48 | } | ||
49 | |||
50 | $this->visitors[$visitor->getPriority()][] = $visitor; | ||
51 | } | ||
52 | |||
53 | /** | ||
54 | * Traverses a node and calls the registered visitors. | ||
55 | * | ||
56 | * @param Twig_NodeInterface $node A Twig_NodeInterface instance | ||
57 | */ | ||
58 | public function traverse(Twig_NodeInterface $node) | ||
59 | { | ||
60 | ksort($this->visitors); | ||
61 | foreach ($this->visitors as $visitors) { | ||
62 | foreach ($visitors as $visitor) { | ||
63 | $node = $this->traverseForVisitor($visitor, $node); | ||
64 | } | ||
65 | } | ||
66 | |||
67 | return $node; | ||
68 | } | ||
69 | |||
70 | protected function traverseForVisitor(Twig_NodeVisitorInterface $visitor, Twig_NodeInterface $node = null) | ||
71 | { | ||
72 | if (null === $node) { | ||
73 | return null; | ||
74 | } | ||
75 | |||
76 | $node = $visitor->enterNode($node, $this->env); | ||
77 | |||
78 | foreach ($node as $k => $n) { | ||
79 | if (false !== $n = $this->traverseForVisitor($visitor, $n)) { | ||
80 | $node->setNode($k, $n); | ||
81 | } else { | ||
82 | $node->removeNode($k); | ||
83 | } | ||
84 | } | ||
85 | |||
86 | return $visitor->leaveNode($node, $this->env); | ||
87 | } | ||
88 | } | ||
diff --git a/inc/Twig/NodeVisitor/Escaper.php b/inc/Twig/NodeVisitor/Escaper.php new file mode 100644 index 00000000..cc4b3d71 --- /dev/null +++ b/inc/Twig/NodeVisitor/Escaper.php | |||
@@ -0,0 +1,167 @@ | |||
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 | } | ||
diff --git a/inc/Twig/NodeVisitor/Optimizer.php b/inc/Twig/NodeVisitor/Optimizer.php new file mode 100644 index 00000000..a254def7 --- /dev/null +++ b/inc/Twig/NodeVisitor/Optimizer.php | |||
@@ -0,0 +1,246 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 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_Optimizer tries to optimizes the AST. | ||
14 | * | ||
15 | * This visitor is always the last registered one. | ||
16 | * | ||
17 | * You can configure which optimizations you want to activate via the | ||
18 | * optimizer mode. | ||
19 | * | ||
20 | * @author Fabien Potencier <fabien@symfony.com> | ||
21 | */ | ||
22 | class Twig_NodeVisitor_Optimizer implements Twig_NodeVisitorInterface | ||
23 | { | ||
24 | const OPTIMIZE_ALL = -1; | ||
25 | const OPTIMIZE_NONE = 0; | ||
26 | const OPTIMIZE_FOR = 2; | ||
27 | const OPTIMIZE_RAW_FILTER = 4; | ||
28 | const OPTIMIZE_VAR_ACCESS = 8; | ||
29 | |||
30 | protected $loops = array(); | ||
31 | protected $optimizers; | ||
32 | protected $prependedNodes = array(); | ||
33 | protected $inABody = false; | ||
34 | |||
35 | /** | ||
36 | * Constructor. | ||
37 | * | ||
38 | * @param integer $optimizers The optimizer mode | ||
39 | */ | ||
40 | public function __construct($optimizers = -1) | ||
41 | { | ||
42 | if (!is_int($optimizers) || $optimizers > 2) { | ||
43 | throw new InvalidArgumentException(sprintf('Optimizer mode "%s" is not valid.', $optimizers)); | ||
44 | } | ||
45 | |||
46 | $this->optimizers = $optimizers; | ||
47 | } | ||
48 | |||
49 | /** | ||
50 | * {@inheritdoc} | ||
51 | */ | ||
52 | public function enterNode(Twig_NodeInterface $node, Twig_Environment $env) | ||
53 | { | ||
54 | if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) { | ||
55 | $this->enterOptimizeFor($node, $env); | ||
56 | } | ||
57 | |||
58 | if (!version_compare(phpversion(), '5.4.0RC1', '>=') && self::OPTIMIZE_VAR_ACCESS === (self::OPTIMIZE_VAR_ACCESS & $this->optimizers) && !$env->isStrictVariables() && !$env->hasExtension('sandbox')) { | ||
59 | if ($this->inABody) { | ||
60 | if (!$node instanceof Twig_Node_Expression) { | ||
61 | if (get_class($node) !== 'Twig_Node') { | ||
62 | array_unshift($this->prependedNodes, array()); | ||
63 | } | ||
64 | } else { | ||
65 | $node = $this->optimizeVariables($node, $env); | ||
66 | } | ||
67 | } elseif ($node instanceof Twig_Node_Body) { | ||
68 | $this->inABody = true; | ||
69 | } | ||
70 | } | ||
71 | |||
72 | return $node; | ||
73 | } | ||
74 | |||
75 | /** | ||
76 | * {@inheritdoc} | ||
77 | */ | ||
78 | public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env) | ||
79 | { | ||
80 | $expression = $node instanceof Twig_Node_Expression; | ||
81 | |||
82 | if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) { | ||
83 | $this->leaveOptimizeFor($node, $env); | ||
84 | } | ||
85 | |||
86 | if (self::OPTIMIZE_RAW_FILTER === (self::OPTIMIZE_RAW_FILTER & $this->optimizers)) { | ||
87 | $node = $this->optimizeRawFilter($node, $env); | ||
88 | } | ||
89 | |||
90 | $node = $this->optimizePrintNode($node, $env); | ||
91 | |||
92 | if (self::OPTIMIZE_VAR_ACCESS === (self::OPTIMIZE_VAR_ACCESS & $this->optimizers) && !$env->isStrictVariables() && !$env->hasExtension('sandbox')) { | ||
93 | if ($node instanceof Twig_Node_Body) { | ||
94 | $this->inABody = false; | ||
95 | } elseif ($this->inABody) { | ||
96 | if (!$expression && get_class($node) !== 'Twig_Node' && $prependedNodes = array_shift($this->prependedNodes)) { | ||
97 | $nodes = array(); | ||
98 | foreach (array_unique($prependedNodes) as $name) { | ||
99 | $nodes[] = new Twig_Node_SetTemp($name, $node->getLine()); | ||
100 | } | ||
101 | |||
102 | $nodes[] = $node; | ||
103 | $node = new Twig_Node($nodes); | ||
104 | } | ||
105 | } | ||
106 | } | ||
107 | |||
108 | return $node; | ||
109 | } | ||
110 | |||
111 | protected function optimizeVariables($node, $env) | ||
112 | { | ||
113 | if ('Twig_Node_Expression_Name' === get_class($node) && $node->isSimple()) { | ||
114 | $this->prependedNodes[0][] = $node->getAttribute('name'); | ||
115 | |||
116 | return new Twig_Node_Expression_TempName($node->getAttribute('name'), $node->getLine()); | ||
117 | } | ||
118 | |||
119 | return $node; | ||
120 | } | ||
121 | |||
122 | /** | ||
123 | * Optimizes print nodes. | ||
124 | * | ||
125 | * It replaces: | ||
126 | * | ||
127 | * * "echo $this->render(Parent)Block()" with "$this->display(Parent)Block()" | ||
128 | * | ||
129 | * @param Twig_NodeInterface $node A Node | ||
130 | * @param Twig_Environment $env The current Twig environment | ||
131 | */ | ||
132 | protected function optimizePrintNode($node, $env) | ||
133 | { | ||
134 | if (!$node instanceof Twig_Node_Print) { | ||
135 | return $node; | ||
136 | } | ||
137 | |||
138 | if ( | ||
139 | $node->getNode('expr') instanceof Twig_Node_Expression_BlockReference || | ||
140 | $node->getNode('expr') instanceof Twig_Node_Expression_Parent | ||
141 | ) { | ||
142 | $node->getNode('expr')->setAttribute('output', true); | ||
143 | |||
144 | return $node->getNode('expr'); | ||
145 | } | ||
146 | |||
147 | return $node; | ||
148 | } | ||
149 | |||
150 | /** | ||
151 | * Removes "raw" filters. | ||
152 | * | ||
153 | * @param Twig_NodeInterface $node A Node | ||
154 | * @param Twig_Environment $env The current Twig environment | ||
155 | */ | ||
156 | protected function optimizeRawFilter($node, $env) | ||
157 | { | ||
158 | if ($node instanceof Twig_Node_Expression_Filter && 'raw' == $node->getNode('filter')->getAttribute('value')) { | ||
159 | return $node->getNode('node'); | ||
160 | } | ||
161 | |||
162 | return $node; | ||
163 | } | ||
164 | |||
165 | /** | ||
166 | * Optimizes "for" tag by removing the "loop" variable creation whenever possible. | ||
167 | * | ||
168 | * @param Twig_NodeInterface $node A Node | ||
169 | * @param Twig_Environment $env The current Twig environment | ||
170 | */ | ||
171 | protected function enterOptimizeFor($node, $env) | ||
172 | { | ||
173 | if ($node instanceof Twig_Node_For) { | ||
174 | // disable the loop variable by default | ||
175 | $node->setAttribute('with_loop', false); | ||
176 | array_unshift($this->loops, $node); | ||
177 | } elseif (!$this->loops) { | ||
178 | // we are outside a loop | ||
179 | return; | ||
180 | } | ||
181 | |||
182 | // when do we need to add the loop variable back? | ||
183 | |||
184 | // the loop variable is referenced for the current loop | ||
185 | elseif ($node instanceof Twig_Node_Expression_Name && 'loop' === $node->getAttribute('name')) { | ||
186 | $this->addLoopToCurrent(); | ||
187 | } | ||
188 | |||
189 | // block reference | ||
190 | elseif ($node instanceof Twig_Node_BlockReference || $node instanceof Twig_Node_Expression_BlockReference) { | ||
191 | $this->addLoopToCurrent(); | ||
192 | } | ||
193 | |||
194 | // include without the only attribute | ||
195 | elseif ($node instanceof Twig_Node_Include && !$node->getAttribute('only')) { | ||
196 | $this->addLoopToAll(); | ||
197 | } | ||
198 | |||
199 | // the loop variable is referenced via an attribute | ||
200 | elseif ($node instanceof Twig_Node_Expression_GetAttr | ||
201 | && (!$node->getNode('attribute') instanceof Twig_Node_Expression_Constant | ||
202 | || 'parent' === $node->getNode('attribute')->getAttribute('value') | ||
203 | ) | ||
204 | && (true === $this->loops[0]->getAttribute('with_loop') | ||
205 | || ($node->getNode('node') instanceof Twig_Node_Expression_Name | ||
206 | && 'loop' === $node->getNode('node')->getAttribute('name') | ||
207 | ) | ||
208 | ) | ||
209 | ) { | ||
210 | $this->addLoopToAll(); | ||
211 | } | ||
212 | } | ||
213 | |||
214 | /** | ||
215 | * Optimizes "for" tag by removing the "loop" variable creation whenever possible. | ||
216 | * | ||
217 | * @param Twig_NodeInterface $node A Node | ||
218 | * @param Twig_Environment $env The current Twig environment | ||
219 | */ | ||
220 | protected function leaveOptimizeFor($node, $env) | ||
221 | { | ||
222 | if ($node instanceof Twig_Node_For) { | ||
223 | array_shift($this->loops); | ||
224 | } | ||
225 | } | ||
226 | |||
227 | protected function addLoopToCurrent() | ||
228 | { | ||
229 | $this->loops[0]->setAttribute('with_loop', true); | ||
230 | } | ||
231 | |||
232 | protected function addLoopToAll() | ||
233 | { | ||
234 | foreach ($this->loops as $loop) { | ||
235 | $loop->setAttribute('with_loop', true); | ||
236 | } | ||
237 | } | ||
238 | |||
239 | /** | ||
240 | * {@inheritdoc} | ||
241 | */ | ||
242 | public function getPriority() | ||
243 | { | ||
244 | return 255; | ||
245 | } | ||
246 | } | ||
diff --git a/inc/Twig/NodeVisitor/SafeAnalysis.php b/inc/Twig/NodeVisitor/SafeAnalysis.php new file mode 100644 index 00000000..c4bbd812 --- /dev/null +++ b/inc/Twig/NodeVisitor/SafeAnalysis.php | |||
@@ -0,0 +1,131 @@ | |||
1 | <?php | ||
2 | |||
3 | class Twig_NodeVisitor_SafeAnalysis implements Twig_NodeVisitorInterface | ||
4 | { | ||
5 | protected $data = array(); | ||
6 | protected $safeVars = array(); | ||
7 | |||
8 | public function setSafeVars($safeVars) | ||
9 | { | ||
10 | $this->safeVars = $safeVars; | ||
11 | } | ||
12 | |||
13 | public function getSafe(Twig_NodeInterface $node) | ||
14 | { | ||
15 | $hash = spl_object_hash($node); | ||
16 | if (isset($this->data[$hash])) { | ||
17 | foreach ($this->data[$hash] as $bucket) { | ||
18 | if ($bucket['key'] === $node) { | ||
19 | return $bucket['value']; | ||
20 | } | ||
21 | } | ||
22 | } | ||
23 | } | ||
24 | |||
25 | protected function setSafe(Twig_NodeInterface $node, array $safe) | ||
26 | { | ||
27 | $hash = spl_object_hash($node); | ||
28 | if (isset($this->data[$hash])) { | ||
29 | foreach ($this->data[$hash] as &$bucket) { | ||
30 | if ($bucket['key'] === $node) { | ||
31 | $bucket['value'] = $safe; | ||
32 | |||
33 | return; | ||
34 | } | ||
35 | } | ||
36 | } | ||
37 | $this->data[$hash][] = array( | ||
38 | 'key' => $node, | ||
39 | 'value' => $safe, | ||
40 | ); | ||
41 | } | ||
42 | |||
43 | public function enterNode(Twig_NodeInterface $node, Twig_Environment $env) | ||
44 | { | ||
45 | return $node; | ||
46 | } | ||
47 | |||
48 | public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env) | ||
49 | { | ||
50 | if ($node instanceof Twig_Node_Expression_Constant) { | ||
51 | // constants are marked safe for all | ||
52 | $this->setSafe($node, array('all')); | ||
53 | } elseif ($node instanceof Twig_Node_Expression_BlockReference) { | ||
54 | // blocks are safe by definition | ||
55 | $this->setSafe($node, array('all')); | ||
56 | } elseif ($node instanceof Twig_Node_Expression_Parent) { | ||
57 | // parent block is safe by definition | ||
58 | $this->setSafe($node, array('all')); | ||
59 | } elseif ($node instanceof Twig_Node_Expression_Conditional) { | ||
60 | // intersect safeness of both operands | ||
61 | $safe = $this->intersectSafe($this->getSafe($node->getNode('expr2')), $this->getSafe($node->getNode('expr3'))); | ||
62 | $this->setSafe($node, $safe); | ||
63 | } elseif ($node instanceof Twig_Node_Expression_Filter) { | ||
64 | // filter expression is safe when the filter is safe | ||
65 | $name = $node->getNode('filter')->getAttribute('value'); | ||
66 | $args = $node->getNode('arguments'); | ||
67 | if (false !== $filter = $env->getFilter($name)) { | ||
68 | $safe = $filter->getSafe($args); | ||
69 | if (null === $safe) { | ||
70 | $safe = $this->intersectSafe($this->getSafe($node->getNode('node')), $filter->getPreservesSafety()); | ||
71 | } | ||
72 | $this->setSafe($node, $safe); | ||
73 | } else { | ||
74 | $this->setSafe($node, array()); | ||
75 | } | ||
76 | } elseif ($node instanceof Twig_Node_Expression_Function) { | ||
77 | // function expression is safe when the function is safe | ||
78 | $name = $node->getAttribute('name'); | ||
79 | $args = $node->getNode('arguments'); | ||
80 | $function = $env->getFunction($name); | ||
81 | if (false !== $function) { | ||
82 | $this->setSafe($node, $function->getSafe($args)); | ||
83 | } else { | ||
84 | $this->setSafe($node, array()); | ||
85 | } | ||
86 | } elseif ($node instanceof Twig_Node_Expression_MethodCall) { | ||
87 | if ($node->getAttribute('safe')) { | ||
88 | $this->setSafe($node, array('all')); | ||
89 | } else { | ||
90 | $this->setSafe($node, array()); | ||
91 | } | ||
92 | } elseif ($node instanceof Twig_Node_Expression_GetAttr && $node->getNode('node') instanceof Twig_Node_Expression_Name) { | ||
93 | $name = $node->getNode('node')->getAttribute('name'); | ||
94 | // attributes on template instances are safe | ||
95 | if ('_self' == $name || in_array($name, $this->safeVars)) { | ||
96 | $this->setSafe($node, array('all')); | ||
97 | } else { | ||
98 | $this->setSafe($node, array()); | ||
99 | } | ||
100 | } else { | ||
101 | $this->setSafe($node, array()); | ||
102 | } | ||
103 | |||
104 | return $node; | ||
105 | } | ||
106 | |||
107 | protected function intersectSafe(array $a = null, array $b = null) | ||
108 | { | ||
109 | if (null === $a || null === $b) { | ||
110 | return array(); | ||
111 | } | ||
112 | |||
113 | if (in_array('all', $a)) { | ||
114 | return $b; | ||
115 | } | ||
116 | |||
117 | if (in_array('all', $b)) { | ||
118 | return $a; | ||
119 | } | ||
120 | |||
121 | return array_intersect($a, $b); | ||
122 | } | ||
123 | |||
124 | /** | ||
125 | * {@inheritdoc} | ||
126 | */ | ||
127 | public function getPriority() | ||
128 | { | ||
129 | return 0; | ||
130 | } | ||
131 | } | ||
diff --git a/inc/Twig/NodeVisitor/Sandbox.php b/inc/Twig/NodeVisitor/Sandbox.php new file mode 100644 index 00000000..fb27045b --- /dev/null +++ b/inc/Twig/NodeVisitor/Sandbox.php | |||
@@ -0,0 +1,92 @@ | |||
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_Sandbox implements sandboxing. | ||
14 | * | ||
15 | * @author Fabien Potencier <fabien@symfony.com> | ||
16 | */ | ||
17 | class Twig_NodeVisitor_Sandbox implements Twig_NodeVisitorInterface | ||
18 | { | ||
19 | protected $inAModule = false; | ||
20 | protected $tags; | ||
21 | protected $filters; | ||
22 | protected $functions; | ||
23 | |||
24 | /** | ||
25 | * Called before child nodes are visited. | ||
26 | * | ||
27 | * @param Twig_NodeInterface $node The node to visit | ||
28 | * @param Twig_Environment $env The Twig environment instance | ||
29 | * | ||
30 | * @return Twig_NodeInterface The modified node | ||
31 | */ | ||
32 | public function enterNode(Twig_NodeInterface $node, Twig_Environment $env) | ||
33 | { | ||
34 | if ($node instanceof Twig_Node_Module) { | ||
35 | $this->inAModule = true; | ||
36 | $this->tags = array(); | ||
37 | $this->filters = array(); | ||
38 | $this->functions = array(); | ||
39 | |||
40 | return $node; | ||
41 | } elseif ($this->inAModule) { | ||
42 | // look for tags | ||
43 | if ($node->getNodeTag()) { | ||
44 | $this->tags[] = $node->getNodeTag(); | ||
45 | } | ||
46 | |||
47 | // look for filters | ||
48 | if ($node instanceof Twig_Node_Expression_Filter) { | ||
49 | $this->filters[] = $node->getNode('filter')->getAttribute('value'); | ||
50 | } | ||
51 | |||
52 | // look for functions | ||
53 | if ($node instanceof Twig_Node_Expression_Function) { | ||
54 | $this->functions[] = $node->getAttribute('name'); | ||
55 | } | ||
56 | |||
57 | // wrap print to check __toString() calls | ||
58 | if ($node instanceof Twig_Node_Print) { | ||
59 | return new Twig_Node_SandboxedPrint($node->getNode('expr'), $node->getLine(), $node->getNodeTag()); | ||
60 | } | ||
61 | } | ||
62 | |||
63 | return $node; | ||
64 | } | ||
65 | |||
66 | /** | ||
67 | * Called after child nodes are visited. | ||
68 | * | ||
69 | * @param Twig_NodeInterface $node The node to visit | ||
70 | * @param Twig_Environment $env The Twig environment instance | ||
71 | * | ||
72 | * @return Twig_NodeInterface The modified node | ||
73 | */ | ||
74 | public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env) | ||
75 | { | ||
76 | if ($node instanceof Twig_Node_Module) { | ||
77 | $this->inAModule = false; | ||
78 | |||
79 | return new Twig_Node_SandboxedModule($node, array_unique($this->filters), array_unique($this->tags), array_unique($this->functions)); | ||
80 | } | ||
81 | |||
82 | return $node; | ||
83 | } | ||
84 | |||
85 | /** | ||
86 | * {@inheritdoc} | ||
87 | */ | ||
88 | public function getPriority() | ||
89 | { | ||
90 | return 0; | ||
91 | } | ||
92 | } | ||
diff --git a/inc/Twig/NodeVisitorInterface.php b/inc/Twig/NodeVisitorInterface.php new file mode 100644 index 00000000..f33c13fc --- /dev/null +++ b/inc/Twig/NodeVisitorInterface.php | |||
@@ -0,0 +1,47 @@ | |||
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_NodeVisitorInterface is the interface the all node visitor classes must implement. | ||
14 | * | ||
15 | * @author Fabien Potencier <fabien@symfony.com> | ||
16 | */ | ||
17 | interface Twig_NodeVisitorInterface | ||
18 | { | ||
19 | /** | ||
20 | * Called before child nodes are visited. | ||
21 | * | ||
22 | * @param Twig_NodeInterface $node The node to visit | ||
23 | * @param Twig_Environment $env The Twig environment instance | ||
24 | * | ||
25 | * @return Twig_NodeInterface The modified node | ||
26 | */ | ||
27 | public function enterNode(Twig_NodeInterface $node, Twig_Environment $env); | ||
28 | |||
29 | /** | ||
30 | * Called after child nodes are visited. | ||
31 | * | ||
32 | * @param Twig_NodeInterface $node The node to visit | ||
33 | * @param Twig_Environment $env The Twig environment instance | ||
34 | * | ||
35 | * @return Twig_NodeInterface|false The modified node or false if the node must be removed | ||
36 | */ | ||
37 | public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env); | ||
38 | |||
39 | /** | ||
40 | * Returns the priority for this visitor. | ||
41 | * | ||
42 | * Priority should be between -10 and 10 (0 is the default). | ||
43 | * | ||
44 | * @return integer The priority level | ||
45 | */ | ||
46 | public function getPriority(); | ||
47 | } | ||
diff --git a/inc/Twig/Parser.php b/inc/Twig/Parser.php new file mode 100644 index 00000000..958e46b3 --- /dev/null +++ b/inc/Twig/Parser.php | |||
@@ -0,0 +1,394 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2009 Fabien Potencier | ||
7 | * (c) 2009 Armin Ronacher | ||
8 | * | ||
9 | * For the full copyright and license information, please view the LICENSE | ||
10 | * file that was distributed with this source code. | ||
11 | */ | ||
12 | |||
13 | /** | ||
14 | * Default parser implementation. | ||
15 | * | ||
16 | * @author Fabien Potencier <fabien@symfony.com> | ||
17 | */ | ||
18 | class Twig_Parser implements Twig_ParserInterface | ||
19 | { | ||
20 | protected $stack = array(); | ||
21 | protected $stream; | ||
22 | protected $parent; | ||
23 | protected $handlers; | ||
24 | protected $visitors; | ||
25 | protected $expressionParser; | ||
26 | protected $blocks; | ||
27 | protected $blockStack; | ||
28 | protected $macros; | ||
29 | protected $env; | ||
30 | protected $reservedMacroNames; | ||
31 | protected $importedSymbols; | ||
32 | protected $traits; | ||
33 | protected $embeddedTemplates = array(); | ||
34 | |||
35 | /** | ||
36 | * Constructor. | ||
37 | * | ||
38 | * @param Twig_Environment $env A Twig_Environment instance | ||
39 | */ | ||
40 | public function __construct(Twig_Environment $env) | ||
41 | { | ||
42 | $this->env = $env; | ||
43 | } | ||
44 | |||
45 | public function getEnvironment() | ||
46 | { | ||
47 | return $this->env; | ||
48 | } | ||
49 | |||
50 | public function getVarName() | ||
51 | { | ||
52 | return sprintf('__internal_%s', hash('sha1', uniqid(mt_rand(), true), false)); | ||
53 | } | ||
54 | |||
55 | public function getFilename() | ||
56 | { | ||
57 | return $this->stream->getFilename(); | ||
58 | } | ||
59 | |||
60 | /** | ||
61 | * Converts a token stream to a node tree. | ||
62 | * | ||
63 | * @param Twig_TokenStream $stream A token stream instance | ||
64 | * | ||
65 | * @return Twig_Node_Module A node tree | ||
66 | */ | ||
67 | public function parse(Twig_TokenStream $stream, $test = null, $dropNeedle = false) | ||
68 | { | ||
69 | // push all variables into the stack to keep the current state of the parser | ||
70 | $vars = get_object_vars($this); | ||
71 | unset($vars['stack'], $vars['env'], $vars['handlers'], $vars['visitors'], $vars['expressionParser']); | ||
72 | $this->stack[] = $vars; | ||
73 | |||
74 | // tag handlers | ||
75 | if (null === $this->handlers) { | ||
76 | $this->handlers = $this->env->getTokenParsers(); | ||
77 | $this->handlers->setParser($this); | ||
78 | } | ||
79 | |||
80 | // node visitors | ||
81 | if (null === $this->visitors) { | ||
82 | $this->visitors = $this->env->getNodeVisitors(); | ||
83 | } | ||
84 | |||
85 | if (null === $this->expressionParser) { | ||
86 | $this->expressionParser = new Twig_ExpressionParser($this, $this->env->getUnaryOperators(), $this->env->getBinaryOperators()); | ||
87 | } | ||
88 | |||
89 | $this->stream = $stream; | ||
90 | $this->parent = null; | ||
91 | $this->blocks = array(); | ||
92 | $this->macros = array(); | ||
93 | $this->traits = array(); | ||
94 | $this->blockStack = array(); | ||
95 | $this->importedSymbols = array(array()); | ||
96 | $this->embeddedTemplates = array(); | ||
97 | |||
98 | try { | ||
99 | $body = $this->subparse($test, $dropNeedle); | ||
100 | |||
101 | if (null !== $this->parent) { | ||
102 | if (null === $body = $this->filterBodyNodes($body)) { | ||
103 | $body = new Twig_Node(); | ||
104 | } | ||
105 | } | ||
106 | } catch (Twig_Error_Syntax $e) { | ||
107 | if (!$e->getTemplateFile()) { | ||
108 | $e->setTemplateFile($this->getFilename()); | ||
109 | } | ||
110 | |||
111 | if (!$e->getTemplateLine()) { | ||
112 | $e->setTemplateLine($this->stream->getCurrent()->getLine()); | ||
113 | } | ||
114 | |||
115 | throw $e; | ||
116 | } | ||
117 | |||
118 | $node = new Twig_Node_Module(new Twig_Node_Body(array($body)), $this->parent, new Twig_Node($this->blocks), new Twig_Node($this->macros), new Twig_Node($this->traits), $this->embeddedTemplates, $this->getFilename()); | ||
119 | |||
120 | $traverser = new Twig_NodeTraverser($this->env, $this->visitors); | ||
121 | |||
122 | $node = $traverser->traverse($node); | ||
123 | |||
124 | // restore previous stack so previous parse() call can resume working | ||
125 | foreach (array_pop($this->stack) as $key => $val) { | ||
126 | $this->$key = $val; | ||
127 | } | ||
128 | |||
129 | return $node; | ||
130 | } | ||
131 | |||
132 | public function subparse($test, $dropNeedle = false) | ||
133 | { | ||
134 | $lineno = $this->getCurrentToken()->getLine(); | ||
135 | $rv = array(); | ||
136 | while (!$this->stream->isEOF()) { | ||
137 | switch ($this->getCurrentToken()->getType()) { | ||
138 | case Twig_Token::TEXT_TYPE: | ||
139 | $token = $this->stream->next(); | ||
140 | $rv[] = new Twig_Node_Text($token->getValue(), $token->getLine()); | ||
141 | break; | ||
142 | |||
143 | case Twig_Token::VAR_START_TYPE: | ||
144 | $token = $this->stream->next(); | ||
145 | $expr = $this->expressionParser->parseExpression(); | ||
146 | $this->stream->expect(Twig_Token::VAR_END_TYPE); | ||
147 | $rv[] = new Twig_Node_Print($expr, $token->getLine()); | ||
148 | break; | ||
149 | |||
150 | case Twig_Token::BLOCK_START_TYPE: | ||
151 | $this->stream->next(); | ||
152 | $token = $this->getCurrentToken(); | ||
153 | |||
154 | if ($token->getType() !== Twig_Token::NAME_TYPE) { | ||
155 | throw new Twig_Error_Syntax('A block must start with a tag name', $token->getLine(), $this->getFilename()); | ||
156 | } | ||
157 | |||
158 | if (null !== $test && call_user_func($test, $token)) { | ||
159 | if ($dropNeedle) { | ||
160 | $this->stream->next(); | ||
161 | } | ||
162 | |||
163 | if (1 === count($rv)) { | ||
164 | return $rv[0]; | ||
165 | } | ||
166 | |||
167 | return new Twig_Node($rv, array(), $lineno); | ||
168 | } | ||
169 | |||
170 | $subparser = $this->handlers->getTokenParser($token->getValue()); | ||
171 | if (null === $subparser) { | ||
172 | if (null !== $test) { | ||
173 | $error = sprintf('Unexpected tag name "%s"', $token->getValue()); | ||
174 | if (is_array($test) && isset($test[0]) && $test[0] instanceof Twig_TokenParserInterface) { | ||
175 | $error .= sprintf(' (expecting closing tag for the "%s" tag defined near line %s)', $test[0]->getTag(), $lineno); | ||
176 | } | ||
177 | |||
178 | throw new Twig_Error_Syntax($error, $token->getLine(), $this->getFilename()); | ||
179 | } | ||
180 | |||
181 | $message = sprintf('Unknown tag name "%s"', $token->getValue()); | ||
182 | if ($alternatives = $this->env->computeAlternatives($token->getValue(), array_keys($this->env->getTags()))) { | ||
183 | $message = sprintf('%s. Did you mean "%s"', $message, implode('", "', $alternatives)); | ||
184 | } | ||
185 | |||
186 | throw new Twig_Error_Syntax($message, $token->getLine(), $this->getFilename()); | ||
187 | } | ||
188 | |||
189 | $this->stream->next(); | ||
190 | |||
191 | $node = $subparser->parse($token); | ||
192 | if (null !== $node) { | ||
193 | $rv[] = $node; | ||
194 | } | ||
195 | break; | ||
196 | |||
197 | default: | ||
198 | throw new Twig_Error_Syntax('Lexer or parser ended up in unsupported state.', 0, $this->getFilename()); | ||
199 | } | ||
200 | } | ||
201 | |||
202 | if (1 === count($rv)) { | ||
203 | return $rv[0]; | ||
204 | } | ||
205 | |||
206 | return new Twig_Node($rv, array(), $lineno); | ||
207 | } | ||
208 | |||
209 | public function addHandler($name, $class) | ||
210 | { | ||
211 | $this->handlers[$name] = $class; | ||
212 | } | ||
213 | |||
214 | public function addNodeVisitor(Twig_NodeVisitorInterface $visitor) | ||
215 | { | ||
216 | $this->visitors[] = $visitor; | ||
217 | } | ||
218 | |||
219 | public function getBlockStack() | ||
220 | { | ||
221 | return $this->blockStack; | ||
222 | } | ||
223 | |||
224 | public function peekBlockStack() | ||
225 | { | ||
226 | return $this->blockStack[count($this->blockStack) - 1]; | ||
227 | } | ||
228 | |||
229 | public function popBlockStack() | ||
230 | { | ||
231 | array_pop($this->blockStack); | ||
232 | } | ||
233 | |||
234 | public function pushBlockStack($name) | ||
235 | { | ||
236 | $this->blockStack[] = $name; | ||
237 | } | ||
238 | |||
239 | public function hasBlock($name) | ||
240 | { | ||
241 | return isset($this->blocks[$name]); | ||
242 | } | ||
243 | |||
244 | public function getBlock($name) | ||
245 | { | ||
246 | return $this->blocks[$name]; | ||
247 | } | ||
248 | |||
249 | public function setBlock($name, $value) | ||
250 | { | ||
251 | $this->blocks[$name] = new Twig_Node_Body(array($value), array(), $value->getLine()); | ||
252 | } | ||
253 | |||
254 | public function hasMacro($name) | ||
255 | { | ||
256 | return isset($this->macros[$name]); | ||
257 | } | ||
258 | |||
259 | public function setMacro($name, Twig_Node_Macro $node) | ||
260 | { | ||
261 | if (null === $this->reservedMacroNames) { | ||
262 | $this->reservedMacroNames = array(); | ||
263 | $r = new ReflectionClass($this->env->getBaseTemplateClass()); | ||
264 | foreach ($r->getMethods() as $method) { | ||
265 | $this->reservedMacroNames[] = $method->getName(); | ||
266 | } | ||
267 | } | ||
268 | |||
269 | if (in_array($name, $this->reservedMacroNames)) { | ||
270 | throw new Twig_Error_Syntax(sprintf('"%s" cannot be used as a macro name as it is a reserved keyword', $name), $node->getLine(), $this->getFilename()); | ||
271 | } | ||
272 | |||
273 | $this->macros[$name] = $node; | ||
274 | } | ||
275 | |||
276 | public function addTrait($trait) | ||
277 | { | ||
278 | $this->traits[] = $trait; | ||
279 | } | ||
280 | |||
281 | public function hasTraits() | ||
282 | { | ||
283 | return count($this->traits) > 0; | ||
284 | } | ||
285 | |||
286 | public function embedTemplate(Twig_Node_Module $template) | ||
287 | { | ||
288 | $template->setIndex(mt_rand()); | ||
289 | |||
290 | $this->embeddedTemplates[] = $template; | ||
291 | } | ||
292 | |||
293 | public function addImportedSymbol($type, $alias, $name = null, Twig_Node_Expression $node = null) | ||
294 | { | ||
295 | $this->importedSymbols[0][$type][$alias] = array('name' => $name, 'node' => $node); | ||
296 | } | ||
297 | |||
298 | public function getImportedSymbol($type, $alias) | ||
299 | { | ||
300 | foreach ($this->importedSymbols as $functions) { | ||
301 | if (isset($functions[$type][$alias])) { | ||
302 | return $functions[$type][$alias]; | ||
303 | } | ||
304 | } | ||
305 | } | ||
306 | |||
307 | public function isMainScope() | ||
308 | { | ||
309 | return 1 === count($this->importedSymbols); | ||
310 | } | ||
311 | |||
312 | public function pushLocalScope() | ||
313 | { | ||
314 | array_unshift($this->importedSymbols, array()); | ||
315 | } | ||
316 | |||
317 | public function popLocalScope() | ||
318 | { | ||
319 | array_shift($this->importedSymbols); | ||
320 | } | ||
321 | |||
322 | /** | ||
323 | * Gets the expression parser. | ||
324 | * | ||
325 | * @return Twig_ExpressionParser The expression parser | ||
326 | */ | ||
327 | public function getExpressionParser() | ||
328 | { | ||
329 | return $this->expressionParser; | ||
330 | } | ||
331 | |||
332 | public function getParent() | ||
333 | { | ||
334 | return $this->parent; | ||
335 | } | ||
336 | |||
337 | public function setParent($parent) | ||
338 | { | ||
339 | $this->parent = $parent; | ||
340 | } | ||
341 | |||
342 | /** | ||
343 | * Gets the token stream. | ||
344 | * | ||
345 | * @return Twig_TokenStream The token stream | ||
346 | */ | ||
347 | public function getStream() | ||
348 | { | ||
349 | return $this->stream; | ||
350 | } | ||
351 | |||
352 | /** | ||
353 | * Gets the current token. | ||
354 | * | ||
355 | * @return Twig_Token The current token | ||
356 | */ | ||
357 | public function getCurrentToken() | ||
358 | { | ||
359 | return $this->stream->getCurrent(); | ||
360 | } | ||
361 | |||
362 | protected function filterBodyNodes(Twig_NodeInterface $node) | ||
363 | { | ||
364 | // check that the body does not contain non-empty output nodes | ||
365 | if ( | ||
366 | ($node instanceof Twig_Node_Text && !ctype_space($node->getAttribute('data'))) | ||
367 | || | ||
368 | (!$node instanceof Twig_Node_Text && !$node instanceof Twig_Node_BlockReference && $node instanceof Twig_NodeOutputInterface) | ||
369 | ) { | ||
370 | if (false !== strpos((string) $node, chr(0xEF).chr(0xBB).chr(0xBF))) { | ||
371 | throw new Twig_Error_Syntax('A template that extends another one cannot have a body but a byte order mark (BOM) has been detected; it must be removed.', $node->getLine(), $this->getFilename()); | ||
372 | } | ||
373 | |||
374 | throw new Twig_Error_Syntax('A template that extends another one cannot have a body.', $node->getLine(), $this->getFilename()); | ||
375 | } | ||
376 | |||
377 | // bypass "set" nodes as they "capture" the output | ||
378 | if ($node instanceof Twig_Node_Set) { | ||
379 | return $node; | ||
380 | } | ||
381 | |||
382 | if ($node instanceof Twig_NodeOutputInterface) { | ||
383 | return; | ||
384 | } | ||
385 | |||
386 | foreach ($node as $k => $n) { | ||
387 | if (null !== $n && null === $n = $this->filterBodyNodes($n)) { | ||
388 | $node->removeNode($k); | ||
389 | } | ||
390 | } | ||
391 | |||
392 | return $node; | ||
393 | } | ||
394 | } | ||
diff --git a/inc/Twig/ParserInterface.php b/inc/Twig/ParserInterface.php new file mode 100644 index 00000000..f0d79009 --- /dev/null +++ b/inc/Twig/ParserInterface.php | |||
@@ -0,0 +1,28 @@ | |||
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 | * Interface implemented by parser classes. | ||
14 | * | ||
15 | * @author Fabien Potencier <fabien@symfony.com> | ||
16 | * @deprecated since 1.12 (to be removed in 2.0) | ||
17 | */ | ||
18 | interface Twig_ParserInterface | ||
19 | { | ||
20 | /** | ||
21 | * Converts a token stream to a node tree. | ||
22 | * | ||
23 | * @param Twig_TokenStream $stream A token stream instance | ||
24 | * | ||
25 | * @return Twig_Node_Module A node tree | ||
26 | */ | ||
27 | public function parse(Twig_TokenStream $stream); | ||
28 | } | ||
diff --git a/inc/Twig/Sandbox/SecurityError.php b/inc/Twig/Sandbox/SecurityError.php new file mode 100644 index 00000000..015bfaea --- /dev/null +++ b/inc/Twig/Sandbox/SecurityError.php | |||
@@ -0,0 +1,19 @@ | |||
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 | * Exception thrown when a security error occurs at runtime. | ||
14 | * | ||
15 | * @author Fabien Potencier <fabien@symfony.com> | ||
16 | */ | ||
17 | class Twig_Sandbox_SecurityError extends Twig_Error | ||
18 | { | ||
19 | } | ||
diff --git a/inc/Twig/Sandbox/SecurityPolicy.php b/inc/Twig/Sandbox/SecurityPolicy.php new file mode 100644 index 00000000..66ee2332 --- /dev/null +++ b/inc/Twig/Sandbox/SecurityPolicy.php | |||
@@ -0,0 +1,119 @@ | |||
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 | * Represents a security policy which need to be enforced when sandbox mode is enabled. | ||
14 | * | ||
15 | * @author Fabien Potencier <fabien@symfony.com> | ||
16 | */ | ||
17 | class Twig_Sandbox_SecurityPolicy implements Twig_Sandbox_SecurityPolicyInterface | ||
18 | { | ||
19 | protected $allowedTags; | ||
20 | protected $allowedFilters; | ||
21 | protected $allowedMethods; | ||
22 | protected $allowedProperties; | ||
23 | protected $allowedFunctions; | ||
24 | |||
25 | public function __construct(array $allowedTags = array(), array $allowedFilters = array(), array $allowedMethods = array(), array $allowedProperties = array(), array $allowedFunctions = array()) | ||
26 | { | ||
27 | $this->allowedTags = $allowedTags; | ||
28 | $this->allowedFilters = $allowedFilters; | ||
29 | $this->setAllowedMethods($allowedMethods); | ||
30 | $this->allowedProperties = $allowedProperties; | ||
31 | $this->allowedFunctions = $allowedFunctions; | ||
32 | } | ||
33 | |||
34 | public function setAllowedTags(array $tags) | ||
35 | { | ||
36 | $this->allowedTags = $tags; | ||
37 | } | ||
38 | |||
39 | public function setAllowedFilters(array $filters) | ||
40 | { | ||
41 | $this->allowedFilters = $filters; | ||
42 | } | ||
43 | |||
44 | public function setAllowedMethods(array $methods) | ||
45 | { | ||
46 | $this->allowedMethods = array(); | ||
47 | foreach ($methods as $class => $m) { | ||
48 | $this->allowedMethods[$class] = array_map('strtolower', is_array($m) ? $m : array($m)); | ||
49 | } | ||
50 | } | ||
51 | |||
52 | public function setAllowedProperties(array $properties) | ||
53 | { | ||
54 | $this->allowedProperties = $properties; | ||
55 | } | ||
56 | |||
57 | public function setAllowedFunctions(array $functions) | ||
58 | { | ||
59 | $this->allowedFunctions = $functions; | ||
60 | } | ||
61 | |||
62 | public function checkSecurity($tags, $filters, $functions) | ||
63 | { | ||
64 | foreach ($tags as $tag) { | ||
65 | if (!in_array($tag, $this->allowedTags)) { | ||
66 | throw new Twig_Sandbox_SecurityError(sprintf('Tag "%s" is not allowed.', $tag)); | ||
67 | } | ||
68 | } | ||
69 | |||
70 | foreach ($filters as $filter) { | ||
71 | if (!in_array($filter, $this->allowedFilters)) { | ||
72 | throw new Twig_Sandbox_SecurityError(sprintf('Filter "%s" is not allowed.', $filter)); | ||
73 | } | ||
74 | } | ||
75 | |||
76 | foreach ($functions as $function) { | ||
77 | if (!in_array($function, $this->allowedFunctions)) { | ||
78 | throw new Twig_Sandbox_SecurityError(sprintf('Function "%s" is not allowed.', $function)); | ||
79 | } | ||
80 | } | ||
81 | } | ||
82 | |||
83 | public function checkMethodAllowed($obj, $method) | ||
84 | { | ||
85 | if ($obj instanceof Twig_TemplateInterface || $obj instanceof Twig_Markup) { | ||
86 | return true; | ||
87 | } | ||
88 | |||
89 | $allowed = false; | ||
90 | $method = strtolower($method); | ||
91 | foreach ($this->allowedMethods as $class => $methods) { | ||
92 | if ($obj instanceof $class) { | ||
93 | $allowed = in_array($method, $methods); | ||
94 | |||
95 | break; | ||
96 | } | ||
97 | } | ||
98 | |||
99 | if (!$allowed) { | ||
100 | throw new Twig_Sandbox_SecurityError(sprintf('Calling "%s" method on a "%s" object is not allowed.', $method, get_class($obj))); | ||
101 | } | ||
102 | } | ||
103 | |||
104 | public function checkPropertyAllowed($obj, $property) | ||
105 | { | ||
106 | $allowed = false; | ||
107 | foreach ($this->allowedProperties as $class => $properties) { | ||
108 | if ($obj instanceof $class) { | ||
109 | $allowed = in_array($property, is_array($properties) ? $properties : array($properties)); | ||
110 | |||
111 | break; | ||
112 | } | ||
113 | } | ||
114 | |||
115 | if (!$allowed) { | ||
116 | throw new Twig_Sandbox_SecurityError(sprintf('Calling "%s" property on a "%s" object is not allowed.', $property, get_class($obj))); | ||
117 | } | ||
118 | } | ||
119 | } | ||
diff --git a/inc/Twig/Sandbox/SecurityPolicyInterface.php b/inc/Twig/Sandbox/SecurityPolicyInterface.php new file mode 100644 index 00000000..6ab48e3c --- /dev/null +++ b/inc/Twig/Sandbox/SecurityPolicyInterface.php | |||
@@ -0,0 +1,24 @@ | |||
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 | * Interfaces that all security policy classes must implements. | ||
14 | * | ||
15 | * @author Fabien Potencier <fabien@symfony.com> | ||
16 | */ | ||
17 | interface Twig_Sandbox_SecurityPolicyInterface | ||
18 | { | ||
19 | public function checkSecurity($tags, $filters, $functions); | ||
20 | |||
21 | public function checkMethodAllowed($obj, $method); | ||
22 | |||
23 | public function checkPropertyAllowed($obj, $method); | ||
24 | } | ||
diff --git a/inc/Twig/SimpleFilter.php b/inc/Twig/SimpleFilter.php new file mode 100644 index 00000000..d35c5633 --- /dev/null +++ b/inc/Twig/SimpleFilter.php | |||
@@ -0,0 +1,94 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2009-2012 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 | * Represents a template filter. | ||
14 | * | ||
15 | * @author Fabien Potencier <fabien@symfony.com> | ||
16 | */ | ||
17 | class Twig_SimpleFilter | ||
18 | { | ||
19 | protected $name; | ||
20 | protected $callable; | ||
21 | protected $options; | ||
22 | protected $arguments = array(); | ||
23 | |||
24 | public function __construct($name, $callable, array $options = array()) | ||
25 | { | ||
26 | $this->name = $name; | ||
27 | $this->callable = $callable; | ||
28 | $this->options = array_merge(array( | ||
29 | 'needs_environment' => false, | ||
30 | 'needs_context' => false, | ||
31 | 'is_safe' => null, | ||
32 | 'is_safe_callback' => null, | ||
33 | 'pre_escape' => null, | ||
34 | 'preserves_safety' => null, | ||
35 | 'node_class' => 'Twig_Node_Expression_Filter', | ||
36 | ), $options); | ||
37 | } | ||
38 | |||
39 | public function getName() | ||
40 | { | ||
41 | return $this->name; | ||
42 | } | ||
43 | |||
44 | public function getCallable() | ||
45 | { | ||
46 | return $this->callable; | ||
47 | } | ||
48 | |||
49 | public function getNodeClass() | ||
50 | { | ||
51 | return $this->options['node_class']; | ||
52 | } | ||
53 | |||
54 | public function setArguments($arguments) | ||
55 | { | ||
56 | $this->arguments = $arguments; | ||
57 | } | ||
58 | |||
59 | public function getArguments() | ||
60 | { | ||
61 | return $this->arguments; | ||
62 | } | ||
63 | |||
64 | public function needsEnvironment() | ||
65 | { | ||
66 | return $this->options['needs_environment']; | ||
67 | } | ||
68 | |||
69 | public function needsContext() | ||
70 | { | ||
71 | return $this->options['needs_context']; | ||
72 | } | ||
73 | |||
74 | public function getSafe(Twig_Node $filterArgs) | ||
75 | { | ||
76 | if (null !== $this->options['is_safe']) { | ||
77 | return $this->options['is_safe']; | ||
78 | } | ||
79 | |||
80 | if (null !== $this->options['is_safe_callback']) { | ||
81 | return call_user_func($this->options['is_safe_callback'], $filterArgs); | ||
82 | } | ||
83 | } | ||
84 | |||
85 | public function getPreservesSafety() | ||
86 | { | ||
87 | return $this->options['preserves_safety']; | ||
88 | } | ||
89 | |||
90 | public function getPreEscape() | ||
91 | { | ||
92 | return $this->options['pre_escape']; | ||
93 | } | ||
94 | } | ||
diff --git a/inc/Twig/SimpleFunction.php b/inc/Twig/SimpleFunction.php new file mode 100644 index 00000000..8ef6aca2 --- /dev/null +++ b/inc/Twig/SimpleFunction.php | |||
@@ -0,0 +1,84 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010-2012 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 | * Represents a template function. | ||
14 | * | ||
15 | * @author Fabien Potencier <fabien@symfony.com> | ||
16 | */ | ||
17 | class Twig_SimpleFunction | ||
18 | { | ||
19 | protected $name; | ||
20 | protected $callable; | ||
21 | protected $options; | ||
22 | protected $arguments = array(); | ||
23 | |||
24 | public function __construct($name, $callable, array $options = array()) | ||
25 | { | ||
26 | $this->name = $name; | ||
27 | $this->callable = $callable; | ||
28 | $this->options = array_merge(array( | ||
29 | 'needs_environment' => false, | ||
30 | 'needs_context' => false, | ||
31 | 'is_safe' => null, | ||
32 | 'is_safe_callback' => null, | ||
33 | 'node_class' => 'Twig_Node_Expression_Function', | ||
34 | ), $options); | ||
35 | } | ||
36 | |||
37 | public function getName() | ||
38 | { | ||
39 | return $this->name; | ||
40 | } | ||
41 | |||
42 | public function getCallable() | ||
43 | { | ||
44 | return $this->callable; | ||
45 | } | ||
46 | |||
47 | public function getNodeClass() | ||
48 | { | ||
49 | return $this->options['node_class']; | ||
50 | } | ||
51 | |||
52 | public function setArguments($arguments) | ||
53 | { | ||
54 | $this->arguments = $arguments; | ||
55 | } | ||
56 | |||
57 | public function getArguments() | ||
58 | { | ||
59 | return $this->arguments; | ||
60 | } | ||
61 | |||
62 | public function needsEnvironment() | ||
63 | { | ||
64 | return $this->options['needs_environment']; | ||
65 | } | ||
66 | |||
67 | public function needsContext() | ||
68 | { | ||
69 | return $this->options['needs_context']; | ||
70 | } | ||
71 | |||
72 | public function getSafe(Twig_Node $functionArgs) | ||
73 | { | ||
74 | if (null !== $this->options['is_safe']) { | ||
75 | return $this->options['is_safe']; | ||
76 | } | ||
77 | |||
78 | if (null !== $this->options['is_safe_callback']) { | ||
79 | return call_user_func($this->options['is_safe_callback'], $functionArgs); | ||
80 | } | ||
81 | |||
82 | return array(); | ||
83 | } | ||
84 | } | ||
diff --git a/inc/Twig/SimpleTest.php b/inc/Twig/SimpleTest.php new file mode 100644 index 00000000..225459c9 --- /dev/null +++ b/inc/Twig/SimpleTest.php | |||
@@ -0,0 +1,46 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010-2012 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 | * Represents a template test. | ||
14 | * | ||
15 | * @author Fabien Potencier <fabien@symfony.com> | ||
16 | */ | ||
17 | class Twig_SimpleTest | ||
18 | { | ||
19 | protected $name; | ||
20 | protected $callable; | ||
21 | protected $options; | ||
22 | |||
23 | public function __construct($name, $callable, array $options = array()) | ||
24 | { | ||
25 | $this->name = $name; | ||
26 | $this->callable = $callable; | ||
27 | $this->options = array_merge(array( | ||
28 | 'node_class' => 'Twig_Node_Expression_Test', | ||
29 | ), $options); | ||
30 | } | ||
31 | |||
32 | public function getName() | ||
33 | { | ||
34 | return $this->name; | ||
35 | } | ||
36 | |||
37 | public function getCallable() | ||
38 | { | ||
39 | return $this->callable; | ||
40 | } | ||
41 | |||
42 | public function getNodeClass() | ||
43 | { | ||
44 | return $this->options['node_class']; | ||
45 | } | ||
46 | } | ||
diff --git a/inc/Twig/Template.php b/inc/Twig/Template.php new file mode 100644 index 00000000..a001ca03 --- /dev/null +++ b/inc/Twig/Template.php | |||
@@ -0,0 +1,455 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2009 Fabien Potencier | ||
7 | * (c) 2009 Armin Ronacher | ||
8 | * | ||
9 | * For the full copyright and license information, please view the LICENSE | ||
10 | * file that was distributed with this source code. | ||
11 | */ | ||
12 | |||
13 | /** | ||
14 | * Default base class for compiled templates. | ||
15 | * | ||
16 | * @author Fabien Potencier <fabien@symfony.com> | ||
17 | */ | ||
18 | abstract class Twig_Template implements Twig_TemplateInterface | ||
19 | { | ||
20 | protected static $cache = array(); | ||
21 | |||
22 | protected $parent; | ||
23 | protected $parents; | ||
24 | protected $env; | ||
25 | protected $blocks; | ||
26 | protected $traits; | ||
27 | |||
28 | /** | ||
29 | * Constructor. | ||
30 | * | ||
31 | * @param Twig_Environment $env A Twig_Environment instance | ||
32 | */ | ||
33 | public function __construct(Twig_Environment $env) | ||
34 | { | ||
35 | $this->env = $env; | ||
36 | $this->blocks = array(); | ||
37 | $this->traits = array(); | ||
38 | } | ||
39 | |||
40 | /** | ||
41 | * Returns the template name. | ||
42 | * | ||
43 | * @return string The template name | ||
44 | */ | ||
45 | abstract public function getTemplateName(); | ||
46 | |||
47 | /** | ||
48 | * {@inheritdoc} | ||
49 | */ | ||
50 | public function getEnvironment() | ||
51 | { | ||
52 | return $this->env; | ||
53 | } | ||
54 | |||
55 | /** | ||
56 | * Returns the parent template. | ||
57 | * | ||
58 | * This method is for internal use only and should never be called | ||
59 | * directly. | ||
60 | * | ||
61 | * @return Twig_TemplateInterface|false The parent template or false if there is no parent | ||
62 | */ | ||
63 | public function getParent(array $context) | ||
64 | { | ||
65 | if (null !== $this->parent) { | ||
66 | return $this->parent; | ||
67 | } | ||
68 | |||
69 | $parent = $this->doGetParent($context); | ||
70 | if (false === $parent) { | ||
71 | return false; | ||
72 | } elseif ($parent instanceof Twig_Template) { | ||
73 | $name = $parent->getTemplateName(); | ||
74 | $this->parents[$name] = $parent; | ||
75 | $parent = $name; | ||
76 | } elseif (!isset($this->parents[$parent])) { | ||
77 | $this->parents[$parent] = $this->env->loadTemplate($parent); | ||
78 | } | ||
79 | |||
80 | return $this->parents[$parent]; | ||
81 | } | ||
82 | |||
83 | protected function doGetParent(array $context) | ||
84 | { | ||
85 | return false; | ||
86 | } | ||
87 | |||
88 | public function isTraitable() | ||
89 | { | ||
90 | return true; | ||
91 | } | ||
92 | |||
93 | /** | ||
94 | * Displays a parent block. | ||
95 | * | ||
96 | * This method is for internal use only and should never be called | ||
97 | * directly. | ||
98 | * | ||
99 | * @param string $name The block name to display from the parent | ||
100 | * @param array $context The context | ||
101 | * @param array $blocks The current set of blocks | ||
102 | */ | ||
103 | public function displayParentBlock($name, array $context, array $blocks = array()) | ||
104 | { | ||
105 | $name = (string) $name; | ||
106 | |||
107 | if (isset($this->traits[$name])) { | ||
108 | $this->traits[$name][0]->displayBlock($name, $context, $blocks); | ||
109 | } elseif (false !== $parent = $this->getParent($context)) { | ||
110 | $parent->displayBlock($name, $context, $blocks); | ||
111 | } else { | ||
112 | throw new Twig_Error_Runtime(sprintf('The template has no parent and no traits defining the "%s" block', $name), -1, $this->getTemplateName()); | ||
113 | } | ||
114 | } | ||
115 | |||
116 | /** | ||
117 | * Displays a block. | ||
118 | * | ||
119 | * This method is for internal use only and should never be called | ||
120 | * directly. | ||
121 | * | ||
122 | * @param string $name The block name to display | ||
123 | * @param array $context The context | ||
124 | * @param array $blocks The current set of blocks | ||
125 | */ | ||
126 | public function displayBlock($name, array $context, array $blocks = array()) | ||
127 | { | ||
128 | $name = (string) $name; | ||
129 | |||
130 | if (isset($blocks[$name])) { | ||
131 | $b = $blocks; | ||
132 | unset($b[$name]); | ||
133 | call_user_func($blocks[$name], $context, $b); | ||
134 | } elseif (isset($this->blocks[$name])) { | ||
135 | call_user_func($this->blocks[$name], $context, $blocks); | ||
136 | } elseif (false !== $parent = $this->getParent($context)) { | ||
137 | $parent->displayBlock($name, $context, array_merge($this->blocks, $blocks)); | ||
138 | } | ||
139 | } | ||
140 | |||
141 | /** | ||
142 | * Renders a parent block. | ||
143 | * | ||
144 | * This method is for internal use only and should never be called | ||
145 | * directly. | ||
146 | * | ||
147 | * @param string $name The block name to render from the parent | ||
148 | * @param array $context The context | ||
149 | * @param array $blocks The current set of blocks | ||
150 | * | ||
151 | * @return string The rendered block | ||
152 | */ | ||
153 | public function renderParentBlock($name, array $context, array $blocks = array()) | ||
154 | { | ||
155 | ob_start(); | ||
156 | $this->displayParentBlock($name, $context, $blocks); | ||
157 | |||
158 | return ob_get_clean(); | ||
159 | } | ||
160 | |||
161 | /** | ||
162 | * Renders a block. | ||
163 | * | ||
164 | * This method is for internal use only and should never be called | ||
165 | * directly. | ||
166 | * | ||
167 | * @param string $name The block name to render | ||
168 | * @param array $context The context | ||
169 | * @param array $blocks The current set of blocks | ||
170 | * | ||
171 | * @return string The rendered block | ||
172 | */ | ||
173 | public function renderBlock($name, array $context, array $blocks = array()) | ||
174 | { | ||
175 | ob_start(); | ||
176 | $this->displayBlock($name, $context, $blocks); | ||
177 | |||
178 | return ob_get_clean(); | ||
179 | } | ||
180 | |||
181 | /** | ||
182 | * Returns whether a block exists or not. | ||
183 | * | ||
184 | * This method is for internal use only and should never be called | ||
185 | * directly. | ||
186 | * | ||
187 | * This method does only return blocks defined in the current template | ||
188 | * or defined in "used" traits. | ||
189 | * | ||
190 | * It does not return blocks from parent templates as the parent | ||
191 | * template name can be dynamic, which is only known based on the | ||
192 | * current context. | ||
193 | * | ||
194 | * @param string $name The block name | ||
195 | * | ||
196 | * @return Boolean true if the block exists, false otherwise | ||
197 | */ | ||
198 | public function hasBlock($name) | ||
199 | { | ||
200 | return isset($this->blocks[(string) $name]); | ||
201 | } | ||
202 | |||
203 | /** | ||
204 | * Returns all block names. | ||
205 | * | ||
206 | * This method is for internal use only and should never be called | ||
207 | * directly. | ||
208 | * | ||
209 | * @return array An array of block names | ||
210 | * | ||
211 | * @see hasBlock | ||
212 | */ | ||
213 | public function getBlockNames() | ||
214 | { | ||
215 | return array_keys($this->blocks); | ||
216 | } | ||
217 | |||
218 | /** | ||
219 | * Returns all blocks. | ||
220 | * | ||
221 | * This method is for internal use only and should never be called | ||
222 | * directly. | ||
223 | * | ||
224 | * @return array An array of blocks | ||
225 | * | ||
226 | * @see hasBlock | ||
227 | */ | ||
228 | public function getBlocks() | ||
229 | { | ||
230 | return $this->blocks; | ||
231 | } | ||
232 | |||
233 | /** | ||
234 | * {@inheritdoc} | ||
235 | */ | ||
236 | public function display(array $context, array $blocks = array()) | ||
237 | { | ||
238 | $this->displayWithErrorHandling($this->env->mergeGlobals($context), $blocks); | ||
239 | } | ||
240 | |||
241 | /** | ||
242 | * {@inheritdoc} | ||
243 | */ | ||
244 | public function render(array $context) | ||
245 | { | ||
246 | $level = ob_get_level(); | ||
247 | ob_start(); | ||
248 | try { | ||
249 | $this->display($context); | ||
250 | } catch (Exception $e) { | ||
251 | while (ob_get_level() > $level) { | ||
252 | ob_end_clean(); | ||
253 | } | ||
254 | |||
255 | throw $e; | ||
256 | } | ||
257 | |||
258 | return ob_get_clean(); | ||
259 | } | ||
260 | |||
261 | protected function displayWithErrorHandling(array $context, array $blocks = array()) | ||
262 | { | ||
263 | try { | ||
264 | $this->doDisplay($context, $blocks); | ||
265 | } catch (Twig_Error $e) { | ||
266 | if (!$e->getTemplateFile()) { | ||
267 | $e->setTemplateFile($this->getTemplateName()); | ||
268 | } | ||
269 | |||
270 | // this is mostly useful for Twig_Error_Loader exceptions | ||
271 | // see Twig_Error_Loader | ||
272 | if (false === $e->getTemplateLine()) { | ||
273 | $e->setTemplateLine(-1); | ||
274 | $e->guess(); | ||
275 | } | ||
276 | |||
277 | throw $e; | ||
278 | } catch (Exception $e) { | ||
279 | throw new Twig_Error_Runtime(sprintf('An exception has been thrown during the rendering of a template ("%s").', $e->getMessage()), -1, null, $e); | ||
280 | } | ||
281 | } | ||
282 | |||
283 | /** | ||
284 | * Auto-generated method to display the template with the given context. | ||
285 | * | ||
286 | * @param array $context An array of parameters to pass to the template | ||
287 | * @param array $blocks An array of blocks to pass to the template | ||
288 | */ | ||
289 | abstract protected function doDisplay(array $context, array $blocks = array()); | ||
290 | |||
291 | /** | ||
292 | * Returns a variable from the context. | ||
293 | * | ||
294 | * This method is for internal use only and should never be called | ||
295 | * directly. | ||
296 | * | ||
297 | * This method should not be overridden in a sub-class as this is an | ||
298 | * implementation detail that has been introduced to optimize variable | ||
299 | * access for versions of PHP before 5.4. This is not a way to override | ||
300 | * the way to get a variable value. | ||
301 | * | ||
302 | * @param array $context The context | ||
303 | * @param string $item The variable to return from the context | ||
304 | * @param Boolean $ignoreStrictCheck Whether to ignore the strict variable check or not | ||
305 | * | ||
306 | * @return The content of the context variable | ||
307 | * | ||
308 | * @throws Twig_Error_Runtime if the variable does not exist and Twig is running in strict mode | ||
309 | */ | ||
310 | final protected function getContext($context, $item, $ignoreStrictCheck = false) | ||
311 | { | ||
312 | if (!array_key_exists($item, $context)) { | ||
313 | if ($ignoreStrictCheck || !$this->env->isStrictVariables()) { | ||
314 | return null; | ||
315 | } | ||
316 | |||
317 | throw new Twig_Error_Runtime(sprintf('Variable "%s" does not exist', $item), -1, $this->getTemplateName()); | ||
318 | } | ||
319 | |||
320 | return $context[$item]; | ||
321 | } | ||
322 | |||
323 | /** | ||
324 | * Returns the attribute value for a given array/object. | ||
325 | * | ||
326 | * @param mixed $object The object or array from where to get the item | ||
327 | * @param mixed $item The item to get from the array or object | ||
328 | * @param array $arguments An array of arguments to pass if the item is an object method | ||
329 | * @param string $type The type of attribute (@see Twig_TemplateInterface) | ||
330 | * @param Boolean $isDefinedTest Whether this is only a defined check | ||
331 | * @param Boolean $ignoreStrictCheck Whether to ignore the strict attribute check or not | ||
332 | * | ||
333 | * @return mixed The attribute value, or a Boolean when $isDefinedTest is true, or null when the attribute is not set and $ignoreStrictCheck is true | ||
334 | * | ||
335 | * @throws Twig_Error_Runtime if the attribute does not exist and Twig is running in strict mode and $isDefinedTest is false | ||
336 | */ | ||
337 | protected function getAttribute($object, $item, array $arguments = array(), $type = Twig_TemplateInterface::ANY_CALL, $isDefinedTest = false, $ignoreStrictCheck = false) | ||
338 | { | ||
339 | // array | ||
340 | if (Twig_TemplateInterface::METHOD_CALL !== $type) { | ||
341 | $arrayItem = is_bool($item) || is_float($item) ? (int) $item : $item; | ||
342 | |||
343 | if ((is_array($object) && array_key_exists($arrayItem, $object)) | ||
344 | || ($object instanceof ArrayAccess && isset($object[$arrayItem])) | ||
345 | ) { | ||
346 | if ($isDefinedTest) { | ||
347 | return true; | ||
348 | } | ||
349 | |||
350 | return $object[$arrayItem]; | ||
351 | } | ||
352 | |||
353 | if (Twig_TemplateInterface::ARRAY_CALL === $type || !is_object($object)) { | ||
354 | if ($isDefinedTest) { | ||
355 | return false; | ||
356 | } | ||
357 | |||
358 | if ($ignoreStrictCheck || !$this->env->isStrictVariables()) { | ||
359 | return null; | ||
360 | } | ||
361 | |||
362 | if (is_object($object)) { | ||
363 | throw new Twig_Error_Runtime(sprintf('Key "%s" in object (with ArrayAccess) of type "%s" does not exist', $arrayItem, get_class($object)), -1, $this->getTemplateName()); | ||
364 | } elseif (is_array($object)) { | ||
365 | throw new Twig_Error_Runtime(sprintf('Key "%s" for array with keys "%s" does not exist', $arrayItem, implode(', ', array_keys($object))), -1, $this->getTemplateName()); | ||
366 | } elseif (Twig_TemplateInterface::ARRAY_CALL === $type) { | ||
367 | throw new Twig_Error_Runtime(sprintf('Impossible to access a key ("%s") on a %s variable ("%s")', $item, gettype($object), $object), -1, $this->getTemplateName()); | ||
368 | } else { | ||
369 | throw new Twig_Error_Runtime(sprintf('Impossible to access an attribute ("%s") on a %s variable ("%s")', $item, gettype($object), $object), -1, $this->getTemplateName()); | ||
370 | } | ||
371 | } | ||
372 | } | ||
373 | |||
374 | if (!is_object($object)) { | ||
375 | if ($isDefinedTest) { | ||
376 | return false; | ||
377 | } | ||
378 | |||
379 | if ($ignoreStrictCheck || !$this->env->isStrictVariables()) { | ||
380 | return null; | ||
381 | } | ||
382 | |||
383 | throw new Twig_Error_Runtime(sprintf('Impossible to invoke a method ("%s") on a %s variable ("%s")', $item, gettype($object), $object), -1, $this->getTemplateName()); | ||
384 | } | ||
385 | |||
386 | $class = get_class($object); | ||
387 | |||
388 | // object property | ||
389 | if (Twig_TemplateInterface::METHOD_CALL !== $type) { | ||
390 | if (isset($object->$item) || array_key_exists((string) $item, $object)) { | ||
391 | if ($isDefinedTest) { | ||
392 | return true; | ||
393 | } | ||
394 | |||
395 | if ($this->env->hasExtension('sandbox')) { | ||
396 | $this->env->getExtension('sandbox')->checkPropertyAllowed($object, $item); | ||
397 | } | ||
398 | |||
399 | return $object->$item; | ||
400 | } | ||
401 | } | ||
402 | |||
403 | // object method | ||
404 | if (!isset(self::$cache[$class]['methods'])) { | ||
405 | self::$cache[$class]['methods'] = array_change_key_case(array_flip(get_class_methods($object))); | ||
406 | } | ||
407 | |||
408 | $lcItem = strtolower($item); | ||
409 | if (isset(self::$cache[$class]['methods'][$lcItem])) { | ||
410 | $method = (string) $item; | ||
411 | } elseif (isset(self::$cache[$class]['methods']['get'.$lcItem])) { | ||
412 | $method = 'get'.$item; | ||
413 | } elseif (isset(self::$cache[$class]['methods']['is'.$lcItem])) { | ||
414 | $method = 'is'.$item; | ||
415 | } elseif (isset(self::$cache[$class]['methods']['__call'])) { | ||
416 | $method = (string) $item; | ||
417 | } else { | ||
418 | if ($isDefinedTest) { | ||
419 | return false; | ||
420 | } | ||
421 | |||
422 | if ($ignoreStrictCheck || !$this->env->isStrictVariables()) { | ||
423 | return null; | ||
424 | } | ||
425 | |||
426 | throw new Twig_Error_Runtime(sprintf('Method "%s" for object "%s" does not exist', $item, get_class($object)), -1, $this->getTemplateName()); | ||
427 | } | ||
428 | |||
429 | if ($isDefinedTest) { | ||
430 | return true; | ||
431 | } | ||
432 | |||
433 | if ($this->env->hasExtension('sandbox')) { | ||
434 | $this->env->getExtension('sandbox')->checkMethodAllowed($object, $method); | ||
435 | } | ||
436 | |||
437 | $ret = call_user_func_array(array($object, $method), $arguments); | ||
438 | |||
439 | // useful when calling a template method from a template | ||
440 | // this is not supported but unfortunately heavily used in the Symfony profiler | ||
441 | if ($object instanceof Twig_TemplateInterface) { | ||
442 | return $ret === '' ? '' : new Twig_Markup($ret, $this->env->getCharset()); | ||
443 | } | ||
444 | |||
445 | return $ret; | ||
446 | } | ||
447 | |||
448 | /** | ||
449 | * This method is only useful when testing Twig. Do not use it. | ||
450 | */ | ||
451 | public static function clearCache() | ||
452 | { | ||
453 | self::$cache = array(); | ||
454 | } | ||
455 | } | ||
diff --git a/inc/Twig/TemplateInterface.php b/inc/Twig/TemplateInterface.php new file mode 100644 index 00000000..879f503e --- /dev/null +++ b/inc/Twig/TemplateInterface.php | |||
@@ -0,0 +1,47 @@ | |||
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 | * Interface implemented by all compiled templates. | ||
14 | * | ||
15 | * @author Fabien Potencier <fabien@symfony.com> | ||
16 | * @deprecated since 1.12 (to be removed in 2.0) | ||
17 | */ | ||
18 | interface Twig_TemplateInterface | ||
19 | { | ||
20 | const ANY_CALL = 'any'; | ||
21 | const ARRAY_CALL = 'array'; | ||
22 | const METHOD_CALL = 'method'; | ||
23 | |||
24 | /** | ||
25 | * Renders the template with the given context and returns it as string. | ||
26 | * | ||
27 | * @param array $context An array of parameters to pass to the template | ||
28 | * | ||
29 | * @return string The rendered template | ||
30 | */ | ||
31 | public function render(array $context); | ||
32 | |||
33 | /** | ||
34 | * Displays the template with the given context. | ||
35 | * | ||
36 | * @param array $context An array of parameters to pass to the template | ||
37 | * @param array $blocks An array of blocks to pass to the template | ||
38 | */ | ||
39 | public function display(array $context, array $blocks = array()); | ||
40 | |||
41 | /** | ||
42 | * Returns the bound environment for this template. | ||
43 | * | ||
44 | * @return Twig_Environment The current environment | ||
45 | */ | ||
46 | public function getEnvironment(); | ||
47 | } | ||
diff --git a/inc/Twig/Test.php b/inc/Twig/Test.php new file mode 100644 index 00000000..3baff885 --- /dev/null +++ b/inc/Twig/Test.php | |||
@@ -0,0 +1,34 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2012 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 | * Represents a template test. | ||
14 | * | ||
15 | * @author Fabien Potencier <fabien@symfony.com> | ||
16 | * @deprecated since 1.12 (to be removed in 2.0) | ||
17 | */ | ||
18 | abstract class Twig_Test implements Twig_TestInterface, Twig_TestCallableInterface | ||
19 | { | ||
20 | protected $options; | ||
21 | protected $arguments = array(); | ||
22 | |||
23 | public function __construct(array $options = array()) | ||
24 | { | ||
25 | $this->options = array_merge(array( | ||
26 | 'callable' => null, | ||
27 | ), $options); | ||
28 | } | ||
29 | |||
30 | public function getCallable() | ||
31 | { | ||
32 | return $this->options['callable']; | ||
33 | } | ||
34 | } | ||
diff --git a/inc/Twig/Test/Function.php b/inc/Twig/Test/Function.php new file mode 100644 index 00000000..4be6b9b9 --- /dev/null +++ b/inc/Twig/Test/Function.php | |||
@@ -0,0 +1,35 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 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 | * Represents a function template test. | ||
14 | * | ||
15 | * @author Fabien Potencier <fabien@symfony.com> | ||
16 | * @deprecated since 1.12 (to be removed in 2.0) | ||
17 | */ | ||
18 | class Twig_Test_Function extends Twig_Test | ||
19 | { | ||
20 | protected $function; | ||
21 | |||
22 | public function __construct($function, array $options = array()) | ||
23 | { | ||
24 | $options['callable'] = $function; | ||
25 | |||
26 | parent::__construct($options); | ||
27 | |||
28 | $this->function = $function; | ||
29 | } | ||
30 | |||
31 | public function compile() | ||
32 | { | ||
33 | return $this->function; | ||
34 | } | ||
35 | } | ||
diff --git a/inc/Twig/Test/IntegrationTestCase.php b/inc/Twig/Test/IntegrationTestCase.php new file mode 100644 index 00000000..724f0941 --- /dev/null +++ b/inc/Twig/Test/IntegrationTestCase.php | |||
@@ -0,0 +1,154 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 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 | * Integration test helper | ||
14 | * | ||
15 | * @author Fabien Potencier <fabien@symfony.com> | ||
16 | * @author Karma Dordrak <drak@zikula.org> | ||
17 | */ | ||
18 | abstract class Twig_Test_IntegrationTestCase extends PHPUnit_Framework_TestCase | ||
19 | { | ||
20 | abstract protected function getExtensions(); | ||
21 | abstract protected function getFixturesDir(); | ||
22 | |||
23 | /** | ||
24 | * @dataProvider getTests | ||
25 | */ | ||
26 | public function testIntegration($file, $message, $condition, $templates, $exception, $outputs) | ||
27 | { | ||
28 | $this->doIntegrationTest($file, $message, $condition, $templates, $exception, $outputs); | ||
29 | } | ||
30 | |||
31 | public function getTests() | ||
32 | { | ||
33 | $fixturesDir = realpath($this->getFixturesDir()); | ||
34 | $tests = array(); | ||
35 | |||
36 | foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($fixturesDir), RecursiveIteratorIterator::LEAVES_ONLY) as $file) { | ||
37 | if (!preg_match('/\.test$/', $file)) { | ||
38 | continue; | ||
39 | } | ||
40 | |||
41 | $test = file_get_contents($file->getRealpath()); | ||
42 | |||
43 | if (preg_match('/ | ||
44 | --TEST--\s*(.*?)\s*(?:--CONDITION--\s*(.*))?\s*((?:--TEMPLATE(?:\(.*?\))?--(?:.*?))+)\s*(?:--DATA--\s*(.*))?\s*--EXCEPTION--\s*(.*)/sx', $test, $match)) { | ||
45 | $message = $match[1]; | ||
46 | $condition = $match[2]; | ||
47 | $templates = $this->parseTemplates($match[3]); | ||
48 | $exception = $match[5]; | ||
49 | $outputs = array(array(null, $match[4], null, '')); | ||
50 | } elseif (preg_match('/--TEST--\s*(.*?)\s*(?:--CONDITION--\s*(.*))?\s*((?:--TEMPLATE(?:\(.*?\))?--(?:.*?))+)--DATA--.*?--EXPECT--.*/s', $test, $match)) { | ||
51 | $message = $match[1]; | ||
52 | $condition = $match[2]; | ||
53 | $templates = $this->parseTemplates($match[3]); | ||
54 | $exception = false; | ||
55 | preg_match_all('/--DATA--(.*?)(?:--CONFIG--(.*?))?--EXPECT--(.*?)(?=\-\-DATA\-\-|$)/s', $test, $outputs, PREG_SET_ORDER); | ||
56 | } else { | ||
57 | throw new InvalidArgumentException(sprintf('Test "%s" is not valid.', str_replace($fixturesDir.'/', '', $file))); | ||
58 | } | ||
59 | |||
60 | $tests[] = array(str_replace($fixturesDir.'/', '', $file), $message, $condition, $templates, $exception, $outputs); | ||
61 | } | ||
62 | |||
63 | return $tests; | ||
64 | } | ||
65 | |||
66 | protected function doIntegrationTest($file, $message, $condition, $templates, $exception, $outputs) | ||
67 | { | ||
68 | if ($condition) { | ||
69 | eval('$ret = '.$condition.';'); | ||
70 | if (!$ret) { | ||
71 | $this->markTestSkipped($condition); | ||
72 | } | ||
73 | } | ||
74 | |||
75 | $loader = new Twig_Loader_Array($templates); | ||
76 | |||
77 | foreach ($outputs as $match) { | ||
78 | $config = array_merge(array( | ||
79 | 'cache' => false, | ||
80 | 'strict_variables' => true, | ||
81 | ), $match[2] ? eval($match[2].';') : array()); | ||
82 | $twig = new Twig_Environment($loader, $config); | ||
83 | $twig->addGlobal('global', 'global'); | ||
84 | foreach ($this->getExtensions() as $extension) { | ||
85 | $twig->addExtension($extension); | ||
86 | } | ||
87 | |||
88 | try { | ||
89 | $template = $twig->loadTemplate('index.twig'); | ||
90 | } catch (Exception $e) { | ||
91 | if (false !== $exception) { | ||
92 | $this->assertEquals(trim($exception), trim(sprintf('%s: %s', get_class($e), $e->getMessage()))); | ||
93 | |||
94 | return; | ||
95 | } | ||
96 | |||
97 | if ($e instanceof Twig_Error_Syntax) { | ||
98 | $e->setTemplateFile($file); | ||
99 | |||
100 | throw $e; | ||
101 | } | ||
102 | |||
103 | throw new Twig_Error(sprintf('%s: %s', get_class($e), $e->getMessage()), -1, $file, $e); | ||
104 | } | ||
105 | |||
106 | try { | ||
107 | $output = trim($template->render(eval($match[1].';')), "\n "); | ||
108 | } catch (Exception $e) { | ||
109 | if (false !== $exception) { | ||
110 | $this->assertEquals(trim($exception), trim(sprintf('%s: %s', get_class($e), $e->getMessage()))); | ||
111 | |||
112 | return; | ||
113 | } | ||
114 | |||
115 | if ($e instanceof Twig_Error_Syntax) { | ||
116 | $e->setTemplateFile($file); | ||
117 | } else { | ||
118 | $e = new Twig_Error(sprintf('%s: %s', get_class($e), $e->getMessage()), -1, $file, $e); | ||
119 | } | ||
120 | |||
121 | $output = trim(sprintf('%s: %s', get_class($e), $e->getMessage())); | ||
122 | } | ||
123 | |||
124 | if (false !== $exception) { | ||
125 | list($class, ) = explode(':', $exception); | ||
126 | $this->assertThat(NULL, new PHPUnit_Framework_Constraint_Exception($class)); | ||
127 | } | ||
128 | |||
129 | $expected = trim($match[3], "\n "); | ||
130 | |||
131 | if ($expected != $output) { | ||
132 | echo 'Compiled template that failed:'; | ||
133 | |||
134 | foreach (array_keys($templates) as $name) { | ||
135 | echo "Template: $name\n"; | ||
136 | $source = $loader->getSource($name); | ||
137 | echo $twig->compile($twig->parse($twig->tokenize($source, $name))); | ||
138 | } | ||
139 | } | ||
140 | $this->assertEquals($expected, $output, $message.' (in '.$file.')'); | ||
141 | } | ||
142 | } | ||
143 | |||
144 | protected static function parseTemplates($test) | ||
145 | { | ||
146 | $templates = array(); | ||
147 | preg_match_all('/--TEMPLATE(?:\((.*?)\))?--(.*?)(?=\-\-TEMPLATE|$)/s', $test, $matches, PREG_SET_ORDER); | ||
148 | foreach ($matches as $match) { | ||
149 | $templates[($match[1] ? $match[1] : 'index.twig')] = $match[2]; | ||
150 | } | ||
151 | |||
152 | return $templates; | ||
153 | } | ||
154 | } | ||
diff --git a/inc/Twig/Test/Method.php b/inc/Twig/Test/Method.php new file mode 100644 index 00000000..17c6c041 --- /dev/null +++ b/inc/Twig/Test/Method.php | |||
@@ -0,0 +1,37 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 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 | * Represents a method template test. | ||
14 | * | ||
15 | * @author Fabien Potencier <fabien@symfony.com> | ||
16 | * @deprecated since 1.12 (to be removed in 2.0) | ||
17 | */ | ||
18 | class Twig_Test_Method extends Twig_Test | ||
19 | { | ||
20 | protected $extension; | ||
21 | protected $method; | ||
22 | |||
23 | public function __construct(Twig_ExtensionInterface $extension, $method, array $options = array()) | ||
24 | { | ||
25 | $options['callable'] = array($extension, $method); | ||
26 | |||
27 | parent::__construct($options); | ||
28 | |||
29 | $this->extension = $extension; | ||
30 | $this->method = $method; | ||
31 | } | ||
32 | |||
33 | public function compile() | ||
34 | { | ||
35 | return sprintf('$this->env->getExtension(\'%s\')->%s', $this->extension->getName(), $this->method); | ||
36 | } | ||
37 | } | ||
diff --git a/inc/Twig/Test/Node.php b/inc/Twig/Test/Node.php new file mode 100644 index 00000000..c832a57b --- /dev/null +++ b/inc/Twig/Test/Node.php | |||
@@ -0,0 +1,37 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 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 | * Represents a template test as a Node. | ||
14 | * | ||
15 | * @author Fabien Potencier <fabien@symfony.com> | ||
16 | * @deprecated since 1.12 (to be removed in 2.0) | ||
17 | */ | ||
18 | class Twig_Test_Node extends Twig_Test | ||
19 | { | ||
20 | protected $class; | ||
21 | |||
22 | public function __construct($class, array $options = array()) | ||
23 | { | ||
24 | parent::__construct($options); | ||
25 | |||
26 | $this->class = $class; | ||
27 | } | ||
28 | |||
29 | public function getClass() | ||
30 | { | ||
31 | return $this->class; | ||
32 | } | ||
33 | |||
34 | public function compile() | ||
35 | { | ||
36 | } | ||
37 | } | ||
diff --git a/inc/Twig/Test/NodeTestCase.php b/inc/Twig/Test/NodeTestCase.php new file mode 100644 index 00000000..b15c85ff --- /dev/null +++ b/inc/Twig/Test/NodeTestCase.php | |||
@@ -0,0 +1,58 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) Fabien Potencier | ||
7 | * | ||
8 | * For the full copyright and license information, please view the LICENSE | ||
9 | * file that was distributed with this source code. | ||
10 | */ | ||
11 | abstract class Twig_Test_NodeTestCase extends PHPUnit_Framework_TestCase | ||
12 | { | ||
13 | abstract public function getTests(); | ||
14 | |||
15 | /** | ||
16 | * @dataProvider getTests | ||
17 | */ | ||
18 | public function testCompile($node, $source, $environment = null) | ||
19 | { | ||
20 | $this->assertNodeCompilation($source, $node, $environment); | ||
21 | } | ||
22 | |||
23 | public function assertNodeCompilation($source, Twig_Node $node, Twig_Environment $environment = null) | ||
24 | { | ||
25 | $compiler = $this->getCompiler($environment); | ||
26 | $compiler->compile($node); | ||
27 | |||
28 | $this->assertEquals($source, trim($compiler->getSource())); | ||
29 | } | ||
30 | |||
31 | protected function getCompiler(Twig_Environment $environment = null) | ||
32 | { | ||
33 | return new Twig_Compiler(null === $environment ? $this->getEnvironment() : $environment); | ||
34 | } | ||
35 | |||
36 | protected function getEnvironment() | ||
37 | { | ||
38 | return new Twig_Environment(); | ||
39 | } | ||
40 | |||
41 | protected function getVariableGetter($name) | ||
42 | { | ||
43 | if (version_compare(phpversion(), '5.4.0RC1', '>=')) { | ||
44 | return sprintf('(isset($context["%s"]) ? $context["%s"] : null)', $name, $name); | ||
45 | } | ||
46 | |||
47 | return sprintf('$this->getContext($context, "%s")', $name); | ||
48 | } | ||
49 | |||
50 | protected function getAttributeGetter() | ||
51 | { | ||
52 | if (function_exists('twig_template_get_attributes')) { | ||
53 | return 'twig_template_get_attributes($this, '; | ||
54 | } | ||
55 | |||
56 | return '$this->getAttribute('; | ||
57 | } | ||
58 | } | ||
diff --git a/inc/Twig/TestCallableInterface.php b/inc/Twig/TestCallableInterface.php new file mode 100644 index 00000000..0db43682 --- /dev/null +++ b/inc/Twig/TestCallableInterface.php | |||
@@ -0,0 +1,21 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2012 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 | * Represents a callable template test. | ||
14 | * | ||
15 | * @author Fabien Potencier <fabien@symfony.com> | ||
16 | * @deprecated since 1.12 (to be removed in 2.0) | ||
17 | */ | ||
18 | interface Twig_TestCallableInterface | ||
19 | { | ||
20 | public function getCallable(); | ||
21 | } | ||
diff --git a/inc/Twig/TestInterface.php b/inc/Twig/TestInterface.php new file mode 100644 index 00000000..30d8a2c4 --- /dev/null +++ b/inc/Twig/TestInterface.php | |||
@@ -0,0 +1,26 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 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 | * Represents a template test. | ||
14 | * | ||
15 | * @author Fabien Potencier <fabien@symfony.com> | ||
16 | * @deprecated since 1.12 (to be removed in 2.0) | ||
17 | */ | ||
18 | interface Twig_TestInterface | ||
19 | { | ||
20 | /** | ||
21 | * Compiles a test. | ||
22 | * | ||
23 | * @return string The PHP code for the test | ||
24 | */ | ||
25 | public function compile(); | ||
26 | } | ||
diff --git a/inc/Twig/Token.php b/inc/Twig/Token.php new file mode 100644 index 00000000..bbca90db --- /dev/null +++ b/inc/Twig/Token.php | |||
@@ -0,0 +1,218 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2009 Fabien Potencier | ||
7 | * (c) 2009 Armin Ronacher | ||
8 | * | ||
9 | * For the full copyright and license information, please view the LICENSE | ||
10 | * file that was distributed with this source code. | ||
11 | */ | ||
12 | |||
13 | /** | ||
14 | * Represents a Token. | ||
15 | * | ||
16 | * @author Fabien Potencier <fabien@symfony.com> | ||
17 | */ | ||
18 | class Twig_Token | ||
19 | { | ||
20 | protected $value; | ||
21 | protected $type; | ||
22 | protected $lineno; | ||
23 | |||
24 | const EOF_TYPE = -1; | ||
25 | const TEXT_TYPE = 0; | ||
26 | const BLOCK_START_TYPE = 1; | ||
27 | const VAR_START_TYPE = 2; | ||
28 | const BLOCK_END_TYPE = 3; | ||
29 | const VAR_END_TYPE = 4; | ||
30 | const NAME_TYPE = 5; | ||
31 | const NUMBER_TYPE = 6; | ||
32 | const STRING_TYPE = 7; | ||
33 | const OPERATOR_TYPE = 8; | ||
34 | const PUNCTUATION_TYPE = 9; | ||
35 | const INTERPOLATION_START_TYPE = 10; | ||
36 | const INTERPOLATION_END_TYPE = 11; | ||
37 | |||
38 | /** | ||
39 | * Constructor. | ||
40 | * | ||
41 | * @param integer $type The type of the token | ||
42 | * @param string $value The token value | ||
43 | * @param integer $lineno The line position in the source | ||
44 | */ | ||
45 | public function __construct($type, $value, $lineno) | ||
46 | { | ||
47 | $this->type = $type; | ||
48 | $this->value = $value; | ||
49 | $this->lineno = $lineno; | ||
50 | } | ||
51 | |||
52 | /** | ||
53 | * Returns a string representation of the token. | ||
54 | * | ||
55 | * @return string A string representation of the token | ||
56 | */ | ||
57 | public function __toString() | ||
58 | { | ||
59 | return sprintf('%s(%s)', self::typeToString($this->type, true, $this->lineno), $this->value); | ||
60 | } | ||
61 | |||
62 | /** | ||
63 | * Tests the current token for a type and/or a value. | ||
64 | * | ||
65 | * Parameters may be: | ||
66 | * * just type | ||
67 | * * type and value (or array of possible values) | ||
68 | * * just value (or array of possible values) (NAME_TYPE is used as type) | ||
69 | * | ||
70 | * @param array|integer $type The type to test | ||
71 | * @param array|string|null $values The token value | ||
72 | * | ||
73 | * @return Boolean | ||
74 | */ | ||
75 | public function test($type, $values = null) | ||
76 | { | ||
77 | if (null === $values && !is_int($type)) { | ||
78 | $values = $type; | ||
79 | $type = self::NAME_TYPE; | ||
80 | } | ||
81 | |||
82 | return ($this->type === $type) && ( | ||
83 | null === $values || | ||
84 | (is_array($values) && in_array($this->value, $values)) || | ||
85 | $this->value == $values | ||
86 | ); | ||
87 | } | ||
88 | |||
89 | /** | ||
90 | * Gets the line. | ||
91 | * | ||
92 | * @return integer The source line | ||
93 | */ | ||
94 | public function getLine() | ||
95 | { | ||
96 | return $this->lineno; | ||
97 | } | ||
98 | |||
99 | /** | ||
100 | * Gets the token type. | ||
101 | * | ||
102 | * @return integer The token type | ||
103 | */ | ||
104 | public function getType() | ||
105 | { | ||
106 | return $this->type; | ||
107 | } | ||
108 | |||
109 | /** | ||
110 | * Gets the token value. | ||
111 | * | ||
112 | * @return string The token value | ||
113 | */ | ||
114 | public function getValue() | ||
115 | { | ||
116 | return $this->value; | ||
117 | } | ||
118 | |||
119 | /** | ||
120 | * Returns the constant representation (internal) of a given type. | ||
121 | * | ||
122 | * @param integer $type The type as an integer | ||
123 | * @param Boolean $short Whether to return a short representation or not | ||
124 | * @param integer $line The code line | ||
125 | * | ||
126 | * @return string The string representation | ||
127 | */ | ||
128 | public static function typeToString($type, $short = false, $line = -1) | ||
129 | { | ||
130 | switch ($type) { | ||
131 | case self::EOF_TYPE: | ||
132 | $name = 'EOF_TYPE'; | ||
133 | break; | ||
134 | case self::TEXT_TYPE: | ||
135 | $name = 'TEXT_TYPE'; | ||
136 | break; | ||
137 | case self::BLOCK_START_TYPE: | ||
138 | $name = 'BLOCK_START_TYPE'; | ||
139 | break; | ||
140 | case self::VAR_START_TYPE: | ||
141 | $name = 'VAR_START_TYPE'; | ||
142 | break; | ||
143 | case self::BLOCK_END_TYPE: | ||
144 | $name = 'BLOCK_END_TYPE'; | ||
145 | break; | ||
146 | case self::VAR_END_TYPE: | ||
147 | $name = 'VAR_END_TYPE'; | ||
148 | break; | ||
149 | case self::NAME_TYPE: | ||
150 | $name = 'NAME_TYPE'; | ||
151 | break; | ||
152 | case self::NUMBER_TYPE: | ||
153 | $name = 'NUMBER_TYPE'; | ||
154 | break; | ||
155 | case self::STRING_TYPE: | ||
156 | $name = 'STRING_TYPE'; | ||
157 | break; | ||
158 | case self::OPERATOR_TYPE: | ||
159 | $name = 'OPERATOR_TYPE'; | ||
160 | break; | ||
161 | case self::PUNCTUATION_TYPE: | ||
162 | $name = 'PUNCTUATION_TYPE'; | ||
163 | break; | ||
164 | case self::INTERPOLATION_START_TYPE: | ||
165 | $name = 'INTERPOLATION_START_TYPE'; | ||
166 | break; | ||
167 | case self::INTERPOLATION_END_TYPE: | ||
168 | $name = 'INTERPOLATION_END_TYPE'; | ||
169 | break; | ||
170 | default: | ||
171 | throw new LogicException(sprintf('Token of type "%s" does not exist.', $type)); | ||
172 | } | ||
173 | |||
174 | return $short ? $name : 'Twig_Token::'.$name; | ||
175 | } | ||
176 | |||
177 | /** | ||
178 | * Returns the english representation of a given type. | ||
179 | * | ||
180 | * @param integer $type The type as an integer | ||
181 | * @param integer $line The code line | ||
182 | * | ||
183 | * @return string The string representation | ||
184 | */ | ||
185 | public static function typeToEnglish($type, $line = -1) | ||
186 | { | ||
187 | switch ($type) { | ||
188 | case self::EOF_TYPE: | ||
189 | return 'end of template'; | ||
190 | case self::TEXT_TYPE: | ||
191 | return 'text'; | ||
192 | case self::BLOCK_START_TYPE: | ||
193 | return 'begin of statement block'; | ||
194 | case self::VAR_START_TYPE: | ||
195 | return 'begin of print statement'; | ||
196 | case self::BLOCK_END_TYPE: | ||
197 | return 'end of statement block'; | ||
198 | case self::VAR_END_TYPE: | ||
199 | return 'end of print statement'; | ||
200 | case self::NAME_TYPE: | ||
201 | return 'name'; | ||
202 | case self::NUMBER_TYPE: | ||
203 | return 'number'; | ||
204 | case self::STRING_TYPE: | ||
205 | return 'string'; | ||
206 | case self::OPERATOR_TYPE: | ||
207 | return 'operator'; | ||
208 | case self::PUNCTUATION_TYPE: | ||
209 | return 'punctuation'; | ||
210 | case self::INTERPOLATION_START_TYPE: | ||
211 | return 'begin of string interpolation'; | ||
212 | case self::INTERPOLATION_END_TYPE: | ||
213 | return 'end of string interpolation'; | ||
214 | default: | ||
215 | throw new LogicException(sprintf('Token of type "%s" does not exist.', $type)); | ||
216 | } | ||
217 | } | ||
218 | } | ||
diff --git a/inc/Twig/TokenParser.php b/inc/Twig/TokenParser.php new file mode 100644 index 00000000..decebd5e --- /dev/null +++ b/inc/Twig/TokenParser.php | |||
@@ -0,0 +1,33 @@ | |||
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 | * Base class for all token parsers. | ||
14 | * | ||
15 | * @author Fabien Potencier <fabien@symfony.com> | ||
16 | */ | ||
17 | abstract class Twig_TokenParser implements Twig_TokenParserInterface | ||
18 | { | ||
19 | /** | ||
20 | * @var Twig_Parser | ||
21 | */ | ||
22 | protected $parser; | ||
23 | |||
24 | /** | ||
25 | * Sets the parser associated with this token parser | ||
26 | * | ||
27 | * @param $parser A Twig_Parser instance | ||
28 | */ | ||
29 | public function setParser(Twig_Parser $parser) | ||
30 | { | ||
31 | $this->parser = $parser; | ||
32 | } | ||
33 | } | ||
diff --git a/inc/Twig/TokenParser/AutoEscape.php b/inc/Twig/TokenParser/AutoEscape.php new file mode 100644 index 00000000..27560288 --- /dev/null +++ b/inc/Twig/TokenParser/AutoEscape.php | |||
@@ -0,0 +1,89 @@ | |||
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 | * Marks a section of a template to be escaped or not. | ||
14 | * | ||
15 | * <pre> | ||
16 | * {% autoescape true %} | ||
17 | * Everything will be automatically escaped in this block | ||
18 | * {% endautoescape %} | ||
19 | * | ||
20 | * {% autoescape false %} | ||
21 | * Everything will be outputed as is in this block | ||
22 | * {% endautoescape %} | ||
23 | * | ||
24 | * {% autoescape true js %} | ||
25 | * Everything will be automatically escaped in this block | ||
26 | * using the js escaping strategy | ||
27 | * {% endautoescape %} | ||
28 | * </pre> | ||
29 | */ | ||
30 | class Twig_TokenParser_AutoEscape extends Twig_TokenParser | ||
31 | { | ||
32 | /** | ||
33 | * Parses a token and returns a node. | ||
34 | * | ||
35 | * @param Twig_Token $token A Twig_Token instance | ||
36 | * | ||
37 | * @return Twig_NodeInterface A Twig_NodeInterface instance | ||
38 | */ | ||
39 | public function parse(Twig_Token $token) | ||
40 | { | ||
41 | $lineno = $token->getLine(); | ||
42 | $stream = $this->parser->getStream(); | ||
43 | |||
44 | if ($stream->test(Twig_Token::BLOCK_END_TYPE)) { | ||
45 | $value = 'html'; | ||
46 | } else { | ||
47 | $expr = $this->parser->getExpressionParser()->parseExpression(); | ||
48 | if (!$expr instanceof Twig_Node_Expression_Constant) { | ||
49 | throw new Twig_Error_Syntax('An escaping strategy must be a string or a Boolean.', $stream->getCurrent()->getLine(), $stream->getFilename()); | ||
50 | } | ||
51 | $value = $expr->getAttribute('value'); | ||
52 | |||
53 | $compat = true === $value || false === $value; | ||
54 | |||
55 | if (true === $value) { | ||
56 | $value = 'html'; | ||
57 | } | ||
58 | |||
59 | if ($compat && $stream->test(Twig_Token::NAME_TYPE)) { | ||
60 | if (false === $value) { | ||
61 | throw new Twig_Error_Syntax('Unexpected escaping strategy as you set autoescaping to false.', $stream->getCurrent()->getLine(), $stream->getFilename()); | ||
62 | } | ||
63 | |||
64 | $value = $stream->next()->getValue(); | ||
65 | } | ||
66 | } | ||
67 | |||
68 | $stream->expect(Twig_Token::BLOCK_END_TYPE); | ||
69 | $body = $this->parser->subparse(array($this, 'decideBlockEnd'), true); | ||
70 | $stream->expect(Twig_Token::BLOCK_END_TYPE); | ||
71 | |||
72 | return new Twig_Node_AutoEscape($value, $body, $lineno, $this->getTag()); | ||
73 | } | ||
74 | |||
75 | public function decideBlockEnd(Twig_Token $token) | ||
76 | { | ||
77 | return $token->test('endautoescape'); | ||
78 | } | ||
79 | |||
80 | /** | ||
81 | * Gets the tag name associated with this token parser. | ||
82 | * | ||
83 | * @return string The tag name | ||
84 | */ | ||
85 | public function getTag() | ||
86 | { | ||
87 | return 'autoescape'; | ||
88 | } | ||
89 | } | ||
diff --git a/inc/Twig/TokenParser/Block.php b/inc/Twig/TokenParser/Block.php new file mode 100644 index 00000000..a2e017f3 --- /dev/null +++ b/inc/Twig/TokenParser/Block.php | |||
@@ -0,0 +1,83 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2009 Fabien Potencier | ||
7 | * (c) 2009 Armin Ronacher | ||
8 | * | ||
9 | * For the full copyright and license information, please view the LICENSE | ||
10 | * file that was distributed with this source code. | ||
11 | */ | ||
12 | |||
13 | /** | ||
14 | * Marks a section of a template as being reusable. | ||
15 | * | ||
16 | * <pre> | ||
17 | * {% block head %} | ||
18 | * <link rel="stylesheet" href="style.css" /> | ||
19 | * <title>{% block title %}{% endblock %} - My Webpage</title> | ||
20 | * {% endblock %} | ||
21 | * </pre> | ||
22 | */ | ||
23 | class Twig_TokenParser_Block extends Twig_TokenParser | ||
24 | { | ||
25 | /** | ||
26 | * Parses a token and returns a node. | ||
27 | * | ||
28 | * @param Twig_Token $token A Twig_Token instance | ||
29 | * | ||
30 | * @return Twig_NodeInterface A Twig_NodeInterface instance | ||
31 | */ | ||
32 | public function parse(Twig_Token $token) | ||
33 | { | ||
34 | $lineno = $token->getLine(); | ||
35 | $stream = $this->parser->getStream(); | ||
36 | $name = $stream->expect(Twig_Token::NAME_TYPE)->getValue(); | ||
37 | if ($this->parser->hasBlock($name)) { | ||
38 | throw new Twig_Error_Syntax(sprintf("The block '$name' has already been defined line %d", $this->parser->getBlock($name)->getLine()), $stream->getCurrent()->getLine(), $stream->getFilename()); | ||
39 | } | ||
40 | $this->parser->setBlock($name, $block = new Twig_Node_Block($name, new Twig_Node(array()), $lineno)); | ||
41 | $this->parser->pushLocalScope(); | ||
42 | $this->parser->pushBlockStack($name); | ||
43 | |||
44 | if ($stream->test(Twig_Token::BLOCK_END_TYPE)) { | ||
45 | $stream->next(); | ||
46 | |||
47 | $body = $this->parser->subparse(array($this, 'decideBlockEnd'), true); | ||
48 | if ($stream->test(Twig_Token::NAME_TYPE)) { | ||
49 | $value = $stream->next()->getValue(); | ||
50 | |||
51 | if ($value != $name) { | ||
52 | throw new Twig_Error_Syntax(sprintf("Expected endblock for block '$name' (but %s given)", $value), $stream->getCurrent()->getLine(), $stream->getFilename()); | ||
53 | } | ||
54 | } | ||
55 | } else { | ||
56 | $body = new Twig_Node(array( | ||
57 | new Twig_Node_Print($this->parser->getExpressionParser()->parseExpression(), $lineno), | ||
58 | )); | ||
59 | } | ||
60 | $stream->expect(Twig_Token::BLOCK_END_TYPE); | ||
61 | |||
62 | $block->setNode('body', $body); | ||
63 | $this->parser->popBlockStack(); | ||
64 | $this->parser->popLocalScope(); | ||
65 | |||
66 | return new Twig_Node_BlockReference($name, $lineno, $this->getTag()); | ||
67 | } | ||
68 | |||
69 | public function decideBlockEnd(Twig_Token $token) | ||
70 | { | ||
71 | return $token->test('endblock'); | ||
72 | } | ||
73 | |||
74 | /** | ||
75 | * Gets the tag name associated with this token parser. | ||
76 | * | ||
77 | * @return string The tag name | ||
78 | */ | ||
79 | public function getTag() | ||
80 | { | ||
81 | return 'block'; | ||
82 | } | ||
83 | } | ||
diff --git a/inc/Twig/TokenParser/Do.php b/inc/Twig/TokenParser/Do.php new file mode 100644 index 00000000..f50939dd --- /dev/null +++ b/inc/Twig/TokenParser/Do.php | |||
@@ -0,0 +1,42 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2011 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 | * Evaluates an expression, discarding the returned value. | ||
14 | */ | ||
15 | class Twig_TokenParser_Do extends Twig_TokenParser | ||
16 | { | ||
17 | /** | ||
18 | * Parses a token and returns a node. | ||
19 | * | ||
20 | * @param Twig_Token $token A Twig_Token instance | ||
21 | * | ||
22 | * @return Twig_NodeInterface A Twig_NodeInterface instance | ||
23 | */ | ||
24 | public function parse(Twig_Token $token) | ||
25 | { | ||
26 | $expr = $this->parser->getExpressionParser()->parseExpression(); | ||
27 | |||
28 | $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE); | ||
29 | |||
30 | return new Twig_Node_Do($expr, $token->getLine(), $this->getTag()); | ||
31 | } | ||
32 | |||
33 | /** | ||
34 | * Gets the tag name associated with this token parser. | ||
35 | * | ||
36 | * @return string The tag name | ||
37 | */ | ||
38 | public function getTag() | ||
39 | { | ||
40 | return 'do'; | ||
41 | } | ||
42 | } | ||
diff --git a/inc/Twig/TokenParser/Embed.php b/inc/Twig/TokenParser/Embed.php new file mode 100644 index 00000000..69cb5f35 --- /dev/null +++ b/inc/Twig/TokenParser/Embed.php | |||
@@ -0,0 +1,66 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2012 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 | * Embeds a template. | ||
14 | */ | ||
15 | class Twig_TokenParser_Embed extends Twig_TokenParser_Include | ||
16 | { | ||
17 | /** | ||
18 | * Parses a token and returns a node. | ||
19 | * | ||
20 | * @param Twig_Token $token A Twig_Token instance | ||
21 | * | ||
22 | * @return Twig_NodeInterface A Twig_NodeInterface instance | ||
23 | */ | ||
24 | public function parse(Twig_Token $token) | ||
25 | { | ||
26 | $stream = $this->parser->getStream(); | ||
27 | |||
28 | $parent = $this->parser->getExpressionParser()->parseExpression(); | ||
29 | |||
30 | list($variables, $only, $ignoreMissing) = $this->parseArguments(); | ||
31 | |||
32 | // inject a fake parent to make the parent() function work | ||
33 | $stream->injectTokens(array( | ||
34 | new Twig_Token(Twig_Token::BLOCK_START_TYPE, '', $token->getLine()), | ||
35 | new Twig_Token(Twig_Token::NAME_TYPE, 'extends', $token->getLine()), | ||
36 | new Twig_Token(Twig_Token::STRING_TYPE, '__parent__', $token->getLine()), | ||
37 | new Twig_Token(Twig_Token::BLOCK_END_TYPE, '', $token->getLine()), | ||
38 | )); | ||
39 | |||
40 | $module = $this->parser->parse($stream, array($this, 'decideBlockEnd'), true); | ||
41 | |||
42 | // override the parent with the correct one | ||
43 | $module->setNode('parent', $parent); | ||
44 | |||
45 | $this->parser->embedTemplate($module); | ||
46 | |||
47 | $stream->expect(Twig_Token::BLOCK_END_TYPE); | ||
48 | |||
49 | return new Twig_Node_Embed($module->getAttribute('filename'), $module->getAttribute('index'), $variables, $only, $ignoreMissing, $token->getLine(), $this->getTag()); | ||
50 | } | ||
51 | |||
52 | public function decideBlockEnd(Twig_Token $token) | ||
53 | { | ||
54 | return $token->test('endembed'); | ||
55 | } | ||
56 | |||
57 | /** | ||
58 | * Gets the tag name associated with this token parser. | ||
59 | * | ||
60 | * @return string The tag name | ||
61 | */ | ||
62 | public function getTag() | ||
63 | { | ||
64 | return 'embed'; | ||
65 | } | ||
66 | } | ||
diff --git a/inc/Twig/TokenParser/Extends.php b/inc/Twig/TokenParser/Extends.php new file mode 100644 index 00000000..f5ecee21 --- /dev/null +++ b/inc/Twig/TokenParser/Extends.php | |||
@@ -0,0 +1,52 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2009 Fabien Potencier | ||
7 | * (c) 2009 Armin Ronacher | ||
8 | * | ||
9 | * For the full copyright and license information, please view the LICENSE | ||
10 | * file that was distributed with this source code. | ||
11 | */ | ||
12 | |||
13 | /** | ||
14 | * Extends a template by another one. | ||
15 | * | ||
16 | * <pre> | ||
17 | * {% extends "base.html" %} | ||
18 | * </pre> | ||
19 | */ | ||
20 | class Twig_TokenParser_Extends extends Twig_TokenParser | ||
21 | { | ||
22 | /** | ||
23 | * Parses a token and returns a node. | ||
24 | * | ||
25 | * @param Twig_Token $token A Twig_Token instance | ||
26 | * | ||
27 | * @return Twig_NodeInterface A Twig_NodeInterface instance | ||
28 | */ | ||
29 | public function parse(Twig_Token $token) | ||
30 | { | ||
31 | if (!$this->parser->isMainScope()) { | ||
32 | throw new Twig_Error_Syntax('Cannot extend from a block', $token->getLine(), $this->parser->getFilename()); | ||
33 | } | ||
34 | |||
35 | if (null !== $this->parser->getParent()) { | ||
36 | throw new Twig_Error_Syntax('Multiple extends tags are forbidden', $token->getLine(), $this->parser->getFilename()); | ||
37 | } | ||
38 | $this->parser->setParent($this->parser->getExpressionParser()->parseExpression()); | ||
39 | |||
40 | $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE); | ||
41 | } | ||
42 | |||
43 | /** | ||
44 | * Gets the tag name associated with this token parser. | ||
45 | * | ||
46 | * @return string The tag name | ||
47 | */ | ||
48 | public function getTag() | ||
49 | { | ||
50 | return 'extends'; | ||
51 | } | ||
52 | } | ||
diff --git a/inc/Twig/TokenParser/Filter.php b/inc/Twig/TokenParser/Filter.php new file mode 100644 index 00000000..2b97475a --- /dev/null +++ b/inc/Twig/TokenParser/Filter.php | |||
@@ -0,0 +1,61 @@ | |||
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 | * Filters a section of a template by applying filters. | ||
14 | * | ||
15 | * <pre> | ||
16 | * {% filter upper %} | ||
17 | * This text becomes uppercase | ||
18 | * {% endfilter %} | ||
19 | * </pre> | ||
20 | */ | ||
21 | class Twig_TokenParser_Filter extends Twig_TokenParser | ||
22 | { | ||
23 | /** | ||
24 | * Parses a token and returns a node. | ||
25 | * | ||
26 | * @param Twig_Token $token A Twig_Token instance | ||
27 | * | ||
28 | * @return Twig_NodeInterface A Twig_NodeInterface instance | ||
29 | */ | ||
30 | public function parse(Twig_Token $token) | ||
31 | { | ||
32 | $name = $this->parser->getVarName(); | ||
33 | $ref = new Twig_Node_Expression_BlockReference(new Twig_Node_Expression_Constant($name, $token->getLine()), true, $token->getLine(), $this->getTag()); | ||
34 | |||
35 | $filter = $this->parser->getExpressionParser()->parseFilterExpressionRaw($ref, $this->getTag()); | ||
36 | $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE); | ||
37 | |||
38 | $body = $this->parser->subparse(array($this, 'decideBlockEnd'), true); | ||
39 | $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE); | ||
40 | |||
41 | $block = new Twig_Node_Block($name, $body, $token->getLine()); | ||
42 | $this->parser->setBlock($name, $block); | ||
43 | |||
44 | return new Twig_Node_Print($filter, $token->getLine(), $this->getTag()); | ||
45 | } | ||
46 | |||
47 | public function decideBlockEnd(Twig_Token $token) | ||
48 | { | ||
49 | return $token->test('endfilter'); | ||
50 | } | ||
51 | |||
52 | /** | ||
53 | * Gets the tag name associated with this token parser. | ||
54 | * | ||
55 | * @return string The tag name | ||
56 | */ | ||
57 | public function getTag() | ||
58 | { | ||
59 | return 'filter'; | ||
60 | } | ||
61 | } | ||
diff --git a/inc/Twig/TokenParser/Flush.php b/inc/Twig/TokenParser/Flush.php new file mode 100644 index 00000000..4e15e785 --- /dev/null +++ b/inc/Twig/TokenParser/Flush.php | |||
@@ -0,0 +1,42 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2011 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 | * Flushes the output to the client. | ||
14 | * | ||
15 | * @see flush() | ||
16 | */ | ||
17 | class Twig_TokenParser_Flush extends Twig_TokenParser | ||
18 | { | ||
19 | /** | ||
20 | * Parses a token and returns a node. | ||
21 | * | ||
22 | * @param Twig_Token $token A Twig_Token instance | ||
23 | * | ||
24 | * @return Twig_NodeInterface A Twig_NodeInterface instance | ||
25 | */ | ||
26 | public function parse(Twig_Token $token) | ||
27 | { | ||
28 | $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE); | ||
29 | |||
30 | return new Twig_Node_Flush($token->getLine(), $this->getTag()); | ||
31 | } | ||
32 | |||
33 | /** | ||
34 | * Gets the tag name associated with this token parser. | ||
35 | * | ||
36 | * @return string The tag name | ||
37 | */ | ||
38 | public function getTag() | ||
39 | { | ||
40 | return 'flush'; | ||
41 | } | ||
42 | } | ||
diff --git a/inc/Twig/TokenParser/For.php b/inc/Twig/TokenParser/For.php new file mode 100644 index 00000000..98a6d079 --- /dev/null +++ b/inc/Twig/TokenParser/For.php | |||
@@ -0,0 +1,136 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2009 Fabien Potencier | ||
7 | * (c) 2009 Armin Ronacher | ||
8 | * | ||
9 | * For the full copyright and license information, please view the LICENSE | ||
10 | * file that was distributed with this source code. | ||
11 | */ | ||
12 | |||
13 | /** | ||
14 | * Loops over each item of a sequence. | ||
15 | * | ||
16 | * <pre> | ||
17 | * <ul> | ||
18 | * {% for user in users %} | ||
19 | * <li>{{ user.username|e }}</li> | ||
20 | * {% endfor %} | ||
21 | * </ul> | ||
22 | * </pre> | ||
23 | */ | ||
24 | class Twig_TokenParser_For extends Twig_TokenParser | ||
25 | { | ||
26 | /** | ||
27 | * Parses a token and returns a node. | ||
28 | * | ||
29 | * @param Twig_Token $token A Twig_Token instance | ||
30 | * | ||
31 | * @return Twig_NodeInterface A Twig_NodeInterface instance | ||
32 | */ | ||
33 | public function parse(Twig_Token $token) | ||
34 | { | ||
35 | $lineno = $token->getLine(); | ||
36 | $stream = $this->parser->getStream(); | ||
37 | $targets = $this->parser->getExpressionParser()->parseAssignmentExpression(); | ||
38 | $stream->expect(Twig_Token::OPERATOR_TYPE, 'in'); | ||
39 | $seq = $this->parser->getExpressionParser()->parseExpression(); | ||
40 | |||
41 | $ifexpr = null; | ||
42 | if ($stream->test(Twig_Token::NAME_TYPE, 'if')) { | ||
43 | $stream->next(); | ||
44 | $ifexpr = $this->parser->getExpressionParser()->parseExpression(); | ||
45 | } | ||
46 | |||
47 | $stream->expect(Twig_Token::BLOCK_END_TYPE); | ||
48 | $body = $this->parser->subparse(array($this, 'decideForFork')); | ||
49 | if ($stream->next()->getValue() == 'else') { | ||
50 | $stream->expect(Twig_Token::BLOCK_END_TYPE); | ||
51 | $else = $this->parser->subparse(array($this, 'decideForEnd'), true); | ||
52 | } else { | ||
53 | $else = null; | ||
54 | } | ||
55 | $stream->expect(Twig_Token::BLOCK_END_TYPE); | ||
56 | |||
57 | if (count($targets) > 1) { | ||
58 | $keyTarget = $targets->getNode(0); | ||
59 | $keyTarget = new Twig_Node_Expression_AssignName($keyTarget->getAttribute('name'), $keyTarget->getLine()); | ||
60 | $valueTarget = $targets->getNode(1); | ||
61 | $valueTarget = new Twig_Node_Expression_AssignName($valueTarget->getAttribute('name'), $valueTarget->getLine()); | ||
62 | } else { | ||
63 | $keyTarget = new Twig_Node_Expression_AssignName('_key', $lineno); | ||
64 | $valueTarget = $targets->getNode(0); | ||
65 | $valueTarget = new Twig_Node_Expression_AssignName($valueTarget->getAttribute('name'), $valueTarget->getLine()); | ||
66 | } | ||
67 | |||
68 | if ($ifexpr) { | ||
69 | $this->checkLoopUsageCondition($stream, $ifexpr); | ||
70 | $this->checkLoopUsageBody($stream, $body); | ||
71 | } | ||
72 | |||
73 | return new Twig_Node_For($keyTarget, $valueTarget, $seq, $ifexpr, $body, $else, $lineno, $this->getTag()); | ||
74 | } | ||
75 | |||
76 | public function decideForFork(Twig_Token $token) | ||
77 | { | ||
78 | return $token->test(array('else', 'endfor')); | ||
79 | } | ||
80 | |||
81 | public function decideForEnd(Twig_Token $token) | ||
82 | { | ||
83 | return $token->test('endfor'); | ||
84 | } | ||
85 | |||
86 | // the loop variable cannot be used in the condition | ||
87 | protected function checkLoopUsageCondition(Twig_TokenStream $stream, Twig_NodeInterface $node) | ||
88 | { | ||
89 | if ($node instanceof Twig_Node_Expression_GetAttr && $node->getNode('node') instanceof Twig_Node_Expression_Name && 'loop' == $node->getNode('node')->getAttribute('name')) { | ||
90 | throw new Twig_Error_Syntax('The "loop" variable cannot be used in a looping condition', $node->getLine(), $stream->getFilename()); | ||
91 | } | ||
92 | |||
93 | foreach ($node as $n) { | ||
94 | if (!$n) { | ||
95 | continue; | ||
96 | } | ||
97 | |||
98 | $this->checkLoopUsageCondition($stream, $n); | ||
99 | } | ||
100 | } | ||
101 | |||
102 | // check usage of non-defined loop-items | ||
103 | // it does not catch all problems (for instance when a for is included into another or when the variable is used in an include) | ||
104 | protected function checkLoopUsageBody(Twig_TokenStream $stream, Twig_NodeInterface $node) | ||
105 | { | ||
106 | if ($node instanceof Twig_Node_Expression_GetAttr && $node->getNode('node') instanceof Twig_Node_Expression_Name && 'loop' == $node->getNode('node')->getAttribute('name')) { | ||
107 | $attribute = $node->getNode('attribute'); | ||
108 | if ($attribute instanceof Twig_Node_Expression_Constant && in_array($attribute->getAttribute('value'), array('length', 'revindex0', 'revindex', 'last'))) { | ||
109 | throw new Twig_Error_Syntax(sprintf('The "loop.%s" variable is not defined when looping with a condition', $attribute->getAttribute('value')), $node->getLine(), $stream->getFilename()); | ||
110 | } | ||
111 | } | ||
112 | |||
113 | // should check for parent.loop.XXX usage | ||
114 | if ($node instanceof Twig_Node_For) { | ||
115 | return; | ||
116 | } | ||
117 | |||
118 | foreach ($node as $n) { | ||
119 | if (!$n) { | ||
120 | continue; | ||
121 | } | ||
122 | |||
123 | $this->checkLoopUsageBody($stream, $n); | ||
124 | } | ||
125 | } | ||
126 | |||
127 | /** | ||
128 | * Gets the tag name associated with this token parser. | ||
129 | * | ||
130 | * @return string The tag name | ||
131 | */ | ||
132 | public function getTag() | ||
133 | { | ||
134 | return 'for'; | ||
135 | } | ||
136 | } | ||
diff --git a/inc/Twig/TokenParser/From.php b/inc/Twig/TokenParser/From.php new file mode 100644 index 00000000..a54054db --- /dev/null +++ b/inc/Twig/TokenParser/From.php | |||
@@ -0,0 +1,74 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 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 | * Imports macros. | ||
14 | * | ||
15 | * <pre> | ||
16 | * {% from 'forms.html' import forms %} | ||
17 | * </pre> | ||
18 | */ | ||
19 | class Twig_TokenParser_From extends Twig_TokenParser | ||
20 | { | ||
21 | /** | ||
22 | * Parses a token and returns a node. | ||
23 | * | ||
24 | * @param Twig_Token $token A Twig_Token instance | ||
25 | * | ||
26 | * @return Twig_NodeInterface A Twig_NodeInterface instance | ||
27 | */ | ||
28 | public function parse(Twig_Token $token) | ||
29 | { | ||
30 | $macro = $this->parser->getExpressionParser()->parseExpression(); | ||
31 | $stream = $this->parser->getStream(); | ||
32 | $stream->expect('import'); | ||
33 | |||
34 | $targets = array(); | ||
35 | do { | ||
36 | $name = $stream->expect(Twig_Token::NAME_TYPE)->getValue(); | ||
37 | |||
38 | $alias = $name; | ||
39 | if ($stream->test('as')) { | ||
40 | $stream->next(); | ||
41 | |||
42 | $alias = $stream->expect(Twig_Token::NAME_TYPE)->getValue(); | ||
43 | } | ||
44 | |||
45 | $targets[$name] = $alias; | ||
46 | |||
47 | if (!$stream->test(Twig_Token::PUNCTUATION_TYPE, ',')) { | ||
48 | break; | ||
49 | } | ||
50 | |||
51 | $stream->next(); | ||
52 | } while (true); | ||
53 | |||
54 | $stream->expect(Twig_Token::BLOCK_END_TYPE); | ||
55 | |||
56 | $node = new Twig_Node_Import($macro, new Twig_Node_Expression_AssignName($this->parser->getVarName(), $token->getLine()), $token->getLine(), $this->getTag()); | ||
57 | |||
58 | foreach ($targets as $name => $alias) { | ||
59 | $this->parser->addImportedSymbol('function', $alias, 'get'.$name, $node->getNode('var')); | ||
60 | } | ||
61 | |||
62 | return $node; | ||
63 | } | ||
64 | |||
65 | /** | ||
66 | * Gets the tag name associated with this token parser. | ||
67 | * | ||
68 | * @return string The tag name | ||
69 | */ | ||
70 | public function getTag() | ||
71 | { | ||
72 | return 'from'; | ||
73 | } | ||
74 | } | ||
diff --git a/inc/Twig/TokenParser/If.php b/inc/Twig/TokenParser/If.php new file mode 100644 index 00000000..3d7d1f51 --- /dev/null +++ b/inc/Twig/TokenParser/If.php | |||
@@ -0,0 +1,94 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2009 Fabien Potencier | ||
7 | * (c) 2009 Armin Ronacher | ||
8 | * | ||
9 | * For the full copyright and license information, please view the LICENSE | ||
10 | * file that was distributed with this source code. | ||
11 | */ | ||
12 | |||
13 | /** | ||
14 | * Tests a condition. | ||
15 | * | ||
16 | * <pre> | ||
17 | * {% if users %} | ||
18 | * <ul> | ||
19 | * {% for user in users %} | ||
20 | * <li>{{ user.username|e }}</li> | ||
21 | * {% endfor %} | ||
22 | * </ul> | ||
23 | * {% endif %} | ||
24 | * </pre> | ||
25 | */ | ||
26 | class Twig_TokenParser_If extends Twig_TokenParser | ||
27 | { | ||
28 | /** | ||
29 | * Parses a token and returns a node. | ||
30 | * | ||
31 | * @param Twig_Token $token A Twig_Token instance | ||
32 | * | ||
33 | * @return Twig_NodeInterface A Twig_NodeInterface instance | ||
34 | */ | ||
35 | public function parse(Twig_Token $token) | ||
36 | { | ||
37 | $lineno = $token->getLine(); | ||
38 | $expr = $this->parser->getExpressionParser()->parseExpression(); | ||
39 | $stream = $this->parser->getStream(); | ||
40 | $stream->expect(Twig_Token::BLOCK_END_TYPE); | ||
41 | $body = $this->parser->subparse(array($this, 'decideIfFork')); | ||
42 | $tests = array($expr, $body); | ||
43 | $else = null; | ||
44 | |||
45 | $end = false; | ||
46 | while (!$end) { | ||
47 | switch ($stream->next()->getValue()) { | ||
48 | case 'else': | ||
49 | $stream->expect(Twig_Token::BLOCK_END_TYPE); | ||
50 | $else = $this->parser->subparse(array($this, 'decideIfEnd')); | ||
51 | break; | ||
52 | |||
53 | case 'elseif': | ||
54 | $expr = $this->parser->getExpressionParser()->parseExpression(); | ||
55 | $stream->expect(Twig_Token::BLOCK_END_TYPE); | ||
56 | $body = $this->parser->subparse(array($this, 'decideIfFork')); | ||
57 | $tests[] = $expr; | ||
58 | $tests[] = $body; | ||
59 | break; | ||
60 | |||
61 | case 'endif': | ||
62 | $end = true; | ||
63 | break; | ||
64 | |||
65 | default: | ||
66 | throw new Twig_Error_Syntax(sprintf('Unexpected end of template. Twig was looking for the following tags "else", "elseif", or "endif" to close the "if" block started at line %d)', $lineno), $stream->getCurrent()->getLine(), $stream->getFilename()); | ||
67 | } | ||
68 | } | ||
69 | |||
70 | $stream->expect(Twig_Token::BLOCK_END_TYPE); | ||
71 | |||
72 | return new Twig_Node_If(new Twig_Node($tests), $else, $lineno, $this->getTag()); | ||
73 | } | ||
74 | |||
75 | public function decideIfFork(Twig_Token $token) | ||
76 | { | ||
77 | return $token->test(array('elseif', 'else', 'endif')); | ||
78 | } | ||
79 | |||
80 | public function decideIfEnd(Twig_Token $token) | ||
81 | { | ||
82 | return $token->test(array('endif')); | ||
83 | } | ||
84 | |||
85 | /** | ||
86 | * Gets the tag name associated with this token parser. | ||
87 | * | ||
88 | * @return string The tag name | ||
89 | */ | ||
90 | public function getTag() | ||
91 | { | ||
92 | return 'if'; | ||
93 | } | ||
94 | } | ||
diff --git a/inc/Twig/TokenParser/Import.php b/inc/Twig/TokenParser/Import.php new file mode 100644 index 00000000..e7050c70 --- /dev/null +++ b/inc/Twig/TokenParser/Import.php | |||
@@ -0,0 +1,49 @@ | |||
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 | * Imports macros. | ||
14 | * | ||
15 | * <pre> | ||
16 | * {% import 'forms.html' as forms %} | ||
17 | * </pre> | ||
18 | */ | ||
19 | class Twig_TokenParser_Import extends Twig_TokenParser | ||
20 | { | ||
21 | /** | ||
22 | * Parses a token and returns a node. | ||
23 | * | ||
24 | * @param Twig_Token $token A Twig_Token instance | ||
25 | * | ||
26 | * @return Twig_NodeInterface A Twig_NodeInterface instance | ||
27 | */ | ||
28 | public function parse(Twig_Token $token) | ||
29 | { | ||
30 | $macro = $this->parser->getExpressionParser()->parseExpression(); | ||
31 | $this->parser->getStream()->expect('as'); | ||
32 | $var = new Twig_Node_Expression_AssignName($this->parser->getStream()->expect(Twig_Token::NAME_TYPE)->getValue(), $token->getLine()); | ||
33 | $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE); | ||
34 | |||
35 | $this->parser->addImportedSymbol('template', $var->getAttribute('name')); | ||
36 | |||
37 | return new Twig_Node_Import($macro, $var, $token->getLine(), $this->getTag()); | ||
38 | } | ||
39 | |||
40 | /** | ||
41 | * Gets the tag name associated with this token parser. | ||
42 | * | ||
43 | * @return string The tag name | ||
44 | */ | ||
45 | public function getTag() | ||
46 | { | ||
47 | return 'import'; | ||
48 | } | ||
49 | } | ||
diff --git a/inc/Twig/TokenParser/Include.php b/inc/Twig/TokenParser/Include.php new file mode 100644 index 00000000..4a317868 --- /dev/null +++ b/inc/Twig/TokenParser/Include.php | |||
@@ -0,0 +1,80 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2009 Fabien Potencier | ||
7 | * (c) 2009 Armin Ronacher | ||
8 | * | ||
9 | * For the full copyright and license information, please view the LICENSE | ||
10 | * file that was distributed with this source code. | ||
11 | */ | ||
12 | |||
13 | /** | ||
14 | * Includes a template. | ||
15 | * | ||
16 | * <pre> | ||
17 | * {% include 'header.html' %} | ||
18 | * Body | ||
19 | * {% include 'footer.html' %} | ||
20 | * </pre> | ||
21 | */ | ||
22 | class Twig_TokenParser_Include extends Twig_TokenParser | ||
23 | { | ||
24 | /** | ||
25 | * Parses a token and returns a node. | ||
26 | * | ||
27 | * @param Twig_Token $token A Twig_Token instance | ||
28 | * | ||
29 | * @return Twig_NodeInterface A Twig_NodeInterface instance | ||
30 | */ | ||
31 | public function parse(Twig_Token $token) | ||
32 | { | ||
33 | $expr = $this->parser->getExpressionParser()->parseExpression(); | ||
34 | |||
35 | list($variables, $only, $ignoreMissing) = $this->parseArguments(); | ||
36 | |||
37 | return new Twig_Node_Include($expr, $variables, $only, $ignoreMissing, $token->getLine(), $this->getTag()); | ||
38 | } | ||
39 | |||
40 | protected function parseArguments() | ||
41 | { | ||
42 | $stream = $this->parser->getStream(); | ||
43 | |||
44 | $ignoreMissing = false; | ||
45 | if ($stream->test(Twig_Token::NAME_TYPE, 'ignore')) { | ||
46 | $stream->next(); | ||
47 | $stream->expect(Twig_Token::NAME_TYPE, 'missing'); | ||
48 | |||
49 | $ignoreMissing = true; | ||
50 | } | ||
51 | |||
52 | $variables = null; | ||
53 | if ($stream->test(Twig_Token::NAME_TYPE, 'with')) { | ||
54 | $stream->next(); | ||
55 | |||
56 | $variables = $this->parser->getExpressionParser()->parseExpression(); | ||
57 | } | ||
58 | |||
59 | $only = false; | ||
60 | if ($stream->test(Twig_Token::NAME_TYPE, 'only')) { | ||
61 | $stream->next(); | ||
62 | |||
63 | $only = true; | ||
64 | } | ||
65 | |||
66 | $stream->expect(Twig_Token::BLOCK_END_TYPE); | ||
67 | |||
68 | return array($variables, $only, $ignoreMissing); | ||
69 | } | ||
70 | |||
71 | /** | ||
72 | * Gets the tag name associated with this token parser. | ||
73 | * | ||
74 | * @return string The tag name | ||
75 | */ | ||
76 | public function getTag() | ||
77 | { | ||
78 | return 'include'; | ||
79 | } | ||
80 | } | ||
diff --git a/inc/Twig/TokenParser/Macro.php b/inc/Twig/TokenParser/Macro.php new file mode 100644 index 00000000..82b4fa6d --- /dev/null +++ b/inc/Twig/TokenParser/Macro.php | |||
@@ -0,0 +1,68 @@ | |||
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 | * Defines a macro. | ||
14 | * | ||
15 | * <pre> | ||
16 | * {% macro input(name, value, type, size) %} | ||
17 | * <input type="{{ type|default('text') }}" name="{{ name }}" value="{{ value|e }}" size="{{ size|default(20) }}" /> | ||
18 | * {% endmacro %} | ||
19 | * </pre> | ||
20 | */ | ||
21 | class Twig_TokenParser_Macro extends Twig_TokenParser | ||
22 | { | ||
23 | /** | ||
24 | * Parses a token and returns a node. | ||
25 | * | ||
26 | * @param Twig_Token $token A Twig_Token instance | ||
27 | * | ||
28 | * @return Twig_NodeInterface A Twig_NodeInterface instance | ||
29 | */ | ||
30 | public function parse(Twig_Token $token) | ||
31 | { | ||
32 | $lineno = $token->getLine(); | ||
33 | $stream = $this->parser->getStream(); | ||
34 | $name = $stream->expect(Twig_Token::NAME_TYPE)->getValue(); | ||
35 | |||
36 | $arguments = $this->parser->getExpressionParser()->parseArguments(true, true); | ||
37 | |||
38 | $stream->expect(Twig_Token::BLOCK_END_TYPE); | ||
39 | $this->parser->pushLocalScope(); | ||
40 | $body = $this->parser->subparse(array($this, 'decideBlockEnd'), true); | ||
41 | if ($stream->test(Twig_Token::NAME_TYPE)) { | ||
42 | $value = $stream->next()->getValue(); | ||
43 | |||
44 | if ($value != $name) { | ||
45 | throw new Twig_Error_Syntax(sprintf("Expected endmacro for macro '$name' (but %s given)", $value), $stream->getCurrent()->getLine(), $stream->getFilename()); | ||
46 | } | ||
47 | } | ||
48 | $this->parser->popLocalScope(); | ||
49 | $stream->expect(Twig_Token::BLOCK_END_TYPE); | ||
50 | |||
51 | $this->parser->setMacro($name, new Twig_Node_Macro($name, new Twig_Node_Body(array($body)), $arguments, $lineno, $this->getTag())); | ||
52 | } | ||
53 | |||
54 | public function decideBlockEnd(Twig_Token $token) | ||
55 | { | ||
56 | return $token->test('endmacro'); | ||
57 | } | ||
58 | |||
59 | /** | ||
60 | * Gets the tag name associated with this token parser. | ||
61 | * | ||
62 | * @return string The tag name | ||
63 | */ | ||
64 | public function getTag() | ||
65 | { | ||
66 | return 'macro'; | ||
67 | } | ||
68 | } | ||
diff --git a/inc/Twig/TokenParser/Sandbox.php b/inc/Twig/TokenParser/Sandbox.php new file mode 100644 index 00000000..9457325a --- /dev/null +++ b/inc/Twig/TokenParser/Sandbox.php | |||
@@ -0,0 +1,68 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 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 | * Marks a section of a template as untrusted code that must be evaluated in the sandbox mode. | ||
14 | * | ||
15 | * <pre> | ||
16 | * {% sandbox %} | ||
17 | * {% include 'user.html' %} | ||
18 | * {% endsandbox %} | ||
19 | * </pre> | ||
20 | * | ||
21 | * @see http://www.twig-project.org/doc/api.html#sandbox-extension for details | ||
22 | */ | ||
23 | class Twig_TokenParser_Sandbox extends Twig_TokenParser | ||
24 | { | ||
25 | /** | ||
26 | * Parses a token and returns a node. | ||
27 | * | ||
28 | * @param Twig_Token $token A Twig_Token instance | ||
29 | * | ||
30 | * @return Twig_NodeInterface A Twig_NodeInterface instance | ||
31 | */ | ||
32 | public function parse(Twig_Token $token) | ||
33 | { | ||
34 | $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE); | ||
35 | $body = $this->parser->subparse(array($this, 'decideBlockEnd'), true); | ||
36 | $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE); | ||
37 | |||
38 | // in a sandbox tag, only include tags are allowed | ||
39 | if (!$body instanceof Twig_Node_Include) { | ||
40 | foreach ($body as $node) { | ||
41 | if ($node instanceof Twig_Node_Text && ctype_space($node->getAttribute('data'))) { | ||
42 | continue; | ||
43 | } | ||
44 | |||
45 | if (!$node instanceof Twig_Node_Include) { | ||
46 | throw new Twig_Error_Syntax('Only "include" tags are allowed within a "sandbox" section', $node->getLine(), $this->parser->getFilename()); | ||
47 | } | ||
48 | } | ||
49 | } | ||
50 | |||
51 | return new Twig_Node_Sandbox($body, $token->getLine(), $this->getTag()); | ||
52 | } | ||
53 | |||
54 | public function decideBlockEnd(Twig_Token $token) | ||
55 | { | ||
56 | return $token->test('endsandbox'); | ||
57 | } | ||
58 | |||
59 | /** | ||
60 | * Gets the tag name associated with this token parser. | ||
61 | * | ||
62 | * @return string The tag name | ||
63 | */ | ||
64 | public function getTag() | ||
65 | { | ||
66 | return 'sandbox'; | ||
67 | } | ||
68 | } | ||
diff --git a/inc/Twig/TokenParser/Set.php b/inc/Twig/TokenParser/Set.php new file mode 100644 index 00000000..70e0b41b --- /dev/null +++ b/inc/Twig/TokenParser/Set.php | |||
@@ -0,0 +1,84 @@ | |||
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 | * Defines a variable. | ||
14 | * | ||
15 | * <pre> | ||
16 | * {% set foo = 'foo' %} | ||
17 | * | ||
18 | * {% set foo = [1, 2] %} | ||
19 | * | ||
20 | * {% set foo = {'foo': 'bar'} %} | ||
21 | * | ||
22 | * {% set foo = 'foo' ~ 'bar' %} | ||
23 | * | ||
24 | * {% set foo, bar = 'foo', 'bar' %} | ||
25 | * | ||
26 | * {% set foo %}Some content{% endset %} | ||
27 | * </pre> | ||
28 | */ | ||
29 | class Twig_TokenParser_Set extends Twig_TokenParser | ||
30 | { | ||
31 | /** | ||
32 | * Parses a token and returns a node. | ||
33 | * | ||
34 | * @param Twig_Token $token A Twig_Token instance | ||
35 | * | ||
36 | * @return Twig_NodeInterface A Twig_NodeInterface instance | ||
37 | */ | ||
38 | public function parse(Twig_Token $token) | ||
39 | { | ||
40 | $lineno = $token->getLine(); | ||
41 | $stream = $this->parser->getStream(); | ||
42 | $names = $this->parser->getExpressionParser()->parseAssignmentExpression(); | ||
43 | |||
44 | $capture = false; | ||
45 | if ($stream->test(Twig_Token::OPERATOR_TYPE, '=')) { | ||
46 | $stream->next(); | ||
47 | $values = $this->parser->getExpressionParser()->parseMultitargetExpression(); | ||
48 | |||
49 | $stream->expect(Twig_Token::BLOCK_END_TYPE); | ||
50 | |||
51 | if (count($names) !== count($values)) { | ||
52 | throw new Twig_Error_Syntax("When using set, you must have the same number of variables and assignments.", $stream->getCurrent()->getLine(), $stream->getFilename()); | ||
53 | } | ||
54 | } else { | ||
55 | $capture = true; | ||
56 | |||
57 | if (count($names) > 1) { | ||
58 | throw new Twig_Error_Syntax("When using set with a block, you cannot have a multi-target.", $stream->getCurrent()->getLine(), $stream->getFilename()); | ||
59 | } | ||
60 | |||
61 | $stream->expect(Twig_Token::BLOCK_END_TYPE); | ||
62 | |||
63 | $values = $this->parser->subparse(array($this, 'decideBlockEnd'), true); | ||
64 | $stream->expect(Twig_Token::BLOCK_END_TYPE); | ||
65 | } | ||
66 | |||
67 | return new Twig_Node_Set($capture, $names, $values, $lineno, $this->getTag()); | ||
68 | } | ||
69 | |||
70 | public function decideBlockEnd(Twig_Token $token) | ||
71 | { | ||
72 | return $token->test('endset'); | ||
73 | } | ||
74 | |||
75 | /** | ||
76 | * Gets the tag name associated with this token parser. | ||
77 | * | ||
78 | * @return string The tag name | ||
79 | */ | ||
80 | public function getTag() | ||
81 | { | ||
82 | return 'set'; | ||
83 | } | ||
84 | } | ||
diff --git a/inc/Twig/TokenParser/Spaceless.php b/inc/Twig/TokenParser/Spaceless.php new file mode 100644 index 00000000..1e3fa8f3 --- /dev/null +++ b/inc/Twig/TokenParser/Spaceless.php | |||
@@ -0,0 +1,59 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 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 | * Remove whitespaces between HTML tags. | ||
14 | * | ||
15 | * <pre> | ||
16 | * {% spaceless %} | ||
17 | * <div> | ||
18 | * <strong>foo</strong> | ||
19 | * </div> | ||
20 | * {% endspaceless %} | ||
21 | * | ||
22 | * {# output will be <div><strong>foo</strong></div> #} | ||
23 | * </pre> | ||
24 | */ | ||
25 | class Twig_TokenParser_Spaceless extends Twig_TokenParser | ||
26 | { | ||
27 | /** | ||
28 | * Parses a token and returns a node. | ||
29 | * | ||
30 | * @param Twig_Token $token A Twig_Token instance | ||
31 | * | ||
32 | * @return Twig_NodeInterface A Twig_NodeInterface instance | ||
33 | */ | ||
34 | public function parse(Twig_Token $token) | ||
35 | { | ||
36 | $lineno = $token->getLine(); | ||
37 | |||
38 | $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE); | ||
39 | $body = $this->parser->subparse(array($this, 'decideSpacelessEnd'), true); | ||
40 | $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE); | ||
41 | |||
42 | return new Twig_Node_Spaceless($body, $lineno, $this->getTag()); | ||
43 | } | ||
44 | |||
45 | public function decideSpacelessEnd(Twig_Token $token) | ||
46 | { | ||
47 | return $token->test('endspaceless'); | ||
48 | } | ||
49 | |||
50 | /** | ||
51 | * Gets the tag name associated with this token parser. | ||
52 | * | ||
53 | * @return string The tag name | ||
54 | */ | ||
55 | public function getTag() | ||
56 | { | ||
57 | return 'spaceless'; | ||
58 | } | ||
59 | } | ||
diff --git a/inc/Twig/TokenParser/Use.php b/inc/Twig/TokenParser/Use.php new file mode 100644 index 00000000..bc0e09ef --- /dev/null +++ b/inc/Twig/TokenParser/Use.php | |||
@@ -0,0 +1,82 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2011 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 | * Imports blocks defined in another template into the current template. | ||
14 | * | ||
15 | * <pre> | ||
16 | * {% extends "base.html" %} | ||
17 | * | ||
18 | * {% use "blocks.html" %} | ||
19 | * | ||
20 | * {% block title %}{% endblock %} | ||
21 | * {% block content %}{% endblock %} | ||
22 | * </pre> | ||
23 | * | ||
24 | * @see http://www.twig-project.org/doc/templates.html#horizontal-reuse for details. | ||
25 | */ | ||
26 | class Twig_TokenParser_Use extends Twig_TokenParser | ||
27 | { | ||
28 | /** | ||
29 | * Parses a token and returns a node. | ||
30 | * | ||
31 | * @param Twig_Token $token A Twig_Token instance | ||
32 | * | ||
33 | * @return Twig_NodeInterface A Twig_NodeInterface instance | ||
34 | */ | ||
35 | public function parse(Twig_Token $token) | ||
36 | { | ||
37 | $template = $this->parser->getExpressionParser()->parseExpression(); | ||
38 | $stream = $this->parser->getStream(); | ||
39 | |||
40 | if (!$template instanceof Twig_Node_Expression_Constant) { | ||
41 | throw new Twig_Error_Syntax('The template references in a "use" statement must be a string.', $stream->getCurrent()->getLine(), $stream->getFilename()); | ||
42 | } | ||
43 | |||
44 | $targets = array(); | ||
45 | if ($stream->test('with')) { | ||
46 | $stream->next(); | ||
47 | |||
48 | do { | ||
49 | $name = $stream->expect(Twig_Token::NAME_TYPE)->getValue(); | ||
50 | |||
51 | $alias = $name; | ||
52 | if ($stream->test('as')) { | ||
53 | $stream->next(); | ||
54 | |||
55 | $alias = $stream->expect(Twig_Token::NAME_TYPE)->getValue(); | ||
56 | } | ||
57 | |||
58 | $targets[$name] = new Twig_Node_Expression_Constant($alias, -1); | ||
59 | |||
60 | if (!$stream->test(Twig_Token::PUNCTUATION_TYPE, ',')) { | ||
61 | break; | ||
62 | } | ||
63 | |||
64 | $stream->next(); | ||
65 | } while (true); | ||
66 | } | ||
67 | |||
68 | $stream->expect(Twig_Token::BLOCK_END_TYPE); | ||
69 | |||
70 | $this->parser->addTrait(new Twig_Node(array('template' => $template, 'targets' => new Twig_Node($targets)))); | ||
71 | } | ||
72 | |||
73 | /** | ||
74 | * Gets the tag name associated with this token parser. | ||
75 | * | ||
76 | * @return string The tag name | ||
77 | */ | ||
78 | public function getTag() | ||
79 | { | ||
80 | return 'use'; | ||
81 | } | ||
82 | } | ||
diff --git a/inc/Twig/TokenParserBroker.php b/inc/Twig/TokenParserBroker.php new file mode 100644 index 00000000..ec3fba67 --- /dev/null +++ b/inc/Twig/TokenParserBroker.php | |||
@@ -0,0 +1,136 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 Fabien Potencier | ||
7 | * (c) 2010 Arnaud Le Blanc | ||
8 | * | ||
9 | * For the full copyright and license information, please view the LICENSE | ||
10 | * file that was distributed with this source code. | ||
11 | */ | ||
12 | |||
13 | /** | ||
14 | * Default implementation of a token parser broker. | ||
15 | * | ||
16 | * @author Arnaud Le Blanc <arnaud.lb@gmail.com> | ||
17 | * @deprecated since 1.12 (to be removed in 2.0) | ||
18 | */ | ||
19 | class Twig_TokenParserBroker implements Twig_TokenParserBrokerInterface | ||
20 | { | ||
21 | protected $parser; | ||
22 | protected $parsers = array(); | ||
23 | protected $brokers = array(); | ||
24 | |||
25 | /** | ||
26 | * Constructor. | ||
27 | * | ||
28 | * @param array|Traversable $parsers A Traversable of Twig_TokenParserInterface instances | ||
29 | * @param array|Traversable $brokers A Traversable of Twig_TokenParserBrokerInterface instances | ||
30 | */ | ||
31 | public function __construct($parsers = array(), $brokers = array()) | ||
32 | { | ||
33 | foreach ($parsers as $parser) { | ||
34 | if (!$parser instanceof Twig_TokenParserInterface) { | ||
35 | throw new LogicException('$parsers must a an array of Twig_TokenParserInterface'); | ||
36 | } | ||
37 | $this->parsers[$parser->getTag()] = $parser; | ||
38 | } | ||
39 | foreach ($brokers as $broker) { | ||
40 | if (!$broker instanceof Twig_TokenParserBrokerInterface) { | ||
41 | throw new LogicException('$brokers must a an array of Twig_TokenParserBrokerInterface'); | ||
42 | } | ||
43 | $this->brokers[] = $broker; | ||
44 | } | ||
45 | } | ||
46 | |||
47 | /** | ||
48 | * Adds a TokenParser. | ||
49 | * | ||
50 | * @param Twig_TokenParserInterface $parser A Twig_TokenParserInterface instance | ||
51 | */ | ||
52 | public function addTokenParser(Twig_TokenParserInterface $parser) | ||
53 | { | ||
54 | $this->parsers[$parser->getTag()] = $parser; | ||
55 | } | ||
56 | |||
57 | /** | ||
58 | * Removes a TokenParser. | ||
59 | * | ||
60 | * @param Twig_TokenParserInterface $parser A Twig_TokenParserInterface instance | ||
61 | */ | ||
62 | public function removeTokenParser(Twig_TokenParserInterface $parser) | ||
63 | { | ||
64 | $name = $parser->getTag(); | ||
65 | if (isset($this->parsers[$name]) && $parser === $this->parsers[$name]) { | ||
66 | unset($this->parsers[$name]); | ||
67 | } | ||
68 | } | ||
69 | |||
70 | /** | ||
71 | * Adds a TokenParserBroker. | ||
72 | * | ||
73 | * @param Twig_TokenParserBroker $broker A Twig_TokenParserBroker instance | ||
74 | */ | ||
75 | public function addTokenParserBroker(Twig_TokenParserBroker $broker) | ||
76 | { | ||
77 | $this->brokers[] = $broker; | ||
78 | } | ||
79 | |||
80 | /** | ||
81 | * Removes a TokenParserBroker. | ||
82 | * | ||
83 | * @param Twig_TokenParserBroker $broker A Twig_TokenParserBroker instance | ||
84 | */ | ||
85 | public function removeTokenParserBroker(Twig_TokenParserBroker $broker) | ||
86 | { | ||
87 | if (false !== $pos = array_search($broker, $this->brokers)) { | ||
88 | unset($this->brokers[$pos]); | ||
89 | } | ||
90 | } | ||
91 | |||
92 | /** | ||
93 | * Gets a suitable TokenParser for a tag. | ||
94 | * | ||
95 | * First looks in parsers, then in brokers. | ||
96 | * | ||
97 | * @param string $tag A tag name | ||
98 | * | ||
99 | * @return null|Twig_TokenParserInterface A Twig_TokenParserInterface or null if no suitable TokenParser was found | ||
100 | */ | ||
101 | public function getTokenParser($tag) | ||
102 | { | ||
103 | if (isset($this->parsers[$tag])) { | ||
104 | return $this->parsers[$tag]; | ||
105 | } | ||
106 | $broker = end($this->brokers); | ||
107 | while (false !== $broker) { | ||
108 | $parser = $broker->getTokenParser($tag); | ||
109 | if (null !== $parser) { | ||
110 | return $parser; | ||
111 | } | ||
112 | $broker = prev($this->brokers); | ||
113 | } | ||
114 | } | ||
115 | |||
116 | public function getParsers() | ||
117 | { | ||
118 | return $this->parsers; | ||
119 | } | ||
120 | |||
121 | public function getParser() | ||
122 | { | ||
123 | return $this->parser; | ||
124 | } | ||
125 | |||
126 | public function setParser(Twig_ParserInterface $parser) | ||
127 | { | ||
128 | $this->parser = $parser; | ||
129 | foreach ($this->parsers as $tokenParser) { | ||
130 | $tokenParser->setParser($parser); | ||
131 | } | ||
132 | foreach ($this->brokers as $broker) { | ||
133 | $broker->setParser($parser); | ||
134 | } | ||
135 | } | ||
136 | } | ||
diff --git a/inc/Twig/TokenParserBrokerInterface.php b/inc/Twig/TokenParserBrokerInterface.php new file mode 100644 index 00000000..3f006e33 --- /dev/null +++ b/inc/Twig/TokenParserBrokerInterface.php | |||
@@ -0,0 +1,45 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 Fabien Potencier | ||
7 | * (c) 2010 Arnaud Le Blanc | ||
8 | * | ||
9 | * For the full copyright and license information, please view the LICENSE | ||
10 | * file that was distributed with this source code. | ||
11 | */ | ||
12 | |||
13 | /** | ||
14 | * Interface implemented by token parser brokers. | ||
15 | * | ||
16 | * Token parser brokers allows to implement custom logic in the process of resolving a token parser for a given tag name. | ||
17 | * | ||
18 | * @author Arnaud Le Blanc <arnaud.lb@gmail.com> | ||
19 | * @deprecated since 1.12 (to be removed in 2.0) | ||
20 | */ | ||
21 | interface Twig_TokenParserBrokerInterface | ||
22 | { | ||
23 | /** | ||
24 | * Gets a TokenParser suitable for a tag. | ||
25 | * | ||
26 | * @param string $tag A tag name | ||
27 | * | ||
28 | * @return null|Twig_TokenParserInterface A Twig_TokenParserInterface or null if no suitable TokenParser was found | ||
29 | */ | ||
30 | public function getTokenParser($tag); | ||
31 | |||
32 | /** | ||
33 | * Calls Twig_TokenParserInterface::setParser on all parsers the implementation knows of. | ||
34 | * | ||
35 | * @param Twig_ParserInterface $parser A Twig_ParserInterface interface | ||
36 | */ | ||
37 | public function setParser(Twig_ParserInterface $parser); | ||
38 | |||
39 | /** | ||
40 | * Gets the Twig_ParserInterface. | ||
41 | * | ||
42 | * @return null|Twig_ParserInterface A Twig_ParserInterface instance or null | ||
43 | */ | ||
44 | public function getParser(); | ||
45 | } | ||
diff --git a/inc/Twig/TokenParserInterface.php b/inc/Twig/TokenParserInterface.php new file mode 100644 index 00000000..bbde7714 --- /dev/null +++ b/inc/Twig/TokenParserInterface.php | |||
@@ -0,0 +1,41 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2010 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 | * Interface implemented by token parsers. | ||
14 | * | ||
15 | * @author Fabien Potencier <fabien@symfony.com> | ||
16 | */ | ||
17 | interface Twig_TokenParserInterface | ||
18 | { | ||
19 | /** | ||
20 | * Sets the parser associated with this token parser | ||
21 | * | ||
22 | * @param $parser A Twig_Parser instance | ||
23 | */ | ||
24 | public function setParser(Twig_Parser $parser); | ||
25 | |||
26 | /** | ||
27 | * Parses a token and returns a node. | ||
28 | * | ||
29 | * @param Twig_Token $token A Twig_Token instance | ||
30 | * | ||
31 | * @return Twig_NodeInterface A Twig_NodeInterface instance | ||
32 | */ | ||
33 | public function parse(Twig_Token $token); | ||
34 | |||
35 | /** | ||
36 | * Gets the tag name associated with this token parser. | ||
37 | * | ||
38 | * @return string The tag name | ||
39 | */ | ||
40 | public function getTag(); | ||
41 | } | ||
diff --git a/inc/Twig/TokenStream.php b/inc/Twig/TokenStream.php new file mode 100644 index 00000000..a78189f6 --- /dev/null +++ b/inc/Twig/TokenStream.php | |||
@@ -0,0 +1,144 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2009 Fabien Potencier | ||
7 | * (c) 2009 Armin Ronacher | ||
8 | * | ||
9 | * For the full copyright and license information, please view the LICENSE | ||
10 | * file that was distributed with this source code. | ||
11 | */ | ||
12 | |||
13 | /** | ||
14 | * Represents a token stream. | ||
15 | * | ||
16 | * @author Fabien Potencier <fabien@symfony.com> | ||
17 | */ | ||
18 | class Twig_TokenStream | ||
19 | { | ||
20 | protected $tokens; | ||
21 | protected $current; | ||
22 | protected $filename; | ||
23 | |||
24 | /** | ||
25 | * Constructor. | ||
26 | * | ||
27 | * @param array $tokens An array of tokens | ||
28 | * @param string $filename The name of the filename which tokens are associated with | ||
29 | */ | ||
30 | public function __construct(array $tokens, $filename = null) | ||
31 | { | ||
32 | $this->tokens = $tokens; | ||
33 | $this->current = 0; | ||
34 | $this->filename = $filename; | ||
35 | } | ||
36 | |||
37 | /** | ||
38 | * Returns a string representation of the token stream. | ||
39 | * | ||
40 | * @return string | ||
41 | */ | ||
42 | public function __toString() | ||
43 | { | ||
44 | return implode("\n", $this->tokens); | ||
45 | } | ||
46 | |||
47 | public function injectTokens(array $tokens) | ||
48 | { | ||
49 | $this->tokens = array_merge(array_slice($this->tokens, 0, $this->current), $tokens, array_slice($this->tokens, $this->current)); | ||
50 | } | ||
51 | |||
52 | /** | ||
53 | * Sets the pointer to the next token and returns the old one. | ||
54 | * | ||
55 | * @return Twig_Token | ||
56 | */ | ||
57 | public function next() | ||
58 | { | ||
59 | if (!isset($this->tokens[++$this->current])) { | ||
60 | throw new Twig_Error_Syntax('Unexpected end of template', $this->tokens[$this->current - 1]->getLine(), $this->filename); | ||
61 | } | ||
62 | |||
63 | return $this->tokens[$this->current - 1]; | ||
64 | } | ||
65 | |||
66 | /** | ||
67 | * Tests a token and returns it or throws a syntax error. | ||
68 | * | ||
69 | * @return Twig_Token | ||
70 | */ | ||
71 | public function expect($type, $value = null, $message = null) | ||
72 | { | ||
73 | $token = $this->tokens[$this->current]; | ||
74 | if (!$token->test($type, $value)) { | ||
75 | $line = $token->getLine(); | ||
76 | throw new Twig_Error_Syntax(sprintf('%sUnexpected token "%s" of value "%s" ("%s" expected%s)', | ||
77 | $message ? $message.'. ' : '', | ||
78 | Twig_Token::typeToEnglish($token->getType(), $line), $token->getValue(), | ||
79 | Twig_Token::typeToEnglish($type, $line), $value ? sprintf(' with value "%s"', $value) : ''), | ||
80 | $line, | ||
81 | $this->filename | ||
82 | ); | ||
83 | } | ||
84 | $this->next(); | ||
85 | |||
86 | return $token; | ||
87 | } | ||
88 | |||
89 | /** | ||
90 | * Looks at the next token. | ||
91 | * | ||
92 | * @param integer $number | ||
93 | * | ||
94 | * @return Twig_Token | ||
95 | */ | ||
96 | public function look($number = 1) | ||
97 | { | ||
98 | if (!isset($this->tokens[$this->current + $number])) { | ||
99 | throw new Twig_Error_Syntax('Unexpected end of template', $this->tokens[$this->current + $number - 1]->getLine(), $this->filename); | ||
100 | } | ||
101 | |||
102 | return $this->tokens[$this->current + $number]; | ||
103 | } | ||
104 | |||
105 | /** | ||
106 | * Tests the current token | ||
107 | * | ||
108 | * @return bool | ||
109 | */ | ||
110 | public function test($primary, $secondary = null) | ||
111 | { | ||
112 | return $this->tokens[$this->current]->test($primary, $secondary); | ||
113 | } | ||
114 | |||
115 | /** | ||
116 | * Checks if end of stream was reached | ||
117 | * | ||
118 | * @return bool | ||
119 | */ | ||
120 | public function isEOF() | ||
121 | { | ||
122 | return $this->tokens[$this->current]->getType() === Twig_Token::EOF_TYPE; | ||
123 | } | ||
124 | |||
125 | /** | ||
126 | * Gets the current token | ||
127 | * | ||
128 | * @return Twig_Token | ||
129 | */ | ||
130 | public function getCurrent() | ||
131 | { | ||
132 | return $this->tokens[$this->current]; | ||
133 | } | ||
134 | |||
135 | /** | ||
136 | * Gets the filename associated with this stream | ||
137 | * | ||
138 | * @return string | ||
139 | */ | ||
140 | public function getFilename() | ||
141 | { | ||
142 | return $this->filename; | ||
143 | } | ||
144 | } | ||
diff --git a/inc/config.php b/inc/config.php index 6e3f80b8..7470f0c3 100644 --- a/inc/config.php +++ b/inc/config.php | |||
@@ -9,65 +9,69 @@ | |||
9 | */ | 9 | */ |
10 | 10 | ||
11 | define ('POCHE_VERSION', '0.3'); | 11 | define ('POCHE_VERSION', '0.3'); |
12 | |||
13 | if (!is_dir('db/')) { | ||
14 | @mkdir('db/',0705); | ||
15 | } | ||
16 | |||
17 | define ('MODE_DEMO', FALSE); | 12 | define ('MODE_DEMO', FALSE); |
18 | define ('ABS_PATH', 'assets/'); | 13 | define ('CONVERT_LINKS_FOOTNOTES', FALSE); |
19 | define ('CONVERT_LINKS_FOOTNOTES', TRUE); | 14 | define ('REVERT_FORCED_PARAGRAPH_ELEMENTS', FALSE); |
20 | define ('REVERT_FORCED_PARAGRAPH_ELEMENTS',FALSE); | 15 | define ('DOWNLOAD_PICTURES', FALSE); |
21 | define ('DOWNLOAD_PICTURES', TRUE); | ||
22 | define ('SALT', '464v54gLLw928uz4zUBqkRJeiPY68zCX'); | 16 | define ('SALT', '464v54gLLw928uz4zUBqkRJeiPY68zCX'); |
17 | define ('ABS_PATH', 'assets/'); | ||
18 | define ('TPL', './tpl'); | ||
19 | define ('LOCALE', './locale'); | ||
20 | define ('CACHE', './cache'); | ||
23 | define ('LANG', 'fr_FR.UTF8'); | 21 | define ('LANG', 'fr_FR.UTF8'); |
24 | 22 | ||
25 | putenv("LC_ALL=".LANG); | ||
26 | setlocale(LC_ALL, LANG); | ||
27 | bindtextdomain(LANG, "./locale"); | ||
28 | textdomain(LANG); | ||
29 | |||
30 | $storage_type = 'sqlite'; # sqlite or file | 23 | $storage_type = 'sqlite'; # sqlite or file |
31 | 24 | ||
32 | include 'functions.php'; | 25 | # /!\ Be careful if you change the lines below /!\ |
26 | |||
27 | require_once 'pocheCore.php'; | ||
33 | require_once 'Readability.php'; | 28 | require_once 'Readability.php'; |
34 | require_once 'Encoding.php'; | 29 | require_once 'Encoding.php'; |
35 | require_once 'rain.tpl.class.php'; | 30 | require_once 'pocheTool.class.php'; |
36 | require_once 'MyTool.class.php'; | ||
37 | require_once 'Session.class.php'; | 31 | require_once 'Session.class.php'; |
32 | require_once 'Twig/Autoloader.php'; | ||
38 | require_once 'store/store.class.php'; | 33 | require_once 'store/store.class.php'; |
39 | require_once 'store/sqlite.class.php'; | 34 | require_once 'store/' . $storage_type . '.class.php'; |
40 | require_once 'store/file.class.php'; | ||
41 | require_once 'class.messages.php'; | ||
42 | 35 | ||
43 | Session::init(); | 36 | if (DOWNLOAD_PICTURES) { |
37 | require_once 'pochePicture.php'; | ||
38 | } | ||
39 | |||
40 | # i18n | ||
41 | putenv('LC_ALL=' . LANG); | ||
42 | setlocale(LC_ALL, LANG); | ||
43 | bindtextdomain(LANG, LOCALE); | ||
44 | textdomain(LANG); | ||
45 | |||
46 | # template engine | ||
47 | Twig_Autoloader::register(); | ||
48 | $loader = new Twig_Loader_Filesystem(TPL); | ||
49 | $twig = new Twig_Environment($loader, array( | ||
50 | 'cache' => CACHE, | ||
51 | )); | ||
52 | $twig->addExtension(new Twig_Extensions_Extension_I18n()); | ||
44 | 53 | ||
45 | $store = new $storage_type(); | 54 | Session::init(); |
46 | # initialisation de RainTPL | 55 | $store = new $storage_type(); |
47 | raintpl::$tpl_dir = './tpl/'; | ||
48 | raintpl::$cache_dir = './cache/'; | ||
49 | raintpl::$base_url = get_poche_url(); | ||
50 | raintpl::configure('path_replace', false); | ||
51 | raintpl::configure('debug', false); | ||
52 | $tpl = new raintpl(); | ||
53 | 56 | ||
57 | # installation | ||
54 | if(!$store->isInstalled()) | 58 | if(!$store->isInstalled()) |
55 | { | 59 | { |
56 | logm('poche still not installed'); | 60 | pocheTool::logm('poche still not installed'); |
57 | $tpl->draw('install'); | 61 | echo $twig->render('install.twig', array( |
62 | 'token' => Session::getToken(), | ||
63 | )); | ||
58 | if (isset($_GET['install'])) { | 64 | if (isset($_GET['install'])) { |
59 | if (($_POST['password'] == $_POST['password_repeat']) | 65 | if (($_POST['password'] == $_POST['password_repeat']) |
60 | && $_POST['password'] != "" && $_POST['login'] != "") { | 66 | && $_POST['password'] != "" && $_POST['login'] != "") { |
67 | # let's rock, install poche baby ! | ||
61 | $store->install($_POST['login'], encode_string($_POST['password'] . $_POST['login'])); | 68 | $store->install($_POST['login'], encode_string($_POST['password'] . $_POST['login'])); |
62 | Session::logout(); | 69 | Session::logout(); |
63 | MyTool::redirect(); | 70 | pocheTool::redirect(); |
64 | } | 71 | } |
65 | } | 72 | } |
66 | exit(); | 73 | exit(); |
67 | } | 74 | } |
68 | 75 | ||
69 | $_SESSION['login'] = (isset ($_SESSION['login'])) ? $_SESSION['login'] : $store->getLogin(); | 76 | $_SESSION['login'] = (isset ($_SESSION['login'])) ? $_SESSION['login'] : $store->getLogin(); |
70 | $_SESSION['pass'] = (isset ($_SESSION['pass'])) ? $_SESSION['pass'] : $store->getPassword(); | 77 | $_SESSION['pass'] = (isset ($_SESSION['pass'])) ? $_SESSION['pass'] : $store->getPassword(); \ No newline at end of file |
71 | |||
72 | $msg = new Messages(); | ||
73 | $tpl->assign('msg', $msg); \ No newline at end of file | ||
diff --git a/inc/functions.php b/inc/functions.php deleted file mode 100644 index ee26fbaa..00000000 --- a/inc/functions.php +++ /dev/null | |||
@@ -1,400 +0,0 @@ | |||
1 | <?php | ||
2 | /** | ||
3 | * poche, a read it later open source system | ||
4 | * | ||
5 | * @category poche | ||
6 | * @author Nicolas Lœuillet <support@inthepoche.com> | ||
7 | * @copyright 2013 | ||
8 | * @license http://www.wtfpl.net/ see COPYING file | ||
9 | */ | ||
10 | |||
11 | /** | ||
12 | * Permet de générer l'URL de poche pour le bookmarklet | ||
13 | */ | ||
14 | function get_poche_url() | ||
15 | { | ||
16 | $protocol = "http"; | ||
17 | if(isset($_SERVER['HTTPS'])) { | ||
18 | if($_SERVER['HTTPS'] != "off" && $_SERVER['HTTPS'] != "") { | ||
19 | $protocol = "https"; | ||
20 | } | ||
21 | } | ||
22 | |||
23 | return $protocol . "://" . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; | ||
24 | } | ||
25 | |||
26 | function encode_string($string) | ||
27 | { | ||
28 | return sha1($string . SALT); | ||
29 | } | ||
30 | |||
31 | // function define to retrieve url content | ||
32 | function get_external_file($url) | ||
33 | { | ||
34 | $timeout = 15; | ||
35 | // spoofing FireFox 18.0 | ||
36 | $useragent="Mozilla/5.0 (Windows NT 5.1; rv:18.0) Gecko/20100101 Firefox/18.0"; | ||
37 | |||
38 | if (in_array ('curl', get_loaded_extensions())) { | ||
39 | // Fetch feed from URL | ||
40 | $curl = curl_init(); | ||
41 | curl_setopt($curl, CURLOPT_URL, $url); | ||
42 | curl_setopt($curl, CURLOPT_TIMEOUT, $timeout); | ||
43 | curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); | ||
44 | curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); | ||
45 | curl_setopt($curl, CURLOPT_HEADER, false); | ||
46 | |||
47 | // FOR SSL do not verified certificate | ||
48 | curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE); | ||
49 | curl_setopt($curl, CURLOPT_AUTOREFERER, TRUE ); | ||
50 | |||
51 | // FeedBurner requires a proper USER-AGENT... | ||
52 | curl_setopt($curl, CURL_HTTP_VERSION_1_1, true); | ||
53 | curl_setopt($curl, CURLOPT_ENCODING, "gzip, deflate"); | ||
54 | curl_setopt($curl, CURLOPT_USERAGENT, $useragent); | ||
55 | |||
56 | $data = curl_exec($curl); | ||
57 | |||
58 | $httpcode = curl_getinfo($curl, CURLINFO_HTTP_CODE); | ||
59 | |||
60 | $httpcodeOK = isset($httpcode) and ($httpcode == 200 or $httpcode == 301); | ||
61 | |||
62 | curl_close($curl); | ||
63 | } else { | ||
64 | |||
65 | // create http context and add timeout and user-agent | ||
66 | $context = stream_context_create(array( | ||
67 | 'http'=>array('timeout' => $timeout, | ||
68 | 'header'=> "User-Agent: ".$useragent, /*spoot Mozilla Firefox*/ | ||
69 | 'follow_location' => true), | ||
70 | // FOR SSL do not verified certificate | ||
71 | 'ssl' => array('verify_peer' => false, | ||
72 | 'allow_self_signed' => true) | ||
73 | ) | ||
74 | ); | ||
75 | |||
76 | // only download page lesser than 4MB | ||
77 | $data = @file_get_contents($url, false, $context, -1, 4000000); // We download at most 4 MB from source. | ||
78 | |||
79 | if(isset($http_response_header) and isset($http_response_header[0])) { | ||
80 | $httpcodeOK = isset($http_response_header) and isset($http_response_header[0]) and ((strpos($http_response_header[0], '200 OK') !== FALSE) or (strpos($http_response_header[0], '301 Moved Permanently') !== FALSE)); | ||
81 | } | ||
82 | } | ||
83 | |||
84 | // if response is not empty and response is OK | ||
85 | if (isset($data) and isset($httpcodeOK) and $httpcodeOK ) { | ||
86 | |||
87 | // take charset of page and get it | ||
88 | preg_match('#<meta .*charset=.*>#Usi', $data, $meta); | ||
89 | |||
90 | // if meta tag is found | ||
91 | if (!empty($meta[0])) { | ||
92 | // retrieve encoding in $enc | ||
93 | preg_match('#charset="?(.*)"#si', $meta[0], $enc); | ||
94 | |||
95 | // if charset is found set it otherwise, set it to utf-8 | ||
96 | $html_charset = (!empty($enc[1])) ? strtolower($enc[1]) : 'utf-8'; | ||
97 | |||
98 | } else { | ||
99 | $html_charset = 'utf-8'; | ||
100 | $enc[1] = ''; | ||
101 | } | ||
102 | |||
103 | // replace charset of url to charset of page | ||
104 | $data = str_replace('charset='.$enc[1], 'charset='.$html_charset, $data); | ||
105 | |||
106 | return $data; | ||
107 | } | ||
108 | else { | ||
109 | return FALSE; | ||
110 | } | ||
111 | } | ||
112 | |||
113 | /** | ||
114 | * Préparation de l'URL avec récupération du contenu avant insertion en base | ||
115 | */ | ||
116 | function prepare_url($url) | ||
117 | { | ||
118 | $parametres = array(); | ||
119 | $url = html_entity_decode(trim($url)); | ||
120 | |||
121 | // We remove the annoying parameters added by FeedBurner and GoogleFeedProxy (?utm_source=...) | ||
122 | // from shaarli, by sebsauvage | ||
123 | $i=strpos($url,'&utm_source='); if ($i!==false) $url=substr($url,0,$i); | ||
124 | $i=strpos($url,'?utm_source='); if ($i!==false) $url=substr($url,0,$i); | ||
125 | $i=strpos($url,'#xtor=RSS-'); if ($i!==false) $url=substr($url,0,$i); | ||
126 | |||
127 | $title = $url; | ||
128 | $html = Encoding::toUTF8(get_external_file($url,15)); | ||
129 | // If get_external_file if not able to retrieve HTTPS content try the same URL with HTTP protocol | ||
130 | if (!preg_match('!^https?://!i', $url) && (!isset($html) || strlen($html) <= 0)) { | ||
131 | $url = 'http://' . $url; | ||
132 | $html = Encoding::toUTF8(get_external_file($url,15)); | ||
133 | } | ||
134 | |||
135 | if (function_exists('tidy_parse_string')) { | ||
136 | $tidy = tidy_parse_string($html, array(), 'UTF8'); | ||
137 | $tidy->cleanRepair(); | ||
138 | $html = $tidy->value; | ||
139 | } | ||
140 | |||
141 | if (isset($html) and strlen($html) > 0) | ||
142 | { | ||
143 | $r = new Readability($html, $url); | ||
144 | |||
145 | $r->convertLinksToFootnotes = CONVERT_LINKS_FOOTNOTES; | ||
146 | $r->revertForcedParagraphElements = REVERT_FORCED_PARAGRAPH_ELEMENTS; | ||
147 | |||
148 | if($r->init()) | ||
149 | { | ||
150 | $content = $r->articleContent->innerHTML; | ||
151 | $parametres['title'] = $r->articleTitle->innerHTML; | ||
152 | $parametres['content'] = $content; | ||
153 | return $parametres; | ||
154 | } | ||
155 | } | ||
156 | |||
157 | return FALSE; | ||
158 | } | ||
159 | |||
160 | /** | ||
161 | * On modifie les URLS des images dans le corps de l'article | ||
162 | */ | ||
163 | function filtre_picture($content, $url, $id) | ||
164 | { | ||
165 | $matches = array(); | ||
166 | preg_match_all('#<\s*(img)[^>]+src="([^"]*)"[^>]*>#Si', $content, $matches, PREG_SET_ORDER); | ||
167 | foreach($matches as $i => $link) | ||
168 | { | ||
169 | $link[1] = trim($link[1]); | ||
170 | if (!preg_match('#^(([a-z]+://)|(\#))#', $link[1]) ) | ||
171 | { | ||
172 | $absolute_path = get_absolute_link($link[2],$url); | ||
173 | $filename = basename(parse_url($absolute_path, PHP_URL_PATH)); | ||
174 | $directory = create_assets_directory($id); | ||
175 | $fullpath = $directory . '/' . $filename; | ||
176 | download_pictures($absolute_path, $fullpath); | ||
177 | $content = str_replace($matches[$i][2], $fullpath, $content); | ||
178 | } | ||
179 | |||
180 | } | ||
181 | |||
182 | return $content; | ||
183 | } | ||
184 | |||
185 | /** | ||
186 | * Retourne le lien absolu | ||
187 | */ | ||
188 | function get_absolute_link($relative_link, $url) | ||
189 | { | ||
190 | /* return if already absolute URL */ | ||
191 | if (parse_url($relative_link, PHP_URL_SCHEME) != '') return $relative_link; | ||
192 | |||
193 | /* queries and anchors */ | ||
194 | if ($relative_link[0]=='#' || $relative_link[0]=='?') return $url . $relative_link; | ||
195 | |||
196 | /* parse base URL and convert to local variables: | ||
197 | $scheme, $host, $path */ | ||
198 | extract(parse_url($url)); | ||
199 | |||
200 | /* remove non-directory element from path */ | ||
201 | $path = preg_replace('#/[^/]*$#', '', $path); | ||
202 | |||
203 | /* destroy path if relative url points to root */ | ||
204 | if ($relative_link[0] == '/') $path = ''; | ||
205 | |||
206 | /* dirty absolute URL */ | ||
207 | $abs = $host . $path . '/' . $relative_link; | ||
208 | |||
209 | /* replace '//' or '/./' or '/foo/../' with '/' */ | ||
210 | $re = array('#(/\.?/)#', '#/(?!\.\.)[^/]+/\.\./#'); | ||
211 | for($n=1; $n>0; $abs=preg_replace($re, '/', $abs, -1, $n)) {} | ||
212 | |||
213 | /* absolute URL is ready! */ | ||
214 | return $scheme.'://'.$abs; | ||
215 | } | ||
216 | |||
217 | /** | ||
218 | * Téléchargement des images | ||
219 | */ | ||
220 | |||
221 | function download_pictures($absolute_path, $fullpath) | ||
222 | { | ||
223 | $rawdata = get_external_file($absolute_path); | ||
224 | |||
225 | if(file_exists($fullpath)) { | ||
226 | unlink($fullpath); | ||
227 | } | ||
228 | $fp = fopen($fullpath, 'x'); | ||
229 | fwrite($fp, $rawdata); | ||
230 | fclose($fp); | ||
231 | } | ||
232 | |||
233 | /** | ||
234 | * Crée un répertoire de médias pour l'article | ||
235 | */ | ||
236 | function create_assets_directory($id) | ||
237 | { | ||
238 | $assets_path = ABS_PATH; | ||
239 | if(!is_dir($assets_path)) { | ||
240 | mkdir($assets_path, 0705); | ||
241 | } | ||
242 | |||
243 | $article_directory = $assets_path . $id; | ||
244 | if(!is_dir($article_directory)) { | ||
245 | mkdir($article_directory, 0705); | ||
246 | } | ||
247 | |||
248 | return $article_directory; | ||
249 | } | ||
250 | |||
251 | /** | ||
252 | * Suppression du répertoire d'images | ||
253 | */ | ||
254 | function remove_directory($directory) | ||
255 | { | ||
256 | if(is_dir($directory)) { | ||
257 | $files = array_diff(scandir($directory), array('.','..')); | ||
258 | foreach ($files as $file) { | ||
259 | (is_dir("$directory/$file")) ? remove_directory("$directory/$file") : unlink("$directory/$file"); | ||
260 | } | ||
261 | return rmdir($directory); | ||
262 | } | ||
263 | } | ||
264 | |||
265 | function display_view($view, $id = 0, $full_head = 'yes') | ||
266 | { | ||
267 | global $tpl, $store, $msg; | ||
268 | |||
269 | switch ($view) | ||
270 | { | ||
271 | case 'export': | ||
272 | $entries = $store->retrieveAll(); | ||
273 | $tpl->assign('export', myTool::renderJson($entries)); | ||
274 | $tpl->draw('export'); | ||
275 | logm('export view'); | ||
276 | break; | ||
277 | case 'config': | ||
278 | $tpl->assign('load_all_js', 0); | ||
279 | $tpl->draw('head'); | ||
280 | $tpl->draw('home'); | ||
281 | $tpl->draw('config'); | ||
282 | $tpl->draw('js'); | ||
283 | $tpl->draw('footer'); | ||
284 | logm('config view'); | ||
285 | break; | ||
286 | case 'view': | ||
287 | $entry = $store->retrieveOneById($id); | ||
288 | |||
289 | if ($entry != NULL) { | ||
290 | $tpl->assign('id', $entry['id']); | ||
291 | $tpl->assign('url', $entry['url']); | ||
292 | $tpl->assign('title', $entry['title']); | ||
293 | $content = $entry['content']; | ||
294 | if (function_exists('tidy_parse_string')) { | ||
295 | $tidy = tidy_parse_string($content, array('indent'=>true, 'show-body-only' => true), 'UTF8'); | ||
296 | $tidy->cleanRepair(); | ||
297 | $content = $tidy->value; | ||
298 | } | ||
299 | $tpl->assign('content', $content); | ||
300 | $tpl->assign('is_fav', $entry['is_fav']); | ||
301 | $tpl->assign('is_read', $entry['is_read']); | ||
302 | $tpl->assign('load_all_js', 0); | ||
303 | $tpl->draw('view'); | ||
304 | } | ||
305 | else { | ||
306 | logm('error in view call : entry is NULL'); | ||
307 | } | ||
308 | |||
309 | logm('view link #' . $id); | ||
310 | break; | ||
311 | default: # home view | ||
312 | $entries = $store->getEntriesByView($view); | ||
313 | |||
314 | $tpl->assign('entries', $entries); | ||
315 | |||
316 | if ($full_head == 'yes') { | ||
317 | $tpl->assign('load_all_js', 1); | ||
318 | $tpl->draw('head'); | ||
319 | $tpl->draw('home'); | ||
320 | } | ||
321 | |||
322 | $tpl->draw('entries'); | ||
323 | |||
324 | if ($full_head == 'yes') { | ||
325 | $tpl->draw('js'); | ||
326 | $tpl->draw('footer'); | ||
327 | } | ||
328 | break; | ||
329 | } | ||
330 | } | ||
331 | |||
332 | /** | ||
333 | * Appel d'une action (mark as fav, archive, delete) | ||
334 | */ | ||
335 | function action_to_do($action, $url, $id = 0) | ||
336 | { | ||
337 | global $store, $msg; | ||
338 | |||
339 | switch ($action) | ||
340 | { | ||
341 | case 'add': | ||
342 | if ($url == '') | ||
343 | continue; | ||
344 | |||
345 | $url = base64_decode($url); | ||
346 | error_log(print_r($url, TRUE)); | ||
347 | if (MyTool::isUrl($url)) { | ||
348 | if($parametres_url = prepare_url($url)) { | ||
349 | if ($store->add($url, $parametres_url['title'], $parametres_url['content'])) { | ||
350 | $last_id = $store->getLastId(); | ||
351 | if (DOWNLOAD_PICTURES) { | ||
352 | $content = filtre_picture($parametres_url['content'], $url, $last_id); | ||
353 | } | ||
354 | $msg->add('s', _('the link has been added successfully')); | ||
355 | } | ||
356 | else { | ||
357 | $msg->add('e', _('error during insertion : the link wasn\'t added')); | ||
358 | } | ||
359 | } | ||
360 | else { | ||
361 | $msg->add('e', _('error during url preparation : the link wasn\'t added')); | ||
362 | logm('error during url preparation'); | ||
363 | } | ||
364 | } | ||
365 | else { | ||
366 | $msg->add('e', _('error during url preparation : the link is not valid')); | ||
367 | logm($url . ' is not a valid url'); | ||
368 | } | ||
369 | |||
370 | logm('add link ' . $url); | ||
371 | break; | ||
372 | case 'delete': | ||
373 | if ($store->deleteById($id)) { | ||
374 | remove_directory(ABS_PATH . $id); | ||
375 | $msg->add('s', _('the link has been deleted successfully')); | ||
376 | logm('delete link #' . $id); | ||
377 | } | ||
378 | else { | ||
379 | $msg->add('e', _('the link wasn\'t deleted')); | ||
380 | logm('error : can\'t delete link #' . $id); | ||
381 | } | ||
382 | break; | ||
383 | case 'toggle_fav' : | ||
384 | $store->favoriteById($id); | ||
385 | logm('mark as favorite link #' . $id); | ||
386 | break; | ||
387 | case 'toggle_archive' : | ||
388 | $store->archiveById($id); | ||
389 | logm('archive link #' . $id); | ||
390 | break; | ||
391 | default: | ||
392 | break; | ||
393 | } | ||
394 | } | ||
395 | |||
396 | function logm($message) | ||
397 | { | ||
398 | $t = strval(date('Y/m/d_H:i:s')).' - '.$_SERVER["REMOTE_ADDR"].' - '.strval($message)."\n"; | ||
399 | file_put_contents('./log.txt',$t,FILE_APPEND); | ||
400 | } | ||
diff --git a/inc/poche/pocheCore.php b/inc/poche/pocheCore.php new file mode 100644 index 00000000..52c603ac --- /dev/null +++ b/inc/poche/pocheCore.php | |||
@@ -0,0 +1,257 @@ | |||
1 | <?php | ||
2 | /** | ||
3 | * poche, a read it later open source system | ||
4 | * | ||
5 | * @category poche | ||
6 | * @author Nicolas Lœuillet <support@inthepoche.com> | ||
7 | * @copyright 2013 | ||
8 | * @license http://www.wtfpl.net/ see COPYING file | ||
9 | */ | ||
10 | |||
11 | function encode_string($string) | ||
12 | { | ||
13 | return sha1($string . SALT); | ||
14 | } | ||
15 | |||
16 | function get_external_file($url) | ||
17 | { | ||
18 | $timeout = 15; | ||
19 | $useragent = "Mozilla/5.0 (Windows NT 5.1; rv:18.0) Gecko/20100101 Firefox/18.0"; | ||
20 | |||
21 | if (in_array ('curl', get_loaded_extensions())) { | ||
22 | # Fetch feed from URL | ||
23 | $curl = curl_init(); | ||
24 | curl_setopt($curl, CURLOPT_URL, $url); | ||
25 | curl_setopt($curl, CURLOPT_TIMEOUT, $timeout); | ||
26 | curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); | ||
27 | curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); | ||
28 | curl_setopt($curl, CURLOPT_HEADER, false); | ||
29 | |||
30 | # for ssl, do not verified certificate | ||
31 | curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE); | ||
32 | curl_setopt($curl, CURLOPT_AUTOREFERER, TRUE ); | ||
33 | |||
34 | # FeedBurner requires a proper USER-AGENT... | ||
35 | curl_setopt($curl, CURL_HTTP_VERSION_1_1, true); | ||
36 | curl_setopt($curl, CURLOPT_ENCODING, "gzip, deflate"); | ||
37 | curl_setopt($curl, CURLOPT_USERAGENT, $useragent); | ||
38 | |||
39 | $data = curl_exec($curl); | ||
40 | $httpcode = curl_getinfo($curl, CURLINFO_HTTP_CODE); | ||
41 | $httpcodeOK = isset($httpcode) and ($httpcode == 200 or $httpcode == 301); | ||
42 | curl_close($curl); | ||
43 | } else { | ||
44 | # create http context and add timeout and user-agent | ||
45 | $context = stream_context_create( | ||
46 | array( | ||
47 | 'http' => array( | ||
48 | 'timeout' => $timeout, | ||
49 | 'header' => "User-Agent: " . $useragent, | ||
50 | 'follow_location' => true | ||
51 | ), | ||
52 | 'ssl' => array( | ||
53 | 'verify_peer' => false, | ||
54 | 'allow_self_signed' => true | ||
55 | ) | ||
56 | ) | ||
57 | ); | ||
58 | |||
59 | # only download page lesser than 4MB | ||
60 | $data = @file_get_contents($url, false, $context, -1, 4000000); | ||
61 | |||
62 | if (isset($http_response_header) and isset($http_response_header[0])) { | ||
63 | $httpcodeOK = isset($http_response_header) and isset($http_response_header[0]) and ((strpos($http_response_header[0], '200 OK') !== FALSE) or (strpos($http_response_header[0], '301 Moved Permanently') !== FALSE)); | ||
64 | } | ||
65 | } | ||
66 | |||
67 | # if response is not empty and response is OK | ||
68 | if (isset($data) and isset($httpcodeOK) and $httpcodeOK) { | ||
69 | |||
70 | # take charset of page and get it | ||
71 | preg_match('#<meta .*charset=.*>#Usi', $data, $meta); | ||
72 | |||
73 | # if meta tag is found | ||
74 | if (!empty($meta[0])) { | ||
75 | preg_match('#charset="?(.*)"#si', $meta[0], $encoding); | ||
76 | # if charset is found set it otherwise, set it to utf-8 | ||
77 | $html_charset = (!empty($encoding[1])) ? strtolower($encoding[1]) : 'utf-8'; | ||
78 | } else { | ||
79 | $html_charset = 'utf-8'; | ||
80 | $encoding[1] = ''; | ||
81 | } | ||
82 | |||
83 | # replace charset of url to charset of page | ||
84 | $data = str_replace('charset=' . $encoding[1], 'charset=' . $html_charset, $data); | ||
85 | |||
86 | return $data; | ||
87 | } | ||
88 | else { | ||
89 | return FALSE; | ||
90 | } | ||
91 | } | ||
92 | |||
93 | function fetch_url_content($url) | ||
94 | { | ||
95 | $url = base64_decode($url); | ||
96 | if (pocheTool::isUrl($url)) { | ||
97 | $url = pocheTool::cleanURL($url); | ||
98 | $html = Encoding::toUTF8(get_external_file($url)); | ||
99 | |||
100 | # if get_external_file if not able to retrieve HTTPS content, try the same URL with HTTP protocol | ||
101 | if (!preg_match('!^https?://!i', $url) && (!isset($html) || strlen($html) <= 0)) { | ||
102 | $url = 'http://' . $url; | ||
103 | $html = Encoding::toUTF8(get_external_file($url)); | ||
104 | } | ||
105 | |||
106 | if (function_exists('tidy_parse_string')) { | ||
107 | $tidy = tidy_parse_string($html, array(), 'UTF8'); | ||
108 | $tidy->cleanRepair(); | ||
109 | $html = $tidy->value; | ||
110 | } | ||
111 | |||
112 | $parameters = array(); | ||
113 | if (isset($html) and strlen($html) > 0) | ||
114 | { | ||
115 | $readability = new Readability($html, $url); | ||
116 | $readability->convertLinksToFootnotes = CONVERT_LINKS_FOOTNOTES; | ||
117 | $readability->revertForcedParagraphElements = REVERT_FORCED_PARAGRAPH_ELEMENTS; | ||
118 | |||
119 | if($readability->init()) | ||
120 | { | ||
121 | $content = $readability->articleContent->innerHTML; | ||
122 | $parameters['title'] = $readability->articleTitle->innerHTML; | ||
123 | $parameters['content'] = $content; | ||
124 | |||
125 | return $parameters; | ||
126 | } | ||
127 | } | ||
128 | } | ||
129 | else { | ||
130 | #$msg->add('e', _('error during url preparation : the link is not valid')); | ||
131 | pocheTool::logm($url . ' is not a valid url'); | ||
132 | } | ||
133 | |||
134 | return FALSE; | ||
135 | } | ||
136 | |||
137 | function display_view($view, $id = 0, $full_head = 'yes') | ||
138 | { | ||
139 | global $tpl, $store, $msg; | ||
140 | |||
141 | switch ($view) | ||
142 | { | ||
143 | case 'export': | ||
144 | $entries = $store->retrieveAll(); | ||
145 | $tpl->assign('export', pocheTool::renderJson($entries)); | ||
146 | $tpl->draw('export'); | ||
147 | pocheTool::logm('export view'); | ||
148 | break; | ||
149 | case 'config': | ||
150 | $tpl->assign('load_all_js', 0); | ||
151 | $tpl->draw('head'); | ||
152 | $tpl->draw('home'); | ||
153 | $tpl->draw('config'); | ||
154 | $tpl->draw('js'); | ||
155 | $tpl->draw('footer'); | ||
156 | pocheTool::logm('config view'); | ||
157 | break; | ||
158 | case 'view': | ||
159 | $entry = $store->retrieveOneById($id); | ||
160 | |||
161 | if ($entry != NULL) { | ||
162 | $tpl->assign('id', $entry['id']); | ||
163 | $tpl->assign('url', $entry['url']); | ||
164 | $tpl->assign('title', $entry['title']); | ||
165 | $content = $entry['content']; | ||
166 | if (function_exists('tidy_parse_string')) { | ||
167 | $tidy = tidy_parse_string($content, array('indent'=>true, 'show-body-only' => true), 'UTF8'); | ||
168 | $tidy->cleanRepair(); | ||
169 | $content = $tidy->value; | ||
170 | } | ||
171 | $tpl->assign('content', $content); | ||
172 | $tpl->assign('is_fav', $entry['is_fav']); | ||
173 | $tpl->assign('is_read', $entry['is_read']); | ||
174 | $tpl->assign('load_all_js', 0); | ||
175 | $tpl->draw('view'); | ||
176 | } | ||
177 | else { | ||
178 | pocheTool::logm('error in view call : entry is NULL'); | ||
179 | } | ||
180 | |||
181 | pocheTool::logm('view link #' . $id); | ||
182 | break; | ||
183 | default: # home view | ||
184 | $entries = $store->getEntriesByView($view); | ||
185 | |||
186 | $tpl->assign('entries', $entries); | ||
187 | |||
188 | if ($full_head == 'yes') { | ||
189 | $tpl->assign('load_all_js', 1); | ||
190 | $tpl->draw('head'); | ||
191 | $tpl->draw('home'); | ||
192 | } | ||
193 | |||
194 | $tpl->draw('entries'); | ||
195 | |||
196 | if ($full_head == 'yes') { | ||
197 | $tpl->draw('js'); | ||
198 | $tpl->draw('footer'); | ||
199 | } | ||
200 | break; | ||
201 | } | ||
202 | } | ||
203 | |||
204 | /** | ||
205 | * Appel d'une action (mark as fav, archive, delete) | ||
206 | */ | ||
207 | function action_to_do($action, $url, $id = 0) | ||
208 | { | ||
209 | global $store, $msg; | ||
210 | |||
211 | switch ($action) | ||
212 | { | ||
213 | case 'add': | ||
214 | if($parametres_url = fetch_url_content($url)) { | ||
215 | if ($store->add($url, $parametres_url['title'], $parametres_url['content'])) { | ||
216 | pocheTool::logm('add link ' . $url); | ||
217 | $last_id = $store->getLastId(); | ||
218 | if (DOWNLOAD_PICTURES) { | ||
219 | $content = filtre_picture($parametres_url['content'], $url, $last_id); | ||
220 | } | ||
221 | #$msg->add('s', _('the link has been added successfully')); | ||
222 | } | ||
223 | else { | ||
224 | #$msg->add('e', _('error during insertion : the link wasn\'t added')); | ||
225 | pocheTool::logm('error during insertion : the link wasn\'t added'); | ||
226 | } | ||
227 | } | ||
228 | else { | ||
229 | #$msg->add('e', _('error during url preparation : the link wasn\'t added')); | ||
230 | pocheTool::logm('error during content fetch'); | ||
231 | } | ||
232 | break; | ||
233 | case 'delete': | ||
234 | if ($store->deleteById($id)) { | ||
235 | if (DOWNLOAD_PICTURES) { | ||
236 | remove_directory(ABS_PATH . $id); | ||
237 | } | ||
238 | #$msg->add('s', _('the link has been deleted successfully')); | ||
239 | pocheTool::logm('delete link #' . $id); | ||
240 | } | ||
241 | else { | ||
242 | #$msg->add('e', _('the link wasn\'t deleted')); | ||
243 | pocheTool::logm('error : can\'t delete link #' . $id); | ||
244 | } | ||
245 | break; | ||
246 | case 'toggle_fav' : | ||
247 | $store->favoriteById($id); | ||
248 | pocheTool::logm('mark as favorite link #' . $id); | ||
249 | break; | ||
250 | case 'toggle_archive' : | ||
251 | $store->archiveById($id); | ||
252 | pocheTool::logm('archive link #' . $id); | ||
253 | break; | ||
254 | default: | ||
255 | break; | ||
256 | } | ||
257 | } | ||
diff --git a/inc/poche/pochePictures.php b/inc/poche/pochePictures.php new file mode 100644 index 00000000..bfc80657 --- /dev/null +++ b/inc/poche/pochePictures.php | |||
@@ -0,0 +1,114 @@ | |||
1 | <?php | ||
2 | /** | ||
3 | * poche, a read it later open source system | ||
4 | * | ||
5 | * @category poche | ||
6 | * @author Nicolas Lœuillet <support@inthepoche.com> | ||
7 | * @copyright 2013 | ||
8 | * @license http://www.wtfpl.net/ see COPYING file | ||
9 | */ | ||
10 | |||
11 | /** | ||
12 | * On modifie les URLS des images dans le corps de l'article | ||
13 | */ | ||
14 | function filtre_picture($content, $url, $id) | ||
15 | { | ||
16 | $matches = array(); | ||
17 | preg_match_all('#<\s*(img)[^>]+src="([^"]*)"[^>]*>#Si', $content, $matches, PREG_SET_ORDER); | ||
18 | foreach($matches as $i => $link) | ||
19 | { | ||
20 | $link[1] = trim($link[1]); | ||
21 | if (!preg_match('#^(([a-z]+://)|(\#))#', $link[1]) ) | ||
22 | { | ||
23 | $absolute_path = get_absolute_link($link[2],$url); | ||
24 | $filename = basename(parse_url($absolute_path, PHP_URL_PATH)); | ||
25 | $directory = create_assets_directory($id); | ||
26 | $fullpath = $directory . '/' . $filename; | ||
27 | download_pictures($absolute_path, $fullpath); | ||
28 | $content = str_replace($matches[$i][2], $fullpath, $content); | ||
29 | } | ||
30 | |||
31 | } | ||
32 | |||
33 | return $content; | ||
34 | } | ||
35 | |||
36 | /** | ||
37 | * Retourne le lien absolu | ||
38 | */ | ||
39 | function get_absolute_link($relative_link, $url) | ||
40 | { | ||
41 | /* return if already absolute URL */ | ||
42 | if (parse_url($relative_link, PHP_URL_SCHEME) != '') return $relative_link; | ||
43 | |||
44 | /* queries and anchors */ | ||
45 | if ($relative_link[0]=='#' || $relative_link[0]=='?') return $url . $relative_link; | ||
46 | |||
47 | /* parse base URL and convert to local variables: | ||
48 | $scheme, $host, $path */ | ||
49 | extract(parse_url($url)); | ||
50 | |||
51 | /* remove non-directory element from path */ | ||
52 | $path = preg_replace('#/[^/]*$#', '', $path); | ||
53 | |||
54 | /* destroy path if relative url points to root */ | ||
55 | if ($relative_link[0] == '/') $path = ''; | ||
56 | |||
57 | /* dirty absolute URL */ | ||
58 | $abs = $host . $path . '/' . $relative_link; | ||
59 | |||
60 | /* replace '//' or '/./' or '/foo/../' with '/' */ | ||
61 | $re = array('#(/\.?/)#', '#/(?!\.\.)[^/]+/\.\./#'); | ||
62 | for($n=1; $n>0; $abs=preg_replace($re, '/', $abs, -1, $n)) {} | ||
63 | |||
64 | /* absolute URL is ready! */ | ||
65 | return $scheme.'://'.$abs; | ||
66 | } | ||
67 | |||
68 | /** | ||
69 | * Téléchargement des images | ||
70 | */ | ||
71 | |||
72 | function download_pictures($absolute_path, $fullpath) | ||
73 | { | ||
74 | $rawdata = get_external_file($absolute_path); | ||
75 | |||
76 | if(file_exists($fullpath)) { | ||
77 | unlink($fullpath); | ||
78 | } | ||
79 | $fp = fopen($fullpath, 'x'); | ||
80 | fwrite($fp, $rawdata); | ||
81 | fclose($fp); | ||
82 | } | ||
83 | |||
84 | /** | ||
85 | * Crée un répertoire de médias pour l'article | ||
86 | */ | ||
87 | function create_assets_directory($id) | ||
88 | { | ||
89 | $assets_path = ABS_PATH; | ||
90 | if(!is_dir($assets_path)) { | ||
91 | mkdir($assets_path, 0705); | ||
92 | } | ||
93 | |||
94 | $article_directory = $assets_path . $id; | ||
95 | if(!is_dir($article_directory)) { | ||
96 | mkdir($article_directory, 0705); | ||
97 | } | ||
98 | |||
99 | return $article_directory; | ||
100 | } | ||
101 | |||
102 | /** | ||
103 | * Suppression du répertoire d'images | ||
104 | */ | ||
105 | function remove_directory($directory) | ||
106 | { | ||
107 | if(is_dir($directory)) { | ||
108 | $files = array_diff(scandir($directory), array('.','..')); | ||
109 | foreach ($files as $file) { | ||
110 | (is_dir("$directory/$file")) ? remove_directory("$directory/$file") : unlink("$directory/$file"); | ||
111 | } | ||
112 | return rmdir($directory); | ||
113 | } | ||
114 | } | ||
diff --git a/inc/pocheTool.class.php b/inc/poche/pocheTool.class.php index 9aab76b9..cade115e 100644 --- a/inc/pocheTool.class.php +++ b/inc/poche/pocheTool.class.php | |||
@@ -38,8 +38,7 @@ class pocheTools | |||
38 | 38 | ||
39 | public static function isUrl($url) | 39 | public static function isUrl($url) |
40 | { | 40 | { |
41 | // http://neo22s.com/check-if-url-exists-and-is-online-php/ | 41 | $pattern = '|^http(s)?://[a-z0-9-]+(.[a-z0-9-]+)*(:[0-9]+)?(/.*)?$|i'; |
42 | $pattern='|^http(s)?://[a-z0-9-]+(.[a-z0-9-]+)*(:[0-9]+)?(/.*)?$|i'; | ||
43 | 42 | ||
44 | return preg_match($pattern, $url); | 43 | return preg_match($pattern, $url); |
45 | } | 44 | } |
@@ -65,37 +64,61 @@ class pocheTools | |||
65 | . $_SERVER["SERVER_NAME"] . $serverport . $scriptname; | 64 | . $_SERVER["SERVER_NAME"] . $serverport . $scriptname; |
66 | } | 65 | } |
67 | 66 | ||
68 | public static function renderJson($data) | 67 | public static function redirect($url = '') |
69 | { | 68 | { |
70 | header('Cache-Control: no-cache, must-revalidate'); | 69 | if ($url === '') { |
71 | header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); | 70 | $url = (empty($_SERVER['HTTP_REFERER'])?'?':$_SERVER['HTTP_REFERER']); |
72 | header('Content-type: application/json; charset=UTF-8'); | ||
73 | |||
74 | echo json_encode($data); | ||
75 | exit(); | ||
76 | } | ||
77 | |||
78 | public static function redirect($rurl = '') | ||
79 | { | ||
80 | if ($rurl === '') { | ||
81 | $rurl = (empty($_SERVER['HTTP_REFERER'])?'?':$_SERVER['HTTP_REFERER']); | ||
82 | if (isset($_POST['returnurl'])) { | 71 | if (isset($_POST['returnurl'])) { |
83 | $rurl = $_POST['returnurl']; | 72 | $url = $_POST['returnurl']; |
84 | } | 73 | } |
85 | } | 74 | } |
86 | 75 | ||
87 | // prevent loop | 76 | # prevent loop |
88 | if (empty($rurl) || parse_url($rurl, PHP_URL_QUERY) === $_SERVER['QUERY_STRING']) { | 77 | if (empty($url) || parse_url($url, PHP_URL_QUERY) === $_SERVER['QUERY_STRING']) { |
89 | $rurl = pocheTool::getUrl(); | 78 | $url = pocheTool::getUrl(); |
90 | } | 79 | } |
91 | 80 | ||
92 | if (substr($rurl, 0, 1) !== '?') { | 81 | if (substr($url, 0, 1) !== '?') { |
93 | $ref = pocheTool::getUrl(); | 82 | $ref = pocheTool::getUrl(); |
94 | if (substr($rurl, 0, strlen($ref)) !== $ref) { | 83 | if (substr($url, 0, strlen($ref)) !== $ref) { |
95 | $rurl = $ref; | 84 | $url = $ref; |
96 | } | 85 | } |
97 | } | 86 | } |
98 | header('Location: '.$rurl); | 87 | header('Location: '.$url); |
99 | exit(); | 88 | exit(); |
100 | } | 89 | } |
90 | |||
91 | public static function cleanURL($url) | ||
92 | { | ||
93 | |||
94 | $url = html_entity_decode(trim($url)); | ||
95 | |||
96 | $stuff = strpos($url,'&utm_source='); | ||
97 | if ($stuff !== FALSE) | ||
98 | $url = substr($url, 0, $stuff); | ||
99 | $stuff = strpos($url,'?utm_source='); | ||
100 | if ($stuff !== FALSE) | ||
101 | $url = substr($url, 0, $stuff); | ||
102 | $stuff = strpos($url,'#xtor=RSS-'); | ||
103 | if ($stuff !== FALSE) | ||
104 | $url = substr($url, 0, $stuff); | ||
105 | |||
106 | return $url; | ||
107 | } | ||
108 | |||
109 | public static function renderJson($data) | ||
110 | { | ||
111 | header('Cache-Control: no-cache, must-revalidate'); | ||
112 | header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); | ||
113 | header('Content-type: application/json; charset=UTF-8'); | ||
114 | |||
115 | echo json_encode($data); | ||
116 | exit(); | ||
117 | } | ||
118 | |||
119 | public static function logm($message) | ||
120 | { | ||
121 | $t = strval(date('Y/m/d_H:i:s')).' - '.$_SERVER["REMOTE_ADDR"].' - '.strval($message)."\n"; | ||
122 | file_put_contents('./log.txt',$t,FILE_APPEND); | ||
123 | } | ||
101 | } \ No newline at end of file | 124 | } \ No newline at end of file |
diff --git a/inc/rain.tpl.class.php b/inc/rain.tpl.class.php deleted file mode 100644 index 6522c798..00000000 --- a/inc/rain.tpl.class.php +++ /dev/null | |||
@@ -1,1043 +0,0 @@ | |||
1 | <?php | ||
2 | |||
3 | /** | ||
4 | * RainTPL | ||
5 | * ------- | ||
6 | * Realized by Federico Ulfo & maintained by the Rain Team | ||
7 | * Distributed under GNU/LGPL 3 License | ||
8 | * | ||
9 | * @version 2.7.2 | ||
10 | */ | ||
11 | |||
12 | |||
13 | class RainTPL{ | ||
14 | |||
15 | // ------------------------- | ||
16 | // CONFIGURATION | ||
17 | // ------------------------- | ||
18 | |||
19 | /** | ||
20 | * Template directory | ||
21 | * | ||
22 | * @var string | ||
23 | */ | ||
24 | static $tpl_dir = "tpl/"; | ||
25 | |||
26 | |||
27 | /** | ||
28 | * Cache directory. Is the directory where RainTPL will compile the template and save the cache | ||
29 | * | ||
30 | * @var string | ||
31 | */ | ||
32 | static $cache_dir = "tmp/"; | ||
33 | |||
34 | |||
35 | /** | ||
36 | * Template base URL. RainTPL will add this URL to the relative paths of element selected in $path_replace_list. | ||
37 | * | ||
38 | * @var string | ||
39 | */ | ||
40 | static $base_url = null; | ||
41 | |||
42 | |||
43 | /** | ||
44 | * Template extension. | ||
45 | * | ||
46 | * @var string | ||
47 | */ | ||
48 | static $tpl_ext = "html"; | ||
49 | |||
50 | |||
51 | /** | ||
52 | * Path replace is a cool features that replace all relative paths of images (<img src="...">), stylesheet (<link href="...">), script (<script src="...">) and link (<a href="...">) | ||
53 | * Set true to enable the path replace. | ||
54 | * | ||
55 | * @var unknown_type | ||
56 | */ | ||
57 | static $path_replace = true; | ||
58 | |||
59 | |||
60 | /** | ||
61 | * You can set what the path_replace method will replace. | ||
62 | * Avaible options: a, img, link, script, input | ||
63 | * | ||
64 | * @var array | ||
65 | */ | ||
66 | static $path_replace_list = array( 'a', 'img', 'link', 'script', 'input' ); | ||
67 | |||
68 | |||
69 | /** | ||
70 | * You can define in the black list what string are disabled into the template tags | ||
71 | * | ||
72 | * @var unknown_type | ||
73 | */ | ||
74 | static $black_list = array( '\$this', 'raintpl::', 'self::', '_SESSION', '_SERVER', '_ENV', 'eval', 'exec', 'unlink', 'rmdir' ); | ||
75 | |||
76 | |||
77 | /** | ||
78 | * Check template. | ||
79 | * true: checks template update time, if changed it compile them | ||
80 | * false: loads the compiled template. Set false if server doesn't have write permission for cache_directory. | ||
81 | * | ||
82 | */ | ||
83 | static $check_template_update = true; | ||
84 | |||
85 | |||
86 | /** | ||
87 | * PHP tags <? ?> | ||
88 | * True: php tags are enabled into the template | ||
89 | * False: php tags are disabled into the template and rendered as html | ||
90 | * | ||
91 | * @var bool | ||
92 | */ | ||
93 | static $php_enabled = true; | ||
94 | |||
95 | |||
96 | /** | ||
97 | * Debug mode flag. | ||
98 | * True: debug mode is used, syntax errors are displayed directly in template. Execution of script is not terminated. | ||
99 | * False: exception is thrown on found error. | ||
100 | * | ||
101 | * @var bool | ||
102 | */ | ||
103 | static $debug = false; | ||
104 | |||
105 | // ------------------------- | ||
106 | |||
107 | |||
108 | // ------------------------- | ||
109 | // RAINTPL VARIABLES | ||
110 | // ------------------------- | ||
111 | |||
112 | /** | ||
113 | * Is the array where RainTPL keep the variables assigned | ||
114 | * | ||
115 | * @var array | ||
116 | */ | ||
117 | public $var = array(); | ||
118 | |||
119 | protected $tpl = array(), // variables to keep the template directories and info | ||
120 | $cache = false, // static cache enabled / disabled | ||
121 | $cache_id = null; // identify only one cache | ||
122 | |||
123 | protected static $config_name_sum = array(); // takes all the config to create the md5 of the file | ||
124 | |||
125 | // ------------------------- | ||
126 | |||
127 | |||
128 | |||
129 | const CACHE_EXPIRE_TIME = 3600; // default cache expire time = hour | ||
130 | |||
131 | |||
132 | |||
133 | /** | ||
134 | * Assign variable | ||
135 | * eg. $t->assign('name','mickey'); | ||
136 | * | ||
137 | * @param mixed $variable_name Name of template variable or associative array name/value | ||
138 | * @param mixed $value value assigned to this variable. Not set if variable_name is an associative array | ||
139 | */ | ||
140 | |||
141 | function assign( $variable, $value = null ){ | ||
142 | if( is_array( $variable ) ) | ||
143 | $this->var += $variable; | ||
144 | else | ||
145 | $this->var[ $variable ] = $value; | ||
146 | } | ||
147 | |||
148 | |||
149 | |||
150 | /** | ||
151 | * Draw the template | ||
152 | * eg. $html = $tpl->draw( 'demo', TRUE ); // return template in string | ||
153 | * or $tpl->draw( $tpl_name ); // echo the template | ||
154 | * | ||
155 | * @param string $tpl_name template to load | ||
156 | * @param boolean $return_string true=return a string, false=echo the template | ||
157 | * @return string | ||
158 | */ | ||
159 | |||
160 | function draw( $tpl_name, $return_string = false ){ | ||
161 | |||
162 | try { | ||
163 | // compile the template if necessary and set the template filepath | ||
164 | $this->check_template( $tpl_name ); | ||
165 | } catch (RainTpl_Exception $e) { | ||
166 | $output = $this->printDebug($e); | ||
167 | die($output); | ||
168 | } | ||
169 | |||
170 | // Cache is off and, return_string is false | ||
171 | // Rain just echo the template | ||
172 | |||
173 | if( !$this->cache && !$return_string ){ | ||
174 | extract( $this->var ); | ||
175 | include $this->tpl['compiled_filename']; | ||
176 | unset( $this->tpl ); | ||
177 | } | ||
178 | |||
179 | |||
180 | // cache or return_string are enabled | ||
181 | // rain get the output buffer to save the output in the cache or to return it as string | ||
182 | |||
183 | else{ | ||
184 | |||
185 | //---------------------- | ||
186 | // get the output buffer | ||
187 | //---------------------- | ||
188 | ob_start(); | ||
189 | extract( $this->var ); | ||
190 | include $this->tpl['compiled_filename']; | ||
191 | $raintpl_contents = ob_get_clean(); | ||
192 | //---------------------- | ||
193 | |||
194 | |||
195 | // save the output in the cache | ||
196 | if( $this->cache ) | ||
197 | file_put_contents( $this->tpl['cache_filename'], "<?php if(!class_exists('raintpl')){exit;}?>" . $raintpl_contents ); | ||
198 | |||
199 | // free memory | ||
200 | unset( $this->tpl ); | ||
201 | |||
202 | // return or print the template | ||
203 | if( $return_string ) return $raintpl_contents; else echo $raintpl_contents; | ||
204 | |||
205 | } | ||
206 | |||
207 | } | ||
208 | |||
209 | |||
210 | |||
211 | /** | ||
212 | * If exists a valid cache for this template it returns the cache | ||
213 | * | ||
214 | * @param string $tpl_name Name of template (set the same of draw) | ||
215 | * @param int $expiration_time Set after how many seconds the cache expire and must be regenerated | ||
216 | * @return string it return the HTML or null if the cache must be recreated | ||
217 | */ | ||
218 | |||
219 | function cache( $tpl_name, $expire_time = self::CACHE_EXPIRE_TIME, $cache_id = null ){ | ||
220 | |||
221 | // set the cache_id | ||
222 | $this->cache_id = $cache_id; | ||
223 | |||
224 | if( !$this->check_template( $tpl_name ) && file_exists( $this->tpl['cache_filename'] ) && ( time() - filemtime( $this->tpl['cache_filename'] ) < $expire_time ) ) | ||
225 | return substr( file_get_contents( $this->tpl['cache_filename'] ), 43 ); | ||
226 | else{ | ||
227 | //delete the cache of the selected template | ||
228 | if (file_exists($this->tpl['cache_filename'])) | ||
229 | unlink($this->tpl['cache_filename'] ); | ||
230 | $this->cache = true; | ||
231 | } | ||
232 | } | ||
233 | |||
234 | |||
235 | |||
236 | /** | ||
237 | * Configure the settings of RainTPL | ||
238 | * | ||
239 | */ | ||
240 | static function configure( $setting, $value = null ){ | ||
241 | if( is_array( $setting ) ) | ||
242 | foreach( $setting as $key => $value ) | ||
243 | self::configure( $key, $value ); | ||
244 | else if( property_exists( __CLASS__, $setting ) ){ | ||
245 | self::$$setting = $value; | ||
246 | self::$config_name_sum[ $setting ] = $value; // take trace of all config | ||
247 | } | ||
248 | } | ||
249 | |||
250 | |||
251 | |||
252 | // check if has to compile the template | ||
253 | // return true if the template has changed | ||
254 | protected function check_template( $tpl_name ){ | ||
255 | |||
256 | if( !isset($this->tpl['checked']) ){ | ||
257 | |||
258 | $tpl_basename = basename( $tpl_name ); // template basename | ||
259 | $tpl_basedir = strpos($tpl_name,"/") ? dirname($tpl_name) . '/' : null; // template basedirectory | ||
260 | $tpl_dir = self::$tpl_dir . $tpl_basedir; // template directory | ||
261 | $this->tpl['tpl_filename'] = $tpl_dir . $tpl_basename . '.' . self::$tpl_ext; // template filename | ||
262 | $temp_compiled_filename = self::$cache_dir . $tpl_basename . "." . md5( $tpl_dir . serialize(self::$config_name_sum)); | ||
263 | $this->tpl['compiled_filename'] = $temp_compiled_filename . '.rtpl.php'; // cache filename | ||
264 | $this->tpl['cache_filename'] = $temp_compiled_filename . '.s_' . $this->cache_id . '.rtpl.php'; // static cache filename | ||
265 | |||
266 | // if the template doesn't exsist throw an error | ||
267 | if( self::$check_template_update && !file_exists( $this->tpl['tpl_filename'] ) ){ | ||
268 | $e = new RainTpl_NotFoundException( 'Template '. $tpl_basename .' not found!' ); | ||
269 | throw $e->setTemplateFile($this->tpl['tpl_filename']); | ||
270 | } | ||
271 | |||
272 | // file doesn't exsist, or the template was updated, Rain will compile the template | ||
273 | if( !file_exists( $this->tpl['compiled_filename'] ) || ( self::$check_template_update && filemtime($this->tpl['compiled_filename']) < filemtime( $this->tpl['tpl_filename'] ) ) ){ | ||
274 | $this->compileFile( $tpl_basename, $tpl_basedir, $this->tpl['tpl_filename'], self::$cache_dir, $this->tpl['compiled_filename'] ); | ||
275 | return true; | ||
276 | } | ||
277 | $this->tpl['checked'] = true; | ||
278 | } | ||
279 | } | ||
280 | |||
281 | |||
282 | /** | ||
283 | * execute stripslaches() on the xml block. Invoqued by preg_replace_callback function below | ||
284 | * @access protected | ||
285 | */ | ||
286 | protected function xml_reSubstitution($capture) { | ||
287 | return "<?php echo '<?xml ".stripslashes($capture[1])." ?>'; ?>"; | ||
288 | } | ||
289 | |||
290 | /** | ||
291 | * Compile and write the compiled template file | ||
292 | * @access protected | ||
293 | */ | ||
294 | protected function compileFile( $tpl_basename, $tpl_basedir, $tpl_filename, $cache_dir, $compiled_filename ){ | ||
295 | |||
296 | //read template file | ||
297 | $this->tpl['source'] = $template_code = file_get_contents( $tpl_filename ); | ||
298 | |||
299 | //xml substitution | ||
300 | $template_code = preg_replace( "/<\?xml(.*?)\?>/s", "##XML\\1XML##", $template_code ); | ||
301 | |||
302 | //disable php tag | ||
303 | if( !self::$php_enabled ) | ||
304 | $template_code = str_replace( array("<?","?>"), array("<?","?>"), $template_code ); | ||
305 | |||
306 | //xml re-substitution | ||
307 | $template_code = preg_replace_callback ( "/##XML(.*?)XML##/s", array($this, 'xml_reSubstitution'), $template_code ); | ||
308 | |||
309 | //compile template | ||
310 | $template_compiled = "<?php if(!class_exists('raintpl')){exit;}?>" . $this->compileTemplate( $template_code, $tpl_basedir ); | ||
311 | |||
312 | |||
313 | // fix the php-eating-newline-after-closing-tag-problem | ||
314 | $template_compiled = str_replace( "?>\n", "?>\n\n", $template_compiled ); | ||
315 | |||
316 | // create directories | ||
317 | if( !is_dir( $cache_dir ) ) | ||
318 | mkdir( $cache_dir, 0755, true ); | ||
319 | |||
320 | if( !is_writable( $cache_dir ) ) | ||
321 | throw new RainTpl_Exception ('Cache directory ' . $cache_dir . 'doesn\'t have write permission. Set write permission or set RAINTPL_CHECK_TEMPLATE_UPDATE to false. More details on http://www.raintpl.com/Documentation/Documentation-for-PHP-developers/Configuration/'); | ||
322 | |||
323 | //write compiled file | ||
324 | file_put_contents( $compiled_filename, $template_compiled ); | ||
325 | } | ||
326 | |||
327 | |||
328 | |||
329 | /** | ||
330 | * Compile template | ||
331 | * @access protected | ||
332 | */ | ||
333 | protected function compileTemplate( $template_code, $tpl_basedir ){ | ||
334 | |||
335 | //tag list | ||
336 | $tag_regexp = array( 'loop' => '(\{loop(?: name){0,1}="\${0,1}[^"]*"\})', | ||
337 | 'loop_close' => '(\{\/loop\})', | ||
338 | 'if' => '(\{if(?: condition){0,1}="[^"]*"\})', | ||
339 | 'elseif' => '(\{elseif(?: condition){0,1}="[^"]*"\})', | ||
340 | 'else' => '(\{else\})', | ||
341 | 'if_close' => '(\{\/if\})', | ||
342 | 'function' => '(\{function="[^"]*"\})', | ||
343 | 'noparse' => '(\{noparse\})', | ||
344 | 'noparse_close'=> '(\{\/noparse\})', | ||
345 | 'ignore' => '(\{ignore\}|\{\*)', | ||
346 | 'ignore_close' => '(\{\/ignore\}|\*\})', | ||
347 | 'include' => '(\{include="[^"]*"(?: cache="[^"]*")?\})', | ||
348 | 'template_info'=> '(\{\$template_info\})', | ||
349 | 'function' => '(\{function="(\w*?)(?:.*?)"\})' | ||
350 | ); | ||
351 | |||
352 | $tag_regexp = "/" . join( "|", $tag_regexp ) . "/"; | ||
353 | |||
354 | //split the code with the tags regexp | ||
355 | $template_code = preg_split ( $tag_regexp, $template_code, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY ); | ||
356 | |||
357 | //path replace (src of img, background and href of link) | ||
358 | $template_code = $this->path_replace( $template_code, $tpl_basedir ); | ||
359 | |||
360 | //compile the code | ||
361 | $compiled_code = $this->compileCode( $template_code ); | ||
362 | |||
363 | //return the compiled code | ||
364 | return $compiled_code; | ||
365 | |||
366 | } | ||
367 | |||
368 | |||
369 | |||
370 | /** | ||
371 | * Compile the code | ||
372 | * @access protected | ||
373 | */ | ||
374 | protected function compileCode( $parsed_code ){ | ||
375 | |||
376 | //variables initialization | ||
377 | $compiled_code = $open_if = $comment_is_open = $ignore_is_open = null; | ||
378 | $loop_level = 0; | ||
379 | |||
380 | //read all parsed code | ||
381 | while( $html = array_shift( $parsed_code ) ){ | ||
382 | |||
383 | //close ignore tag | ||
384 | if( !$comment_is_open && ( strpos( $html, '{/ignore}' ) !== FALSE || strpos( $html, '*}' ) !== FALSE ) ) | ||
385 | $ignore_is_open = false; | ||
386 | |||
387 | //code between tag ignore id deleted | ||
388 | elseif( $ignore_is_open ){ | ||
389 | //ignore the code | ||
390 | } | ||
391 | |||
392 | //close no parse tag | ||
393 | elseif( strpos( $html, '{/noparse}' ) !== FALSE ) | ||
394 | $comment_is_open = false; | ||
395 | |||
396 | //code between tag noparse is not compiled | ||
397 | elseif( $comment_is_open ) | ||
398 | $compiled_code .= $html; | ||
399 | |||
400 | //ignore | ||
401 | elseif( strpos( $html, '{ignore}' ) !== FALSE || strpos( $html, '{*' ) !== FALSE ) | ||
402 | $ignore_is_open = true; | ||
403 | |||
404 | //noparse | ||
405 | elseif( strpos( $html, '{noparse}' ) !== FALSE ) | ||
406 | $comment_is_open = true; | ||
407 | |||
408 | //include tag | ||
409 | elseif( preg_match( '/\{include="([^"]*)"(?: cache="([^"]*)"){0,1}\}/', $html, $code ) ){ | ||
410 | |||
411 | //variables substitution | ||
412 | $include_var = $this->var_replace( $code[ 1 ], $left_delimiter = null, $right_delimiter = null, $php_left_delimiter = '".' , $php_right_delimiter = '."', $loop_level ); | ||
413 | |||
414 | // if the cache is active | ||
415 | if( isset($code[ 2 ]) ){ | ||
416 | |||
417 | //dynamic include | ||
418 | $compiled_code .= '<?php $tpl = new '.get_class($this).';' . | ||
419 | 'if( $cache = $tpl->cache( $template = basename("'.$include_var.'") ) )' . | ||
420 | ' echo $cache;' . | ||
421 | 'else{' . | ||
422 | ' $tpl_dir_temp = self::$tpl_dir;' . | ||
423 | ' $tpl->assign( $this->var );' . | ||
424 | ( !$loop_level ? null : '$tpl->assign( "key", $key'.$loop_level.' ); $tpl->assign( "value", $value'.$loop_level.' );' ). | ||
425 | ' $tpl->draw( dirname("'.$include_var.'") . ( substr("'.$include_var.'",-1,1) != "/" ? "/" : "" ) . basename("'.$include_var.'") );'. | ||
426 | '} ?>'; | ||
427 | } | ||
428 | else{ | ||
429 | |||
430 | //dynamic include | ||
431 | $compiled_code .= '<?php $tpl = new '.get_class($this).';' . | ||
432 | '$tpl_dir_temp = self::$tpl_dir;' . | ||
433 | '$tpl->assign( $this->var );' . | ||
434 | ( !$loop_level ? null : '$tpl->assign( "key", $key'.$loop_level.' ); $tpl->assign( "value", $value'.$loop_level.' );' ). | ||
435 | '$tpl->draw( dirname("'.$include_var.'") . ( substr("'.$include_var.'",-1,1) != "/" ? "/" : "" ) . basename("'.$include_var.'") );'. | ||
436 | '?>'; | ||
437 | |||
438 | |||
439 | } | ||
440 | |||
441 | } | ||
442 | |||
443 | //loop | ||
444 | elseif( preg_match( '/\{loop(?: name){0,1}="\${0,1}([^"]*)"\}/', $html, $code ) ){ | ||
445 | |||
446 | //increase the loop counter | ||
447 | $loop_level++; | ||
448 | |||
449 | //replace the variable in the loop | ||
450 | $var = $this->var_replace( '$' . $code[ 1 ], $tag_left_delimiter=null, $tag_right_delimiter=null, $php_left_delimiter=null, $php_right_delimiter=null, $loop_level-1 ); | ||
451 | |||
452 | //loop variables | ||
453 | $counter = "\$counter$loop_level"; // count iteration | ||
454 | $key = "\$key$loop_level"; // key | ||
455 | $value = "\$value$loop_level"; // value | ||
456 | |||
457 | //loop code | ||
458 | $compiled_code .= "<?php $counter=-1; if( isset($var) && is_array($var) && sizeof($var) ) foreach( $var as $key => $value ){ $counter++; ?>"; | ||
459 | |||
460 | } | ||
461 | |||
462 | //close loop tag | ||
463 | elseif( strpos( $html, '{/loop}' ) !== FALSE ) { | ||
464 | |||
465 | //iterator | ||
466 | $counter = "\$counter$loop_level"; | ||
467 | |||
468 | //decrease the loop counter | ||
469 | $loop_level--; | ||
470 | |||
471 | //close loop code | ||
472 | $compiled_code .= "<?php } ?>"; | ||
473 | |||
474 | } | ||
475 | |||
476 | //if | ||
477 | elseif( preg_match( '/\{if(?: condition){0,1}="([^"]*)"\}/', $html, $code ) ){ | ||
478 | |||
479 | //increase open if counter (for intendation) | ||
480 | $open_if++; | ||
481 | |||
482 | //tag | ||
483 | $tag = $code[ 0 ]; | ||
484 | |||
485 | //condition attribute | ||
486 | $condition = $code[ 1 ]; | ||
487 | |||
488 | // check if there's any function disabled by black_list | ||
489 | $this->function_check( $tag ); | ||
490 | |||
491 | //variable substitution into condition (no delimiter into the condition) | ||
492 | $parsed_condition = $this->var_replace( $condition, $tag_left_delimiter = null, $tag_right_delimiter = null, $php_left_delimiter = null, $php_right_delimiter = null, $loop_level ); | ||
493 | |||
494 | //if code | ||
495 | $compiled_code .= "<?php if( $parsed_condition ){ ?>"; | ||
496 | |||
497 | } | ||
498 | |||
499 | //elseif | ||
500 | elseif( preg_match( '/\{elseif(?: condition){0,1}="([^"]*)"\}/', $html, $code ) ){ | ||
501 | |||
502 | //tag | ||
503 | $tag = $code[ 0 ]; | ||
504 | |||
505 | //condition attribute | ||
506 | $condition = $code[ 1 ]; | ||
507 | |||
508 | //variable substitution into condition (no delimiter into the condition) | ||
509 | $parsed_condition = $this->var_replace( $condition, $tag_left_delimiter = null, $tag_right_delimiter = null, $php_left_delimiter = null, $php_right_delimiter = null, $loop_level ); | ||
510 | |||
511 | //elseif code | ||
512 | $compiled_code .= "<?php }elseif( $parsed_condition ){ ?>"; | ||
513 | } | ||
514 | |||
515 | //else | ||
516 | elseif( strpos( $html, '{else}' ) !== FALSE ) { | ||
517 | |||
518 | //else code | ||
519 | $compiled_code .= '<?php }else{ ?>'; | ||
520 | |||
521 | } | ||
522 | |||
523 | //close if tag | ||
524 | elseif( strpos( $html, '{/if}' ) !== FALSE ) { | ||
525 | |||
526 | //decrease if counter | ||
527 | $open_if--; | ||
528 | |||
529 | // close if code | ||
530 | $compiled_code .= '<?php } ?>'; | ||
531 | |||
532 | } | ||
533 | |||
534 | //function | ||
535 | elseif( preg_match( '/\{function="(\w*)(.*?)"\}/', $html, $code ) ){ | ||
536 | |||
537 | //tag | ||
538 | $tag = $code[ 0 ]; | ||
539 | |||
540 | //function | ||
541 | $function = $code[ 1 ]; | ||
542 | |||
543 | // check if there's any function disabled by black_list | ||
544 | $this->function_check( $tag ); | ||
545 | |||
546 | if( empty( $code[ 2 ] ) ) | ||
547 | $parsed_function = $function . "()"; | ||
548 | else | ||
549 | // parse the function | ||
550 | $parsed_function = $function . $this->var_replace( $code[ 2 ], $tag_left_delimiter = null, $tag_right_delimiter = null, $php_left_delimiter = null, $php_right_delimiter = null, $loop_level ); | ||
551 | |||
552 | //if code | ||
553 | $compiled_code .= "<?php echo $parsed_function; ?>"; | ||
554 | } | ||
555 | |||
556 | // show all vars | ||
557 | elseif ( strpos( $html, '{$template_info}' ) !== FALSE ) { | ||
558 | |||
559 | //tag | ||
560 | $tag = '{$template_info}'; | ||
561 | |||
562 | //if code | ||
563 | $compiled_code .= '<?php echo "<pre>"; print_r( $this->var ); echo "</pre>"; ?>'; | ||
564 | } | ||
565 | |||
566 | |||
567 | //all html code | ||
568 | else{ | ||
569 | |||
570 | //variables substitution (es. {$title}) | ||
571 | $html = $this->var_replace( $html, $left_delimiter = '\{', $right_delimiter = '\}', $php_left_delimiter = '<?php ', $php_right_delimiter = ';?>', $loop_level, $echo = true ); | ||
572 | //const substitution (es. {#CONST#}) | ||
573 | $html = $this->const_replace( $html, $left_delimiter = '\{', $right_delimiter = '\}', $php_left_delimiter = '<?php ', $php_right_delimiter = ';?>', $loop_level, $echo = true ); | ||
574 | //functions substitution (es. {"string"|functions}) | ||
575 | $compiled_code .= $this->func_replace( $html, $left_delimiter = '\{', $right_delimiter = '\}', $php_left_delimiter = '<?php ', $php_right_delimiter = ';?>', $loop_level, $echo = true ); | ||
576 | } | ||
577 | } | ||
578 | |||
579 | if( $open_if > 0 ) { | ||
580 | $e = new RainTpl_SyntaxException('Error! You need to close an {if} tag in ' . $this->tpl['tpl_filename'] . ' template'); | ||
581 | throw $e->setTemplateFile($this->tpl['tpl_filename']); | ||
582 | } | ||
583 | return $compiled_code; | ||
584 | } | ||
585 | |||
586 | |||
587 | /** | ||
588 | * Reduce a path, eg. www/library/../filepath//file => www/filepath/file | ||
589 | * @param type $path | ||
590 | * @return type | ||
591 | */ | ||
592 | protected function reduce_path( $path ){ | ||
593 | $path = str_replace( "://", "@not_replace@", $path ); | ||
594 | $path = str_replace( "//", "/", $path ); | ||
595 | $path = str_replace( "@not_replace@", "://", $path ); | ||
596 | return preg_replace('/\w+\/\.\.\//', '', $path ); | ||
597 | } | ||
598 | |||
599 | |||
600 | |||
601 | /** | ||
602 | * replace the path of image src, link href and a href. | ||
603 | * url => template_dir/url | ||
604 | * url# => url | ||
605 | * http://url => http://url | ||
606 | * | ||
607 | * @param string $html | ||
608 | * @return string html sostituito | ||
609 | */ | ||
610 | protected function path_replace( $html, $tpl_basedir ){ | ||
611 | |||
612 | if( self::$path_replace ){ | ||
613 | |||
614 | $tpl_dir = self::$base_url . self::$tpl_dir . $tpl_basedir; | ||
615 | |||
616 | // reduce the path | ||
617 | $path = $this->reduce_path($tpl_dir); | ||
618 | |||
619 | $exp = $sub = array(); | ||
620 | |||
621 | if( in_array( "img", self::$path_replace_list ) ){ | ||
622 | $exp = array( '/<img(.*?)src=(?:")(http|https)\:\/\/([^"]+?)(?:")/i', '/<img(.*?)src=(?:")([^"]+?)#(?:")/i', '/<img(.*?)src="(.*?)"/', '/<img(.*?)src=(?:\@)([^"]+?)(?:\@)/i' ); | ||
623 | $sub = array( '<img$1src=@$2://$3@', '<img$1src=@$2@', '<img$1src="' . $path . '$2"', '<img$1src="$2"' ); | ||
624 | } | ||
625 | |||
626 | if( in_array( "script", self::$path_replace_list ) ){ | ||
627 | $exp = array_merge( $exp , array( '/<script(.*?)src=(?:")(http|https)\:\/\/([^"]+?)(?:")/i', '/<script(.*?)src=(?:")([^"]+?)#(?:")/i', '/<script(.*?)src="(.*?)"/', '/<script(.*?)src=(?:\@)([^"]+?)(?:\@)/i' ) ); | ||
628 | $sub = array_merge( $sub , array( '<script$1src=@$2://$3@', '<script$1src=@$2@', '<script$1src="' . $path . '$2"', '<script$1src="$2"' ) ); | ||
629 | } | ||
630 | |||
631 | if( in_array( "link", self::$path_replace_list ) ){ | ||
632 | $exp = array_merge( $exp , array( '/<link(.*?)href=(?:")(http|https)\:\/\/([^"]+?)(?:")/i', '/<link(.*?)href=(?:")([^"]+?)#(?:")/i', '/<link(.*?)href="(.*?)"/', '/<link(.*?)href=(?:\@)([^"]+?)(?:\@)/i' ) ); | ||
633 | $sub = array_merge( $sub , array( '<link$1href=@$2://$3@', '<link$1href=@$2@' , '<link$1href="' . $path . '$2"', '<link$1href="$2"' ) ); | ||
634 | } | ||
635 | |||
636 | if( in_array( "a", self::$path_replace_list ) ){ | ||
637 | $exp = array_merge( $exp , array( '/<a(.*?)href=(?:")(http\:\/\/|https\:\/\/|javascript:)([^"]+?)(?:")/i', '/<a(.*?)href="(.*?)"/', '/<a(.*?)href=(?:\@)([^"]+?)(?:\@)/i' ) ); | ||
638 | $sub = array_merge( $sub , array( '<a$1href=@$2$3@', '<a$1href="' . self::$base_url . '$2"', '<a$1href="$2"' ) ); | ||
639 | } | ||
640 | |||
641 | if( in_array( "input", self::$path_replace_list ) ){ | ||
642 | $exp = array_merge( $exp , array( '/<input(.*?)src=(?:")(http|https)\:\/\/([^"]+?)(?:")/i', '/<input(.*?)src=(?:")([^"]+?)#(?:")/i', '/<input(.*?)src="(.*?)"/', '/<input(.*?)src=(?:\@)([^"]+?)(?:\@)/i' ) ); | ||
643 | $sub = array_merge( $sub , array( '<input$1src=@$2://$3@', '<input$1src=@$2@', '<input$1src="' . $path . '$2"', '<input$1src="$2"' ) ); | ||
644 | } | ||
645 | |||
646 | return preg_replace( $exp, $sub, $html ); | ||
647 | |||
648 | } | ||
649 | else | ||
650 | return $html; | ||
651 | |||
652 | } | ||
653 | |||
654 | |||
655 | |||
656 | |||
657 | |||
658 | // replace const | ||
659 | function const_replace( $html, $tag_left_delimiter, $tag_right_delimiter, $php_left_delimiter = null, $php_right_delimiter = null, $loop_level = null, $echo = null ){ | ||
660 | // const | ||
661 | return preg_replace( '/\{\#(\w+)\#{0,1}\}/', $php_left_delimiter . ( $echo ? " echo " : null ) . '\\1' . $php_right_delimiter, $html ); | ||
662 | } | ||
663 | |||
664 | |||
665 | |||
666 | // replace functions/modifiers on constants and strings | ||
667 | function func_replace( $html, $tag_left_delimiter, $tag_right_delimiter, $php_left_delimiter = null, $php_right_delimiter = null, $loop_level = null, $echo = null ){ | ||
668 | |||
669 | preg_match_all( '/' . '\{\#{0,1}(\"{0,1}.*?\"{0,1})(\|\w.*?)\#{0,1}\}' . '/', $html, $matches ); | ||
670 | |||
671 | for( $i=0, $n=count($matches[0]); $i<$n; $i++ ){ | ||
672 | |||
673 | //complete tag ex: {$news.title|substr:0,100} | ||
674 | $tag = $matches[ 0 ][ $i ]; | ||
675 | |||
676 | //variable name ex: news.title | ||
677 | $var = $matches[ 1 ][ $i ]; | ||
678 | |||
679 | //function and parameters associate to the variable ex: substr:0,100 | ||
680 | $extra_var = $matches[ 2 ][ $i ]; | ||
681 | |||
682 | // check if there's any function disabled by black_list | ||
683 | $this->function_check( $tag ); | ||
684 | |||
685 | $extra_var = $this->var_replace( $extra_var, null, null, null, null, $loop_level ); | ||
686 | |||
687 | |||
688 | // check if there's an operator = in the variable tags, if there's this is an initialization so it will not output any value | ||
689 | $is_init_variable = preg_match( "/^(\s*?)\=[^=](.*?)$/", $extra_var ); | ||
690 | |||
691 | //function associate to variable | ||
692 | $function_var = ( $extra_var and $extra_var[0] == '|') ? substr( $extra_var, 1 ) : null; | ||
693 | |||
694 | //variable path split array (ex. $news.title o $news[title]) or object (ex. $news->title) | ||
695 | $temp = preg_split( "/\.|\[|\-\>/", $var ); | ||
696 | |||
697 | //variable name | ||
698 | $var_name = $temp[ 0 ]; | ||
699 | |||
700 | //variable path | ||
701 | $variable_path = substr( $var, strlen( $var_name ) ); | ||
702 | |||
703 | //parentesis transform [ e ] in [" e in "] | ||
704 | $variable_path = str_replace( '[', '["', $variable_path ); | ||
705 | $variable_path = str_replace( ']', '"]', $variable_path ); | ||
706 | |||
707 | //transform .$variable in ["$variable"] | ||
708 | $variable_path = preg_replace('/\.\$(\w+)/', '["$\\1"]', $variable_path ); | ||
709 | |||
710 | //transform [variable] in ["variable"] | ||
711 | $variable_path = preg_replace('/\.(\w+)/', '["\\1"]', $variable_path ); | ||
712 | |||
713 | //if there's a function | ||
714 | if( $function_var ){ | ||
715 | |||
716 | // check if there's a function or a static method and separate, function by parameters | ||
717 | $function_var = str_replace("::", "@double_dot@", $function_var ); | ||
718 | |||
719 | // get the position of the first : | ||
720 | if( $dot_position = strpos( $function_var, ":" ) ){ | ||
721 | |||
722 | // get the function and the parameters | ||
723 | $function = substr( $function_var, 0, $dot_position ); | ||
724 | $params = substr( $function_var, $dot_position+1 ); | ||
725 | |||
726 | } | ||
727 | else{ | ||
728 | |||
729 | //get the function | ||
730 | $function = str_replace( "@double_dot@", "::", $function_var ); | ||
731 | $params = null; | ||
732 | |||
733 | } | ||
734 | |||
735 | // replace back the @double_dot@ with :: | ||
736 | $function = str_replace( "@double_dot@", "::", $function ); | ||
737 | $params = str_replace( "@double_dot@", "::", $params ); | ||
738 | |||
739 | |||
740 | } | ||
741 | else | ||
742 | $function = $params = null; | ||
743 | |||
744 | $php_var = $var_name . $variable_path; | ||
745 | |||
746 | // compile the variable for php | ||
747 | if( isset( $function ) ){ | ||
748 | if( $php_var ) | ||
749 | $php_var = $php_left_delimiter . ( !$is_init_variable && $echo ? 'echo ' : null ) . ( $params ? "( $function( $php_var, $params ) )" : "$function( $php_var )" ) . $php_right_delimiter; | ||
750 | else | ||
751 | $php_var = $php_left_delimiter . ( !$is_init_variable && $echo ? 'echo ' : null ) . ( $params ? "( $function( $params ) )" : "$function()" ) . $php_right_delimiter; | ||
752 | } | ||
753 | else | ||
754 | $php_var = $php_left_delimiter . ( !$is_init_variable && $echo ? 'echo ' : null ) . $php_var . $extra_var . $php_right_delimiter; | ||
755 | |||
756 | $html = str_replace( $tag, $php_var, $html ); | ||
757 | |||
758 | } | ||
759 | |||
760 | return $html; | ||
761 | |||
762 | } | ||
763 | |||
764 | |||
765 | |||
766 | function var_replace( $html, $tag_left_delimiter, $tag_right_delimiter, $php_left_delimiter = null, $php_right_delimiter = null, $loop_level = null, $echo = null ){ | ||
767 | |||
768 | //all variables | ||
769 | if( preg_match_all( '/' . $tag_left_delimiter . '\$(\w+(?:\.\${0,1}[A-Za-z0-9_]+)*(?:(?:\[\${0,1}[A-Za-z0-9_]+\])|(?:\-\>\${0,1}[A-Za-z0-9_]+))*)(.*?)' . $tag_right_delimiter . '/', $html, $matches ) ){ | ||
770 | |||
771 | for( $parsed=array(), $i=0, $n=count($matches[0]); $i<$n; $i++ ) | ||
772 | $parsed[$matches[0][$i]] = array('var'=>$matches[1][$i],'extra_var'=>$matches[2][$i]); | ||
773 | |||
774 | foreach( $parsed as $tag => $array ){ | ||
775 | |||
776 | //variable name ex: news.title | ||
777 | $var = $array['var']; | ||
778 | |||
779 | //function and parameters associate to the variable ex: substr:0,100 | ||
780 | $extra_var = $array['extra_var']; | ||
781 | |||
782 | // check if there's any function disabled by black_list | ||
783 | $this->function_check( $tag ); | ||
784 | |||
785 | $extra_var = $this->var_replace( $extra_var, null, null, null, null, $loop_level ); | ||
786 | |||
787 | // check if there's an operator = in the variable tags, if there's this is an initialization so it will not output any value | ||
788 | $is_init_variable = preg_match( "/^[a-z_A-Z\.\[\](\-\>)]*=[^=]*$/", $extra_var ); | ||
789 | |||
790 | //function associate to variable | ||
791 | $function_var = ( $extra_var and $extra_var[0] == '|') ? substr( $extra_var, 1 ) : null; | ||
792 | |||
793 | //variable path split array (ex. $news.title o $news[title]) or object (ex. $news->title) | ||
794 | $temp = preg_split( "/\.|\[|\-\>/", $var ); | ||
795 | |||
796 | //variable name | ||
797 | $var_name = $temp[ 0 ]; | ||
798 | |||
799 | //variable path | ||
800 | $variable_path = substr( $var, strlen( $var_name ) ); | ||
801 | |||
802 | //parentesis transform [ e ] in [" e in "] | ||
803 | $variable_path = str_replace( '[', '["', $variable_path ); | ||
804 | $variable_path = str_replace( ']', '"]', $variable_path ); | ||
805 | |||
806 | //transform .$variable in ["$variable"] and .variable in ["variable"] | ||
807 | $variable_path = preg_replace('/\.(\${0,1}\w+)/', '["\\1"]', $variable_path ); | ||
808 | |||
809 | // if is an assignment also assign the variable to $this->var['value'] | ||
810 | if( $is_init_variable ) | ||
811 | $extra_var = "=\$this->var['{$var_name}']{$variable_path}" . $extra_var; | ||
812 | |||
813 | |||
814 | |||
815 | //if there's a function | ||
816 | if( $function_var ){ | ||
817 | |||
818 | // check if there's a function or a static method and separate, function by parameters | ||
819 | $function_var = str_replace("::", "@double_dot@", $function_var ); | ||
820 | |||
821 | |||
822 | // get the position of the first : | ||
823 | if( $dot_position = strpos( $function_var, ":" ) ){ | ||
824 | |||
825 | // get the function and the parameters | ||
826 | $function = substr( $function_var, 0, $dot_position ); | ||
827 | $params = substr( $function_var, $dot_position+1 ); | ||
828 | |||
829 | } | ||
830 | else{ | ||
831 | |||
832 | //get the function | ||
833 | $function = str_replace( "@double_dot@", "::", $function_var ); | ||
834 | $params = null; | ||
835 | |||
836 | } | ||
837 | |||
838 | // replace back the @double_dot@ with :: | ||
839 | $function = str_replace( "@double_dot@", "::", $function ); | ||
840 | $params = str_replace( "@double_dot@", "::", $params ); | ||
841 | } | ||
842 | else | ||
843 | $function = $params = null; | ||
844 | |||
845 | //if it is inside a loop | ||
846 | if( $loop_level ){ | ||
847 | //verify the variable name | ||
848 | if( $var_name == 'key' ) | ||
849 | $php_var = '$key' . $loop_level; | ||
850 | elseif( $var_name == 'value' ) | ||
851 | $php_var = '$value' . $loop_level . $variable_path; | ||
852 | elseif( $var_name == 'counter' ) | ||
853 | $php_var = '$counter' . $loop_level; | ||
854 | else | ||
855 | $php_var = '$' . $var_name . $variable_path; | ||
856 | }else | ||
857 | $php_var = '$' . $var_name . $variable_path; | ||
858 | |||
859 | // compile the variable for php | ||
860 | if( isset( $function ) ) | ||
861 | $php_var = $php_left_delimiter . ( !$is_init_variable && $echo ? 'echo ' : null ) . ( $params ? "( $function( $php_var, $params ) )" : "$function( $php_var )" ) . $php_right_delimiter; | ||
862 | else | ||
863 | $php_var = $php_left_delimiter . ( !$is_init_variable && $echo ? 'echo ' : null ) . $php_var . $extra_var . $php_right_delimiter; | ||
864 | |||
865 | $html = str_replace( $tag, $php_var, $html ); | ||
866 | |||
867 | |||
868 | } | ||
869 | } | ||
870 | |||
871 | return $html; | ||
872 | } | ||
873 | |||
874 | |||
875 | |||
876 | /** | ||
877 | * Check if function is in black list (sandbox) | ||
878 | * | ||
879 | * @param string $code | ||
880 | * @param string $tag | ||
881 | */ | ||
882 | protected function function_check( $code ){ | ||
883 | |||
884 | $preg = '#(\W|\s)' . implode( '(\W|\s)|(\W|\s)', self::$black_list ) . '(\W|\s)#'; | ||
885 | |||
886 | // check if the function is in the black list (or not in white list) | ||
887 | if( count(self::$black_list) && preg_match( $preg, $code, $match ) ){ | ||
888 | |||
889 | // find the line of the error | ||
890 | $line = 0; | ||
891 | $rows=explode("\n",$this->tpl['source']); | ||
892 | while( !strpos($rows[$line],$code) ) | ||
893 | $line++; | ||
894 | |||
895 | // stop the execution of the script | ||
896 | $e = new RainTpl_SyntaxException('Unallowed syntax in ' . $this->tpl['tpl_filename'] . ' template'); | ||
897 | throw $e->setTemplateFile($this->tpl['tpl_filename']) | ||
898 | ->setTag($code) | ||
899 | ->setTemplateLine($line); | ||
900 | } | ||
901 | |||
902 | } | ||
903 | |||
904 | /** | ||
905 | * Prints debug info about exception or passes it further if debug is disabled. | ||
906 | * | ||
907 | * @param RainTpl_Exception $e | ||
908 | * @return string | ||
909 | */ | ||
910 | protected function printDebug(RainTpl_Exception $e){ | ||
911 | if (!self::$debug) { | ||
912 | throw $e; | ||
913 | } | ||
914 | $output = sprintf('<h2>Exception: %s</h2><h3>%s</h3><p>template: %s</p>', | ||
915 | get_class($e), | ||
916 | $e->getMessage(), | ||
917 | $e->getTemplateFile() | ||
918 | ); | ||
919 | if ($e instanceof RainTpl_SyntaxException) { | ||
920 | if (null != $e->getTemplateLine()) { | ||
921 | $output .= '<p>line: ' . $e->getTemplateLine() . '</p>'; | ||
922 | } | ||
923 | if (null != $e->getTag()) { | ||
924 | $output .= '<p>in tag: ' . htmlspecialchars($e->getTag()) . '</p>'; | ||
925 | } | ||
926 | if (null != $e->getTemplateLine() && null != $e->getTag()) { | ||
927 | $rows=explode("\n", htmlspecialchars($this->tpl['source'])); | ||
928 | $rows[$e->getTemplateLine()] = '<font color=red>' . $rows[$e->getTemplateLine()] . '</font>'; | ||
929 | $output .= '<h3>template code</h3>' . implode('<br />', $rows) . '</pre>'; | ||
930 | } | ||
931 | } | ||
932 | $output .= sprintf('<h3>trace</h3><p>In %s on line %d</p><pre>%s</pre>', | ||
933 | $e->getFile(), $e->getLine(), | ||
934 | nl2br(htmlspecialchars($e->getTraceAsString())) | ||
935 | ); | ||
936 | return $output; | ||
937 | } | ||
938 | } | ||
939 | |||
940 | |||
941 | /** | ||
942 | * Basic Rain tpl exception. | ||
943 | */ | ||
944 | class RainTpl_Exception extends Exception{ | ||
945 | /** | ||
946 | * Path of template file with error. | ||
947 | */ | ||
948 | protected $templateFile = ''; | ||
949 | |||
950 | /** | ||
951 | * Returns path of template file with error. | ||
952 | * | ||
953 | * @return string | ||
954 | */ | ||
955 | public function getTemplateFile() | ||
956 | { | ||
957 | return $this->templateFile; | ||
958 | } | ||
959 | |||
960 | /** | ||
961 | * Sets path of template file with error. | ||
962 | * | ||
963 | * @param string $templateFile | ||
964 | * @return RainTpl_Exception | ||
965 | */ | ||
966 | public function setTemplateFile($templateFile) | ||
967 | { | ||
968 | $this->templateFile = (string) $templateFile; | ||
969 | return $this; | ||
970 | } | ||
971 | } | ||
972 | |||
973 | /** | ||
974 | * Exception thrown when template file does not exists. | ||
975 | */ | ||
976 | class RainTpl_NotFoundException extends RainTpl_Exception{ | ||
977 | } | ||
978 | |||
979 | /** | ||
980 | * Exception thrown when syntax error occurs. | ||
981 | */ | ||
982 | class RainTpl_SyntaxException extends RainTpl_Exception{ | ||
983 | /** | ||
984 | * Line in template file where error has occured. | ||
985 | * | ||
986 | * @var int | null | ||
987 | */ | ||
988 | protected $templateLine = null; | ||
989 | |||
990 | /** | ||
991 | * Tag which caused an error. | ||
992 | * | ||
993 | * @var string | null | ||
994 | */ | ||
995 | protected $tag = null; | ||
996 | |||
997 | /** | ||
998 | * Returns line in template file where error has occured | ||
999 | * or null if line is not defined. | ||
1000 | * | ||
1001 | * @return int | null | ||
1002 | */ | ||
1003 | public function getTemplateLine() | ||
1004 | { | ||
1005 | return $this->templateLine; | ||
1006 | } | ||
1007 | |||
1008 | /** | ||
1009 | * Sets line in template file where error has occured. | ||
1010 | * | ||
1011 | * @param int $templateLine | ||
1012 | * @return RainTpl_SyntaxException | ||
1013 | */ | ||
1014 | public function setTemplateLine($templateLine) | ||
1015 | { | ||
1016 | $this->templateLine = (int) $templateLine; | ||
1017 | return $this; | ||
1018 | } | ||
1019 | |||
1020 | /** | ||
1021 | * Returns tag which caused an error. | ||
1022 | * | ||
1023 | * @return string | ||
1024 | */ | ||
1025 | public function getTag() | ||
1026 | { | ||
1027 | return $this->tag; | ||
1028 | } | ||
1029 | |||
1030 | /** | ||
1031 | * Sets tag which caused an error. | ||
1032 | * | ||
1033 | * @param string $tag | ||
1034 | * @return RainTpl_SyntaxException | ||
1035 | */ | ||
1036 | public function setTag($tag) | ||
1037 | { | ||
1038 | $this->tag = (string) $tag; | ||
1039 | return $this; | ||
1040 | } | ||
1041 | } | ||
1042 | |||
1043 | // -- end | ||
@@ -10,7 +10,7 @@ | |||
10 | 10 | ||
11 | include dirname(__FILE__).'/inc/config.php'; | 11 | include dirname(__FILE__).'/inc/config.php'; |
12 | 12 | ||
13 | myTool::initPhp(); | 13 | pocheTool::initPhp(); |
14 | 14 | ||
15 | # XSRF protection with token | 15 | # XSRF protection with token |
16 | if (!empty($_POST)) { | 16 | if (!empty($_POST)) { |
@@ -20,14 +20,13 @@ if (!empty($_POST)) { | |||
20 | unset($_SESSION['tokens']); | 20 | unset($_SESSION['tokens']); |
21 | } | 21 | } |
22 | 22 | ||
23 | $ref = empty($_SERVER['HTTP_REFERER']) ? '' : $_SERVER['HTTP_REFERER']; | 23 | $referer = empty($_SERVER['HTTP_REFERER']) ? '' : $_SERVER['HTTP_REFERER']; |
24 | 24 | ||
25 | if (isset($_GET['login'])) { | 25 | if (isset($_GET['login'])) { |
26 | // Login | 26 | // Login |
27 | if (!empty($_POST['login']) && !empty($_POST['password'])) { | 27 | if (!empty($_POST['login']) && !empty($_POST['password'])) { |
28 | if (Session::login($_SESSION['login'], $_SESSION['pass'], $_POST['login'], encode_string($_POST['password'] . $_POST['login']))) { | 28 | if (Session::login($_SESSION['login'], $_SESSION['pass'], $_POST['login'], encode_string($_POST['password'] . $_POST['login']))) { |
29 | logm('login successful'); | 29 | pocheTool::logm('login successful'); |
30 | $msg->add('s', 'welcome in your poche!'); | ||
31 | if (!empty($_POST['longlastingsession'])) { | 30 | if (!empty($_POST['longlastingsession'])) { |
32 | $_SESSION['longlastingsession'] = 31536000; | 31 | $_SESSION['longlastingsession'] = 31536000; |
33 | $_SESSION['expires_on'] = time() + $_SESSION['longlastingsession']; | 32 | $_SESSION['expires_on'] = time() + $_SESSION['longlastingsession']; |
@@ -37,33 +36,33 @@ if (isset($_GET['login'])) { | |||
37 | } | 36 | } |
38 | session_regenerate_id(true); | 37 | session_regenerate_id(true); |
39 | 38 | ||
40 | MyTool::redirect($ref); | 39 | pocheTool::redirect($referer); |
41 | } | 40 | } |
42 | logm('login failed'); | 41 | pocheTool::logm('login failed'); |
43 | die(_("Login failed !")); | 42 | die(_("Login failed !")); |
44 | } else { | 43 | } else { |
45 | logm('login failed'); | 44 | pocheTool::logm('login failed'); |
46 | } | 45 | } |
47 | } | 46 | } |
48 | elseif (isset($_GET['logout'])) { | 47 | elseif (isset($_GET['logout'])) { |
49 | logm('logout'); | 48 | pocheTool::logm('logout'); |
50 | Session::logout(); | 49 | Session::logout(); |
51 | MyTool::redirect(); | 50 | pocheTool::redirect(); |
52 | } | 51 | } |
53 | elseif (isset($_GET['config'])) { | 52 | elseif (isset($_GET['config'])) { |
54 | if (isset($_POST['password']) && isset($_POST['password_repeat'])) { | 53 | if (isset($_POST['password']) && isset($_POST['password_repeat'])) { |
55 | if ($_POST['password'] == $_POST['password_repeat'] && $_POST['password'] != "") { | 54 | if ($_POST['password'] == $_POST['password_repeat'] && $_POST['password'] != "") { |
56 | logm('password updated'); | 55 | pocheTool::logm('password updated'); |
57 | if (!MODE_DEMO) { | 56 | if (!MODE_DEMO) { |
58 | $store->updatePassword(encode_string($_POST['password'] . $_SESSION['login'])); | 57 | $store->updatePassword(encode_string($_POST['password'] . $_SESSION['login'])); |
59 | $msg->add('s', _('your password has been updated')); | 58 | #your password has been updated |
60 | } | 59 | } |
61 | else { | 60 | else { |
62 | $msg->add('i', _('in demo mode, you can\'t update password')); | 61 | #in demo mode, you can\'t update password |
63 | } | 62 | } |
64 | } | 63 | } |
65 | else | 64 | #else |
66 | $msg->add('e', _('your password can\'t be empty and you have to repeat it in the second field')); | 65 | #your password can\'t be empty and you have to repeat it in the second field |
67 | } | 66 | } |
68 | } | 67 | } |
69 | 68 | ||
@@ -75,18 +74,21 @@ $_SESSION['sort'] = (isset ($_REQUEST['sort'])) ? htmlentities($_REQUEST['sort | |||
75 | $id = (isset ($_REQUEST['id'])) ? htmlspecialchars($_REQUEST['id']) : ''; | 74 | $id = (isset ($_REQUEST['id'])) ? htmlspecialchars($_REQUEST['id']) : ''; |
76 | $url = (isset ($_GET['url'])) ? $_GET['url'] : ''; | 75 | $url = (isset ($_GET['url'])) ? $_GET['url'] : ''; |
77 | 76 | ||
78 | $tpl->assign('isLogged', Session::isLogged()); | 77 | $tpl_vars = array( |
79 | $tpl->assign('referer', $ref); | 78 | 'isLogged' => Session::isLogged(), |
80 | $tpl->assign('view', $view); | 79 | 'referer' => $referer, |
81 | $tpl->assign('poche_url', myTool::getUrl()); | 80 | 'view' => $view, |
82 | $tpl->assign('demo', MODE_DEMO); | 81 | 'poche_url' => pocheTool::getUrl(), |
83 | $tpl->assign('title', _('poche, a read it later open source system')); | 82 | 'demo' => MODE_DEMO, |
83 | 'title' => _('poche, a read it later open source system'), | ||
84 | ); | ||
84 | 85 | ||
85 | if (Session::isLogged()) { | 86 | if (Session::isLogged()) { |
86 | action_to_do($action, $url, $id); | 87 | action_to_do($action, $url, $id); |
87 | display_view($view, $id, $full_head); | 88 | display_view($view, $id, $full_head); |
88 | } | 89 | } |
89 | else { | 90 | else { |
90 | 91 | $template = $twig->loadTemplate('login.twig'); | |
91 | $tpl->draw('login'); | ||
92 | } | 92 | } |
93 | |||
94 | echo $template->render($tpl_vars); \ No newline at end of file | ||
diff --git a/locale/fr_FR.UTF8/LC_MESSAGES/fr_FR.UTF8.mo b/locale/fr_FR.UTF8/LC_MESSAGES/fr_FR.UTF8.mo index d996d46c..70d310d9 100644 --- a/locale/fr_FR.UTF8/LC_MESSAGES/fr_FR.UTF8.mo +++ b/locale/fr_FR.UTF8/LC_MESSAGES/fr_FR.UTF8.mo | |||
Binary files differ | |||
diff --git a/locale/fr_FR.UTF8/LC_MESSAGES/fr_FR.UTF8.po b/locale/fr_FR.UTF8/LC_MESSAGES/fr_FR.UTF8.po index d4e7f2e9..a33a6a6e 100644 --- a/locale/fr_FR.UTF8/LC_MESSAGES/fr_FR.UTF8.po +++ b/locale/fr_FR.UTF8/LC_MESSAGES/fr_FR.UTF8.po | |||
@@ -1,8 +1,8 @@ | |||
1 | msgid "" | 1 | msgid "" |
2 | msgstr "" | 2 | msgstr "" |
3 | "Project-Id-Version: poche\n" | 3 | "Project-Id-Version: poche\n" |
4 | "POT-Creation-Date: 2013-08-02 10:26+0100\n" | 4 | "POT-Creation-Date: 2013-08-02 15:38+0100\n" |
5 | "PO-Revision-Date: 2013-08-02 10:26+0100\n" | 5 | "PO-Revision-Date: 2013-08-02 21:57+0100\n" |
6 | "Last-Translator: Nicolas Lœuillet <nicolas.loeuillet@gmail.com>\n" | 6 | "Last-Translator: Nicolas Lœuillet <nicolas.loeuillet@gmail.com>\n" |
7 | "Language-Team: poche <support@inthepoche.com>\n" | 7 | "Language-Team: poche <support@inthepoche.com>\n" |
8 | "Language: Français\n" | 8 | "Language: Français\n" |
@@ -75,7 +75,7 @@ msgstr "" | |||
75 | "Votre mot de passe ne peut être vide et vous devez le répéter dans le second " | 75 | "Votre mot de passe ne peut être vide et vous devez le répéter dans le second " |
76 | "champ." | 76 | "champ." |
77 | 77 | ||
78 | #: /var/www/poche-i18n/index.php:83 | 78 | #: /var/www/poche-i18n/index.php:84 |
79 | msgid "poche, a read it later open source system" | 79 | msgid "poche, a read it later open source system" |
80 | msgstr "poche, a read it later open source system" | 80 | msgstr "poche, a read it later open source system" |
81 | 81 | ||
@@ -83,27 +83,27 @@ msgstr "poche, a read it later open source system" | |||
83 | msgid "Oops, it seems you don't have PHP 5." | 83 | msgid "Oops, it seems you don't have PHP 5." |
84 | msgstr "Oups, il semblerait que PHP 5 ne soit pas installé. " | 84 | msgstr "Oups, il semblerait que PHP 5 ne soit pas installé. " |
85 | 85 | ||
86 | #: /var/www/poche-i18n/inc/functions.php:352 | 86 | #: /var/www/poche-i18n/inc/functions.php:354 |
87 | msgid "the link has been added successfully" | 87 | msgid "the link has been added successfully" |
88 | msgstr "le lien a été ajouté avec succès" | 88 | msgstr "le lien a été ajouté avec succès" |
89 | 89 | ||
90 | #: /var/www/poche-i18n/inc/functions.php:355 | 90 | #: /var/www/poche-i18n/inc/functions.php:357 |
91 | msgid "error during insertion : the link wasn't added" | 91 | msgid "error during insertion : the link wasn't added" |
92 | msgstr "erreur durant l'insertion : le lien n'a pas été ajouté" | 92 | msgstr "erreur durant l'insertion : le lien n'a pas été ajouté" |
93 | 93 | ||
94 | #: /var/www/poche-i18n/inc/functions.php:359 | 94 | #: /var/www/poche-i18n/inc/functions.php:361 |
95 | msgid "error during url preparation : the link wasn't added" | 95 | msgid "error during url preparation : the link wasn't added" |
96 | msgstr "erreur durant l'insertion : le lien n'a pas été ajouté" | 96 | msgstr "erreur durant l'insertion : le lien n'a pas été ajouté" |
97 | 97 | ||
98 | #: /var/www/poche-i18n/inc/functions.php:364 | 98 | #: /var/www/poche-i18n/inc/functions.php:366 |
99 | msgid "error during url preparation : the link is not valid" | 99 | msgid "error during url preparation : the link is not valid" |
100 | msgstr "erreur durant la préparation de l'URL : le lien n'est pas valide" | 100 | msgstr "erreur durant la préparation de l'URL : le lien n'est pas valide" |
101 | 101 | ||
102 | #: /var/www/poche-i18n/inc/functions.php:373 | 102 | #: /var/www/poche-i18n/inc/functions.php:375 |
103 | msgid "the link has been deleted successfully" | 103 | msgid "the link has been deleted successfully" |
104 | msgstr "le lien a été ajouté avec succès" | 104 | msgstr "le lien a été supprimé avec succès" |
105 | 105 | ||
106 | #: /var/www/poche-i18n/inc/functions.php:377 | 106 | #: /var/www/poche-i18n/inc/functions.php:379 |
107 | msgid "the link wasn't deleted" | 107 | msgid "the link wasn't deleted" |
108 | msgstr "le lien n'a pas été supprimé" | 108 | msgstr "le lien n'a pas été supprimé" |
109 | 109 | ||
diff --git a/tpl/home.html b/tpl/home.html index 90e247f7..8b602a25 100644 --- a/tpl/home.html +++ b/tpl/home.html | |||
@@ -3,6 +3,7 @@ | |||
3 | <h1><a href="index.php"><img src="./img/logo.png" alt="logo poche" /></a>poche</h1> | 3 | <h1><a href="index.php"><img src="./img/logo.png" alt="logo poche" /></a>poche</h1> |
4 | </header> | 4 | </header> |
5 | <div id="main"> | 5 | <div id="main"> |
6 | {% block menu %} | ||
6 | <ul id="links"> | 7 | <ul id="links"> |
7 | <li><a href="index.php" {if="$view == 'index'"}class="current"{/if}>home</a></li> | 8 | <li><a href="index.php" {if="$view == 'index'"}class="current"{/if}>home</a></li> |
8 | <li><a href="?view=fav" {if="$view == 'fav'"}class="current"{/if}>favorites</a></li> | 9 | <li><a href="?view=fav" {if="$view == 'fav'"}class="current"{/if}>favorites</a></li> |
@@ -10,10 +11,11 @@ | |||
10 | <li><a href="?view=config" {if="$view == 'config'"}class="current"{/if}>config</a></li> | 11 | <li><a href="?view=config" {if="$view == 'config'"}class="current"{/if}>config</a></li> |
11 | <li><a href="?logout" title="Logout">logout</a></li> | 12 | <li><a href="?logout" title="Logout">logout</a></li> |
12 | </ul> | 13 | </ul> |
13 | {if condition="isset($entries)"} | 14 | {% endblock %} |
15 | {% block precontent %} | ||
14 | <ul id="sort"> | 16 | <ul id="sort"> |
15 | <li><img src="img/up.png" onclick="sort_links('{$view}', 'ia');" title="by date asc" /> by date <img src="img/down.png" onclick="sort_links('{$view}', 'id');" title="by date desc" /></li> | 17 | <li><img src="img/up.png" onclick="sort_links('{$view}', 'ia');" title="by date asc" /> by date <img src="img/down.png" onclick="sort_links('{$view}', 'id');" title="by date desc" /></li> |
16 | <li><img src="img/up.png" onclick="sort_links('{$view}', 'ta');" title="by title asc" /> by title <img src="img/down.png" onclick="sort_links('{$view}', 'td');" title="by title desc" /></li> | 18 | <li><img src="img/up.png" onclick="sort_links('{$view}', 'ta');" title="by title asc" /> by title <img src="img/down.png" onclick="sort_links('{$view}', 'td');" title="by title desc" /></li> |
17 | </ul> | 19 | </ul> |
18 | {/if} | 20 | {% endblock %} |
19 | {include="messages"} \ No newline at end of file | 21 | {include="messages"} \ No newline at end of file |
diff --git a/tpl/install.twig b/tpl/install.twig new file mode 100644 index 00000000..4342df2e --- /dev/null +++ b/tpl/install.twig | |||
@@ -0,0 +1,28 @@ | |||
1 | {% extends "layout.twig" %} | ||
2 | {% block title %}Installation{% endblock %} | ||
3 | {% block content %} | ||
4 | <form method="post" action="?install" name="loginform"> | ||
5 | <fieldset class="w500p center"> | ||
6 | <h2 class="mbs txtcenter">{% trans "install your poche" %}</h2> | ||
7 | <p> | ||
8 | {% trans "poche is still not installed. Please fill the below form to install it. Don't hesitate to <a href='http://inthepoche.com/?pages/Documentation'>read the documentation on poche website</a>." %} | ||
9 | </p> | ||
10 | <div class="row"> | ||
11 | <label class="col w150p" for="login">{% trans "Login" %}</label> | ||
12 | <input class="col" type="text" id="login" name="login" placeholder="Login" tabindex="1" autofocus /> | ||
13 | </div> | ||
14 | <div class="row"> | ||
15 | <label class="col w150p" for="password">{% trans "Password" %}</label> | ||
16 | <input class="col" type="password" id="password" name="password" placeholder="Password" tabindex="2"> | ||
17 | </div> | ||
18 | <div class="row"> | ||
19 | <label class="col w150p" for="password_repeat">{% trans "Repeat your password" %}</label> | ||
20 | <input class="col" type="password" id="password_repeat" name="password_repeat" placeholder="Password" tabindex="3"> | ||
21 | </div> | ||
22 | <div class="row mts txtcenter"> | ||
23 | <button class="bouton" type="submit" tabindex="4">{% trans "Install" %}</button> | ||
24 | </div> | ||
25 | </fieldset> | ||
26 | <input type="hidden" name="token" value="{{ token }}"> | ||
27 | </form> | ||
28 | {% endblock %} \ No newline at end of file | ||
diff --git a/tpl/js.html b/tpl/js.html index a02212b0..3a51af6e 100644 --- a/tpl/js.html +++ b/tpl/js.html | |||
@@ -1,22 +1,22 @@ | |||
1 | <script type="text/javascript" src="js/jquery-1.9.1.min.js"></script> | 1 | <script type="text/javascript" src="js/jquery-1.9.1.min.js"></script> |
2 | <script type="text/javascript" src="js/poche.js"></script> | 2 | <script type="text/javascript" src="js/poche.js"></script> |
3 | 3 | ||
4 | {if="$load_all_js == '1'"} | 4 | {if="$load_all_js == '1'"} |
5 | <script type="text/javascript" src="js/jquery.masonry.min.js"></script> | 5 | <script type="text/javascript" src="js/jquery.masonry.min.js"></script> |
6 | <script type="text/javascript"> | 6 | <script type="text/javascript"> |
7 | $( window ).load( function() | 7 | $( window ).load( function() |
8 | { | 8 | { |
9 | var columns = 3, | 9 | var columns = 3, |
10 | setColumns = function() { columns = $( window ).width() > 640 ? 3 : $( window ).width() > 320 ? 2 : 1; }; | 10 | setColumns = function() { columns = $( window ).width() > 640 ? 3 : $( window ).width() > 320 ? 2 : 1; }; |
11 | 11 | ||
12 | setColumns(); | 12 | setColumns(); |
13 | $( window ).resize( setColumns ); | 13 | $( window ).resize( setColumns ); |
14 | 14 | ||
15 | $( '#content' ).masonry( | 15 | $( '#content' ).masonry( |
16 | { | 16 | { |
17 | itemSelector: '.entrie', | 17 | itemSelector: '.entrie', |
18 | columnWidth: function( containerWidth ) { return containerWidth / columns; } | 18 | columnWidth: function( containerWidth ) { return containerWidth / columns; } |
19 | }); | ||
19 | }); | 20 | }); |
20 | }); | 21 | </script> |
21 | </script> | 22 | {/if} \ No newline at end of file |
22 | {/if} \ No newline at end of file | ||
diff --git a/tpl/layout.twig b/tpl/layout.twig new file mode 100644 index 00000000..c5f52bbd --- /dev/null +++ b/tpl/layout.twig | |||
@@ -0,0 +1,58 @@ | |||
1 | <!DOCTYPE html> | ||
2 | <!--[if lte IE 6]> <html class="no-js ie6 ie67 ie678" lang="en"> <![endif]--> | ||
3 | <!--[if lte IE 7]> <html class="no-js ie7 ie67 ie678" lang="en"> <![endif]--> | ||
4 | <!--[if IE 8]> <html class="no-js ie8 ie678" lang="en"> <![endif]--> | ||
5 | <!--[if gt IE 8]><!--> <html class="no-js" lang="en"> <!--<![endif]--> | ||
6 | <html> | ||
7 | <head> | ||
8 | <meta name="viewport" content="width=device-width, minimum-scale=1.0, maximum-scale=1.0"> | ||
9 | <meta charset="utf-8"> | ||
10 | <meta http-equiv="X-UA-Compatible" content="IE=10"> | ||
11 | <title>{% block title %}{% endblock %} - poche</title> | ||
12 | <link rel="shortcut icon" type="image/x-icon" href="./img/favicon.ico" /> | ||
13 | <link rel="apple-touch-icon-precomposed" sizes="144x144" href="./img/apple-touch-icon-144x144-precomposed.png"> | ||
14 | <link rel="apple-touch-icon-precomposed" sizes="72x72" href="./img/apple-touch-icon-72x72-precomposed.png"> | ||
15 | <link rel="apple-touch-icon-precomposed" href="./img/apple-touch-icon-precomposed.png"> | ||
16 | <link rel="stylesheet" href="./css/knacss.css" media="all"> | ||
17 | <link rel="stylesheet" href="./css/style.css" media="all"> | ||
18 | <!-- Light Theme --> | ||
19 | <link rel="stylesheet" href="./css/style-light.css" media="all" title="light-style"> | ||
20 | <!-- Dark Theme --> | ||
21 | <link rel="alternate stylesheet" href="./css/style-dark.css" media="all" title="dark-style"> | ||
22 | <script> | ||
23 | top["bookmarklet-url@inthepoche.com"] = '' | ||
24 | +'<!DOCTYPE html>' | ||
25 | +'<html>' | ||
26 | +'<head>' | ||
27 | +'<title>poche it !</title>' | ||
28 | +'<link rel="icon" href="{$poche_url}img/favicon.ico" />' | ||
29 | +'</head>' | ||
30 | +'<body>' | ||
31 | +'<script>' | ||
32 | +'window.onload=function(){' | ||
33 | +'window.setTimeout(function(){' | ||
34 | +'history.back();' | ||
35 | +'},250);' | ||
36 | +'};' | ||
37 | +'</scr'+'ipt>' | ||
38 | +'</body>' | ||
39 | +'</html>' | ||
40 | ; | ||
41 | </script> | ||
42 | </head> | ||
43 | <body class="light-style"> | ||
44 | <header> | ||
45 | <h1><a href="/"><img src="./img/logo.png" alt="logo poche" /></a>poche</h1> | ||
46 | </header> | ||
47 | <div id="main"> | ||
48 | {% block menu %}{% endblock %} | ||
49 | {% block precontent %}{% endblock %} | ||
50 | {% block content %}{% endblock %} | ||
51 | {% block js %}{% endblock %} | ||
52 | </div> | ||
53 | <footer class="mr2 mt3 smaller"> | ||
54 | <p>powered by <a href="http://inthepoche.com">poche</a></p> | ||
55 | </footer> | ||
56 | |||
57 | </body> | ||
58 | </html> \ No newline at end of file | ||
diff --git a/tpl/login.twig b/tpl/login.twig new file mode 100644 index 00000000..390718b6 --- /dev/null +++ b/tpl/login.twig | |||
@@ -0,0 +1,31 @@ | |||
1 | {% extends "layout.twig" %} | ||
2 | {% block title %}Login{% endblock %} | ||
3 | {% block content %} | ||
4 | <form method="post" action="?login" name="loginform"> | ||
5 | <fieldset class="w500p center"> | ||
6 | <h2 class="mbs txtcenter">{% trans "login to your poche" %}</h2> | ||
7 | {% if demo == 1 %}<p>{% trans "you are in demo mode, some features may be disabled." %}</p>{% endif %} | ||
8 | <div class="row"> | ||
9 | <label class="col w150p" for="login">{% trans "Login" %}</label> | ||
10 | <input class="col" type="text" id="login" name="login" placeholder="Login" tabindex="1" autofocus {% if demo == 1 %}value="poche"{% endif %} /> | ||
11 | </div> | ||
12 | |||
13 | <div class="row"> | ||
14 | <label class="col w150p" for="password">{% trans "Password" %}</label> | ||
15 | <input class="col" type="password" id="password" name="password" placeholder="Password" tabindex="2" {% if demo == 1 %}value="poche"{% endif %} /> | ||
16 | </div> | ||
17 | <div class="row"> | ||
18 | <label class="col w150p">{% trans "Stay signed in" %}</label> | ||
19 | <div class="col"> | ||
20 | <input type="checkbox" name="longlastingsession" tabindex="3"> | ||
21 | <small class="inbl">(Do not check on public computers)</small> | ||
22 | </div> | ||
23 | </div> | ||
24 | <div class="row mts txtcenter"> | ||
25 | <button class="bouton" type="submit" tabindex="4">{% trans "Sign in" %}</button> | ||
26 | </div> | ||
27 | </fieldset> | ||
28 | <input type="hidden" name="returnurl" value="{{ referer }}"> | ||
29 | <input type="hidden" name="token" value="{{ token }}"> | ||
30 | </form> | ||
31 | {% endblock %} \ No newline at end of file | ||