aboutsummaryrefslogtreecommitdiffhomepage
path: root/inc
diff options
context:
space:
mode:
Diffstat (limited to 'inc')
-rw-r--r--inc/3rdparty/Encoding.php (renamed from inc/Encoding.php)0
-rw-r--r--inc/3rdparty/JSLikeHTMLElement.php (renamed from inc/JSLikeHTMLElement.php)0
-rw-r--r--inc/3rdparty/Readability.php (renamed from inc/Readability.php)0
-rw-r--r--inc/3rdparty/Session.class.php (renamed from inc/Session.class.php)0
-rw-r--r--inc/3rdparty/simple_html_dom.php (renamed from inc/simple_html_dom.php)0
-rw-r--r--inc/Twig/Autoloader.php48
-rw-r--r--inc/Twig/Compiler.php267
-rw-r--r--inc/Twig/CompilerInterface.php35
-rw-r--r--inc/Twig/Environment.php1224
-rw-r--r--inc/Twig/Error.php239
-rw-r--r--inc/Twig/Error/Loader.php31
-rw-r--r--inc/Twig/Error/Runtime.php20
-rw-r--r--inc/Twig/Error/Syntax.php20
-rw-r--r--inc/Twig/ExistsLoaderInterface.php28
-rw-r--r--inc/Twig/ExpressionParser.php600
-rw-r--r--inc/Twig/Extension.php93
-rw-r--r--inc/Twig/Extension/Core.php1355
-rw-r--r--inc/Twig/Extension/Debug.php71
-rw-r--r--inc/Twig/Extension/Escaper.php107
-rw-r--r--inc/Twig/Extension/Optimizer.php35
-rw-r--r--inc/Twig/Extension/Sandbox.php112
-rw-r--r--inc/Twig/Extension/Staging.php113
-rw-r--r--inc/Twig/Extension/StringLoader.php64
-rw-r--r--inc/Twig/ExtensionInterface.php83
-rw-r--r--inc/Twig/Extensions/Autoloader.php45
-rw-r--r--inc/Twig/Extensions/Extension/Debug.php34
-rw-r--r--inc/Twig/Extensions/Extension/I18n.php44
-rw-r--r--inc/Twig/Extensions/Extension/Intl.php66
-rw-r--r--inc/Twig/Extensions/Extension/Text.php109
-rw-r--r--inc/Twig/Extensions/Gettext/Extractor.php95
-rw-r--r--inc/Twig/Extensions/Gettext/Loader/Filesystem.php58
-rw-r--r--inc/Twig/Extensions/Gettext/Routing/Generator/UrlGenerator.php39
-rw-r--r--inc/Twig/Extensions/Gettext/Test/ExtractorTest.php123
-rw-r--r--inc/Twig/Extensions/Gettext/Test/Fixtures/twig/empty.twig1
-rw-r--r--inc/Twig/Extensions/Gettext/Test/Fixtures/twig/plural.twig5
-rw-r--r--inc/Twig/Extensions/Gettext/Test/Fixtures/twig/singular.twig9
-rw-r--r--inc/Twig/Extensions/Grammar.php30
-rw-r--r--inc/Twig/Extensions/Grammar/Arguments.php22
-rw-r--r--inc/Twig/Extensions/Grammar/Array.php22
-rw-r--r--inc/Twig/Extensions/Grammar/Body.php39
-rw-r--r--inc/Twig/Extensions/Grammar/Boolean.php24
-rw-r--r--inc/Twig/Extensions/Grammar/Constant.php37
-rw-r--r--inc/Twig/Extensions/Grammar/Expression.php22
-rw-r--r--inc/Twig/Extensions/Grammar/Hash.php22
-rw-r--r--inc/Twig/Extensions/Grammar/Number.php24
-rw-r--r--inc/Twig/Extensions/Grammar/Optional.php69
-rw-r--r--inc/Twig/Extensions/Grammar/Switch.php24
-rw-r--r--inc/Twig/Extensions/Grammar/Tag.php56
-rw-r--r--inc/Twig/Extensions/GrammarInterface.php18
-rw-r--r--inc/Twig/Extensions/Node/Debug.php69
-rw-r--r--inc/Twig/Extensions/Node/Trans.php133
-rw-r--r--inc/Twig/Extensions/SimpleTokenParser.php132
-rw-r--r--inc/Twig/Extensions/TokenParser/Debug.php42
-rw-r--r--inc/Twig/Extensions/TokenParser/Trans.php80
-rw-r--r--inc/Twig/Filter.php81
-rw-r--r--inc/Twig/Filter/Function.php37
-rw-r--r--inc/Twig/Filter/Method.php39
-rw-r--r--inc/Twig/Filter/Node.php39
-rw-r--r--inc/Twig/FilterCallableInterface.php23
-rw-r--r--inc/Twig/FilterInterface.php42
-rw-r--r--inc/Twig/Function.php71
-rw-r--r--inc/Twig/Function/Function.php38
-rw-r--r--inc/Twig/Function/Method.php40
-rw-r--r--inc/Twig/Function/Node.php39
-rw-r--r--inc/Twig/FunctionCallableInterface.php23
-rw-r--r--inc/Twig/FunctionInterface.php39
-rw-r--r--inc/Twig/Gettext/Extractor.php95
-rw-r--r--inc/Twig/Gettext/Loader/Filesystem.php58
-rw-r--r--inc/Twig/Gettext/Routing/Generator/UrlGenerator.php39
-rw-r--r--inc/Twig/Gettext/Test/ExtractorTest.php123
-rw-r--r--inc/Twig/Gettext/Test/Fixtures/twig/empty.twig1
-rw-r--r--inc/Twig/Gettext/Test/Fixtures/twig/plural.twig5
-rw-r--r--inc/Twig/Gettext/Test/Fixtures/twig/singular.twig9
-rw-r--r--inc/Twig/Lexer.php408
-rw-r--r--inc/Twig/LexerInterface.php29
-rw-r--r--inc/Twig/Loader/Array.php98
-rw-r--r--inc/Twig/Loader/Chain.php139
-rw-r--r--inc/Twig/Loader/Filesystem.php223
-rw-r--r--inc/Twig/Loader/String.php59
-rw-r--r--inc/Twig/LoaderInterface.php52
-rw-r--r--inc/Twig/Markup.php37
-rw-r--r--inc/Twig/Node.php226
-rw-r--r--inc/Twig/Node/AutoEscape.php39
-rw-r--r--inc/Twig/Node/Block.php44
-rw-r--r--inc/Twig/Node/BlockReference.php37
-rw-r--r--inc/Twig/Node/Body.php19
-rw-r--r--inc/Twig/Node/Do.php38
-rw-r--r--inc/Twig/Node/Embed.php38
-rw-r--r--inc/Twig/Node/Expression.php20
-rw-r--r--inc/Twig/Node/Expression/Array.php86
-rw-r--r--inc/Twig/Node/Expression/AssignName.php28
-rw-r--r--inc/Twig/Node/Expression/Binary.php40
-rw-r--r--inc/Twig/Node/Expression/Binary/Add.php18
-rw-r--r--inc/Twig/Node/Expression/Binary/And.php18
-rw-r--r--inc/Twig/Node/Expression/Binary/BitwiseAnd.php18
-rw-r--r--inc/Twig/Node/Expression/Binary/BitwiseOr.php18
-rw-r--r--inc/Twig/Node/Expression/Binary/BitwiseXor.php18
-rw-r--r--inc/Twig/Node/Expression/Binary/Concat.php18
-rw-r--r--inc/Twig/Node/Expression/Binary/Div.php18
-rw-r--r--inc/Twig/Node/Expression/Binary/Equal.php17
-rw-r--r--inc/Twig/Node/Expression/Binary/FloorDiv.php29
-rw-r--r--inc/Twig/Node/Expression/Binary/Greater.php17
-rw-r--r--inc/Twig/Node/Expression/Binary/GreaterEqual.php17
-rw-r--r--inc/Twig/Node/Expression/Binary/In.php33
-rw-r--r--inc/Twig/Node/Expression/Binary/Less.php17
-rw-r--r--inc/Twig/Node/Expression/Binary/LessEqual.php17
-rw-r--r--inc/Twig/Node/Expression/Binary/Mod.php18
-rw-r--r--inc/Twig/Node/Expression/Binary/Mul.php18
-rw-r--r--inc/Twig/Node/Expression/Binary/NotEqual.php17
-rw-r--r--inc/Twig/Node/Expression/Binary/NotIn.php33
-rw-r--r--inc/Twig/Node/Expression/Binary/Or.php18
-rw-r--r--inc/Twig/Node/Expression/Binary/Power.php33
-rw-r--r--inc/Twig/Node/Expression/Binary/Range.php33
-rw-r--r--inc/Twig/Node/Expression/Binary/Sub.php18
-rw-r--r--inc/Twig/Node/Expression/BlockReference.php51
-rw-r--r--inc/Twig/Node/Expression/Call.php178
-rw-r--r--inc/Twig/Node/Expression/Conditional.php31
-rw-r--r--inc/Twig/Node/Expression/Constant.php23
-rw-r--r--inc/Twig/Node/Expression/ExtensionReference.php33
-rw-r--r--inc/Twig/Node/Expression/Filter.php36
-rw-r--r--inc/Twig/Node/Expression/Filter/Default.php43
-rw-r--r--inc/Twig/Node/Expression/Function.php35
-rw-r--r--inc/Twig/Node/Expression/GetAttr.php53
-rw-r--r--inc/Twig/Node/Expression/MethodCall.php41
-rw-r--r--inc/Twig/Node/Expression/Name.php88
-rw-r--r--inc/Twig/Node/Expression/Parent.php47
-rw-r--r--inc/Twig/Node/Expression/TempName.php26
-rw-r--r--inc/Twig/Node/Expression/Test.php32
-rw-r--r--inc/Twig/Node/Expression/Test/Constant.php46
-rw-r--r--inc/Twig/Node/Expression/Test/Defined.php54
-rw-r--r--inc/Twig/Node/Expression/Test/Divisibleby.php33
-rw-r--r--inc/Twig/Node/Expression/Test/Even.php32
-rw-r--r--inc/Twig/Node/Expression/Test/Null.php31
-rw-r--r--inc/Twig/Node/Expression/Test/Odd.php32
-rw-r--r--inc/Twig/Node/Expression/Test/Sameas.php29
-rw-r--r--inc/Twig/Node/Expression/Unary.php30
-rw-r--r--inc/Twig/Node/Expression/Unary/Neg.php18
-rw-r--r--inc/Twig/Node/Expression/Unary/Not.php18
-rw-r--r--inc/Twig/Node/Expression/Unary/Pos.php18
-rw-r--r--inc/Twig/Node/Flush.php36
-rw-r--r--inc/Twig/Node/For.php112
-rw-r--r--inc/Twig/Node/ForLoop.php55
-rw-r--r--inc/Twig/Node/If.php66
-rw-r--r--inc/Twig/Node/Import.php50
-rw-r--r--inc/Twig/Node/Include.php99
-rw-r--r--inc/Twig/Node/Macro.php96
-rw-r--r--inc/Twig/Node/Module.php371
-rw-r--r--inc/Twig/Node/Print.php39
-rw-r--r--inc/Twig/Node/Sandbox.php47
-rw-r--r--inc/Twig/Node/SandboxedModule.php60
-rw-r--r--inc/Twig/Node/SandboxedPrint.php59
-rw-r--r--inc/Twig/Node/Set.php101
-rw-r--r--inc/Twig/Node/SetTemp.php35
-rw-r--r--inc/Twig/Node/Spaceless.php40
-rw-r--r--inc/Twig/Node/Text.php39
-rw-r--r--inc/Twig/NodeInterface.php30
-rw-r--r--inc/Twig/NodeOutputInterface.php19
-rw-r--r--inc/Twig/NodeTraverser.php88
-rw-r--r--inc/Twig/NodeVisitor/Escaper.php167
-rw-r--r--inc/Twig/NodeVisitor/Optimizer.php246
-rw-r--r--inc/Twig/NodeVisitor/SafeAnalysis.php131
-rw-r--r--inc/Twig/NodeVisitor/Sandbox.php92
-rw-r--r--inc/Twig/NodeVisitorInterface.php47
-rw-r--r--inc/Twig/Parser.php394
-rw-r--r--inc/Twig/ParserInterface.php28
-rw-r--r--inc/Twig/Sandbox/SecurityError.php19
-rw-r--r--inc/Twig/Sandbox/SecurityPolicy.php119
-rw-r--r--inc/Twig/Sandbox/SecurityPolicyInterface.php24
-rw-r--r--inc/Twig/SimpleFilter.php94
-rw-r--r--inc/Twig/SimpleFunction.php84
-rw-r--r--inc/Twig/SimpleTest.php46
-rw-r--r--inc/Twig/Template.php455
-rw-r--r--inc/Twig/TemplateInterface.php47
-rw-r--r--inc/Twig/Test.php34
-rw-r--r--inc/Twig/Test/Function.php35
-rw-r--r--inc/Twig/Test/IntegrationTestCase.php154
-rw-r--r--inc/Twig/Test/Method.php37
-rw-r--r--inc/Twig/Test/Node.php37
-rw-r--r--inc/Twig/Test/NodeTestCase.php58
-rw-r--r--inc/Twig/TestCallableInterface.php21
-rw-r--r--inc/Twig/TestInterface.php26
-rw-r--r--inc/Twig/Token.php218
-rw-r--r--inc/Twig/TokenParser.php33
-rw-r--r--inc/Twig/TokenParser/AutoEscape.php89
-rw-r--r--inc/Twig/TokenParser/Block.php83
-rw-r--r--inc/Twig/TokenParser/Do.php42
-rw-r--r--inc/Twig/TokenParser/Embed.php66
-rw-r--r--inc/Twig/TokenParser/Extends.php52
-rw-r--r--inc/Twig/TokenParser/Filter.php61
-rw-r--r--inc/Twig/TokenParser/Flush.php42
-rw-r--r--inc/Twig/TokenParser/For.php136
-rw-r--r--inc/Twig/TokenParser/From.php74
-rw-r--r--inc/Twig/TokenParser/If.php94
-rw-r--r--inc/Twig/TokenParser/Import.php49
-rw-r--r--inc/Twig/TokenParser/Include.php80
-rw-r--r--inc/Twig/TokenParser/Macro.php68
-rw-r--r--inc/Twig/TokenParser/Sandbox.php68
-rw-r--r--inc/Twig/TokenParser/Set.php84
-rw-r--r--inc/Twig/TokenParser/Spaceless.php59
-rw-r--r--inc/Twig/TokenParser/Use.php82
-rw-r--r--inc/Twig/TokenParserBroker.php136
-rw-r--r--inc/Twig/TokenParserBrokerInterface.php45
-rw-r--r--inc/Twig/TokenParserInterface.php41
-rw-r--r--inc/Twig/TokenStream.php144
-rw-r--r--inc/config.php76
-rw-r--r--inc/functions.php400
-rw-r--r--inc/poche/pocheCore.php257
-rw-r--r--inc/poche/pochePictures.php114
-rw-r--r--inc/poche/pocheTool.class.php (renamed from inc/pocheTool.class.php)69
-rw-r--r--inc/rain.tpl.class.php1043
210 files changed, 16198 insertions, 1502 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 */
17class 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 */
18class 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 */
18interface 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 */
17class 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 */
34class 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 */
25class 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 */
18class 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 */
18class 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 */
18interface 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 */
23class 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 */
11abstract 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
3if (!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 */
15class 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 */
319function 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 */
341function 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 */
401function 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 */
428function 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 */
451function 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 */
499function 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 */
525function 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
538if (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
578function _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 */
601function 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 */
621function 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 */
648function 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 */
663function 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 */
688function 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 */
720function 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.
732function _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 */
756function 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 */
778function 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 */
814function twig_sort_filter($array)
815{
816 asort($array);
817
818 return $array;
819}
820
821/* used internally */
822function 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 */
848function 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 */
974function 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
987if (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
1004function _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
1019function _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 */
1045function _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 '&#xFFFD;';
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
1096if (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
1180else {
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 */
1222function 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 */
1245function 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 */
1268function 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 */
1284function 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 */
1318function 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 */
1336function 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 */
11class 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
46function 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 */
11class 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 */
104function 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 */
11class 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 */
11class 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 */
19class 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 */
11class 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 */
44function 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 */
17interface 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 */
18class 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 */
11class 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 */
11class 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
12class 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
44function 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 */
15class 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
47function twig_nl2br_filter($value, $sep = '<br />')
48{
49 return str_replace("\n", $sep."\n", $value);
50}
51
52if (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
12namespace Twig\Gettext;
13
14use Symfony\Component\Filesystem\Filesystem;
15
16/**
17 * Extracts translations from twig templates.
18 *
19 * @author Саша Стаменковић <umpirsky@gmail.com>
20 */
21class 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
12namespace Twig\Gettext\Loader;
13
14/**
15 * Loads template from the filesystem.
16 *
17 * @author Саша Стаменковић <umpirsky@gmail.com>
18 */
19class 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
12namespace Twig\Gettext\Routing\Generator;
13
14use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
15use Symfony\Component\Routing\RequestContext;
16
17/**
18 * Dummy url generator.
19 *
20 * @author Саша Стаменковић <umpirsky@gmail.com>
21 */
22class 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
12namespace Twig\Gettext\Test;
13
14use Twig\Gettext\Extractor;
15use Twig\Gettext\Loader\Filesystem;
16use Symfony\Component\Translation\Loader\PoFileLoader;
17
18/**
19 * @author Саша Стаменковић <umpirsky@gmail.com>
20 */
21class 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 */
11abstract 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 */
11class 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 */
11class 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 */
11class 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 */
11class 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 */
11class 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 */
11class 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 */
11class 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 */
11class 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 */
11class 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 */
11class 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 */
11class 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 */
11interface 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 */
20class 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 */
18class 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 */
11abstract 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 */
11class 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 */
11class 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 */
20abstract 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 */
20class 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 */
20class 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 */
20class 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 */
20interface 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 */
20interface 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 */
20abstract 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 */
21class 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 */
21class 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 */
20class 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 */
20interface 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 */
21interface 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
12namespace Twig\Gettext;
13
14use Symfony\Component\Filesystem\Filesystem;
15
16/**
17 * Extracts translations from twig templates.
18 *
19 * @author Саша Стаменковић <umpirsky@gmail.com>
20 */
21class 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
12namespace Twig\Gettext\Loader;
13
14/**
15 * Loads template from the filesystem.
16 *
17 * @author Саша Стаменковић <umpirsky@gmail.com>
18 */
19class 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
12namespace Twig\Gettext\Routing\Generator;
13
14use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
15use Symfony\Component\Routing\RequestContext;
16
17/**
18 * Dummy url generator.
19 *
20 * @author Саша Стаменковић <umpirsky@gmail.com>
21 */
22class 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
12namespace Twig\Gettext\Test;
13
14use Twig\Gettext\Extractor;
15use Twig\Gettext\Loader\Filesystem;
16use Symfony\Component\Translation\Loader\PoFileLoader;
17
18/**
19 * @author Саша Стаменковић <umpirsky@gmail.com>
20 */
21class 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 */
18class 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 */
18interface 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 */
22class 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 */
17class 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 */
17class 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 */
26class 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 */
17interface 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 */
17class 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 */
18class 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 */
23class 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 */
18class 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 */
18class 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 */
17class 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 */
17class 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 */
17class 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 */
18abstract 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 */
11class 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
13class 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 */
12abstract 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 */
12class 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 */
12class 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 */
12class 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 */
12class 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 */
12class 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 */
12class 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 */
12class 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 */
11class 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 */
11class 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 */
11class 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 */
11class 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 */
11class 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 */
11class 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 */
11class 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 */
12class 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 */
12class 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 */
11class 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 */
11class 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 */
12class 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 */
11class 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 */
11class 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 */
12class 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 */
18class 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 */
11abstract 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 */
12class 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 */
12class 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 */
17class 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 */
12class 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 */
21class 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 */
11class 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 */
12class 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 */
11class 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 */
12class 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 */
18class 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 */
11class 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 */
11class 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 */
23class 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 */
24class 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 */
21class 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 */
21class 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 */
21class 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 */
21class 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 */
17class 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 */
12abstract 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 */
12class 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 */
12class 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 */
12class 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 */
17class 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 */
18class 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 */
17class 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 */
18class 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 */
17class 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 */
18class 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 */
17class 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 */
18class 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 */
18class 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 */
17class 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 */
18class 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 */
22class 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 */
17class 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
12class 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 */
19class 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 */
18class 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 */
18interface 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 */
17interface 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 */
19class 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 */
17class 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 */
22class 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
3class 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 */
17class 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 */
17interface 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 */
18class 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 */
18interface 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 */
17class 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 */
17class 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 */
17interface 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 */
17class 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 */
17class 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 */
17class 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 */
18abstract 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 */
18interface 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 */
18abstract 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 */
18class 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 */
18abstract 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 */
18class 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 */
18class 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 */
11abstract 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 */
18interface 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 */
18interface 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 */
18class 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 */
17abstract 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 */
30class 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 */
23class 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 */
15class 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 */
15class 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 */
20class 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 */
21class 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 */
17class 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 */
24class 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 */
19class 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 */
26class 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 */
19class 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 */
22class 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 */
21class 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 */
23class 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 */
29class 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 */
25class 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 */
26class 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 */
19class 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 */
21interface 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 */
17interface 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 */
18class 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
11define ('POCHE_VERSION', '0.3'); 11define ('POCHE_VERSION', '0.3');
12
13if (!is_dir('db/')) {
14 @mkdir('db/',0705);
15}
16
17define ('MODE_DEMO', FALSE); 12define ('MODE_DEMO', FALSE);
18define ('ABS_PATH', 'assets/'); 13define ('CONVERT_LINKS_FOOTNOTES', FALSE);
19define ('CONVERT_LINKS_FOOTNOTES', TRUE); 14define ('REVERT_FORCED_PARAGRAPH_ELEMENTS', FALSE);
20define ('REVERT_FORCED_PARAGRAPH_ELEMENTS',FALSE); 15define ('DOWNLOAD_PICTURES', FALSE);
21define ('DOWNLOAD_PICTURES', TRUE);
22define ('SALT', '464v54gLLw928uz4zUBqkRJeiPY68zCX'); 16define ('SALT', '464v54gLLw928uz4zUBqkRJeiPY68zCX');
17define ('ABS_PATH', 'assets/');
18define ('TPL', './tpl');
19define ('LOCALE', './locale');
20define ('CACHE', './cache');
23define ('LANG', 'fr_FR.UTF8'); 21define ('LANG', 'fr_FR.UTF8');
24 22
25putenv("LC_ALL=".LANG);
26setlocale(LC_ALL, LANG);
27bindtextdomain(LANG, "./locale");
28textdomain(LANG);
29
30$storage_type = 'sqlite'; # sqlite or file 23$storage_type = 'sqlite'; # sqlite or file
31 24
32include 'functions.php'; 25# /!\ Be careful if you change the lines below /!\
26
27require_once 'pocheCore.php';
33require_once 'Readability.php'; 28require_once 'Readability.php';
34require_once 'Encoding.php'; 29require_once 'Encoding.php';
35require_once 'rain.tpl.class.php'; 30require_once 'pocheTool.class.php';
36require_once 'MyTool.class.php';
37require_once 'Session.class.php'; 31require_once 'Session.class.php';
32require_once 'Twig/Autoloader.php';
38require_once 'store/store.class.php'; 33require_once 'store/store.class.php';
39require_once 'store/sqlite.class.php'; 34require_once 'store/' . $storage_type . '.class.php';
40require_once 'store/file.class.php';
41require_once 'class.messages.php';
42 35
43Session::init(); 36if (DOWNLOAD_PICTURES) {
37 require_once 'pochePicture.php';
38}
39
40# i18n
41putenv('LC_ALL=' . LANG);
42setlocale(LC_ALL, LANG);
43bindtextdomain(LANG, LOCALE);
44textdomain(LANG);
45
46# template engine
47Twig_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(); 54Session::init();
46# initialisation de RainTPL 55$store = new $storage_type();
47raintpl::$tpl_dir = './tpl/';
48raintpl::$cache_dir = './cache/';
49raintpl::$base_url = get_poche_url();
50raintpl::configure('path_replace', false);
51raintpl::configure('debug', false);
52$tpl = new raintpl();
53 56
57# installation
54if(!$store->isInstalled()) 58if(!$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 */
14function 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
26function encode_string($string)
27{
28 return sha1($string . SALT);
29}
30
31// function define to retrieve url content
32function 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 */
116function 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 */
163function 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 */
188function 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
221function 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 */
236function 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 */
254function 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
265function 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 */
335function 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
396function 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
11function encode_string($string)
12{
13 return sha1($string . SALT);
14}
15
16function 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
93function 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
137function 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 */
207function 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 */
14function 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 */
39function 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
72function 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 */
87function 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 */
105function 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
13class 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("&lt;?","?&gt;"), $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 */
944class 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 */
976class RainTpl_NotFoundException extends RainTpl_Exception{
977}
978
979/**
980 * Exception thrown when syntax error occurs.
981 */
982class 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